Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Основы многопользовательской игры на Unity3D

Anna | 17.06.2014 | нет комментариев
Здравствуй, Програпрогр!

Я, как и многие из вас, огромный поклонник многопользовательских игр. В них меня прельщает в основном дух соревнования и вероятность приобретать совершенствования, накапливая достижения. Да и сама идея выхода в свет все большего числа игр данного типа побуждает к действию.
С недавнего времени я и сам взялся за разработку собственного плана. И от того что на Програпрогре статей на эту тематику не обнаружил – решил поделиться своим навыком написания многопользовательской игры на движке Unity3D. Также хочу рассказать о компонентах Network и NetworkView, признаке RPC и встроенных способах-ивентах. В конце статьи подан пример игры и, разумеется, сам план для Unity. Итак…

Класс Network

Данный класс необходим для организации соединения «заказчик-сервер». Основные функции: создание сервера, подключение к серверу, создание сетевого экземпляра префаба.

Основные способы:

Network.Connect (string host, int remotePort, string password = “”) – исполняет подключение к серверуhost с портом remotePort и паролем password. Способ возвращает перечисление NetworkConnectionError.

Network.InitializeServer(int connections, int listenPort, bool useNat) – создает сервер с максимально разрешенным числом подключений connections; порт входящих подключений listenPort, а также useNat: применять либо нет NAT. Также возвращает перечисление NetworkConnectionError.

Network.InitializeSecurity() – вызывается перед Network.InitializeServer() для охраны от читерства. Подробности в официальной документации. Не вызывать на заказчике!

Network.Instantiate(Object prefab, Vector3 position, Quaternion rotation, int group) – создает экземпляр префаба prefab в сети в позиции position с поворотом rotation и группой group. Возвращает каждый сделанный объект, с которым позже создания дозволено исполнить добавочные действия. Подробности – дальше в статье.

Основные свойства:

bool Network.isClient и bool Network.isServer – определяют, является ваша игра сервером либо заказчиком. Оба свойства являются false, если не был сделан сервер либо не было подключения к серверу.

string Network.incomingPassword – качество задает пароль для входящих подключений.

NetworkPlayer Network.player – возвращает экземпляр локального игрока NetworkPlayer.

NetworkPeerType Network.peerType – возвращает нынешнее состояние подключения: Disconnected(отключен), Server (запущен как сервер), Client (подключен к серверу), Connecting (попытка, в процессе подключения).

NetworkPlayer[] Network.connections – возвращает всех подключенных игроков. На заказчике возвращает только игрока сервера.

Основные ивенты (для унаследованного от MonoBehaviour):

OnConnectedToServer() – вызывается на заказчике при удачном подключении к серверу.

OnDisconnectedFromServer(NetworkDisconnection info) – вызывается на заказчике при отключении от сервера и на сервере при заключении подключений Network.Disconnect(). В info содержится повод отключения: LostConnection (потеря связи) и Disconnected (при удачном отключении).

OnFailedToConnect(NetworkConnectionError error) — вызывается на заказчике при ошибке подключения.error содержит ошибку типа NetworkConnectionError.

OnNetworkInstantiate(NetworkMessageInfo info) — вызывается на заказчике и сервере, если был сделан новейший экземпляр способом Network.Instantiate(). Содержит info типа NetworkMessageInfo.

OnPlayerConnected(NetworkPlayer player) — вызывается на сервере при удачном подключении заказчика и содержит player типа NetworkPlayer.

OnPlayerDisconnected(NetworkPlayer player) — вызывается на сервере при отключении заказчика и содержит player типа NetworkPlayer.

OnServerInitialized() — вызывается на сервере, позже того как сервер был удачно сделан.

OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) — значимый ивент для синхронизации компонента с сетью. Подробности – дальше в статье.

Класс NetwokView

Данный класс существует также и как компонент для Unity, и предуготовлен он для синхронизации компонентов в сети и для вызова RPC.
Владеет такими свойствами синхронизации NetworkStateSynchronization:

  • Off — не исполняет синхронизацию объекта, впрочем разрешает вызывать удаленные процедуры.
  • ReliableDeltaCompressed — исполняет передачу пакетов поочередно и проверяет, доставлен ли пакет (аналогично протоколу TCP).
  • Unreliable — исполняет стремительную отправку пакетов, не гарантируя доставки (аналогично протоколу UDP).
Основные способы:

networkView.RPC(string name, RPCMode mode, params object[] args) — вызывает удаленную процедуру name, mode определяет получателей, args – доводы для передачи процедуре.

networkView.RPC(string name, NetworkPlayer target, params object[] args) – то же, что и предшествующий способ, впрочем исполняет отправку определенному игроку NetworkPlayer.

Основные свойства:

bool networkView.isMine – качество, определяющее, является ли объект локальным. Крайне Зачастую применяется для проверки обладателя объекта.

Component networkView.observed – компонент, тот, что будет синхронизироваться. Если это скрипт, то он должен содержать способ OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info), упомянутый выше.

NetworkPlayer networkView.owner – качество, возвращающее обладателя объекта.

NetworkStateSynchronization networkView.stateSynchronization — тип синхронизации: Off,ReliableDeltaCompressedUnreliable.

NetworkViewID networkView.viewID — неповторимый идентификатор в сети для NetworkView.

Признак RPC

Согласно данным из Википедии RPC — класс спецтехнологий, разрешающих компьютерным программам вызывать функции либо процедуры в ином адресном пространстве (как правило, на удалённых компьютерах).
Признак применяется для назначения способа, вызываемого из сети. Для его функционирования нужно добавить компонент NetworkView.

Способ OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)

Данный способ применяется для синхронизации компонента в сети. Он вызывается каждый раз при приобретении либо отправке данных по сети.
Вот типы данных, которые могут быть получены/отправлены способом Serialize: bool, char, short, int, float, Quaternion, Vector3, NetworkPlayer, NetworkViewID.
Для проверки, идет ли прием либо передача, применяются свойства isReading либо isWriting.

Привожу пример применения:

void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) {
	Vector3 syncPosition = Vector3.zero; // непременно должен быть инициализирован перед приемом
	if (stream.isWriting) {
		syncPosition = rigidbody.position; // считываем нынешнюю позицию
		stream.Serialize(ref syncPosition); // синхронизируем с сетью
	} else {
		stream.Serialize(ref syncPosition); // получаем позицию из сети
		rigidbody.position = syncPosition; // записываем позицию в наш объект.
	}
}

Данный пример не безупречен, от того что при его работе наши объекты будут «дергаться». Дабы избежать этого, необходимо воспользоваться интерполяцией. Подробнее – дальше в статье.

Интерполяция

Суть интерполяции заключается в том, что между чтением расположения из сети мы плавно перемещаем наш объект через функцию Lerp во время обновления экрана.
Берем нынешнюю позицию как предисловие и синхронизированную — как конец, и по мере обновления кадров перемещаем наш объект.

Подробнее о способах оптимизации синхронизации по сети глядите на сайте разработчиков: Valve Developer Community — Source Multiplayer Networking

Пример многопользовательской игры

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

Создаем скрипт ServerSide.cs и пишем туда следующее:

using UnityEngine;
using System.Collections;

[RequireComponent( typeof( NetworkView ) )] // информирует Unity о том, что нам необходим компонент NetworkView. Данному компоненту NetworkStateSynchronization дозволено выставить Off.

public class ServerSide : MonoBehaviour {
	private int playerCount = 0; // хранит число подключенных игроков
	public int PlayersCount { get { return playerCount; } } // публичный доступ для внешних компонентов касательно числа игроков на сервере

    void OnServerInitialized() {
        SendMessage( "SpawnPlayer", "Player Server" ); // создаем локального игрока сервера
    }

    void OnPlayerConnected( NetworkPlayer player ) {
		  playerCount; // при подключении всякого нового игрока увеличиваем число подключенных игроков
        networkView.RPC( "SpawnPlayer", player, "Player "   playerCount.ToString() ); // вызываем у игрока процедуру создания экземпляра префаба
    }

    void OnPlayerDisconnected( NetworkPlayer player ) {
        --playerCount; // сокращаем число игроков
        Network.RemoveRPCs( player ); // очищаем список процедур игрока
        Network.DestroyPlayerObjects( player ); // уничтожаем все объекты игрока
    }
}

Сейчас создаем скрипт заказчика ClientSide.cs:

using UnityEngine;
using System.Collections;

[RequireComponent( typeof( NetworkView ) )] // уведомляет Unity о том, что нам необходим компонент NetworkView. Данному компоненту NetworkStateSynchronization дозволено выставить Off.

