...
「つよい方が勝ち」というカードゲームを完成させた。
今こんな感じにカードを設定してるんだけど、味気ないからいい感じのカスタムエディターが欲しいんだよね。
たとえばこんなカードがあったとする。「番号」「名前」「こうげき」「タイプ」「イラスト」はそのまま入力すればいい。
残りが「こうげき100〜200には勝つ!」みたいな部分で、ここは効果処理のためにこんな構造を組んでるんだよね。
問題なのは、選択したTypeによっては必要ないプロパティまで設定項目に存在してるってこと。あと効果文もゲームを再生してみないと表示されない。(この図作ってて思ったけど、Condition
はBattleCondition
に改名した方がいいし、CardRange
はCardCondition
に改名した方がいいな)
ただUnityのカスタムエディタって、作るの超絶めんどいんだよね……。AIに投げてもいいけど、テストフロー整備できないからそんな期待できないし。
/serena CardDataを視覚的に編集しやすいよう、カスタムエディタを作成してください -t -s -c
要件
・視覚的にわかりやすく、誰が見ても一目でわかるUI/UXにする
・無意味な設定項目は非表示にする
・極力少ないクリック回数で、新規カードを設定できるようにする
・効果説明文をプレビューできる
うん。まあ、そうだよね。そりゃ無理(スクショすら撮ってなかった)
UI Toolkitの挙動と表示をテストする機構、待ってます。
マルチプレイ。ずっとこれがやってみたかった。
構想はあって、UnityのNetcode for GameObjectsを使ってカードゲーム部分のクライアントとサーバーを実装し、サーバーアプリはDedicated Serverでビルドする。このサーバーアプリ1個が、要するに1ルーム分に相当する。
discord用の変なbotたちを動かしてる個人用VPSがあるので、そこでサーバーアプリを5個くらい稼働させて、ポートで接続先を分岐させる。こうすれば、ルームを選択してみんなでカードゲームができる!
ゲームサーバーを建てちゃうよ
— Rinia/りにあ (@2RiniaR) August 19, 2025
時代を逆行するVPS上プロセス直立てホスティング
超原始的だけどこのくらいでいい。こういうクラフト感のあるやつが好き。
ロビーサーバーを作るつもりはないけど、作るとしたら複数のゲームサーバーからHTTPリクエストを送ってもらって状態更新を通知するイメージかな。
余談だけど、ゲームのマルチプレイっていうのは大きく2つの実装方法があって、それが「P2Pでマッチングさせる方式」と「サーバーにみんなでアクセスする方式」。
「P2Pでマッチングさせる方式」は、N人のクライアントアプリが接続しあってネットワークを構築し、誰か1人がホストとなってサーバー兼クライアントの振る舞いをする。ただ、インターネットの海を超えて接続先を見つけたり見つけてもらったりするのは相当な手間がかかるので、そこで運営が用意したサーバーがアプリ同士のマッチングを担当する。これが「リレーサーバー」って呼ばれてるやつだね。
Minecraftを身内でマルチプレイしようと思うと、サーバーを建てて、一緒に遊ぶ人に自分のIPアドレスとポートを教えて入力してもらう必要があるじゃん?あれをリレーサーバーが自動で管理してるイメージ。試したければ、リレーサーバーはUnity CloudとかPhotonが一定接続数までなら無料で提供してくれてるので、それを使うこともできる。
そして今回使うのが、「サーバーにみんなでアクセスする方式」。これは言わずもがな、クライアント・サーバーシステムそのままだね。運営が用意したサーバーでゲームを動かして、そこに各プレイヤーがクライアントとして接続する。サーバーはセッション(簡単に言えば状態)を保持するので、この手のゲームなら1ルームにつき1プロセスにしておくと最小単位で区切れていい感じ。ロビーみたいなのを用意してリアルタイムに各ルームの状況を表示したいってなると、専用のロビーサーバーを立ててそこに接続する形になる。
Netcode for GameObjectsを導入して、実装に組み込んでくよ。
まず、カードゲームって相手の手札は見えないから、プレイヤーによって違うものを表示できるように、データとビューを分けないとね。
構想としては、Viewだけを別クラスに分けて、サーバー側ではNetworkObjectにしたモデルクラスをRPCで呼び出し、そこからクライアントでViewを呼び出す。こうすれば、Game, Card, Field, Player, Deck あたりをNetworkObject化するだけでよさそう。
ただちょっと複雑なのが、リソースを同期する仕組み上、今回はサーバーもクライアントも同じソースコードで実装するんだよね。要は、こんな感じの分岐でサーバーの処理とクライアントの処理を書き分けなきゃいけない。
if (isServer)
{
// サーバーで実行する処理
}
else
{
// クライアントで実行する処理
}
「サーバー専用」「クライアント専用」「共通」の処理が散らばってると収集付かなくなるから、わかりやすくしておこう。こんな感じでサーバーとクライアント用のシンボルを切っておいて、専用処理はこの中に書く。
フォルダもServer, Client, Networkで分ける。Serverにはサーバー用、Clientにはクライアント用のエントリーポイントがそれぞれ入っていて、そこからNetworkにあるオブジェクトを操作する感じ。
一旦、アドレスとポート指定でサーバーへ接続して、接続されたら画面に番号が表示される実装だけ入れてみた。これで接続テストしてみよう。
コマンドライン引数を読めば、サーバー起動時にアドレスやポートを指定することができるらしい。ちなみにAwake()
にするとまだNetworkManagerの初期化が終わってなかったりするから、Start()
でやると良いよ。
#if MARE_SERVER
private void Start()
{
var args = Environment.GetCommandLineArgs();
ushort port = 7777;
var addr = "127.0.0.1";
for (var i = 0; i < args.Length; i++)
{
if (args[i] == "-server")
{
addr = args[++i];
}
if (args[i] == "-port")
{
port = ushort.Parse(args[++i]);
}
}
StartServer(addr, port);
}
#endif
サーバー用のLinux Serverビルド、クライアント用のMacOSビルドが両方できた。テストしづらいのでMacOSにしてるけど、本番は多分WebGLビルドにする。
当たり前だけど、Macでサーバービルドしないとlocalhostで動かせないんだよね。だけど本番想定はLinuxだし、dockerでいい感じにできないかなって。
そう思ったけど手元にあるのはAppleSiliconのMacだった、amd64向けのビルドだからそりゃ動かないね。頑張ってarmからamd64のdockerイメージを動かせないか試したけど、どうやら無理そう。armからx86_64向けのコンテナをビルドするのは簡単にできるのに、その逆ってこんなにしんどいのか。
もういっそ、amd64のdockerイメージにして個人VPSに置いちゃうか!
FROM ubuntu:latest AS runtime
COPY ./dist .
ENTRYPOINT ["./mare-server.x86_64"]
CMD ["-server", "0.0.0.0", "-port", "7777"]
そういえば初歩的だけど、ビルドの出力先は空のディレクトリを指定した方が良い。間違って変な場所にばら撒いちゃって仕方なくかき集めてたら、普通にビルドバイナリの依存ファイルが漏れたから。
dockerイメージにできたので、個人VPSに移動してMakefileで起動。適当な専用ポートを決めて、ファイアウォールの設定を書き換えるよ。(ufwのreload忘れにより一敗)
なんか上手く行かない?って思ったら、--port
っていうコマンドライン引数がUnity組み込みの引数と衝突して吸われてた。
無事起動を確認。そしたら今度はnetcatでpingが通るか確認してみる。
通らないな、なぜ……ああ、dockerコンテナ内なのにアプリでバインドしてるアドレスがホストマシンのIPだった。0.0.0.0
に変更。
$ docker run -d --name mare-server-container --platform linux/amd64 -p 7777:7777 mare-server-image -server 0.0.0.0 -p 7777
それでも通らないなって思ったら、どうやらUnityTransportってデフォルトでUDPらしい。TCPしかフォワーディングしてなかった。設定で選べるみたいなので、今回はカードゲームであることを考慮してWebSocketへ変更。
再度クライアントアプリをEditorから起動してみる。
通過!!!この瞬間が一番楽しい!!!
無事にサーバーとアプリの疎通が確認できたから、次はゲーム部分をマルチプレイ化して、オンライン対戦ができるようにしていくよ!そのために、マルチプレイを手元でデバッグできる開発環境を整備しないとね。