创建实时脚本 - 亚马逊 GameLift
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 Amazon Web Services 服务入门

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

创建实时脚本

要在游戏中使用实时服务器,你需要提供一个脚本(以某些 JavaScript代码的形式)来配置和选择自定义一组实时服务器。本主题涵盖了创建实时脚本的关键步骤。脚本准备就绪后,将其上传到亚马逊 GameLift服务并使用它来创建队列(参见将 Realtime Servertime 脚本 GameLift)。

要准备用于实时服务器的脚本,请将以下功能添加到您的实时脚本中。

管理游戏会话生命周期(必需)

实时脚本至少必须包含该Init()函数,该函数使实时服务器为启动游戏会话做好准备。同时还强烈建议您提供一种终止游戏会话的方式,以确保队组上可以继续启动新游戏会话。

Init()调用回调函数时,会传递一个实时会话对象,该对象包含实时服务器的接口。有关此接口的详细信息,请参阅 实时服务器接口

要优雅地结束游戏会话,脚本还必须调用实时服务器的session.processEnding函数。这需要一些机制来确定何时结束会话。此脚本示例代码演示了一个简单的机制,该机制检查玩家连接,并在指定时间长度中没有玩家连接到会话时触发游戏会话中止。

具有最基本配置(服务器进程初始化和终止)的实时服务器本质上充当无状态中继服务器。实时服务器在连接到游戏的游戏客户端之间中继消息和游戏数据,但不采取任何独立操作来处理数据或执行逻辑。您可以根据需要选择性地为游戏添加游戏逻辑(由游戏事件或其他机制触发)。

添加服务器端游戏逻辑(可选)

您可以选择将游戏逻辑添加到您的实时脚本中。例如,您可以执行以下任一或所有操作。脚本示例代码提供了说明。请参阅亚马逊 GameLift 实时服务器脚本参考

  • 添加事件驱动的逻辑。实施回调函数来响应客户端-服务器事件。有关终端节点的完整列表,请参阅实时服务器的脚本回调

  • 通过发送消息到服务器来触发逻辑。为从游戏客户端发送到服务器的消息创建一组特殊操作代码,并添加函数来处理接收。使用回调 onMessage,并使用 gameMessage 接口解析消息内容(请参阅 gameMessage.opcode)。

  • 启用游戏逻辑以访问您的其他Amazon资源。有关详细信息,请参阅与舰队中的其他Amazon资源通信

  • 允许游戏逻辑访问正在运行的实例的舰队信息。有关详细信息,请参阅获取 GameLift 实例的舰队数据

实时服务器脚本示例

此示例说明了部署实时服务器所需的基本脚本以及一些自定义逻辑。它包含必需的 Init() 函数,并使用计时器机制,根据没有玩家连接的时间长度来触发游戏会话终止。它还包括一些针对自定义逻辑的挂钩,以及一些回调实施。

// Example Realtime Server Script 'use strict'; // Example override configuration const configuration = { pingIntervalTime: 30000, maxPlayers: 32 }; // Timing mechanism used to trigger end of game session. Defines how long, in milliseconds, between each tick in the example tick loop const tickTime = 1000; // Defines how to long to wait in Seconds before beginning early termination check in the example tick loop const minimumElapsedTime = 120; var session; // The Realtime server session object var logger; // Log at appropriate level via .info(), .warn(), .error(), .debug() var startTime; // Records the time the process started var activePlayers = 0; // Records the number of connected players var onProcessStartedCalled = false; // Record if onProcessStarted has been called // Example custom op codes for user-defined messages // Any positive op code number can be defined here. These should match your client code. const OP_CODE_CUSTOM_OP1 = 111; const OP_CODE_CUSTOM_OP1_REPLY = 112; const OP_CODE_PLAYER_ACCEPTED = 113; const OP_CODE_DISCONNECT_NOTIFICATION = 114; // Example groups for user-defined groups // Any positive group number can be defined here. These should match your client code. // When referring to user-defined groups, "-1" represents all groups, "0" is reserved. const RED_TEAM_GROUP = 1; const BLUE_TEAM_GROUP = 2; // Called when game server is initialized, passed server's object of current session function init(rtSession) { session = rtSession; logger = session.getLogger(); } // On Process Started is called when the process has begun and we need to perform any // bootstrapping. This is where the developer should insert any code to prepare // the process to be able to host a game session, for example load some settings or set state // // Return true if the process has been appropriately prepared and it is okay to invoke the // GameLift ProcessReady() call. function onProcessStarted(args) { onProcessStartedCalled = true; logger.info("Starting process with args: " + args); logger.info("Ready to host games..."); return true; } // Called when a new game session is started on the process function onStartGameSession(gameSession) { // Complete any game session set-up // Set up an example tick loop to perform server initiated actions startTime = getTimeInS(); tickLoop(); } // Handle process termination if the process is being terminated by GameLift // You do not need to call ProcessEnding here function onProcessTerminate() { // Perform any clean up } // Return true if the process is healthy function onHealthCheck() { return true; } // On Player Connect is called when a player has passed initial validation // Return true if player should connect, false to reject function onPlayerConnect(connectMsg) { // Perform any validation needed for connectMsg.payload, connectMsg.peerId return true; } // Called when a Player is accepted into the game function onPlayerAccepted(player) { // This player was accepted -- let's send them a message const msg = session.newTextGameMessage(OP_CODE_PLAYER_ACCEPTED, player.peerId, "Peer " + player.peerId + " accepted"); session.sendReliableMessage(msg, player.peerId); activePlayers++; } // On Player Disconnect is called when a player has left or been forcibly terminated // Is only called for players that actually connected to the server and not those rejected by validation // This is called before the player is removed from the player list function onPlayerDisconnect(peerId) { // send a message to each remaining player letting them know about the disconnect const outMessage = session.newTextGameMessage(OP_CODE_DISCONNECT_NOTIFICATION, session.getServerId(), "Peer " + peerId + " disconnected"); session.getPlayers().forEach((player, playerId) => { if (playerId != peerId) { session.sendReliableMessage(outMessage, playerId); } }); activePlayers--; } // Handle a message to the server function onMessage(gameMessage) { switch (gameMessage.opCode) { case OP_CODE_CUSTOM_OP1: { // do operation 1 with gameMessage.payload for example sendToGroup const outMessage = session.newTextGameMessage(OP_CODE_CUSTOM_OP1_REPLY, session.getServerId(), gameMessage.payload); session.sendGroupMessage(outMessage, RED_TEAM_GROUP); break; } } } // Return true if the send should be allowed function onSendToPlayer(gameMessage) { // This example rejects any payloads containing "Reject" return (!gameMessage.getPayloadAsText().includes("Reject")); } // Return true if the send to group should be allowed // Use gameMessage.getPayloadAsText() to get the message contents function onSendToGroup(gameMessage) { return true; } // Return true if the player is allowed to join the group function onPlayerJoinGroup(groupId, peerId) { return true; } // Return true if the player is allowed to leave the group function onPlayerLeaveGroup(groupId, peerId) { return true; } // A simple tick loop example // Checks to see if a minimum amount of time has passed before seeing if the game has ended async function tickLoop() { const elapsedTime = getTimeInS() - startTime; logger.info("Tick... " + elapsedTime + " activePlayers: " + activePlayers); // In Tick loop - see if all players have left early after a minimum period of time has passed // Call processEnding() to terminate the process and quit if ( (activePlayers == 0) && (elapsedTime > minimumElapsedTime)) { logger.info("All players disconnected. Ending game"); const outcome = await session.processEnding(); logger.info("Completed process ending with: " + outcome); process.exit(0); } else { setTimeout(tickLoop, tickTime); } } // Calculates the current time in seconds function getTimeInS() { return Math.round(new Date().getTime()/1000); } exports.ssExports = { configuration: configuration, init: init, onProcessStarted: onProcessStarted, onMessage: onMessage, onPlayerConnect: onPlayerConnect, onPlayerAccepted: onPlayerAccepted, onPlayerDisconnect: onPlayerDisconnect, onSendToPlayer: onSendToPlayer, onSendToGroup: onSendToGroup, onPlayerJoinGroup: onPlayerJoinGroup, onPlayerLeaveGroup: onPlayerLeaveGroup, onStartGameSession: onStartGameSession, onProcessTerminate: onProcessTerminate, onHealthCheck: onHealthCheck };