Доска для совместной работы AngularJS с Socket.io

Автор: Peter Berry
Дата создания: 14 Июль 2021
Дата обновления: 13 Май 2024
Anonim
Develop Collaborative White Board : Web socket, Node JS & React JS
Видео: Develop Collaborative White Board : Web socket, Node JS & React JS

Содержание

  • Необходимые знания: Промежуточный JavaScript
  • Требует: Node.js, NPM
  • Время проекта: 2 часа

AngularJS особенно хорошо подходит для создания полнофункциональных клиентских приложений в браузере, и когда вы добавляете немного Socket.io в микс, все становится действительно интересным. В этой статье мы собираемся создать доску для совместной работы в реальном времени, которая использует AngularJS для клиентского приложения и Socket.io для обмена состоянием между всеми подключенными клиентами.

Прежде чем мы начнем, давайте немного поговорим о домашнем хозяйстве. Я предполагаю, что у вас есть фундаментальные знания в области HTML и JavaScript, поскольку я не собираюсь описывать каждый уголок кода. Например, я не буду вызывать файлы CSS и JavaScript, которые я включил в заголовок файла HTML, поскольку там нет новой информации.

Кроме того, я рекомендую вам взять код из моей учетной записи GitHub, чтобы следить за ним. У моего хорошего друга Брайана Форда также есть отличное семя Socket.io, на котором я основал некоторые из моих оригинальных идей.

Четыре основные функции, которые нам нужны в доске для совместной работы, - это возможность создавать заметки, читать заметки, обновлять заметки, удалять заметки и, для удовольствия, перемещать заметки на доске. Да, верно, мы делаем упор на стандартные функции CRUD. Я считаю, что, сосредоточив внимание на этих фундаментальных функциях, мы охватим достаточно кода для появления шаблонов, чтобы вы могли использовать их в других местах.


01. Сервер

Сначала мы начнем с сервера Node.js, поскольку он послужит фундаментом, на котором мы будем строить все остальное.

Мы собираемся создать сервер Node.js с Express и Socket.io. Причина, по которой мы используем Express, заключается в том, что он предоставляет удобный механизм для настройки сервера статических ресурсов в Node.js. Express поставляется с множеством действительно потрясающих функций, но в данном случае мы собираемся использовать его, чтобы аккуратно разделить приложение между сервером и клиентом.

(Я исхожу из предположения, что у вас установлены Node.js и NPM. Быстрый поиск в Google покажет вам, как их установить, если вы этого не сделали.)

02. Голые кости

Итак, чтобы построить базовый костяк сервера, нам нужно сделать пару вещей, чтобы он начал работать.

// app.js

// A.1
var express = require (’express’),
приложение = экспресс ();
server = require (’http’). createServer (app),
io = require (’socket.io’). listen (server);

// A.2
app.configure (function () {
app.use (express.static (__ dirname + ’/ public’));
});

// A.3
server.listen (1337);


A.1 Мы объявляем и создаем экземпляры наших модулей Node.js, чтобы мы могли использовать их в нашем приложении. Мы объявляем Express, создаем экземпляр Express, а затем создаем HTTP-сервер и отправляем в него экземпляр Express. И оттуда мы создаем экземпляр Socket.io и говорим ему, чтобы он следил за нашим экземпляром сервера.

A.2 Затем мы говорим нашему приложению Express использовать наш общедоступный каталог для обслуживания файлов.

A.3 Мы запускаем сервер и приказываем ему прослушивать порт 1337.

Пока это было довольно быстро и безболезненно. Я считаю, что у нас меньше 10 строк в коде, и у нас уже есть работающий сервер Node.js. Вперед!

03. Объявите свои зависимости

// packages.json
{
"name": "угловая-коллаборация",
"description": "Доска для совместной работы AngularJS",
"версия": "0.0.1-1",
"частный": правда,
"dependencies": {
"экспресс": "3.x",
"socket.io": "0.9.x"
}
}

Одна из самых приятных функций NPM - это возможность объявлять свои зависимости в packages.json файл, а затем автоматически установить их через npm install в командной строке.


04. Подключите Socket.io

Мы уже определили основные функции, которые нам нужны в приложении, поэтому нам нужно настроить прослушиватели событий Socket.io и соответствующее закрытие для обработки события для каждой операции.

В приведенном ниже коде вы заметите, что по сути это конфигурация прослушивателей событий и обратных вызовов. Первое событие - это связь event, который мы используем для подключения других наших событий в закрытии.

