【延長戦】ゲームサーバーについて【QAC24】


# ゲームサーバーとは？

ゲームサーバーと一口に言っても様々なものがあります。

私が特に好きで開発したいなって思うのは、リアルタイム通信ではないようなタイプのサーバーですかね。
ﾏﾁｶﾞｲﾅｲﾈ

# リアルタイムな方とそうじゃない方

リアルタイム通信を必要とするゲーム...それは簡単、オンラインゲームですね。
例を挙げるなら FPS だったりはリアルタイム通信を必要とします。

現在のプレイヤーの位置を取得して...とか、弾を撃ったら相手にダメージを与える...とか、そういった処理がリアルタイム通信を必要とします。

一方で、リアルタイム通信を必要としないゲームもあります。

例えば、ただのセーブデータバックアップ機能だけを有していたり、ランキング機能だけを有していたり、そういったものです。
このようなものは、リアルタイム通信を必要としません。

## Epic Online Services という手

もしあなたが、リアルタイム通信を必要とするゲームを開発したいと思っているのであれば、
Epic Online Services (以下 EOS) というものを使ってみるのもいいかもしれません。

EOS は、名の通り Epic Games が提供しているオンラインサービスです。
Epic と言ったら Fortnite とか有名ですよね。

この EOS は、Fortnite のために開発されていたオンライン通信システムを、何と心優しきか、無料で提供してくれています。
なので、リアルタイム通信をめちゃめちゃにさばけるサーバーとかを用意することなく、EOS を使ってリアルタイム通信を実現することができます。

また、無料とはいっても Fornite で使用されているサービスと同じものなので、機能に不満なく利用することが出来ると思います。

ロビーやマッチメイキングシステムなんかが用意されており、これを使うことで、リアルタイム通信を必要とするゲームを開発することができます。

## じゃあ常時じゃないほうは...？

これは...作りましょう。

リアルタイム通信を必要としないゲームサーバーは、簡単に作ることができます。
原理としては一般に HTTP 通信を使うことが多いです。

そのため、Web サーバーを立てる方法さえ知っていれば、簡単に作ることができます。
あれですね、Web アプリとか作ったことあるなら。

私も以前、Python の aiohttp で簡単な API サーバーを立てて、それを使ってゲームを作ったことがあります。

こっちの利点というか楽しいな～って思った理由としては、ゲーム開発と完全並行で作業することが出来るところでしょうか。
欲しい機能などは自分でエンドポイントを増やしていくことで作っていけますし、その API を叩くコードさえ書いておけば、ゲームの開発も進めることができます。

# 非常時接続型ゲームサーバー

ここでは凄く簡単に、ゲームの新しいバージョンが利用可能ならアップデートを促すようなものを作っていきましょう。
言語は久しぶりに Python、フレームワークは aiohttp を使っていきます。

まずは、サーバーを作る場所に移動しましょうか。
んでとりあえず、`pip install aiohttp`で aiohttp をインストールしておきます。

```sh
$ pip install aiohttp
```

次に、サーバーを立てるコードを書いていきます。

```python:main.py
from aiohttp import web

routes = web.RouteTableDef()

@routes.get('/')
async def index(request):
    return web.Response(text='Hello, World!')

app = web.Application()
app.add_routes(routes)

if __name__ == '__main__':
    web.run_app(app)
```

まずはこれで、`python main.py`でサーバーが立ち上がるはずです。

そしたらコンソールに表示されているポートにブラウザでアクセスしてみましょう。
`Hello, World!`と表示されていれば成功です。

わぁ！凄い簡単に Web サーバーを立ち上げられました。

次に、バージョンの検証を行うためのエンドポイントを作っていきます。

```python:main.py

VERSION = '1.0.0'

@routes.get('/version')
async def version(request):
    client_version = request.query.get('version')
    if client_version == VERSION:
        return web.Response(text='OK', status=200)
    else:
        return web.Response(text='NG', status=400)
```

これで、サーバー内でのバージョンをまず`'1.0.0'`としました。
ブラウザで`http://localhost:8080/version?version=1.0.0`にアクセスすると、`OK`と表示されるはずです。
逆に、`http://localhost:8080/version?version=1.0.1`など、異なるバージョンを渡してアクセスすると、`NG`と表示されるはずです。

これで、クライアント側でバージョンの検証ができるようになりました。

じゃあそのコードを Tauri にぶち込んでみますか。

`main.ts`辺りをいじりましょう。

```ts:main.ts
...

const VERSION = '1.0.0';

const main = async () => {
  ...

  const res = await fetch(`http://localhost:8080/version?version=${VERSION}`);
  if (res.status === 200) {
    console.log('OK');
  } else {
    alert('バージョンが古いです。アップデートしてください。');
  }
  ...
}
```

すんません、愛染隊長...実行環境が今手元にないので試せてないです。

が...多分動かないんじゃないかな...？

最後そこだけ...HTTP のことになりますがお話ししましょう。

# CORS とは？

CORS とは、Cross-Origin Resource Sharing の略で、クロスオリジンリソース共有のことです。
まぁ何言ってるか分からないと思うんですが、要は URL 違うとかでアクセスを拒否したりする仕組みのことです。

現在、Tauri が動いているのは`http://localhost:1420`(正確には違うが...まぁ多めに見て)です。
一方、Python のサーバーは`http://localhost:8080`です。

この 2 つの URL は違うので、`http://localhost:1420`から`http://localhost:8080`に`fetch`をしようとしてもエラーが発生します。

おおじゃあ終わったやんけ？って思うかもしれませんが、これは簡単に解決できます。

Python のサーバー側で、CORS を許可するように設定を追加するだけです。

```python:main.py
...

CORS_HEADER = {
  'Access-Control-Allow-Origin': '*',
}

@routes.get('/version')
async def version(request):
    client_version = request.query.get('version')
    if client_version == VERSION:
        return web.Response(text='OK', status=200, headers=CORS_HEADER)
    else:
        return web.Response(text='NG', status=400, headers=CORS_HEADER)
```

このように、レスポンスヘッダーに対して`Access-Control-Allow-Origin`を設定することで、そのオリジンからのアクセスを許可することができます。
現在は`*`となっていますが、これは全てのオリジンからのアクセスを許可するという意味です。

まぁ、本来はセキュリティ上、ちゃんと制限するべきですが。

ちなみに、先ほどのバージョン取得コードを Rust 側で`reqwest`とかを使ってかけば、おおよそ CORS の問題は発生しないです。
あくまでブラウザ上での問題なので。

これは覚えておくと便利かもしれませんね。

# まとめ

ゲームを作ると一言で言った手前、やることが多すぎて何が何だか訳わからなくなってしまった...
私も皆さんもそうなっているでしょう。

ゲームと言ったっていろいろな技術が複雑に絡みあうため、凄く大変です。
ですが、一つ一つの技術を理解していけば、ゲームを作ることもできるようになると思います。

そのための一歩となれば幸いです。

そして私の代わりに、ゲームを作ってください。（？？？）

それでは！
Qiita Advent Calendar 2024 はこれにて完走です！

2 年連続で「女の子の隣で書く」ことを目標にしていたのですが、一回冷静になって考えてみたところ、
普通に考えて女の子の隣にいるのに、なぜ 1 人で記事を書くのだろうか。否、私なら書かない（反語）。

という訳で、この目標は破綻してしまいました。
（正確には、「記事を書いているなら隣に女の子がいない」という証明になってしまった）

ま、とりあえず今年も無事に終わりましたので、2025 年以降もよろしくお願いします。
よいお年を...