e-sportsプロジェクトのOraga eSportsの制作過程をここに記します。
開発過程
まず、GraphQLをバックエンドサービスとしてNode.jsで動かしますので、ローカル環境とバックエンドのNode.jsのバージョンを念の為に合わせます。
なぜGraphQLかと言いますと、eSportsは世界全体で競う賞金制ゲームであるため、例えば自分が賞金1万円を獲得するためゲーム攻略を頑張っていたとしたら、その間に他のプレイヤーがその賞金を取っていたとしたら、すぐに通知してあげる必要があるからです。そうしないとそのプレイヤーはひどく落胆し、ゲーム自体に対して怒りの感情を持ってしまうからです。
GraphQLはTwitterがリアルタイム通知の環境のために積極採用しているWebSocketライブラリであり、AWS Amplifyを使うとAWS Lambdaをリゾルバとして利用できるなど、有利な点があります。現在AWS Lambdaは最新Node.jsバージョンがv22であるため、ローカル環境のNode.jsもv22に念の為合わせます。
nvm ls-remote
nvm install v22.16.0 #Latest LTS
nvm alias default v22.16.0
ゲーム作成
eSportsは Vite + Svelteの環境で作成しました。
Vite + Svelte環境はnpm run buildしてできたファイルをCroudFrontに上げるだけですのでインフラ作業がとても簡単です。
eSportsの第一弾としてシューティングゲームを作成していくのですが、HTML5ライブラリのPixi.JSとSvelteの組み合わせは、検索すると良い情報がたくさん出てきます。Svelteはパフォーマンスが良いのでHTML5ゲームと相性がよく、この組み合わせは比較的簡単にゲームを作成することができます。
シューティングゲームの作成方法はこちらを参考にしました。
キャラクターのデザインは商用利用が可能なこちらのサイトを活用しました。
GraphQL環境の構築
ゲームの作成が約10時間で終わりましたので、次にGraphQL環境を用意します。この環境設立にAWS Amplifyを活用しますので、amplifyのcliを使用して環境をセットアップしていきます。
Amplifyの環境構築にはCLIを使用するGen1と、CDKを使用するGen2の2種類があります。Gen2を使用すると、毎月$15が取られてしまうので今回はGen1を利用します。
Gen1であればLambda + CloudFrontの環境を自分で自由に作れるので費用はほぼDNS費用だけとなり、比較的お得です。(AWSのAPIは良心的な従量課金制であるため結構安い)
Gen1による構築方法については以下のサイトが詳しいです。ほぼ同じ手順でGraphQL APIをセットアップしています。
GraphQLスキーマの編集
amplify add api
上記の段階まで終わるとGraphQLスキーマの編集を行います。
amplify/backend/api/{project name}/schema.graphql
にはサンプルとしてTodoのtypeがあります。それを利用して、私はこのファイルを以下のように変更しました。
type GameServerProcess
@model
@auth(rules: [
{ allow: public, provider: apiKey }
]
) {
id: ID!
type: String!
message: String!
playerId: String!
}
世界中のゲームプレイヤーにゲームをプレイしてもらわなければなりませんので、認証はproviderをAPIキーとして、allowをpublicにします。
ここまで来たら、あとはamplify pushするだけです。これによって、GraphQLが利用するDynamoDBなどは全てAWS Cloud上に用意されます。ローカルの環境もあとはクエリーを呼び出すだけの状態にまで準備されています。
私はschema.graphql
ファイルにSubscriptionのtypeを宣言してませんが、自動でSubscriptionクエリーメソッドもAWSによって用意されています。
APIキーのローテーション方法
これは、以前はすごく分かりにくかったのですが、今は改善されて簡単にできるようになっています。
amplify/backend/api/{project name}/
フォルダの下にparameters.json
というファイルがあります。ここに
"CreateAPIKey": 0
をセットしてamplify push
を行うと現在のAPIキーが削除されます。
そして同じファイルを以下のように変更してもう一度amplify push
を行うとAPIキーを新しく作成し直してくれます。(初期状態ではAPIキーは7日間しか有効期間がありません)
"CreateAPIKey": 1,
"APIKeyExpirationEpoch": 1768000000 // 2026年1月9日(約半年後)
(ここにこのようにすると書いてあったのですが、再生成しても私の場合有効期間が1週間のままで変わりませんでした。そのため、私はAWSコンソールで直接、有効期間を変更しました。その方が意外と簡単で確実です)
AWS Lambdaファイルの作成
GraphQLサーバーとして働くLambdaファルを作成するためにAWSのコンソールを開き、AWSサービスの中でLambdaを開きます。
そしてindex.js(mjs)ファイルを以下のようにします。
export const handler = async (event) => {
return {
id: new Date().getTime(),
type: 'This is from Lambda',
message: 'This is from Lambda',
playerId: 'This is from Lambda',
createdAt: new Date(),
updatedAt: new Date(),
};
};
この値は全てGraphQLのスキーマで設定した値を返すようにしたものです。意外かもしれませんが、DateオブジェクトをAWSはインターネット上で受け渡しすることができます。
DataSourceの作成
AppSyncサービスをWebコンソールで開くとAPIが一つ作成されています。そこで以下のようにData Sourceを開くボタンを押します。今作ったAWS Lambda FunctionをData Sourceとして作成します。
Schemaのリゾルバに設定
そしてそれをGraphQL Schemaのリゾルバに設定します。具体的には以下のリンクをクリックして変更します。(PipelineというのはAWSがデフォルトで設定している少し複雑な設定です。ここの作業手順を詳しく知りたい方はこちらを買って読んでみてください。)
AppSyncに接続するコードを書く
以下のコマンドを実行して、接続するコードを書いていきます。
npm install aws-amplify
後はGraphQLを呼び出したい時に呼び出すだけですので、ゲームに勝利した時に他のプレイヤーに賞金は私がいただいた、とリアルタイムに通知できるようにゲーム勝利時のコードを変更します。
<script>
import * as PIXI from 'pixi.js'
import { Sprite, Graphics, Text, Ticker, getApp } from 'svelte-pixi'
import { generateClient } from 'aws-amplify/api';
import { createGameServerProcess } from '../../graphql/mutations';
const client = generateClient();
async function onGameWonOrLose() {
clearInterval(timerCtrl2)
started = false
const query = {
type: 'Test',
message: 'Test',
playerId: 'Test',
};
/* create a todo */
await client.graphql({
query: createGameServerProcess,
variables: {
input: query
}
});
}
ゲームの勝敗が決着がついた時にこのメソッドを呼び出せば..
このようにGraphQLのクエリが呼び出されます。この辺りは本当に簡単ですね。
GraphQLのDataSourceに設定しているAWS Lambda Functionのサイトを見てみます。以下のようにLambda Functionが呼ばれていることを確認できます。
次にSubscriptionを実装してみます。これができれば、まだプレイ中の他のゲームユーザーに「おっと誰かが賞金をかっさらったようだ。あなたの賞金は₣1にリセットされてしまったよ。」と伝えることが出来ます。
import * as subscriptions from '../../graphql/subscriptions';
const createSub = client
.graphql({ query: subscriptions.onCreateGameServerProcess })
.subscribe({
next: ({ data }) => console.log(data),
error: (error) => console.warn(error)
});
呼ばれました!一回で成功です。簡単ですね。messageとplayerIdとtypeを見てみてください。”This is from Lambda”と書かれてあります。このページ内の「AWS Lambdaファイルの作成」の項目を見てください。全く同じ情報です。
素晴らしい。Amplify Gen1は本当に簡単にLambdaをGraphQLサーバーとして設定できますね。
後はLambdaからjs(mjs)ファイルをダウンロードして必要なnpmパッケージをインポートしてzip化してLambdaに再アップロードすればブロックチェーンにトランザクションを送ることもできます。その場合は、GraphQLのSubscriptionは3秒ほど遅れることになります。トランザクションがちゃんと受理されるまではトランザクションIDが発行されないためです。トランザクションIDは重要な情報ですのでそれをawaitで待ってからLambdaはreturnするようにします。
CloudFrontで公開
ゲームはできており、実機のスマホでも動作を確認してみたい為、試しにCloudFrontで公開してみます。
npm run build
(以下のコマンドを叩くと、ビルドしたファイルをチェックすることが出来ます。)
npm run preview
もし、画像のパスを/public/assets
のように/public
からにしていると、この時点で画像取得エラーが起こります。画像のパスは/public
を含めない形で設定する必要があります。
次は、distフォルダ以下のindex.html
とassets
フォルダをS3のバケットにアップロードします。
次にCloudFrontをAWSコンソールで開きます。S3をオリジンにするのがデファクトスタンダートなので何も考えなくても設定することが出来ます。
途中でoraga-esports.com
のドメインを買いました。意外に.comが安かったです。(年1400円)
私はWAFをオフにしました。Vite + フレームワークは静的ファイルを生成するだけですので攻撃されても大丈夫と判断しています。APIキーが静的ファイル内にないこともWAFをオンにしない判断材料になりました。
Trouble Shooting
私が一つ詰まったのはDefault root object
を空の状態でDistributionを作成してしまったことです。この場合、https://oraga-esports.com/index.htmlにはアクセスすることが出来ますが、https://oraga-esports.com/にアクセスしようとすると403 Access Deniedとなります。
Default root objectにindex.html
を設定し忘れた場合は、CroudFrontのGeneral Tab > Settings section > Edit > scroll down to Default root objectからindex.htmlを設定して保存するとDomain Nameからアクセスができるようになります。
以上で開発の準備が完了しました。(ゲームのURLは以下)
https://oraga-esports.com/
最後に
GraphQLに通信する箇所のソースコードは以下のURLにあります。
一つAWS AppSyncとDirect Lambda Resolverの凄いところを書くとすれば、ゲームの内容にもよりけりですが、一度amplify pushをした後は、もうamplify push をする必要がない、ということが挙げられます。バックエンドのGraphQL処理の変更はAWS Lambdaからダウンロードしたjsファイルに編集してzip化してAWS Lambdaにアップロードすれば次回からはその変更が反映されるためです。
次の記事 >