io.sockets.on (’соединение’, функция (сокет) {
socket.on (’createNote’, function (data) {
socket.broadcast.emit (’onNoteCreated’, данные);
});

socket.on (’updateNote’, function (data) {
socket.broadcast.emit (’onNoteUpdated’, данные);
});

socket.on (’deleteNote’, function (data) {
socket.broadcast.emit (’onNoteDeleted’, данные);
});

socket.on (’moveNote’, function (data) {
socket.broadcast.emit (’onNoteMoved’, данные);
});
});

Отсюда мы добавляем слушателей в createNote, updateNote, deleteNote а также moveNote. А в функции обратного вызова мы просто транслируем, какое событие произошло, чтобы любой слушающий клиент мог быть уведомлен о том, что событие произошло.

Следует отметить несколько моментов, касающихся функций обратного вызова в отдельных обработчиках событий. Во-первых, если вы хотите отправить событие всем, кроме клиента, который отправил событие, которое вы вставляете транслировать перед испускают вызов функции. Во-вторых, мы просто передаем полезную нагрузку события заинтересованным сторонам, чтобы они могли обработать его так, как сочтут нужным.

05. Запустите двигатели!

Теперь, когда мы определили наши зависимости и настроили приложение Node.js с возможностями Express и Socket.io, инициализировать сервер Node.js довольно просто.

Сначала вы устанавливаете свои зависимости Node.js следующим образом:

npm install

И тогда вы запускаете сервер так:

узел app.js

А потом! Вы переходите по этому адресу в своем браузере. Бац!

06. Несколько откровенных мыслей перед тем, как двигаться дальше.

Я в первую очередь фронтенд-разработчик, и сначала меня немного пугало подключение сервера Node.js к моему приложению. Часть AngularJS была несложной, но серверный JavaScript? Поставьте в очередь жуткую музыку из фильма ужасов.

Но я был абсолютно поражен, обнаружив, что могу настроить статический веб-сервер всего в нескольких строках кода, а еще в нескольких строках использовать Socket.io для обработки всех событий между браузерами. И это все еще был просто JavaScript! Ради своевременности мы коснемся только нескольких функций, но я надеюсь, что к концу статьи вы увидите, что плавать легко - а глубокий конец бассейна не так страшен.

07. Клиент

Теперь, когда у нас есть прочный фундамент с нашим сервером, давайте перейдем к моей любимой части - клиенту! Мы собираемся использовать AngularJS, jQueryUI для перетаскиваемой части и Twitter Bootstrap для основы стилей.

08. Голые кости

Что касается личных предпочтений, когда я запускаю новое приложение AngularJS, мне нравится быстро определять самый минимум, который, как я знаю, мне понадобится для начала, а затем начать повторять его как можно быстрее.

Каждое приложение AngularJS должно быть загружено по крайней мере с одним контроллером, поэтому я всегда начинаю именно с него.

Для автоматической загрузки приложения вам нужно просто добавить ng-app к узлу HTML, в котором вы хотите разместить приложение. В большинстве случаев добавление его в тег HTML будет вполне приемлемым. Я также добавил атрибут к ng-app сказать ему, что я хочу использовать приложение модуль, который я определю чуть позже.

// общедоступный / index.html
html ng-app = "приложение">

Я знаю, что мне понадобится хотя бы один контроллер, поэтому я назову его, используя ng-контроллер и присвоив ему свойство MainCtrl.

body ng-controller = "MainCtrl"> / body>

Итак, теперь мы на крючке для модуля с именем приложение и контроллер с именем MainCtrl. Давайте продолжим и создадим их сейчас.

Создать модуль довольно просто. Вы определяете это, вызывая angular.module и дать ему имя. Для справки в будущем, второй параметр пустого массива - это то место, куда вы можете вставлять подмодули для использования в приложении. Это выходит за рамки данного руководства, но это удобно, когда ваше приложение начинает усложняться и требовать большего.

// общедоступный / js / collab.js
вар приложение = angular.module (’приложение’, []);

Мы собираемся объявить несколько пустых заполнителей в приложение модуль, начинающийся с MainCtrl ниже.Мы заполним все это позже, но я хотел проиллюстрировать основную структуру с самого начала.

app.controller (’MainCtrl’, функция ($ scope) {});

Мы также собираемся обернуть функциональность Socket.io в разъем service, чтобы мы могли инкапсулировать этот объект и не оставлять его плавающим в глобальном пространстве имен.

app.factory (’socket’, функция ($ rootScope) {});

