Глобальное состояние
Глобальное состояние содержит общие игровые сущности и значения, которыми управляет только хост:
- враги и NPC;
- предметы на карте;
- состояние и таймеры раунда;
- общий счёт и цели.
Глобальное состояние синхронизируется отдельно от состояния игроков.
Список методов
Действия:
gp.multiplayer.defineGlobalSchema()- определить синхронизируемые общие поля. FREEgp.multiplayer.setGlobalState()- установить глобальное состояние под управлением хоста. FREEgp.multiplayer.onTick()иoffTick()- управлять циклом логики общего мира. FREE
Свойства:
gp.multiplayer.globalState- получить последнее глобальное состояние. FREE
События:
globalStateUpdated- отслеживать обновления глобального состояния на пирах.becameHostиbecamePeer- запускать и останавливать логику хоста.
Определение глобальной схемы
Глобальное состояние использует те же настройки схемы, что и состояние игрока:
- JavaScript
- Unity
gp.multiplayer.defineGlobalSchema({
enemies: {
id: { readonly: true },
type: { readonly: true },
x: { interpolate: true },
y: { interpolate: true },
hp: { interpolate: false },
},
items: {
id: { readonly: true },
type: { readonly: true },
x: { interpolate: false },
y: { interpolate: false },
},
round: {
number: { interpolate: false },
timeLeft: { interpolate: true },
status: { interpolate: false },
},
});
GP_Multiplayer.defineGlobalSchema(new GP_Data(@"{
""enemies"": {
""id"": { ""readonly"": true },
""type"": { ""readonly"": true },
""x"": { ""interpolate"": true },
""y"": { ""interpolate"": true },
""hp"": { ""interpolate"": false }
},
""round"": {
""number"": { ""interpolate"": false },
""timeLeft"": { ""interpolate"": true },
""status"": { ""interpolate"": false }
}
}"));
Включайте интерполяцию для значений, которые должны изменяться плавно, например координат. Отключайте её для значений, которые должны применяться сразу, например здоровья, состояния анимации или идентификатора предмета.
Создание состояния на хосте
Вызывать setGlobalState() может только хост:
- JavaScript
- Unity
let hostState = null;
function startHostLogic() {
hostState = gp.multiplayer.globalState ?? {
enemies: [],
items: [],
round: {
number: 1,
timeLeft: 60,
status: 'playing',
},
};
gp.multiplayer.setGlobalState(hostState);
}
function stopHostLogic() {
hostState = null;
}
gp.multiplayer.on('becameHost', startHostLogic);
gp.multiplayer.on('becamePeer', stopHostLogic);
GP_Data hostState;
void StartHostLogic()
{
hostState = GP_Multiplayer.globalState ?? new GP_Data(@"{
""enemies"": [],
""items"": [],
""round"": {
""number"": 1,
""timeLeft"": 60,
""status"": ""playing""
}
}");
GP_Multiplayer.setGlobalState(hostState);
}
void StopHostLogic() => hostState = null;
GP_Multiplayer.on("becameHost", StartHostLogic);
GP_Multiplayer.on("becamePeer", StopHostLogic);
В JavaScript setGlobalState() сохраняет ссылку на переданный объект, поэтому хост может изменять этот объект напрямую. В Unity после изменения C#-модели вызовите setGlobalState(), чтобы передать SDK её последнее сериализованное значение.
- JavaScript
- Unity
function spawnEnemy() {
if (!gp.multiplayer.isHost || !hostState) {
return;
}
hostState.enemies.push({
id: crypto.randomUUID(),
type: 'slime',
x: 100,
y: 200,
hp: 50,
});
}
function damageEnemy(enemyId, damage) {
if (!gp.multiplayer.isHost || !hostState) {
return;
}
const enemy = hostState.enemies.find((item) => item.id === enemyId);
if (enemy) {
enemy.hp = Math.max(0, enemy.hp - damage);
}
}
void SpawnEnemy()
{
if (!GP_Multiplayer.isHost)
return;
worldState.enemies.Add(new EnemyState
{
id = Guid.NewGuid().ToString(),
type = "slime",
x = 100,
y = 200,
hp = 50
});
GP_Multiplayer.setGlobalState(
new GP_Data(JsonUtility.ToJson(worldState))
);
}
В JavaScript повторно вызывать setGlobalState() после каждого push, splice или изменения поля не требуется. Хост отправляет содержимое объекта с частотой выбранного режима. В Unity после изменения C#-модели снова передайте её сериализованное состояние.
Обновление общих сущностей
Выполняйте авторитетную логику общего мира только на хосте:
- JavaScript
- Unity
function updateGlobalState(delta) {
if (!gp.multiplayer.isHost || !hostState) {
return;
}
const seconds = delta / 1000;
for (const enemy of hostState.enemies) {
enemy.x += 40 * seconds;
}
hostState.round.timeLeft = Math.max(
0,
hostState.round.timeLeft - seconds,
);
}
gp.multiplayer.onTick(updateGlobalState);
void UpdateGlobalState(float delta)
{
if (!GP_Multiplayer.isHost)
return;
float seconds = delta / 1000f;
foreach (EnemyState enemy in worldState.enemies)
enemy.x += 40f * seconds;
worldState.round.timeLeft = Mathf.Max(
0,
worldState.round.timeLeft - seconds
);
GP_Multiplayer.setGlobalState(
new GP_Data(JsonUtility.ToJson(worldState))
);
}
GP_Multiplayer.onTick(UpdateGlobalState);
Пиры не должны изменять gp.multiplayer.globalState. Они получают интерполированный снимок для отрисовки.
Чтение глобального состояния
Последнее глобальное состояние доступно хосту и пирам:
- JavaScript
- Unity
function renderWorld() {
const globalState = gp.multiplayer.globalState;
if (!globalState) {
return;
}
for (const enemy of globalState.enemies) {
drawEnemy(enemy);
}
for (const item of globalState.items) {
drawItem(item);
}
}
GP_Data globalState = GP_Multiplayer.globalState;
if (globalState != null)
Debug.Log(globalState.Data);
Пиры могут реагировать на изменения через globalStateUpdated:
- JavaScript
- Unity
function onGlobalStateUpdated(globalState) {
updateRoundInterface(globalState.round);
}
gp.multiplayer.on('globalStateUpdated', onGlobalStateUpdated);
void OnGlobalStateUpdated(GP_Data globalState)
{
Debug.Log(globalState.Data);
}
GP_Multiplayer.on("globalStateUpdated", OnGlobalStateUpdated);
Обработчик события получает непосредственно объект глобального состояния. Событие вызывается на пирах после интерполяции; хост уже владеет актуальной ссылкой на состояние.
Миграция хоста
Когда пир становится новым хостом, gp.multiplayer.globalState содержит последний полученный им снимок. Передайте этот объект в setGlobalState(), чтобы сделать его новой живой ссылкой хоста:
- JavaScript
- Unity
function startHostLogic() {
const latestState = gp.multiplayer.globalState;
hostState = latestState ?? {
enemies: [],
items: [],
round: {
number: 1,
timeLeft: 60,
status: 'playing',
},
};
gp.multiplayer.setGlobalState(hostState);
}
void StartHostLogic()
{
GP_Data latestState = GP_Multiplayer.globalState;
hostState = latestState ?? CreateInitialGlobalState();
GP_Multiplayer.setGlobalState(hostState);
}
Так общий мир продолжит работу с последнего известного состояния, а не перезапустится после миграции хоста.
После подключения также обработайте случай, когда роль уже выбрана:
- JavaScript
- Unity
await gp.multiplayer.connect({ channelId });
if (gp.multiplayer.isHost) {
startHostLogic();
}
await GP_Multiplayer.connect(new MultiplayerChannelQuery { channelId = channelId });
if (GP_Multiplayer.isHost)
StartHostLogic();
Сохраняйте подписку на becameHost, поскольку миграция хоста может произойти позднее.
Очистка
- JavaScript
- Unity
gp.multiplayer.offTick(updateGlobalState);
gp.multiplayer.off('globalStateUpdated', onGlobalStateUpdated);
gp.multiplayer.off('becameHost', startHostLogic);
gp.multiplayer.off('becamePeer', stopHostLogic);
hostState = null;
GP_Multiplayer.offTick(UpdateGlobalState);
GP_Multiplayer.off("globalStateUpdated", OnGlobalStateUpdated);
GP_Multiplayer.off("becameHost", StartHostLogic);
GP_Multiplayer.off("becamePeer", StopHostLogic);
hostState = null;
Оставайтесь на связи
С другими документами раздела вы можете ознакомиться здесь. Для начала работы с GamePush ознакомьтесь с нашими туториалами.
Сообщество GamePush в Telegram: @gs_community.
Для ваших обращений e-mail: official@gamepush.com
Желаем вам успехов!