Using messages to implement game logic in Amazon GameSparks - Amazon GameSparks

Amazon GameSparks is currently in preview. Changes might be made to this service and to this documentation. We don’t recommend using this service for production workloads.

Using messages to implement game logic in Amazon GameSparks

You implement your game logic by developing messages in Amazon GameSparks, and then sending messages over connections from your game client. You specify the shape of your custom message in its fields. You implement your game logic as cloud code (JavaScript ES5.1) in the message handlers. You can also override system-defined messages.

Your game client code conducts the game, and calls a message when something needs to happen on the backend (an event), or when data from the backend is needed (a request).

To work with messages, in the GameSparks navigation pane select Dev, expand Configuration, and then choose Cloud code.

Note

If you are new to GameSparks, we recommend that you begin with Getting started.

Message types

You can use the following types of messages:

Event

A message that the client raises to the backend with no response required. For example, the player acquires one of eight magic gems. An event can optionally send a notification to the client.

Requests

When the client makes a request, a response is required from the backend. Requests are essentially the backend API to the game client. For example, the client requests a list of the magic gems the player has acquired. A request can optionally send a notification to the client.

Notification

Events and requests can send notifications from the backend to the client. For example, when the player has acquired all eight magic gems, a congratulations message is sent to the player.

Create a custom message

A custom message is shaped and implemented by you. You specify the shape of the message by adding fields and choosing their shapes. In your events and requests, you add zero or more handlers. In these handlers you develop cloud code—​JavaScript that implements your game logic.

To create a custom message:

  1. On the Cloud code page, choose Create message.

  2. Choose Event, Request, or Notification.

  3. Specify a Name, and optionally, a Description.

Message fields

You can add zero or more fields to your messages to specify the data that can be passed in or out of the message.

Primitive shapes

You can use the following primitive shapes for your fields:

  • String

  • Integer

  • Boolean

  • Decimal

  • Any

Custom shapes

You can add custom shapes and use them as fields for your messages.

  1. On the Cloud code page, choose Configure, and then choose Custom shapes.

  2. On the Manage custom shapes page choose Create shape.

  3. The Create shape page appears.

Shape type Description Shapes

List

A single-dimensional array of elements

You must specify a single shape for the elements. You can use system-defined and custom shapes (except List) for the element shape.

Map

A mapping of keys to values

The key shape is String. You must specify a value shape. You can use system-defined and custom shapes (except Map) for the value shape.

Enum

A string type that can be passed as one of a list of string values

String

Structure

A passive data structure with named fields.

You can specify zero or more fields. You can use system-defined and custom shapes (except Structure) for the field shapes. For each field you can specify if it is required or not.

Variant

A variant data type. This shape is similar in nature to a C union data type.

You can specify zero or more fields. You can use system-defined and custom shapes (except Variant) for the field shapes. The calling code can pass in only one of the fields. If Required is set to Yes, then one of the fields must be passed.

Access data passed into your event or request

You use fields to pass data into your event or request. To access the data, reference the field from the message object. For example, your game client passes some data in the enemiesDefeated integer field. Your message handlers can reference the field as message.enemiesDefeated. To learn more see the example below.

Example: Personal high score event

Suppose you wanted your game to raise an event that contains the score at the end of each game. On the backend, you want to determine whether the score is a new personal best for the player.

The following example demonstrates:

  • Getting data from the client via a custom field.

  • Getting the player ID.

  • Reading and writing player data.

  • Sending a notification and raising an error.

  • Logging messages to CloudWatch.

Note

Although this example is an event, you could use a similar approach in a request. To send the result to the client, the event below sends the HighScoreMessage notification to the player. In a request, you could instead return the message as a response: return GameSparks().Messaging().Response( { "msg": outgoingMessage } ).

Create the game

Create a new game named PostScore.

HighScoreOutcome notification

Create a HighScoreOutcome notification with the following field.

Field name

Shape

Required

msg

String

Yes

PostScore event

Create a PostScore event.

Important

Select Enable client permission.

Add the the following field.

Field name

Shape

Required

score

Integer

Yes

ProcessScore handler

Add a ProcessScore handler and copy and paste the following code into it.

GameSparks().Logging().Debug("Inside PostScore event, ProcessScore handler"); // Log player ID GameSparks().Logging().Debug("Checking the score for player ".concat(GameSparks().CurrentPlayer().Id())); let data = GameSparks().CurrentPlayer().GetData(["HighScore"]); // Handle initial case with no high score if (data.HighScore === undefined) { data.HighScore = 0; } GameSparks().Logging().Debug("High score: ".concat(parseInt(data.HighScore))); // Process the result and compose the notification message let maxScore = 5000; let outgoingMessage = ""; if (message.score > maxScore) { outgoingMessage = "A score of ".concat(parseInt(message.score), " is too high! The maximum score is ", parseInt(maxScore)); GameSparks().Logging().Error(outgoingMessage); GameSparks().CurrentPlayer().SendNotification("Custom.Game.HighScoreOutcome", { "msg": outgoingMessage }); return; } if (message.score > data.HighScore) { outgoingMessage = "A new high score! ".concat(parseInt(message.score), " is more than ", parseInt(data.HighScore), "."); // save the new high score data.HighScore = message.score; GameSparks().CurrentPlayer().SetData(data); } else { outgoingMessage = "Not a new high score. ".concat(parseInt(message.score), " is not more than ", parseInt(data.HighScore), "."); } GameSparks().Logging().Debug(outgoingMessage); // notify the player of the outcome GameSparks().CurrentPlayer().SendNotification("Custom.Game.HighScoreOutcome", { "msg": outgoingMessage });

Unity game client code

Add the code below to your Unity game client.

Note

If you are just getting started with GameSparks, see Build Hello World to learn how to implement the client code below in a Unity game.

using System; using System.Collections; using Amazon.GameSparks.Unity.DotNet; using Amazon.GameSparks.Unity.Generated; using Amazon.GameSparks.Unity.Editor.Assets; using Amazon.GameSparks.Unity.EngineIntegration; using UnityEngine; namespace Amazon.GameSparks.Unity.Samples.UsingScriptableObjects.Scripts { public sealed class Player : MonoBehaviour { [SerializeField] private ConnectionScriptableObject connectionScriptableObject = default; private IDisposable subscription; private void Awake() { subscription = connectionScriptableObject.Connection.SubscribeToHighScoreOutcome(OnPostScoreNotification); } private void OnMouseUp() { var scoreEvent = new PostScoreOperations.PostScore(50); Debug.Log($"Sending payload: {scoreEvent}"); connectionScriptableObject.Connection.EnqueuePostScore( scoreEvent, error => { Debug.LogError("Backend says PostScore failed: " + error); }); } private void OnPostScoreNotification(PostScoreOperations.HighScoreOutcome notification) { Debug.Log($"Received from PostScore event: {notification}"); } } }

Play the game. Increment the argument in PostScore() to higher values, and then to 5001 to see the different outcomes.

Exit an event handler

Use the return function where you want to exit an event handler before subsequent code is run. For example:

//... let outgoingMessage = ""; if (message.score > maxScore) { outgoingMessage = "A score of " + parseInt(message.score) + " is too high! The maximum score is " + parseInt(maxScore); GameSparks().Logging().Error(outgoingMessage); GameSparks().CurrentPlayer().SendNotification("Custom.Game.HighScoreOutcome", { "Message": outgoingMessage }); return; } //...

Send a notification to another player

You can you send notifications to another player using a PlayerId and the SendNotification() cloud code API.

// Player 1 adds themselves to a global 'matchmaking'. var playerId = GameSparks().CurrentPlayer().Id() GameSparks().Lambda('Matchmaking.Register').InvokeEvent({"playerId" : playerId}) // Player 2 retrieves let response = GameSparks().Lambda('Matchmaking.FindOpponent').Invoke({"playerId" : playerId})
var data = GameSparks().CurrentPlayer().GetData(["opponentId"]); try { GameSparks().Player(data.opponentId).SendNotification("Custom.Game.TurnComplete", {}); GameSparks().Logging().Debug("Notification sent to opponent with id: " + data.opponentId); } catch (e) { if (e instanceof GameSparks().Exceptions().Messaging().PlayerNotConnectedException) { GameSparks().Logging().Debug("Opponent disconnected."); GameSparks().CurrentPlayer().SendNotification("Custom.Game.OpponentDisconnected", {}); } else { throw e; } }