И пока мы это делаем, мы собираемся объявить директиву под названием заметка который мы собираемся использовать для инкапсуляции функции стикеров в.

app.directive (’stickyNote’, функция (сокет) {});

Итак, давайте рассмотрим, что мы уже сделали. Мы загрузили приложение, используя ng-app и объявил наш контроллер приложения в HTML. Мы также определили модуль приложения и создали MainCtrl контроллер, разъем сервис и заметка директива.

09. Создание заметки

Теперь, когда у нас есть скелет приложения AngularJS, мы приступим к созданию функции создания.

app.controller (’MainCtrl’, function ($ scope, socket) {// B.1
$ scope.notes = []; // БИ 2

// Входящий
socket.on (’onNoteCreated’, function (data) {// B.3
$ scope.notes.push (данные);
});

// Исходящий
$ scope.createNote = function () {// B.4
var note = {
id: новая дата (). getTime (),
title: ’New Note’,
body: ’Pending’
};

$ scope.notes.push (примечание);
socket.emit (’createNote’, примечание);
};

B.1 AngularJS имеет встроенную функцию внедрения зависимостей, поэтому мы внедряем $ scope объект и разъем услуга. В $ scope Объект служит ViewModel и, по сути, является объектом JavaScript с некоторыми событиями, встроенными в него, чтобы обеспечить двустороннюю привязку данных.

B.2. Мы объявляем массив, к которому будем привязать представление.

B.3 Мы добавляем слушателя для onNoteCreated событие на разъем сервис и отправка полезной нагрузки события в $ scope.notes множество.

B.4 Мы объявили createNote метод, который создает значение по умолчанию Примечание объект и толкает его в $ scope.notes множество. Он также использует разъем сервис по выпуску createNote мероприятие и пройти новая заметка объект вместе.

Итак, теперь, когда у нас есть метод создания заметки, как его назвать? Это хороший вопрос! В HTML-файл мы добавляем встроенную директиву AngularJS нг-щелчок к кнопке, а затем добавьте createNote вызов метода в качестве значения атрибута.

button id = "createButton" ng-click = "createNote ()"> Создать заметку / кнопку>

Пришло время сделать быстрый обзор того, что мы уже сделали. Мы добавили массив в $ scope объект в MainCtrl это будет содержать все заметки для приложения. Мы также добавили createNote метод на $ scope объект, чтобы создать новую локальную заметку, а затем передать ее другим клиентам через разъем услуга. Мы также добавили прослушиватель событий на разъем service, чтобы мы могли знать, когда другие клиенты создали заметку, чтобы мы могли добавить ее в нашу коллекцию.

10. Отображение заметок

Теперь у нас есть возможность создать объект заметки и поделиться им между браузерами, но как мы на самом деле отображаем его? Вот тут-то и нужны директивы.

Директивы и их сложности - обширная тема, но краткая версия заключается в том, что они предоставляют способ расширения элементов и атрибутов с помощью настраиваемых функций. Директивы - моя любимая часть в AngularJS, потому что они позволяют вам, по сути, создать весь DSL (язык, специфичный для домена) вокруг вашего приложения в HTML.

Естественно, что, поскольку мы собираемся создавать стикеры для нашей доски для совместной работы, мы должны создать заметка директива. Директивы определяются путем вызова метода директивы в модуле, который вы хотите объявить, и передачи имени и функции, которые возвращают объект определения директивы. Объект определения директивы имеет множество возможных свойств, которые вы можете определить для него, но мы собираемся использовать здесь лишь некоторые из них.

Я рекомендую вам проверить документацию AngularJS, чтобы увидеть полные списки свойств, которые вы можете определить для объекта определения директивы.

app.directive (’stickyNote’, function (socket) {
var linker = function (scope, element, attrs) {};

var controller = function ($ scope) {};

возвращаться {
ограничить: ’A’, // C.1
ссылка: компоновщик, // C.2
контроллер: контроллер, // C.3
область действия: {// C.4
примечание: ’=’,
ondelete: ’&’
}
};
});

C.1 Вы можете ограничить свою директиву определенным типом элемента HTML. Двумя наиболее распространенными являются элемент или атрибут, которые вы объявляете с помощью E а также А соответственно. Вы также можете ограничить его классом CSS или комментарием, но это не так часто.

C.2 Функция ссылки - это место, куда вы помещаете весь свой код манипулирования DOM. Я обнаружил несколько исключений, но это всегда верно (по крайней мере, в 99% случаев). Это фундаментальное правило AngularJS, и поэтому я подчеркнул его.

C.3 Функция контроллера работает так же, как основной контроллер, который мы определили для приложения, но $ scope объект, который мы передаем, является специфическим для элемента DOM, в котором находится директива.

C.4 AngularJS имеет концепцию изолированной области видимости, которая позволяет вам явно определять, как область действия директивы взаимодействует с внешним миром. Если бы мы не объявили область видимости, директива неявно унаследовала бы от родительской области с отношениями родитель-потомок. Во многих случаях это не оптимально. Изолируя область действия, мы уменьшаем вероятность того, что внешний мир может непреднамеренно и отрицательно повлиять на состояние вашей директивы.

Я объявил двустороннюю привязку данных к Примечание с = символ и выражение, привязанное к ondelete с & символ. Пожалуйста, прочтите документацию AngularJS для полного объяснения изолированной области видимости, поскольку это одна из наиболее сложных тем во фреймворке.

Итак, давайте добавим заметку в DOM.

Как и любой хороший фреймворк, AngularJS имеет несколько действительно замечательных функций прямо из коробки. Одна из самых удобных функций - нг-повтор. Эта директива AngularJS позволяет вам передавать массив объектов и дублирует любой тег, на котором он находится, столько раз, сколько элементов в массиве. В приведенном ниже случае мы перебираем заметки массив и дублирование div элемент и его дочерние элементы на длину заметки множество.

div sticky-note ng-repeat = "заметка в заметках" note = "note" ondelete = "deleteNote (id)">
button type = "button" ng-click = "deleteNote (note.id)"> × / button>
input ng-model = "note.title" ng-change = "updateNote (note)" type = "text">
textarea ng-model = "note.body" ng-change = "updateNote (примечание)"
> {{note.body}} / textarea>
/ div>

Красота нг-повтор заключается в том, что он привязан к любому массиву, который вы передаете, и когда вы добавляете элемент в массив, ваш элемент DOM будет автоматически обновляться. Вы можете пойти дальше и повторить не только стандартные элементы DOM, но и другие настраиваемые директивы. Вот почему вы видите заметка как атрибут элемента.

Есть еще два фрагмента пользовательского кода, которые необходимо уточнить. Мы изолировали прицел на заметки директива о двух свойствах. Первый - это привязка, определенная изолированной областью на Примечание имущество. Это означает, что всякий раз, когда объект примечания изменяется в родительской области, он автоматически обновляет соответствующий объект примечания в директиве и наоборот. Другая определенная изолированная область видимости находится на ondelete атрибут. Это означает, что когда ondelete вызывается в директиве, он будет вызывать любое выражение в ondelete атрибут в элементе DOM, который создает экземпляр директивы.

Когда директива создается, она добавляется в DOM и вызывается функция связывания. Это прекрасная возможность установить для элемента некоторые свойства DOM по умолчанию. Параметр элемента, который мы передаем, на самом деле является объектом jQuery, поэтому мы можем выполнять с ним операции jQuery.

(AngularJS на самом деле поставляется со встроенным подмножеством jQuery, но если вы уже включили полную версию jQuery, AngularJS будет относиться к этому.)

app.directive (’stickyNote’, function (socket) {
var linker = function (scope, element, attrs) {
// Некоторая инициализация DOM для удобства
element.css (’left’, ’10px’);
element.css (’top’, ’50px’);
element.hide (). fadeIn ();
};
});

В приведенном выше коде мы просто помещаем заметку на сцену и постепенно ее увеличиваем.

11. Удаление заметки

Итак, теперь, когда мы можем добавлять и отображать стикеры, пришло время удалить стикеры. Создание и удаление заметок - это добавление и удаление элементов из массива, к которому привязаны заметки. Это ответственность родительской области за поддержание этого массива, поэтому мы отправляем запрос на удаление из директивы, но позволяем родительской области выполнять фактическую тяжелую работу.

Вот почему мы прошли через все проблемы, связанные с созданием изолированной области видимости, определенной выражением в директиве: чтобы директива могла получить событие удаления внутри себя и передать его своему родительскому объекту для обработки.

Обратите внимание на HTML внутри директивы.

button type = "button" ng-click = "deleteNote (note.id)"> × / button>

Само следующее, что я собираюсь сказать, может показаться долгим, но помните, что мы на одной стороне, и это будет иметь смысл после того, как я уточню. Когда нажимается кнопка в правом верхнем углу заметки, мы вызываем deleteNote на контроллере директивы и передавая note.id значение. Затем контроллер вызывает ondelete, который затем выполняет любое связанное нами выражение. Все идет нормально? Мы вызываем локальный метод контроллера, который затем передает его, вызывая любое выражение, определенное в изолированной области. Выражение, которое вызывается для родителя, просто случайно вызывается deleteNote также.

app.directive (’stickyNote’, function (socket) {
var controller = function ($ scope) {
$ scope.deleteNote = function (id) {
$ scope.ondelete ({
я сделал
});
};
};

возвращаться {
ограничить: ’A’,
ссылка: компоновщик,
контроллер: контроллер,
сфера: {
примечание: ’=’,
ondelete: ’&’
}
};
});

(При использовании изолированной области видимости, определенной выражением, параметры отправляются в карте объектов.)

В родительской области deleteNote вызывается и выполняет довольно стандартное удаление с помощью angular.forEach служебная функция для перебора массива заметок. После того, как функция обработала свой местный бизнес, она переходит к следующему этапу и генерирует событие, чтобы остальной мир отреагировал соответствующим образом.

app.controller (’MainCtrl’, function ($ scope, socket) {
$ scope.notes = [];

// Входящий
socket.on (’onNoteDeleted’, function (data) {
$ scope.deleteNote (data.id);
});

// Исходящий
$ scope.deleteNote = function (id) {
var oldNotes = $ scope.notes,
newNotes = [];

angular.forEach (oldNotes, function (note) {
если (note.id! == id) newNotes.push (примечание);
});

$ scope.notes = newNotes;
socket.emit (’deleteNote’, {id: id});
};
});

12. Обновление заметки.

Мы добиваемся фантастических успехов! К настоящему времени я надеюсь, что вы начинаете видеть некоторые закономерности, возникающие в результате этого бурного тура, который мы совершаем. Следующий пункт в списке - функция обновления.

Мы собираемся начать с фактических элементов DOM и проследить их вплоть до сервера и обратно до клиента. Сначала нам нужно знать, когда изменяется заголовок или тело заметки. AngularJS рассматривает элементы формы как часть модели данных, поэтому вы можете мгновенно подключить двустороннюю привязку данных. Для этого используйте нг-модель директиву и введите свойство, к которому вы хотите привязать. В этом случае мы будем использовать note.title а также примечание. тело соответственно.

Когда любое из этих свойств изменяется, мы хотим зафиксировать эту информацию для передачи. Мы достигаем этого с помощью нг-изменение директиву и используйте ее для вызова updateNote и передайте сам объект заметки. AngularJS выполняет очень умную грязную проверку, чтобы определить, находится ли значение того, что находится в нг-модель изменилось, а затем выполняет выражение, которое находится в нг-изменение.

input ng-model = "note.title" ng-change = "updateNote (note)" type = "text">
textarea ng-model = "note.body" ng-change = "updateNote (примечание)"> {{note.body}} / textarea>

Положительные стороны использования нг-изменение заключается в том, что локальная трансформация уже произошла, и мы просто несем ответственность за передачу сообщения. В контроллере updateNote вызывается, и оттуда мы собираемся испустить updateNote событие для нашего сервера, чтобы транслировать его другим клиентам.

app.directive (’stickyNote’, function (socket) {
var controller = function ($ scope) {
$ scope.updateNote = function (note) {
socket.emit (’updateNote’, примечание);
};
};
});

А в контроллере директив мы слушаем onNoteUpdated , чтобы узнать, когда была обновлена ​​заметка от другого клиента, чтобы мы могли обновить нашу локальную версию.

var controller = function ($ scope) {
// Входящий
socket.on (’onNoteUpdated’, function (data) {
// Обновить, если такая же заметка
if (data.id == $ scope.note.id) {

$ scope.note.title = data.title;
$ scope.note.body = data.body;
}
});
};

13. Перемещение заметки

На этом этапе мы в основном объехали детский бассейн CRUD, и жизнь прекрасна! Чтобы произвести впечатление на друзей, мы добавим возможность перемещать заметки по экрану и обновлять координаты в реальном времени. Не паникуйте - это всего лишь несколько строк кода. Вся эта тяжелая работа окупится. Я обещаю!

Мы пригласили на вечеринку специального гостя, jQueryUI, и все это сделали для перетаскиваемых файлов. Добавление возможности перетаскивать заметку локально занимает всего одну строку кода. Если вы добавите element.draggable (); в свою функцию компоновщика вы услышите «Глаз тигра» от Survivor, потому что теперь вы можете перетаскивать свои заметки.

Мы хотим знать, когда перетаскивание прекратилось, и фиксировать новые координаты для прохождения. jQueryUI был создан некоторыми очень умными людьми, поэтому, когда перетаскивание останавливается, вам просто нужно определить функцию обратного вызова для события остановки. Мы хватаем note.id вне области видимости, а также левое и верхнее значения CSS из ui объект. Обладая этими знаниями, мы делаем то, что делали все это время: испускаем!

app.directive (’stickyNote’, function (socket) {
var linker = function (scope, element, attrs) {
element.draggable ({
stop: function (event, ui) {
socket.emit (’moveNote’, {
id: scope.note.id,
x: ui.position.left,
y: ui.position.top
});
}
});

socket.on (’onNoteMoved’, function (data) {
// Обновить, если такая же заметка
if (data.id == scope.note.id) {
element.animate ({
слева: data.x,
вверху: data.y
});
}
});
};
});

На этом этапе неудивительно, что мы также отслеживаем событие, связанное с перемещением, из службы сокета. В данном случае это onNoteMoved событие, и если заметка совпадает, мы обновляем левое и верхнее свойства CSS. Бац! Сделанный!

14. Бонус

Это бонусный раздел, который я бы не включил, если бы не был абсолютно уверен, что вы сможете достичь его менее чем за 10 минут. Мы собираемся развернуть его на реальном сервере (я до сих пор удивляюсь, насколько это легко сделать).

Во-первых, вам нужно подписаться на бесплатную пробную версию Nodejitsu. Пробная версия бесплатна в течение 30 дней, что идеально подходит для того, чтобы намочить ноги.

После того, как вы создали свою учетную запись, вам необходимо установить пакет jitsu, что вы можете сделать из командной строки через $ npm установить jitsu -g.

Затем вам нужно войти в систему из командной строки через $ jitsu войти и введите свои учетные данные.

Убедитесь, что вы находитесь в своем приложении напрямую, введите $ jitsu развернуть и задавайте вопросы. Я обычно оставляю как можно больше значений по умолчанию, что означает, что я даю своему приложению имя, но не субдомен и т. Д.

И, мои дорогие друзья, это все! Вы получите URL-адрес своего приложения из выходных данных сервера, когда оно будет развернуто и будет готово к работе.

15. Заключение

В этой статье мы рассмотрели много вопросов, связанных с AngularJS, и я надеюсь, что вам понравился этот процесс. Я думаю, что это действительно здорово, что вы можете достичь с помощью AngularJS и Socket.io примерно в 200 строках кода.

Было несколько вещей, которые я не затронул, чтобы сосредоточиться на основных моментах, но я рекомендую вам загрузить исходный код и поэкспериментировать с приложением. Мы заложили прочный фундамент, но есть еще много функций, которые вы могли бы добавить. Получите взлом!

Лукас Руббелке (Lukas Ruebbelke) - энтузиаст технологий и является соавтором AngularJS в действии для Manning Publications. Его любимое занятие - так же увлекать людей новыми технологиями, как он сам. Он руководит группой пользователей веб-приложений Phoenix и провел несколько хакатонов со своими товарищами по преступности.

Понравилось это? Прочтите это!

  • Как сделать приложение
  • Наши любимые веб-шрифты - и они не стоят ни копейки
  • Узнайте, что будет дальше с дополненной реальностью
  • Скачать текстуры бесплатно: высокое разрешение и готово к использованию.
Рекомендовано вам
Этот новый фирменный стиль сочетает в себе арт-деко с современным стилем.
Далее

Этот новый фирменный стиль сочетает в себе арт-деко с современным стилем.

Kokoro & Moi создали айдентику для финской пищевой компании Fazer cafe, включая историю бренда, дизайн логотипа, нестандартную типографику и печатные материалы. Он работал с дизайнерами интерьеров...
Смоделируйте инопланетное пиратское существо в ZBrush
Далее

Смоделируйте инопланетное пиратское существо в ZBrush

Чтобы помочь вам научиться создавать трехмерного инопланетного пирата, я покажу вам, как я вылепил лицо своего существа, Воргрока (вверху), и поместил его в драматическую позу. ZBru h - мое главное ор...
Как нарисовать человека, наполовину погруженного в воду
Далее

Как нарисовать человека, наполовину погруженного в воду

Многое зависит от выбранного вами сценария того, как будет выглядеть вода. Поверхность неспокойного моря сильно отличается от спокойного пруда - я выберу последний.Для начала я выбираю по своей фигуре...