public class ClientSide : MonoBehaviour {
    public GameObject playerPrefab; // префаб игрока, тот, что будет сделан в процессе игры
    public Vector2 spawnArea = new Vector2( 8.0f, 8.0f ); // зона спауна

	private Vector3 RandomPosition { // случайная позиция в зоне спауна
        get {
            return transform.position  
                    transform.right * ( Random.Range( 0.0f, spawnArea.x ) - spawnArea.x * 0.5f )  
                    transform.forward * ( Random.Range( 0.0f, spawnArea.y ) - spawnArea.y * 0.5f );
        }
    }

    [RPC] // уведомляет Unity о том, что данный способ дозволено вызвать из сети
    private void SpawnPlayer( string playerName ) {
        Vector3 position = RandomPosition; // делаем случайную позицию создания персонажа
        GameObject newPlayer = Network.Instantiate( playerPrefab, position, Quaternion.LookRotation( transform.position - position, Vector3.up ), 0 ) as GameObject; // создаем нового персонажа в сети
        newPlayer.BroadcastMessage( "SetPlayerName", playerName ); // задаем сделанному персонажу имя (оно будет механически синхронизировано по сети)
    }

	void OnDisconnectedFromServer( NetworkDisconnection info ) {
        Network.DestroyPlayerObjects( Network.player ); // удаляемся из игры
    }
}

Таким образом, клиентская и серверная логика есть, сейчас для нее необходимо сделать управлениеMainMenu.cs:

using UnityEngine;
using System.Collections;

public class MultiplayerMenu : MonoBehaviour {
    const int NETWORK_PORT = 4585; // сетевой порт
    const int MAX_CONNECTIONS = 20; // наивысшее число входящих подключений
    const bool USE_NAT = false; // применять NAT?

    private string remoteServer = "127.0.0.1"; // адрес сервера (также дозволено localhost)

    void OnGUI() {
        if ( Network.peerType == NetworkPeerType.Disconnected ) { // если не подключен
            if ( GUILayout.Button( "Start Server" ) ) { // кнопка «запустить сервер»
                Network.InitializeSecurity(); // инициализируем охрану
                Network.InitializeServer( MAX_CONNECTIONS, NETWORK_PORT, USE_NAT ); // запускаем сервер
            }
            GUILayout.Space(30f); // отступ
            remoteServer = GUILayout.TextField( remoteServer ); // поле адреса сервера
            if ( GUILayout.Button( "Connect to server" ) ) { // кнопка «подключиться»
                Network.Connect( remoteServer, NETWORK_PORT ); // подключаемся к серверу
            }
        } else if ( Network.peerType == NetworkPeerType.Connecting ) { // во время подключения
            GUILayout.Label( "Trying to connect to server" ); // выводим текст
        } else { // в остальных слтакже сокращает в инспекторе число компонентов на объекте. 
К примеру, у меня был случай: когда, на 1-й взор, все было сделано верно, впрочем мой скрипт не делал интерполяцию, и все мои действия в синхронизации игнорировал. Так вот оплошностью оказалось то, что Observed был не моим скриптом, а трансформ объекта.

Выходит, сейчас у нас есть все нужные скрипты для написания мини-игры. 
Создаем пустой объект и назначаем ему скрипты MultiplayerMenu, ServerSide, ClientSide. 
Создаем плоскость и немножко спускаем. 
Создаем префаб игрока (в моем примере это будут шары). Создаем объект «сфера», назначаем ему скрипт PlayerControls и добавляем в префаб. Префаб перетягиваем на ClientSide в поле Player Prefab. 
На этом все, компилируем план (не забывая в настройках игрока включить Run in background) и запускаем несколько раз. В одном из окон жмем сервер, на остальных – заказчик, и глядим на итог.

Ссылка на план.
*В плане могут быть логические ошибки, но на суть данной статьи они не влияют.

Всех благодарствую за внимание!
Хочу фуроров в создании многопользовательских игр!


Источник: programmingmaster.ru
Оставить комментарий
БАЗА ЗНАНИЙ
СЛУЧАЙНАЯ СТАТЬЯ
СЛУЧАЙНЫЙ БЛОГ
СЛУЧАЙНЫЙ МОД
СЛУЧАЙНЫЙ СКИН
НОВЫЕ МОДЫ
НОВЫЕ СКИНЫ
НАКОПЛЕННЫЙ ОПЫТ
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB