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.
If you are new to GameSparks, we recommend that you begin with Getting started using Amazon GameSparks.
Topics
- Message types
- Create a custom message
- Message fields
- Access data passed into your event or request
- Example: Personal high score event
- Exit an event handler
- Send a notification to another player
- Override system-defined requests
- Raise an event from cloud code
- Write log entries to CloudWatch
- Handle errors and exceptions
- Save and deploy your game
- Test your messages in the test harness
- Generate and download code
- Message constraints
- Walkthroughs and more examples
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
-
On the Cloud code page, choose Create message.
-
Choose Event, Request, or Notification.
-
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.
To add custom shapes
-
On the Cloud code page, choose Configure, and then choose Custom shapes.
-
On the Manage custom shapes page choose Create shape.
-
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 |
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.
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.
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.
If you are just getting started with GameSparks, see Getting started using Amazon GameSparks 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.
To create a new player on a Windows machine
-
In the GameSparks navigation pane select Dev to view your game key in the Dev stage configuration card.
-
On your Windows machine, navigate to
C:\Users\<user id>\AppData\Local\GameSparks
. -
Look for a folder with the same name as your game key.
-
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 Monitoring Amazon GameSparks 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 fromSendNotification()
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 exceptPlayerNotConnectedException
.
// 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
To test or use your messages
-
Select your message and make sure that Enable client permission is selected.
-
Make sure the handlers you want to use are not disabled (change this using the Actions button).
-
Save all of your messages.
-
In the navigation pane, select Dev.
-
Choose Deploy as new snapshot.
-
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.
-
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
-
Make sure that you have saved and deployed your changes (see above).
-
Select the message that you want to test, and then populate and modify the fields as needed to run your test.
-
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.
To generate and download code
-
Confirm that you have saved and deployed as described above.
-
On the Snapshot card choose Actions, and then choose Generate Code.
-
Choose the Game client platform, the Language, and then choose Generate Code.
-
Choose Download.
-
On your development machine, extract from the .ZIP file the <GameName>Operations.CS file.
-
Add the .CS file to your game project in Unity (for example, drag and drop the file onto the Project pane).
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:
-
You can't use the keyword
message
except to access input fields. See Access data passed into your event or request. -
You can't use the keyword
GameSparks
. -
For other constraints, see Amazon GameSparks endpoints and quotas.
Walkthroughs and more examples
-
To learn the basics of how to connect from the game client and make a request, see Hello World from Amazon GameSparks.