Override system-defined requests

You can override system-defined messages. For example, you can modify the cloud code in GameSparks.Identity requests such as CreatePlayer, GetPlayerToken, and ValidateAuthenticationToken.

These messages are called by the system during different events in the game. The messages call cloud code API actions, and you can add your own code before and after those calls.

If you override the handler of a system-defined message and you later decide you want to go back to the original system-defined code, choose Actions and then choose Restore to default.

Example: CreatePlayer

When a game client that has never connected to the game connects, the CreatePlayer request is made. Here’s an example of overriding the CreatePlayer request:

GameSparks().Logging().Debug("Inside CreatePlayer request, Request handler"); try { GameSparks().Logging().Debug("Creating a new player"); let result = GameSparks().Identity().CreatePlayer(); GameSparks().Logging().Debug("New player ID: " + result.PlayerId); return GameSparks().Messaging().Response({ "PlayerId" : result.PlayerId }); } catch (e) { return GameSparks().Messaging().Error("GameSparks.Core.ExecutionError", e.getMessage); }

Create a new player

If you already have a player authenticated on your dev machine, you can move or delete it so that you can debug your CreatePlayer request handler.

On a Windows machine:

  1. In the GameSparks navigation pane select Dev to view your game key in the Dev stage configuration card.

  2. On your Windows machine, navigate to C:\Users\<user id>\AppData\Local\GameSparks.

  3. Look for a folder with the same name as your game key.

  4. If you need the player data, move the game key folder to another location. If you don’t need the data, delete the game key folder.

The next time you play your game, a new player is created.

Example: ValidateAuthenticationToken

GameSparks().Logging().Debug("Inside ValidateAuthenticationToken request, Request handler"); try { GameSparks().Logging().Debug("Validating auth token"); let result = GameSparks().Identity().ValidateAuthenticationToken(message.AuthenticationToken, message.AuthenticationProvider); GameSparks().Logging().Debug("PlayerId: " + JSON.stringify(result.PlayerId)); GameSparks().Logging().Debug("Authentication provider: " + JSON.stringify(message.AuthenticationProvider)); return GameSparks().Messaging().Response({ "PlayerId" : result.PlayerId }); } catch (e) { return GameSparks().Messaging().Error("GameSparks.Core.ExecutionError", e.getMessage); }

Raise an event from cloud code

You can raise events from cloud code:

GameSparks().Messaging().RaiseEvent("Custom.Game.MyEvent", {});

Write log entries to CloudWatch

You can log important cloud code events to CloudWatch. For example:

GameSparks().Logging().Info("Just a message to let you know."); GameSparks().Logging().Debug("These are helpful for solving problems in your cloud code."); GameSparks().Logging().Warn("A warning is not quite an error."); GameSparks().Logging().Error("An error occurred!");

GameSparks also logs cloud code execution errors in CloudWatch.

To learn about how the logs work, see Log cloud code events with Amazon CloudWatch.

Handle errors and exceptions

When things go wrong in your game, you’ve got a few ways to handle errors and send messages about them.

Errors returned by GameSparks

When GameSparks encounters an error while processing cloud code, the service returns one of two errors to the client:

ExecutionError

Sent when an uncaught exception is thrown within cloud code.

  • This error is like a 5XX HTTP server error.

  • You can avoid this error reaching the client by catching exceptions with try/catch statements (see below).

  • You can debug this error by looking at your CloudWatch logs.

InvalidMessageError

Sent when a request sent to the backend is malformed, resulting in a failure to invoke the cloud code.

  • This error is like a 4XX HTTP client error.

  • You can avoid this error by validating messages before sending them from the client.

  • You can debug this client error by looking at your CloudWatch logs.

Return an error from your cloud code

You can return an error from your cloud code and halt processing of subsequent code.

return GameSparks().Messaging().Error("GameSparks.Core.ExecutionError", "Something went wrong."); // no code runs after this

Exceptions and try/catch blocks

Cloud code follows JavaScript exception standards. When a cloud code statement encounters an error, it throws an exception. If you don’t catch the the exception, the cloud code ends prematurely, your client receives an error, and a generic message is written to CloudWatch. The message sent to CloudWatch might have some details, but the message sent to the client is opaque to avoid exposing internal details of your game to players.

By catching exceptions, your cloud code can continue and handle the error gracefully. Furthermore, an error does not have to be sent to the client, and you can write a more specific entry the CloudWatch log. After your code that handles the error, you can either continue processing or return your own error and halt processing (see above).

All exceptions thrown from cloud code share a base exception type of GameSparks().Exceptions().Core().ExecutionException. There are two types of exceptions:

  • GameSparks().Exceptions().Messaging().PlayerNotConnectedException: Thrown from SendNotification() when the recipient does not have an active connection to the game.

  • GameSparks().Exceptions().Core().ExecutionException: The base exception for all GameSparks exception types. It is thrown for all error cases except PlayerNotConnectedException.

// Unconditional catch block try { GameSparks().CurrentPlayer().SetData(data) } catch (e) { GameSparks().Logging().Error(e.toString()); } // Conditional catch block (uses 'instanceof' to check exception type). try { GameSparks().Player(friendId).SendNotification("Custom.Game.SayHello", {}) } catch (e) { if (e instanceof GameSparks().Exceptions().Messaging().PlayerNotConnectedException) { GameSparks().Logging().Debug("Friend not connected."); } else { GameSparks().Logging().Error(e.toString()); } }

Save and deploy your game

When you are ready to test or use your messages:

  1. Select your message and make sure that Enable client permission is selected.

  2. Make sure the handlers you want to use are not disabled (change this using the Actions button).

  3. Save all of your messages.

  4. In the navigation pane, select Dev.

  5. Choose Deploy as new snapshot.

  6. On the Deploy as new snapshot dialog box:

    • It’s helpful to you and your team to enter a snapshot description of what changed for your history.

    • Choose Save.

Tip

During deployment you don’t have to stay on the Dev page to be notified about the deployment. The successfully deployed message appears on Dev page and on other pages such as the Cloud code page.

Test your messages in the test harness

Use the test harness to quickly test and iterate on your cloud code messages. You can test without the need repeatedly integrate new code into your game client.

Select Test harness from the navigation pane or choose Test from your cloud code message. A new player is automatically created and connected for you.

To test your message:

  1. Make sure that you have saved and deployed your changes (see above).

  2. Select the message that you want to test, and then populate and modify the fields as needed to run your test.

  3. After the Player is connected, you can send the message.

After you send the message, in the Log inspector information from the message run appears, including the message, notifications, responses, and errors.

Generate and download code

For developing on your game client, we strongly recommend that you generate and download code. You can use this code to reduce bugs by making strongly-typed calls to your messages.

  1. Confirm that you have saved and deployed as described above.

  2. On the Snapshot card choose Actions, and then choose Generate Code.

  3. Choose the Game client platform, the Language, and then choose Generate Code.

  4. Choose Download.

  5. On your development machine, extract from the .ZIP file the <GameName>Operations.CS file.

  6. Add the .CS file to your game project in Unity (for example, drag and drop the file onto the Project pane).

Tip

You’ll need to generate code again when you…​

  • Add, rename, or remove messages.

  • Change the fields of a message.

Otherwise (for example you changed some code in a handler) you don’t need to generate code again.

If your message doesn’t appear…​

…​in the _<GameName>Operations.cs in Unity, then go to GameSparks and select the message on the Cloud code page. Double-check that you have selected Enable client permission.

Message constraints

The following constraints apply to messages:

Walkthroughs and more examples

  • To learn the basics of how to connect from the game client and make a request, see Build Hello World.