Skip to content

Instantly share code, notes, and snippets.

@BatteryAcid
Last active October 26, 2021 14:28
Show Gist options
  • Save BatteryAcid/a8d687f460a5b90fc403c600d1541707 to your computer and use it in GitHub Desktop.
Save BatteryAcid/a8d687f460a5b90fc403c600d1541707 to your computer and use it in GitHub Desktop.
Unity + Amazon GameLift setup in web console Part 2 snippets
// Example Lambda Client Service for Realtime Server example
// Original source: https://aws.amazon.com/blogs/gametech/creating-servers-for-multiplayer-mobile-games-with-amazon-gamelift/
// Contains @BatteryAcid's edits to accompany video Unity + Amazon GameLift setup in web console
// https://youtu.be/WaAZyqgkXDY
//const uuid = require('uuid');
const {"v4": uuidv4} = require('uuid');
const AWS = require('aws-sdk');
const GameLift = new AWS.GameLift({region: 'us-east-1'});
const MegaFrogRaceFleetID = "YOUR_FLEET_ID";
exports.handler = async (event) => {
let response;
let gameSessions;
// find any sessions that have available players
await GameLift.searchGameSessions({
FleetId: MegaFrogRaceFleetID,
FilterExpression: "hasAvailablePlayerSessions=true"
}).promise().then(data => {
gameSessions = data.GameSessions;
}).catch(err => {
response = err;
});
// if the response object has any value at any point before the end of
// the function that indicates a failure condition so return the response
if(response != null)
{
return response;
}
// if there are no sessions, then we need to create a game session
let selectedGameSession;
if(gameSessions.length == 0)
{
console.log("No game session detected, creating a new one");
await GameLift.createGameSession({
MaximumPlayerSessionCount: 2, // only two players allowed per game
FleetId: MegaFrogRaceFleetID
}).promise().then(data => {
selectedGameSession = data.GameSession;
}).catch(err => {
response = err;
});
if(response != null)
{
return response;
}
}
else
{
// we grab the first session we find and join it
selectedGameSession = gameSessions[0];
console.log("Game session exists, will join session ", selectedGameSession.GameSessionId);
}
// there isn't a logical way selectedGameSession could be null at this point
// but it's worth checking for in case other logic is added
if(selectedGameSession != null)
{
// now we have a game session one way or the other, create a session for this player
await GameLift.createPlayerSession({
GameSessionId : selectedGameSession.GameSessionId ,
PlayerId: uuidv4()//.v4()
}).promise().then(data => {
console.log("Created player session ID: ", data.PlayerSession.PlayerSessionId);
response = data.PlayerSession;
}).catch(err => {
response = err;
});
}
else
{
response = {
statusCode: 500,
body: JSON.stringify({
message: "Unable to find game session, check GameLift API status"
})
};
}
return response;
};
// Example Realtime Server Script
// Original source: https://docs.aws.amazon.com/gamelift/latest/developerguide/realtime-script.html
// Contains @BatteryAcid's edits to accompany video Unity + Amazon GameLift setup in web console
// https://youtu.be/WaAZyqgkXDY
'use strict';
// Example override configuration
const configuration = {
pingIntervalTime: 30000
};
// 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.
const RED_TEAM_GROUP = 1;
const BLUE_TEAM_GROUP = 2;
// @BatteryAcid
const THROW_OP_CODE = 201;
const BOX_HIT_OP_CODE = 202;
const GAMEOVER_OP_CODE = 209;
let players = [];
let winner = null;
// Called when game server is initialized, passed server's object of current session
function init(rtSession) {
session = rtSession;
logger = session.getLogger();
logger.info("init");
}
// 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) {
logger.info("onStartGameSession: ");
logger.info(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) {
logger.info("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) {
logger.info("onPlayerAccepted: ");
logger.info(player);
players.push(player.peerId);
// 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) {
logger.info("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, peerId);
}
});
activePlayers--;
}
// @BatteryAcid
// Handle a message to the server
function onMessage(gameMessage) {
logger.info("onMessage: ");
logger.info(gameMessage);
switch (gameMessage.opCode) {
case THROW_OP_CODE: {
logger.info("Received message THROW_OP_CODE");
var testReturnCode = 200;
const outMessage = session.newTextGameMessage(testReturnCode, session.getServerId(), "OK");
var allSessionPlayers = players;//session.getPlayers();
logger.info(allSessionPlayers);
let allPlayersLength = allSessionPlayers.length;
logger.info(allPlayersLength);
for (let index = 0; index < allPlayersLength; ++index) {
session.getLogger().info("[app]: sendMessage " + outMessage.toString() + " " + allSessionPlayers[index].toString());
session.sendMessage(outMessage, allSessionPlayers[index]);
};
break;
}
case BOX_HIT_OP_CODE: {
logger.info("Received message BOX_HIT_OP_CODE");
if (winner == null) {
// we have a winner
winner = gameMessage.sender
logger.info("winner:");
logger.info(gameMessage);
logger.info(winner);
// tell all players game is over
var allSessionPlayers = players;
let allPlayersLength = allSessionPlayers.length;
for (let index = 0; index < allPlayersLength; ++index) {
let outMessage = session.newTextGameMessage(GAMEOVER_OP_CODE, session.getServerId(), "You Lost!");
if (allSessionPlayers[index] == gameMessage.sender) {
outMessage = session.newTextGameMessage(GAMEOVER_OP_CODE, session.getServerId(), "You Won!");
}
session.getLogger().info("[app]: game over " + outMessage.toString() + " " + allSessionPlayers[index].toString());
session.sendMessage(outMessage, allSessionPlayers[index]);
};
}
break;
}
}
}
// Return true if the send should be allowed
function onSendToPlayer(gameMessage) {
logger.info("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) {
logger.info("onSendToGroup: " + gameMessage);
return true;
}
// Return true if the player is allowed to join the group
function onPlayerJoinGroup(groupId, peerId) {
logger.info("onPlayerJoinGroup: " + groupId + ", " + peerId);
return true;
}
// Return true if the player is allowed to leave the group
function onPlayerLeaveGroup(groupId, peerId) {
logger.info("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
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment