Создание мультиплеера в unity3d. Информационный портал по безопасности. Обработка на клиенте пришедшего айдишника


В природе не существует строго прямых углов. Это нужно помнить при моделировании, чтобы 3d модели получались красивыми и реалистичными. Поэтому важно научиться скруглять углы объектов. Рассмотрим 2 способа скругления углов: с помощью модификатора MeshSmooth и инструмента Chamfer (Фаска).

Модификатор MeshSmooth

Для примеры мы создали пару кубов, поделив их гранями.

Во вкладке Modify в листе Модификаторов (Modifier List) находим MeshSmooth. Щелкаем и применяем к нашим объектам.

Разбираемся в параметрах.

  • Iterations - число проводимых действий. Чем выше показатель повторений модификатора, тем плотнее сетка.
  • Smoothness задает гладкость. Лучше оставить это значение = 1. Это даст более точною сетку.

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

Также остроту и форму углов можно подредактировать, работая с отдельными точками. Нажимаем на модификатор, выбираем Vertex. Меняем расстояние и положение ключевых точек так, как нам нужно.

Модификатор можно применять как к целому объекту (при моделировании подушек, диванов, кресел), так и к отдельной части (закруглить край стола).

Создание фаски

Конвертируем нашу модель в Editable Poly, вызвав панель ПКМ.

Переходим во вкладку Modify. Выбираем грань, которую нужно скруглить. Чтобы сделать фаску, воспользуемся инструментом Chamfer.

Нажимаем на квадратик возле кнопки Chamfer. Открывается панель параметров. Задаем нужные значения.

Фаска готова. Нажимаем зеленую галочку, чтобы сохранить результат, или крестик, чтобы снять фаску.

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

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

Сразу отмечу некоторые моменты:
— урок расчитан на начинающих-продолжающих пользователей, которые ощутили тяжесть негативных последствий булевых операций.
— пример был реализован в 5 версии 3D Studio Max, поэтому с наличием тех или иных кнопок проблем быть не должно.
— задняя сетка в окнах проекции отключена клавишей G (для удобства работы).
— габаритный контейнер вокруг объекта также отключен, клавиша — J (также для удобства работы).
— для отображения граней (edges) на поверхности модели используйте клавишу F4 .

1. Создаем объект Plane с одним сегментом по длине и ширине:

2. Конвертируем Plane в Editable Poly , переключаемся на уровень граней (Edges) и выделяем две грани, как показано на рисунке:

Нам необходимо добавить дополнительные грани по одной с каждой стороны, их пересечение создаст центр нашего полигона. Для создания новых граней используем команду Connect .

Количество новых сегментов (граней) — 1

Результат должен получится таким:

Выделяем первую и нажимаем команду Ring , чтобы выделить "кольцо" граней.

После выделяем соседнюю и снова Ring .

После выделения граней применяем Connect .
Далее выделяем противоположные грани, пользуясь командой Ring .

И при помощи Connect с одним сегментом создаем 2 новых грани.
Результат. Конечно, можно было бы сразу создать Plane с 4-мя сегментами по длине и ширине, но нам важно научиться использовать все команды полигонального моделирования, да и предугадать сколько сегментов может понадобится очень сложно, поэтому в полигональном моделировании часто бывает лучше добавить, чем удалить...

4. Теперь передвинем созданные грани к крайним. Выделим первую грань и нажмем команду Loop .

При помощи этой команды мы получим выделение всей петли граней. После этого переместим грани как показано на рисунке.

Аналогичным способом поступим и с другими сторонами полигона.

В результате должно получиться вот так. Для чего мы это делали? Для того, чтобы при сглаживании модели края основания держали форму прямоугольника а не сглаживались в овал. Чем ближе друг к другу расположены грани, тем острее (грубее) угол при сглаживании.

5. Теперь нужно создать контур, по которому мы будем создавать на поверхности круглое отверстие. Для этого создадим сплайн Circle (окружность) с настройками Sides: 8 , Steps: 1

Для точности построения включим трехмерную привязку.

Щелкаем правой кнопкой мыши и устанавливаем привязку к вершинам (Vertex).

От центра строим окружность.

Теперь переходим к редактированию нашего объекта. В режиме Polygon выбираем команду Cut и разрезаем объект по точкам созданной окружности.

Получается такой результат.

Выделяем указанные грани и удаляем их,

выбрав команду Remove .

Модель принимает такой вид. Контур для создания круглого отверстия сделан.

6. Выделяем полигоны внутри контура

и применяем к ним команду Insert , которая позволяет добавить набор полигонов на основе выделенных.

Применяем команду Insert с небольшим расстоянием.

Дополнительные полигоны необходимы также для корректного сглаживания модели, чтобы в этом месте был достаточно острый угол.
Не сбрасывая выделения полигонов, выполняем выдавливание отверстия командой Extrude . Iterations:2 На этом все! Удачного моделинга.

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


    Я вкратце опишу, как можно это сделать с помощью HLAPI на Unet, но не через NetworkingManager, а чуть более низкоуровнево. По доброй традиции приложу пример своей реализации такого клиент-серверного взаимодействия. Пример прошу не судить строго, так как я прекрасно понимаю, что архитектура данного решения никуда не годится и создаёт кучу проблем в перспективе. Моей целью было максимально быстро (за выходные) написать систему, в которой можно показать принцип работы с сетью. Также скажу с какими проблемами пришлось столкнуться. В данном примере реализации предполагается, что сервер так же является Unity приложением.

    В интернете самым частым примером того, как сделать мультиплеер является чат, но я люблю игры, и делать чат мне показалось скучным. Поэтому я решил разобрать то, как можно сделать мультиплеер на примере крестиков-ноликов. Допустим, что вся логика игры, определения победителя, смены ходов и т. п. написаны, и нам осталось прикрутить сервер. В простейшем случае в крестиках-ноликах нам надо обрабатывать 2 сообщения. Определение порядка хода (раздача айдишников) и захват клетки.

    В целом игра в данном примере работает очень просто. Есть 2 сцены. Загрузочная и геймплейная. При загрузке геймплейной сцены у нас генерируется поле на котором играют игроки. Там же происходит проверка условия победы, есть классы отвечающие за работу UI, а так же порядок ходов и в целом логику игры, но нам это не особо интересно. Основные классы, которые отвечают за сетку - это Server, Client, NetManager и отдельный файл для сообщений NetMessages и определённый в нём enum MyMsgType. Они представляют из себя обёртку над средствами Unet. С точки зрения Unet основные классы, которые мы будем использовать - это NetworkClient, NetworkServer, MessageBase и MsgType. Что это за классы?

    Самые простые классы - это MessageBase и MsgType. Первый - это абстрактный класс, от которого надо наследовать все наши сообщения, чтобы пересылать их между клиентом и сервером. MsgType - это просто класс, который хранит в себе константы, отвечающие за определённый набор вшитых в Unet сообщений.

    NetworkServer - это синглтон, который предоставляет возможность обрабатывать общение с удалёнными клиентами. Под капотом он использует экземпляр NetworkServerSimple и по сути представляет из себя удобную обёртку над ним. Для начала нам надо запустить сервер на определённом порте. Для этого необходимо вызвать метод Listen(int serverPort) - этот метод запускает сервер на порте serverPort. (Напомню, что все порты в диапазоне от 0 до 1023 являются системными и их не стоит использовать в качестве параметра данного метода)

    Отлично сервер работает и слушает какой-то порт. Теперь нужно, чтобы он реагировал на сообщения. Для этого нужно зарегистрировать хэндлер с помощью метода RegisterHandler(short msgType, Networking.NetworkMessageDelegate handler) . Данный метод принимает параметром тип сообщения и делегат. Делегат в свою очередь должен принимать входным параметром NetworkMessage. Допустим мы хотим, чтобы в тот момент, когда к нему присоединялся игрок на сервере начала загружаться геймплейная сцена, а так же раздавались айдишники игроков. Тогда нужно зарегистрировать хэндлер на соответствующее сообщение, а так же реализовать метод, который мы будем передавать в качестве делегата для регистрации.

    Выглядит это примерно так:

    Пример регистрации хэндлера и делегата

    NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); private void OnConnect(NetworkMessage msg) { Debug.Log(string.Concat("Connected: ", msg.conn.address)); var connId = msg.conn.connectionId; if (NetworkServer.connections.Count > Constants.PLAYERS_COUNT) { SendPlayerID(connId, -1); } else { int index = Random.Range(0, Constants.PLAYERS_IDS.Length); SendPlayerID(connId, Constants.PLAYERS_IDS); _CurrentUser.PlayerID = Constants.PLAYERS_IDS[(index + 1) % Constants.PLAYERS_COUNT]; SceneManager.LoadScene(1); } }


    Теперь метод OnConnect будет вызываться при каждом коннекте какого-то пользователя. Стоит уточнить, что в данной реализации айдишники определяют порядок хода, поэтому при первом коннекте выбираются айдишники для клиента и сервера. Остальные клиенты автоматом получают айди равный -1, что означает, что этот клиент является зрителем.

    Простой сервер есть. Теперь бы клиенты не помешали. Для этого воспользуемся классом NetworkClient. Для того, чтобы присоединиться к серверу надо просто вызвать метод Connect(string serverIp, int serverPort) . В качестве порта мы устанавливаем порт, который слушает наш сервер, в качестве айпи ставим localhost, если мы тестируем наше приложение на одной машине или же айпи компа в локальной сети, который мы используем в качестве сервера (Узнать его можно или в настройках сети, или в консоли с помощью команды ipconfig на том компе, который будет выступать в роли сервера).

    Замечательно, мы может подконнектиться. Ранее говорилось, что сервак у нас раздаёт айдишники. Значит нам надо во-первых, послать сообщение о айдишнике, а так же зарегистрировать хэндлер этого сообщения на клиенте. Как упоминалось ранее все сообщения надо наследовать от MessageBase. Для начала определим их (я предпочитаю это делать в отдельном файле):

    Сообщение и его тип

    public class PlayerIDMessage: MessageBase { public int PlayerID; } public enum MyMsgType: short { PlayerID = MsgType.Highest + 1, }


    Чтобы послать данное сообщение вызываем на клиенте метод Send(short msgType, Networking.MessageBase msg) , который отправит сообщение msg «типа» msgType серверу, или на сервере один из методов в зависимости от цели SendToAll(short msgType, Networking.MessageBase msg) или SendToClient(int connectionId, short msgType, Networking.MessageBase msg) , где connectionId - это id определённого клиента.

    Обработка на клиенте пришедшего айдишника

    private void OnPlayerID(NetworkMessage msg) { PlayerIDMessage message = msg.reader.ReadMessage(); _CurrentUser.PlayerID = message.PlayerID; SceneManager.LoadScene(1); }


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

    О чём ещё хочется сказать, и на что пришлось потратить время, так это пару вещей, которые вам могут быть не очевидны, если вы не работали с сетями.

    1. Проверьте, что редактор юнити не заблокирован вашим фаерволом для общения по протоколам TCP и UDP. Однажды я потратил на это некоторое время, при том, что добрался до фаерволла и поставил нужный порт в исключения, но не проверил то, что редактор незаблокирован.

    2. Передавайте в сообщениях value-type или сериализуемые reference-type, а так же не передавайте наследников MonoBehavior. Так же важно понимать что reference-type в данном случае будет передавать копию объекта, а не сам объект, и его надо обрабатывать соответствующим образом.

    В случае, когда мы говорим про локальную сеть и заранее известное железо, нет многих проблем возникающих в случае “настоящего” мультиплеера. Практически не нужно задумываться о задержках, флуктуациях, потерях пакетов и прочем. Поэтому такое решение может быть полезно, хотя и эти проблемы можно решать с таким подходом до некоторого предела. В сравнении с более высоким уровнем абстракции в Unet, через NetworkManager, NetworkBehavior и т.п., оно предоставляет более понятную и очевидную гибкость (на мой взгляд), если на клиентах требуется загружать разные сцены и т. п., а скажем сервер используется, как стриминг, и показывает то, что отображается на девайсе игрока, принимая его позицию и поворот + дублируя происходящее на своей стороне. В остальных случаях, когда необходимо решение быстрее и вы умеете работать с сетью, Unet предоставляет возможность писать на транспортном уровне.

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

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

    Я вкратце опишу, как можно это сделать с помощью HLAPI на Unet, но не через NetworkingManager, а чуть более низкоуровнево. По доброй традиции приложу пример своей реализации такого клиент-серверного взаимодействия. Пример прошу не судить строго, так как я прекрасно понимаю, что архитектура данного решения никуда не годится и создаёт кучу проблем в перспективе. Моей целью было максимально быстро (за выходные) написать систему, в которой можно показать принцип работы с сетью. Также скажу с какими проблемами пришлось столкнуться. В данном примере реализации предполагается, что сервер так же является Unity приложением.

    В интернете самым частым примером того, как сделать мультиплеер является чат, но я люблю игры, и делать чат мне показалось скучным. Поэтому я решил разобрать то, как можно сделать мультиплеер на примере крестиков-ноликов. Допустим, что вся логика игры, определения победителя, смены ходов и т. п. написаны, и нам осталось прикрутить сервер. В простейшем случае в крестиках-ноликах нам надо обрабатывать 2 сообщения. Определение порядка хода (раздача айдишников) и захват клетки.


    В целом игра в данном примере работает очень просто. Есть 2 сцены. Загрузочная и геймплейная. При загрузке геймплейной сцены у нас генерируется поле на котором играют игроки. Там же происходит проверка условия победы, есть классы отвечающие за работу UI, а так же порядок ходов и в целом логику игры, но нам это не особо интересно. Основные классы, которые отвечают за сетку - это Server, Client, NetManager и отдельный файл для сообщений NetMessages и определённый в нём enum MyMsgType. Они представляют из себя обёртку над средствами Unet. С точки зрения Unet основные классы, которые мы будем использовать - это NetworkClient, NetworkServer, MessageBase и MsgType. Что это за классы?

    Самые простые классы - это MessageBase и MsgType. Первый - это абстрактный класс, от которого надо наследовать все наши сообщения, чтобы пересылать их между клиентом и сервером. MsgType - это просто класс, который хранит в себе константы, отвечающие за определённый набор вшитых в Unet сообщений.


    NetworkServer - это синглтон, который предоставляет возможность обрабатывать общение с удалёнными клиентами. Под капотом он использует экземпляр NetworkServerSimple и по сути представляет из себя удобную обёртку над ним. Для начала нам надо запустить сервер на определённом порте. Для этого необходимо вызвать метод Listen(int serverPort) - этот метод запускает сервер на порте serverPort. (Напомню, что все порты в диапазоне от 0 до 1023 являются системными и их не стоит использовать в качестве параметра данного метода)

    Отлично сервер работает и слушает какой-то порт. Теперь нужно, чтобы он реагировал на сообщения. Для этого нужно зарегистрировать хэндлер с помощью метода RegisterHandler(short msgType, Networking.NetworkMessageDelegate handler) . Данный метод принимает параметром тип сообщения и делегат. Делегат в свою очередь должен принимать входным параметром NetworkMessage. Допустим мы хотим, чтобы в тот момент, когда к нему присоединялся игрок на сервере начала загружаться геймплейная сцена, а так же раздавались айдишники игроков. Тогда нужно зарегистрировать хэндлер на соответствующее сообщение, а так же реализовать метод, который мы будем передавать в качестве делегата для регистрации.

    Выглядит это примерно так:

    Пример регистрации хэндлера и делегата

    NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); private void OnConnect(NetworkMessage msg) { Debug.Log(string.Concat("Connected: ", msg.conn.address)); var connId = msg.conn.connectionId; if (NetworkServer.connections.Count > Constants.PLAYERS_COUNT) { SendPlayerID(connId, -1); } else { int index = Random.Range(0, Constants.PLAYERS_IDS.Length); SendPlayerID(connId, Constants.PLAYERS_IDS); _CurrentUser.PlayerID = Constants.PLAYERS_IDS[(index + 1) % Constants.PLAYERS_COUNT]; SceneManager.LoadScene(1); } }

    Теперь метод OnConnect будет вызываться при каждом коннекте какого-то пользователя. Стоит уточнить, что в данной реализации айдишники определяют порядок хода, поэтому при первом коннекте выбираются айдишники для клиента и сервера. Остальные клиенты автоматом получают айди равный -1, что означает, что этот клиент является зрителем.

    Простой сервер есть. Теперь бы клиенты не помешали. Для этого воспользуемся классом NetworkClient. Для того, чтобы присоединиться к серверу надо просто вызвать метод Connect(string serverIp, int serverPort) . В качестве порта мы устанавливаем порт, который слушает наш сервер, в качестве айпи ставим localhost, если мы тестируем наше приложение на одной машине или же айпи компа в локальной сети, который мы используем в качестве сервера (Узнать его можно или в настройках сети, или в консоли с помощью команды ipconfig на том компе, который будет выступать в роли сервера).

    Замечательно, мы может подконнектиться. Ранее говорилось, что сервак у нас раздаёт айдишники. Значит нам надо во-первых, послать сообщение о айдишнике, а так же зарегистрировать хэндлер этого сообщения на клиенте. Как упоминалось ранее все сообщения надо наследовать от MessageBase. Для начала определим их (я предпочитаю это делать в отдельном файле):

    Сообщение и его тип

    Public class PlayerIDMessage: MessageBase { public int PlayerID; } public enum MyMsgType: short { PlayerID = MsgType.Highest + 1, }

    Чтобы послать данное сообщение вызываем на клиенте метод Send(short msgType, Networking.MessageBase msg) , который отправит сообщение msg «типа» msgType серверу, или на сервере один из методов в зависимости от цели SendToAll(short msgType, Networking.MessageBase msg) или SendToClient(int connectionId, short msgType, Networking.MessageBase msg) , где connectionId - это id определённого клиента.

    Обработка на клиенте пришедшего айдишника

    Private void OnPlayerID(NetworkMessage msg) { PlayerIDMessage message = msg.reader.ReadMessage(); _CurrentUser.PlayerID = message.PlayerID; SceneManager.LoadScene(1); }

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

    О чём ещё хочется сказать, и на что пришлось потратить время, так это пару вещей, которые вам могут быть не очевидны, если вы не работали с сетями.

    1. Проверьте, что редактор юнити не заблокирован вашим фаерволом для общения по протоколам TCP и UDP. Однажды я потратил на это некоторое время, при том, что добрался до фаерволла и поставил нужный порт в исключения, но не проверил то, что редактор незаблокирован.

    2. Передавайте в сообщениях value-type. Reference-type будут передавать чушь, так как по адресу который вы хотите передать в другую аппу, неизвестно что лежит. (Я думаю, что это и так понятно тем, кто понимает, как работают value и reference type)

    В случае, когда мы говорим про локальную сеть и заранее известное железо, нет многих проблем возникающих в случае “настоящего” мультиплеера. Практически не нужно задумываться о задержках, флуктуациях, потерях пакетов и прочем. Поэтому такое решение может быть полезно, хотя и эти проблемы можно решать с таким подходом до некоторого предела. В сравнении с более высоким уровнем абстракции в Unet, через NetworkManager, NetworkBehavior и т.п., оно предоставляет более понятную и очевидную гибкость (на мой взгляд), если на клиентах требуется загружать разные сцены и т. п., а скажем сервер используется, как стриминг, и показывает то, что отображается на девайсе игрока, принимая его позицию и поворот + дублируя происходящее на своей стороне. В остальных случаях, когда необходимо решение быстрее и вы умеете работать с сетью, Unet предоставляет возможность писать на транспортном уровне.

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

    Спасибо за внимание!