メインコンテンツまでスキップ

さまざまなAGSイベントをリッスンし、処理する

Last updated on April 4, 2025

Overview

This guide provides information on how to modify the Extend Event Handler app template to listen and handle different events from AccelByte Gaming Services (AGS).

Prerequisites

Clone an Extend Event Handler app template.

git clone https://github.com/AccelByte/extend-event-handler-csharp.git

Identify and download specific event descriptors

Here is how you can get the protobuf event descriptor for the AGS event you are looking for.

  • Identify your AGS event: Go to the API Events page and find the AGS event you are looking for.

  • Locate the Protobuf event descriptor: After finding the AGS event, get the URL to the protobuf (*.proto) file on the same page.

  • Download the protobuf event descriptor: As an example for this guide, we need the userLoggedIn event. Therefore, we need to download the IAM directory which contains the userLoggedIn event.

Generate stub from event descriptor

Put the iam directory you have downloaded in the src/AccelByte.PluginArch.EventHandler.Demo.Server/Protos directory in your Extend Event Handler project.

Then, you need to include the specific protobuf file to be generated by .NET gRPC Tool. Modify src/AccelByte.PluginArch.EventHandler.Demo.Server/AccelByte.PluginArch.EventHandler.Demo.Server.csproj file and add or update the following section.

<ItemGroup>
<Protobuf Include="Protos\iam\account\v1\account.proto" GrpcServices="Server" />
</ItemGroup>
ヒント

You can include more than one protobuf file. Just add more <Protobuf> lines inside <ItemGroup>.

Next, use the following command to generate code from the included protobuf files.

dotnet build

Creating your callback implementation

  • Create a New C# File: Go to directory src/AccelByte.PluginArch.EventHandler.Demo.Server/Services and create a new C# file. For example UserLoggedInService.cs.

  • Embed the Generated Stub: In the new C# file, create a new class which inherits UserAuthenticationUserLoggedInService.UserAuthenticationUserLoggedInServiceBase. For example, UserLoggedInService.

    namespace AccelByte.PluginArch.EventHandler.Demo.Server.Services
    {
    public class UserLoggedInService : UserAuthenticationUserLoggedInService.UserAuthenticationUserLoggedInServiceBase
    {
    ...
    }
    }
  • Define OnMessage Methods: Override the OnMessage method and implement the logic which will be executed when the AGS event is received.

    public override Task<Empty> OnMessage(UserLoggedIn request, ServerCallContext context)
    {
    ...
    }

Register the event handler struct into the service

Go to src/AccelByte.PluginArch.EventHandler.Demo.Server/Program.cs in the Event Handler project and register the gRPC service we have created as follows.

app.MapGrpcService<UserLoggedInService>();

Protobuf Event Descriptors limitations

Protocol Buffers (Protobuf) has specific limitations when it comes to nesting data structures. Direct nesting of repeated fields or maps is not allowed; for instance, you cannot have a repeated field within another repeated field or a map within another map.

Similarly, Protobuf does not support using repeated fields or maps as the values of a map. To achieve nested structures, we opted to replace these types with google.protobuf.ListValue and google.protobuf.Struct. However, this approach introduces slight changes to how users access values in the Protobuf-generated code, as dynamic types like google.protobuf.ListValue and google.protobuf.Struct require additional parsing and handling compared to statically defined fields, though they ensure seamless compatibility with the original JSON data structure.

Example payload for the matchmakingChannelCreated event:

{ // MatchmakingChannelCreated
"id": "string",
...
"payload": { // MatchmakingChannelCreatedPayload
...
"ruleset": { // Ruleset
...
"alliance": { // Alliance
...
"combination": { // AllianceCombination
...
"alliances": [ // google.protobuf.ListValue
// should have been:
// repeated repeated AllianceCombinationAlliance
// or simply: a list of list of AllianceCombinationAlliance
[
{ // AllianceCombinationAlliance
"name": "string",
"min": 0,
"max": 0
}
]
]
}
}
}
}
}

Accessing values within google.protobuf.Value, google.protobuf.ListValue, google.protobuf.Struct

google.protobuf.Value, google.protobuf.ListValue, and google.protobuf.Struct are dynamic types in Protocol Buffers that provide support for JSON-like data structures. These types are particularly useful for cases where the schema needs to accommodate heterogeneous or nested data, such as arrays and objects with mixed types. The following example demonstrates how to define and interact with these types in the context of an EventData message.

message EventData {
string id = 1 [json_name = "id"];
string name = 2 [json_name = "name"];

google.protobuf.Value value = 10 [json_name = "value"];
google.protobuf.ListValue list_value = 20 [json_name = "listValue"];
google.protobuf.Struct struct = 30 [json_name = "struct"];
}

The EventData message above includes three dynamic fields:

  • value: A google.protobuf.Value that can store any valid JSON value (e.g., a string, number, boolean, null, object, or array).
  • list_value: A google.protobuf.ListValue representing a JSON array, where each element can be of a different type.
  • struct: A google.protobuf.Struct representing a JSON object with key-value pairs, where the values can be of any valid JSON type.

Below is an example JSON payload for the EventData message:

{
"id": "id0",
"name": "name1",
"value": "value2",
"listValue": [
true,
123,
"abc",
["x", "y"],
{"secret": "UFOs are real"}
],
"struct": {
"bool": true,
"number": 123,
"string": "abc",
"array": ["x", "y"],
"object": {"secret": "UFOs are real"}
}
}

This JSON demonstrates the flexibility of these dynamic types, showcasing mixed and nested data types in listValue and struct.

// EventData eventData = ...

AssertEquals("id0", eventData.Id);
AssertEquals("name1", eventData.Name);
AssertEquals("value2", eventData.Value.StringValue);

AssertEquals(5, eventData.ListValue.Values.Count);
AssertEquals(true, eventData.ListValue.Values[0].BoolValue);
AssertEquals(123, eventData.ListValue.Values[1].NumberValue);
AssertEquals("abc", eventData.ListValue.Values[2].StringValue);
AssertEquals(2, eventData.ListValue.Values[3].ListValue.Values.Count);
AssertEquals("x", eventData.ListValue.Values[3].ListValue.Values[0].StringValue);
AssertEquals("y", eventData.ListValue.Values[3].ListValue.Values[1].StringValue);
AssertEquals(1, eventData.ListValue.Values[4].StructValue.Fields.Count);
AssertEquals("UFOs are real", eventData.ListValue.Values[4].StructValue.Fields["secret"].StringValue);

AssertEquals(5, eventData.Struct.Fields.Count);
AssertEquals(true, eventData.Struct.Fields["bool"].BoolValue);
AssertEquals(123, eventData.Struct.Fields["number"].NumberValue);
AssertEquals("abc", eventData.Struct.Fields["string"].StringValue);
AssertEquals(2, eventData.Struct.Fields["array"].ListValue.Values.Count);
AssertEquals("x", eventData.Struct.Fields["array"].ListValue.Values[0].StringValue);
AssertEquals("y", eventData.Struct.Fields["array"].ListValue.Values[1].StringValue);
AssertEquals(1, eventData.Struct.Fields["object"].StructValue.Fields.Count);
AssertEquals("UFOs are real", eventData.Struct.Fields["object"].StructValue.Fields["secret"].StringValue);