Многопользовательские игры в реальном времени с Photon
Кликните по полу, чтобы переместиться
Полный проект, включая матчмейкинг, можно найти здесь.
Photon (также известный как PUN) используется во многих играх и имеет доступный JavaScript SDK для игр на HTML5.
Photon бесплатен для проектов с не более чем 20 онлайн-игроками (CCU).
Вы узнаете
- Как добавить Photon SDK в PlayCanvas
- Реализация многопользовательской игры с Photon
Настройка
Проект PlayCanvas
Мы начинаем с создания форка учебного проекта здесь.
Учетная запись Photon
Для использования SDK и просмотра документации требуется регистрация учетной записи.
Создайте свою учетную запись Photon здесь - (Photon Engine).
Создать новое приложение
Нажмите CREATE NEW APP на панели инструментов
Выберите тип Photon и имя приложения.
Введите следующее
- Тип Photon: RealTime
- Имя: PlayCanvas-Photon и т.д.
Копирование AppID
Пожалуйста, запишите этот AppId, так как он вам понадобится в будущем.
Загрузка SDK
Загрузите SDK с панели инструментов.
Нажмите SDK на панели инструментов
Выберите RealTime JavaScript
Нажмите Загрузить SDK
Распакуйте SDK
SDK будет загружен в формате ZIP, распакуйте его: photon-javascript-sdk_vX-X-X-X
→ lib
→
Photon-Javascript_SDK.min.js
.
Импорт SDK
Импортируйте только что загруженный SDK в редактор PlayCanvas.
Загрузите SDK в редакторе
Перетащите и поместите SDK в редакторе ассетов.
Измените тип загрузки "Asset" на "Before Engine"
Реализация многопользовательской игры
Реализация многопользовательской игры будет делать следующее:
- Использовать класс Photon для общения в реальном времени и балансировки нагрузки
- Подключение к мастер-серверу Photon
- Создание или присоединение к комнате
- Синхронизация действий и движений других игроков
Справочник API и глоссарий доступны на сайте Photon.
Использование Photon с PlayCanvas
Создание экземпляров классов из PlayCanvas для использования Photon
Создайте скриптовый ассет с именем photon-loadbalancing-playcanvas.js в проекте для инициализации Photon.
// photon-loadbalancing-playcanvas.js
const PhotonLoadBalancingPlayCanvas = pc.createScript("PhotonLoadBalancingPlayCanvas");
PhotonLoadBalancingPlayCanvas.attributes.add("appId", { type: "string" });
PhotonLoadBalancingPlayCanvas.attributes.add("appVersion", { type: "string", default: "1.0" });
PhotonLoadBalancingPlayCanvas.attributes.add("wss", { type: "boolean", default: true });
PhotonLoadBalancingPlayCanvas.attributes.add("region", {
type: "string", default: "jp",
description: "Photon Cloud имеет серверы в нескольких регионах, распределенных по нескольким хостинг-центрам по всему миру. Вы можете выбрать оптимизированный регион для себя.",
enum: [
{ "Выберите регион": "default" },
{ "Азия, Сингапур": "asia" },
{ "Австралия, Мельбурн": "au" },
{ "Канада, Восток Монреаль": "cae" },
{ "Китайский материк (См. инструкции) Шанхай": "cn" },
{ "Европа, Амстердам": "eu" },
{ "Индия, Ченнай": "in" },
{ "Япония, Токио": "jp" },
{ "Россия Москва": "ru" },
{ "Россия, Восток Хабаровск": "rue" },
{ "Южная Африка Йоханнесбург": "za" },
{ "Южная Америка, Сан-Паулу": "sa" },
{ "Южная Корея, Сеул": "kr" },
{ "Турция Стамбул": "tr" },
{ "США, Восток Вашингтон": "us" },
{ "США, Запад Сан-Хосе": "usw" },
],
});
PhotonLoadBalancingPlayCanvas.prototype.initialize = function () {
// Настройки Photon
this.loadBalancingClient = new Photon.LoadBalancing.LoadBalancingClient(this.wss ? 1 : 0, this.appId, this.appVersion);
// pc.Application
this.loadBalancingClient.app = this.app;
};
- Photon.LoadBalancing.LoadBalancingClient Этот класс содержит множество функций Photon SDK для обмена данными в реальном времени.
Установить скрипт для корневой сущности
Создайте новый ассет скрипта photon-loadbalancing-playcanvas.js и прикрепите его к корневой сущности в редакторе.
Вставьте AppId в атрибут скрипта.
Введите AppId в качестве атрибута скрипта.
this.loadBalancingClient = new Photon.LoadBalancing.LoadBalancingClient( this.wss ? 1 : 0, this.appId, this.appVersion );
- wss Безопасное соединение через WebSocket.
- appId Значение идентификатора приложения.
- appVersion Используется для версионирования. Разные версии не могут быть подключены друг к другу.
Подключение к мастер-серверу Photon
Подключение к мастер-серверу с использованием
connectToRegionMaster
PhotonLoadBalancingPlayCanvas.prototype.initialize = function () {
// Настройки Photon
this.loadBalancingClient = new Photon.LoadBalancing.LoadBalancingClient(this.wss ? 1 : 0, this.appId, this.appVersion);
// pc.Application
this.loadBalancingClient.app = this.app;
// Подключение к мастер-серверу
if (!this.loadBalancingClient.isInLobby()) {
this.loadBalancingClient.connectToRegionMaster(this.region);
}
};
- connectToRegionMaster Подключается к мастер-серверу в указанном регионе.
- this.region Используется для настройки региона.
Если вы успешно подключитесь к лобби, выполнив connectToRegionMaster, в журнале будет отображаться JoinedLobby.
Создать или присоединиться к комнате
Функция onRoomList вызывается при подключении к лобби.
JoinRandomOrCreateRoom для присоединения к комнате, если она существует, или случайного присоединения к комнате, если она не существует.
PhotonLoadBalancingPlayCanvas.prototype.initialize = function () {
// Настройки Photon
this.loadBalancingClient = new Photon.LoadBalancing.LoadBalancingClient(this.wss ? 1 : 0, this.appId, this.appVersion);
// pc.Application
this.loadBalancingClient.app = this.app;
// Подключение к мастер-серверу
if (!this.loadBalancingClient.isInLobby()) {
this.loadBalancingClient.connectToRegionMaster(this.region);
}
// Добавлено
this.loadBalancingClient.onRoomList = this.onRoomList;
this.loadBalancingClient.onJoinRoom = this.onJoinRoom;
};
PhotonLoadBalancingPlayCanvas.prototype.onRoomList = function () {
this.joinRandomOrCreateRoom();
};
PhotonLoadBalancingPlayCanvas.prototype.onJoinRoom = function (createdByMe) {
console.log("Присоединились к комнате.");
};
- onRoomList(rooms) Список комнат в лобби.
- joinRandomOrCreateRoom(options, createRoomName, createOptions) Присоединиться к случайной комнате. Если комната не существует, будет создана новая комната.
- onJoinRoom Вызывается при присоединении к комнате.
Присоединение и выход
Когда игрок присоединяется к комнате, это синхронизируется с другими игроками. Используйте onActorJoin и onActorLeave.
PhotonLoadBalancingPlayCanvas.prototype.initialize = function () {
// Настройки Photon
this.loadBalancingClient = new Photon.LoadBalancing.LoadBalancingClient(this.wss ? 1 : 0, this.appId, this.appVersion);
// pc.Application
this.loadBalancingClient.app = this.app;
// Подключение к мастер-серверу
if (!this.loadBalancingClient.isInLobby()) {
this.loadBalancingClient.connectToRegionMaster(this.region);
}
this.loadBalancingClient.onRoomList = this.onRoomList;
this.loadBalancingClient.onJoinRoom = this.onJoinRoom;
// Добавлено
this.loadBalancingClient.onActorJoin = this.onActorJoin;
this.loadBalancingClient.onActorLeave = this.onActorLeave;
};
PhotonLoadBalancingPlayCanvas.prototype.onRoomList = function () {
this.joinRandomOrCreateRoom();
};
PhotonLoadBalancingPlayCanvas.prototype.onJoinRoom = function (createdByMe) {
console.log("Присоединился к комнате.");
};
PhotonLoadBalancingPlayCanvas.prototype.onActorJoin = function (actor) {
const { actorNr } = actor;
if (actor.isLocal) return;
const otherPlayer = new pc.Entity();
otherPlayer.addComponent("render", { type: "capsule" });
otherPlayer.setLocalPosition(0, 1, 0);
otherPlayer.name = actorNr;
this.app.root.children[0].addChild(otherPlayer);
};
PhotonLoadBalancingPlayCanvas.prototype.onActorLeave = function (actor) {
const { actorNr } = actor;
const otherPlayer = this.app.root.children[0].findByName(actorNr);
if (actor.isLocal || !otherPlayer) return;
otherPlayer.destroy();
};
- actor содержит
name
,actorNr
,isLocal
иuserId
. - onActorJoin когда новый пользователь подключается, вы можете получить присоединившегося актера.
- onActorLeave когда пользователь отключается, вы можете получить отключившегося актера.
Если успешно, сущность добавляется при присоединении игрока.
Движение игрока
Создайте новый player.js для движения персонажа.
const Player = pc.createScript("player");
Player.prototype.update = function (dt) {
const pos = new pc.Vec3(0, 0, 0);
if (this.app.keyboard.isPressed(pc.KEY_LEFT)) {
pos.x = -dt;
}
if (this.app.keyboard.isPressed(pc.KEY_RIGHT)) {
pos.x = dt;
}
if (this.app.keyboard.isPressed(pc.KEY_UP)) {
pos.z = -dt;
}
if (this.app.keyboard.isPressed(pc.KEY_DOWN)) {
pos.z = dt;
}
if (!pos.equals(new pc.Vec3(0, 0, 0))) {
this.entity.translate(pos);
}
};
- this.app.keyboard.isPressed: проверьте, нажата ли клавиатура
Синхронизация других игроков
Используйте raiseEvent и onEvent для синхронизации местоположения игрока.
Синхронизация позиции с использованием raiseEvent.
const PhotonLoadBalancingPlayCanvas = pc.createScript("PhotonLoadBalancingPlayCanvas");
PhotonLoadBalancingPlayCanvas.attributes.add("appId", { type: "string" });
PhotonLoadBalancingPlayCanvas.attributes.add("appVersion", {
type: "string",
default: "1.0",
});
PhotonLoadBalancingPlayCanvas.attributes.add("wss", {
type: "boolean",
default: true,
});
PhotonLoadBalancingPlayCanvas.attributes.add("region", {
type: "string",
default: "jp",
description:
"Photon Cloud имеет серверы в нескольких регионах, распределенных по нескольким хостинг-центрам по всему миру. Вы можете выбрать оптимизированный регион для себя.",
enum: [
{ "Выберите регион": "default" },
{ "Азия, Сингапур": "asia" },
{ "Австралия, Мельбурн": "au" },
{ "Канада, Восток Монреаль": "cae" },
{ "Китайский материк (См. инструкции) Шанхай": "cn" },
{ "Европа, Амстердам": "eu" },
{ "Индия, Ченнай": "in" },
{ "Япония, Токио": "jp" },
{ "Россия Москва": "ru" },
{ "Россия, Восток Хабаровск": "rue" },
{ "Южная Африка Йоханнесбург": "za" },
{ "Южная Америка, Сан-Паулу": "sa" },
{ "Южная Корея, Сеул": "kr" },
{ "Турция Стамбул": "tr" },
{ "США, Восток Вашингтон": "us" },
{ "США, Запад Сан-Хосе": "usw" },
],
});
PhotonLoadBalancingPlayCanvas.prototype.initialize = function () {
// Настройки Photon
this.loadBalancingClient = new Photon.LoadBalancing.LoadBalancingClient(this.wss ? 1 : 0, this.appId, this.appVersion);
// pc.Application
this.loadBalancingClient.app = this.app;
// Подключение к мастер-серверу
if (!this.loadBalancingClient.isInLobby()) {
this.loadBalancingClient.connectToRegionMaster(this.region);
}
this.loadBalancingClient.onRoomList = this.onRoomList;
this.loadBalancingClient.onJoinRoom = this.onJoinRoom;
this.loadBalancingClient.onActorJoin = this.onActorJoin;
this.loadBalancingClient.onActorLeave = this.onActorLeave;
// Добавлено
this.loadBalancingClient.onEvent = this.onEvent;
this.app.on("createOtherPlayerEntity", this.createOtherPlayerEntity, this);
this.app.on("loadbalancing:sendPlayerPosition", this.sendPlayerPosition, this);
};
PhotonLoadBalancingPlayCanvas.prototype.onRoomList = function () {
this.joinRandomOrCreateRoom();
};
PhotonLoadBalancingPlayCanvas.prototype.onJoinRoom = function (createdByMe) {
this.myRoomActorsArray().forEach((actor) => {
if (actor.isLocal) return;
this.app.fire("createOtherPlayerEntity", actor);
});
};
PhotonLoadBalancingPlayCanvas.prototype.onActorJoin = function (actor) {
if (actor.isLocal) return;
this.app.fire("createOtherPlayerEntity", actor);
const { x, y, z } = this.app.root.findByName("Player").getLocalPosition();
this.app.fire("loadbalancing:sendPlayerPosition", { x, y, z });
};
PhotonLoadBalancingPlayCanvas.prototype.onActorLeave = function (actor) {
const { actorNr } = actor;
const otherPlayer = this.app.root.findByName(actorNr);
if (actor.isLocal || !otherPlayer) return;
otherPlayer.destroy();
};
PhotonLoadBalancingPlayCanvas.prototype.createOtherPlayerEntity = function (actor) {
const { actorNr } = actor;
const entity = new pc.Entity();
entity.addComponent("render", { type: "capsule" });
entity.setLocalPosition(0, 1, 0);
entity.name = actorNr;
this.app.root.children[0].addChild(entity);
};
PhotonLoadBalancingPlayCanvas.prototype.sendPlayerPosition = function (position) {
this.loadBalancingClient.raiseEvent(1, { position });
};
PhotonLoadBalancingPlayCanvas.prototype.onEvent = function (code, content, actorNr) {
switch (code) {
case 1: {
const otherPlayer = this.app.root.findByName(actorNr);
if (otherPlayer) {
const { x, y, z } = content.position;
otherPlayer.setLocalPosition(x, y, z);
}
break;
}
default:
}
};
- raiseEvent(eventCode,data, options) отправить
eventCode
иdata
. - onEvent(code, content, actorNr) получить данные. Включает
actorNr
иeventCode
.
Изменено на отправку событий при перемещении игрока
const Player = pc.createScript("игрок");
Player.prototype.update = function (dt) {
const pos = new pc.Vec3(0, 0, 0);
if (this.app.keyboard.isPressed(pc.KEY_LEFT)) {
pos.x = -dt;
}
if (this.app.keyboard.isPressed(pc.KEY_RIGHT)) {
pos.x = dt;
}
if (this.app.keyboard.isPressed(pc.KEY_UP)) {
pos.z = -dt;
}
if (this.app.keyboard.isPressed(pc.KEY_DOWN)) {
pos.z = dt;
}
if (!pos.equals(new pc.Vec3(0, 0, 0))) {
this.entity.translate(pos);
// Добавлено
const { x, y, z } = this.entity.getPosition();
this.app.fire("loadbalancing:sendPlayerPosition", { x, y, z });
}
};
- this.app.fire связь между скриптами.
Готово!
Теперь вы можете играть в многопользовательский режим в Photon!
Вы можете создать комнату с помощью Photon и синхронизировать позиции игроков друг с другом.
Хотя этот проект был лишь простым обменом сообщениями в реальном времени между игроками, вы также можете создать проект, включающий матчмейкинг. Для полного проекта, включая создание комнат и списки комнат, пожалуйста, нажмите здесь.