はじめに
プライベートでゲーム制作をしていること、その開発にUnity + F#を使っていることについて書きました。本稿では、
- どんな人が
- なぜ
- どんなゲームを
- どのように
作っているか、そして、
- なぜF#を使ったのか
- F#をどの程度使えればメリットが得られるか
について書きました。
どんな人
30代男性、ゲームが好きな職業プログラマです。
友人や妻と一緒にゲームをやることが多いです。1人プレイのゲームもやりますが、多人数でやることのほうが多いです。ゲームは頭を使ってやらないとすぐ飽きてしまうので、難しめのゲームか対戦ゲームばかりやっています。
仕事では業務システムの受託開発をしています。地味なBtoBのシステムばかりで、ゲーム開発とは無縁です(今回初めてゲーム開発をしています)。プログラミング言語もC#とPHPがほとんど、仕事で関数型言語を使うことはありません。趣味でHaskellを触ることはありますが、F#は使ったことがありません。
#なぜ自分でゲームを作っているか
友人とやるゲームは、協力ゲームもやりますが対戦ゲームもやります。主に4人でゲームをすることが多いです。協力ゲームには困っていませんが、2対2の対戦ゲームで満足できるものが少ないと感じています。
2対2の対戦ゲームには、
- 2対2の対戦用に設計されたもの
- 2対2以外の対戦用に設計されているが、2対2も出来るもの
- 対戦用に設計されていないが対戦も出来るもの
などがあります。このうち「2対2の対戦用に設計されたもの」は非常に少なく、それ以外は(2対2の対戦をしようとすると)ゲームシステムやゲームバランスが悪い場合がほとんどです。最初から2対2の対戦用に設計されたゲームでなければ、なかなか満足できません。
また、(市販の)対戦ゲームのゲームバランスは、ベテラン向けに調整されているという点も問題です。私たちは若いころこそヘビーゲーマーだったものの今ではおっさん週末ゲーマーです。家庭や仕事があり、ゲームの練習に時間を割きたくありません。そうすると、たまに集まって対戦するときには、それぞれの習熟度が全然違います。「ストーリーモードは一通りクリアした」から「ボタン配置教えて」まで。こんな状態の4人で、ある程度白熱した対戦を実現したいのです。
まとめますと
- 2対2の対戦ゲームは新しいゲームを待っていても期待できない
- 市販の対戦ゲームのゲームバランスは、私たちに適していない
という理由から自分でゲームを作ることにしました。
どんなゲームを作っているか
ガ〇ダムバーサスシリーズのパクりです。
- 3D対戦ゲーム
- 2対2のみ。それ以外の人数は考慮しない
- 敵は自動ロックオン。エイム操作、視点移動は不要
- オンライン対戦可
動画
主な設計
このゲームの設計で解決しなければならない課題は、
- オンライン対戦でも遅延を感じさせない
- 全てのプレイヤーの間で状態を同期すること
です。これらの課題を解決するため、以下のような戦略を取りました。
- 全てのプレイヤーの間で直接通信する
- 通信ではプレイヤーの操作情報のみを送る
- 操作をしてからゲームに反映するまでに一定の遅延(現在は100ms)を設ける
この戦略で実装し、4人対戦しても十分に遅延が隠せることを確認しました。
しかし、これだけでは状態の同期が取れない問題が見つかりました。同期が取れないということは、各々の画面で別の状態が表示されていて、ある人は負けそうだから頑張ろうと考えていたり、ある人は既に勝利を収めて喜んでいたり…ゲームが成り立ちません。調べてみると、Unityの物理演算は毎回結果が同じにはならないようです。Unityでリプレイ機能(同じ結果を再現する機能)を実装するのに苦労されている方がたくさんいらっしゃることも分かりました。今回はUnityの物理演算は使わずに、独自の実装でオブジェクトを動かすことにしました。これでかなり改善されましたが、まだ完全に同期を取れていません。実はオブジェクトの移動こそ独自に実装したのですが、衝突判定だけはUnityの機能をまだ使っています。これが原因であるか特定していませんが、衝突判定まで実装するのは結構大変そうです…。
開発体制
開発は私1人で行っています。週末土日のうち、どちらかの午前に開発を進めることが多いですが、全く開発の時間を取れない週もあります。また、スキルと優先順位の問題で
- プログラム→作る
- 3Dモデル→全てアセットストア
- 2D画像→フリー素材
- 音楽→フリー素材
としています。
プライベートの空き時間で開発をしているため、
- 開発時間を短くしたい
- 自分がいらない機能は後回し
- UIは汚くてよい
- 影響の小さいバグは無視
- モチベーションを保ちたい
- 機能追加を優先
- 楽しくなるものから実装
- 機能追加の単位を小さくしたい
- まとまった作業時間が取れない
- 実装の途中で時間が経つと忘れる
- 仕様書、設計書を作りたくない
- 楽しくない
- 「今月は設計書を書いただけ」とかは嫌
といった私自身からの要望があります。
Unity + C#で作り始めた
始めはUnityとC#で作り始めました。F#に興味はあったものの、以下の理由でC#を選択しました。
- UnityではC#を使うのが普通
- F#は構文すら知らない。学習コストがかかる
- ゲーム制作すら初めてなのに、使ったことのない言語を選んだら開発が進まないかも
- すぐに開発を始めることを重視
難しいことをあれこれ考えるよりも開発を進めることを優先しました。機能をどんどん追加して行き、必要になってから設計を見直してリファクタリングすることにしました。
しばらく開発を進めると、予想通り品質の悪いコードが増えてきました。特に問題になったのが状態の管理です。ゲームではかなり多くの状態(キャラの位置、向き、ライフ、残弾、硬直…)を持たなければなりません。例えばライフだけを取っても、キャラ生成時、弾が当たったとき、格闘が当たったとき、回復アイテムを使ったとき…など様々な場合にライフという状態が変更されます。これらを正しく管理しなければ、バグの検出も難しくなりゲームを正しく動作させられません。こんな複雑なことを考えていたらバグも増えますし、何より機能追加のスピードが遅くなります。機能追加はゲーム制作で一番楽しいところで、これが気持ちよく出来ることはとても大事です。
F#に移植した
状態を正しく管理するのは、「状態を変更するコード」と「状態を変更しないコード」を分離出来れば簡単になります。これってF#が得意なことじゃありませんか?Immutableな変数と純粋な関数で「状態を変更しないコード」つまり副作用のないコードを書き、限られたコードだけで状態を変更するようにします。
移植にはかなりの時間がかかりました。3か月間の週末プログラミングで書いたC#のコードを移植するのにゴールデンウィーク中5日間ぐらいかかりました。移植の手順は以下の通りです。
- C#のクラスやロジックをそのままF#に移す
- while、for、foreachをmapやiterに変える
- 変数をimmutableに、クラスをrecord、algebric dataに変える
- Unityオブジェクトに変更を加えるコードを限定するようにリファクタリング
これだけでコードの管理がとても楽になりました。移植作業後の機能追加がとても速くなり驚きました。
Unityでゲーム制作するならF#?
今回C#をやめてF#を使うことにした目的はコードを管理しやすくすることで、その目的は達せられたと考えています。
一方でF#を使わなくても達成する方法はいくらでもあります。そもそもコードの構成をしっかり考えてから実装すればC#でも品質の良いコードは書けます。F#でも設計方針がなければ同じようにコードの品質が悪くなったでしょう。今回はF#を使うことで改善する見込みがあったからF#を使いました。
私はF#に移植したことで良かったと信じていますが、
F#の恩恵を受けるための最低基準
既にC#でゲーム開発をしている人がF#に興味を持った場合、どれくらいF#について学べば恩恵を受けられるでしょうか?今回、私はF#の構文すら事前に知らずに移植を始めました。構文は調べながら、だいたいこのページのDos and Don’tsに従ってF#を書けば十分に恩恵を受けられると思います。F#初心者や関数型言語初心者は、これ以上の難しい話はまずは無視しても良いのではないでしょうか。ただし、Unityを使う以上、mutableな変数やclassを使うことは避けられないと思います。避けられないとはいえmutableな変数とclassをほとんどなくすことは出来ます。完璧にしなくとも十分メリットを感じられるはずです。