🐼

CursorでVRChat向け将棋ゲームを作った体験談

に公開

はじめに

ずっとAIコード生成をまともに使っていなくて時代に置いていかれていた中、MCPとか出てきてようやく危機感を覚えたのでCursorを触ることにしました。
(Claude Desktopと迷いましたがChatGPTが勧めてくれたのでCursorにしました。)

何を作ろうかというところで、前々から考えてたVRChat向けのリアルタイム将棋バトルの機能を作ることにしました。
Cursor記事でよく見るReactでのTODOアプリに興味がなかったので。

・出来上がったもの
https://x.com/i/status/1919810663154598384

この記事はその体験談です。
結果は失敗寄りでしたが、CursorでUnityプロジェクト(特にVRChat向け)を対象にした記事はあまり見受けられなかったので書きました。

開発方針

開発範囲

  • コード生成
    Cursorによる実装
  • Unity Editorのシーン
    手動での構築
  • 単体テスト
    TestRunnerを使用。テストコードはCursorに生成させる
  • 結合テスト
    xUnitを使用。自作のUnity+UdonVMシミュレーターを活用。テストコードはCursorに生成させる
  • 各種ドキュメント
    Cursorに生成させる

自動エラー修正の仕組み

コード生成後、コンパイルチェックとテストチェックを自動で行い、
修正をしてくれるようにするため以下のスクリプトを用意しました。
結果はCursorが理解しやすい形式に整形しています。

  • C#コンパイルチェック : MS Buildによる結果の取得
  • U#コンパイルチェック : UnityEditor上でのUdonSharpコンパイルチェックの実行
  • 単体テスト実行 : UnityEditor上でのTestRunnerの実行

U#コンパイルチェックは、UdonSharp非対応のコードが生成された場合の対策として実装しました。
逆にU#コンパイルチェックのみでは、C#の通常のコンパイルエラーの場合に
「UdonSharpコンパイル前にC#のコンパイルエラーを修正してください」という役立たない情報しか得られません。
そのため、C#とU#の両方のチェックが必要と判断しました。

既存のツールがあれば活用したかったのですが、見つからなかったため独自に実装しました。

要件と仕様と設計

とりあえず作りたいことをまとめたいのでCursorにrequirements.mdを作ってもらいました。
requirements.mdなのはChatGPTに相談したらそう言われたからです。
後日web検索してもCursorでrequirements.mdって言ってる記事は見つからなかったのでたぶんChatGPTの吹かしです。
でもまあ、まとめてくれるなら別になんでもよかったです。

そしてここで失敗して、実装寄りのことを混ぜてcursorに話してしまいました。
操作の肝である、Canvas上のUIをポインターでドラッグ操作する機能を予め作っていたため、それとくっつける実装をして欲しかったのです。

そのためか、どんなモジュール構成なのかとか盤面のデータはどう持たせるとか、設計の質問を無限に返されるようになりました。
そこはCursorに勝手に考えてほしかったです。
このあたりで、このドキュメントは要件なのか仕様なのか設計なのかよくわからないものになっていきました。

ここらでうんざりして、分からないところはTODOでいいから実装してとお願いし、当たり前にTODOだらけのコードが生成されました。あと機能もかなり抜け落ちていました。
でもまあ全体の骨格はできたと言えなくもない状態と思うことにしました。

要件駆動生成の試行錯誤

TODOと未実装機能の実装のため、requirements.mdを基に機能ごとに依頼を進めていきました。
「XXメソッドを使用し、XXフィールドを参照し、XXはSerializeFieldに設定する」といった詳細な指示が必要となり、かなりの「要介護感」を感じる状況でした。
ただし、どこかで見た記事に「Cursorのインデックスを育てるため、簡単な実装でもCursorに任せるべき」と書かれていたため、その方針に従いました。
(執筆時点ではその記事を見つけられていないため、幻術だったかもしれません。)

指示を考えるのが面倒になり、一部機能は自ら実装することもありましたが、
変更内容は逐一Cursorに報告するようにしていました。

実装の終盤になってようやくCursor Rulesの重要性に気づき、設定を開始しました。
手遅れ感はありますが、以降はCursorとのコミュニケーションが格段に改善されました。

単体テスト

実装と並行して、各機能の単体テストコードをCursorに生成してもらいました。
単体テストの作成は嫌いなので、これは大きな助けとなりました。

失敗例として、コード生成 → コンパイル/単体テスト → 失敗時は修正というサイクルを回していた際、
Cursorが実装の問題なのかテストコードの問題なのかの判別を間違えることが多く、
放置している間に無限にコード変更とテストと修正が繰り返されることがありました。
この過程でFast requestsを大量に消費してしまいました。
その後は、テスト失敗時は原因分析までで止め、自動修正は行わないようにルールを変更しました。
実装の問題かテストの問題かの判別は、最後まで上手く機能しませんでした。
ただし、この判別さえできれば、特に終盤は適切な修正ができていたと思います。

結合テスト

自作のUnity + UdonVMシミュレーターを基に、xUnitで結合テストを実装しました。
主な特徴:
・Unityシーンファイルを基にGameObject階層構造と各コンポーネントを構築
・参加プレイヤーごとにシーンObjectを保持
・VMのUpdateをフレーム時間を指定して好きなタイミングで呼び出し可能
自信作です。

結合テストの実装は比較的楽しく進めることができました。
テストコードの作成は以下の流れで行いました:

  1. requirements.mdから必要なテストを特定し、結合テスト内容.mdを作成
  2. 各テスト内容について、Cursorに「どんなテスト用APIがあればテストケースを作れるか」質問
  3. 必要なテスト用APIを空関数で作成し、中身は手動で実装(プレイヤーの参加や駒の移動など)
  4. テストコードを生成

3.については、シミュレーターが自作であるため、その使用方法をCursorに学習させるのは非効率と判断し、
手動で実装することにしました。
この判断は自分の開発スタイルに合っていたと思います。
Cursorの得意分野(テストコード生成)と苦手分野(シミュレーターとの連携実装)、
自分の嫌いな作業(テストコード生成)と好きな作業(シミュレーターとの連携実装)がうまくかみ合った感じがします。

テスト用APIのインターフェース(引数/戻り値)はCursorに設計してもらったため、
使用方法に関する質問を受けることもなく、ストレスなく開発を進められました。
また、結合テストの終盤には既存のテスト用APIでテストコードを生成できるケースが増え、効率が向上していきました。

ただし、テストの実行と修正は手動で行いました。
理由は、単体テストの経験から、テストコード・実装・シミュレーターの
どの部分に問題があるかの自動判別が難しいと判断したことと、
単純に修正作業が楽しかったためです。

開発結果

今回のCursorを使用した開発は、残念ながら成功とは言えませんでした。

理由1 : Cursorの活用が不十分

Cursor Rulesなど、重要な機能を終盤まで活用できなかったことが致命的でした。
ルールの書き方にも改善の余地があります。
@シンボルもファイル指定以外の機能をまだ理解できていません。
また、これまでプロンプトの重要性を軽視していたため、Cursorに意図が伝わりにくい指示になっていました。
ただし、使用を重ねる中で改善され、Cursorとの意思疎通が徐々にスムーズになっていった実感はあります。
次回の開発に期待しています。

理由2 : 要件・仕様・設計の混在

言うまでもありませんが、要件と仕様は別途、設計はさらに別のドキュメントとして整理すべきでした。
自作モジュールとの連携についても、設計段階で明確にしておくべきでした。

理由3 : Unityとの連携不足

UnityEditor上でのシーン更新を手動で行い、そのフィードバックをCursorに伝えていませんでした。
シーン情報を共有できていれば、Cursorがプロジェクト全体をより正確に把握できた可能性があります。
これは開発終盤に以下の記事で知ったUnity MCPで解決できるかもしれません。
https://zenn.dev/tkada/articles/3cc10bf3733ae6
次回の開発で試してみる予定です。
...
後日、別のUnityMCPも発見しました。

記事内で使っていたUnity MCP

https://github.com/justinpbarnett/unity-mcp

後日発見したUnity MCP

https://github.com/isuzu-shiranui/UnityMCP

両方とも試す予定ですが、後者が日本人の開発者によるものなので、より自分に合っているかもしれません。

おわりに

今回の開発ではCursorを十分に活用できませんでしたが、それでも良い点や可能性を感じることができたため、今後も使用を継続していきたいと考えています。
Cursor自体がまだ過渡期にあり、今後さらに進化するでしょうし、MCP関連のUnity連携も充実していくと思われます。

ただし、いくつかの記事で無条件にCursorによる開発効率の向上を謳っていますが、必ずしもそうとは限りません。まず、Cursorには得意分野と不得意分野があります。
Web開発は得意そうですが、Unity開発はそうでもないようです。
これは執筆時点でのZennにおけるUnity + Cursorの記事の少なさからの推測です。

そして何より、AIを効果的に活用するためのスキルは必要だと思います。
少なくとも現時点では。

その他メモ

  • Cursor Rulesは必ずしも守られない
    書き方が曖昧だったり、他のルールやチャット内容と競合する場合は機能しないことがあります。
    また、リロード直後は明示的にルールに従うよう指示しないと無視されることが多い印象です。

  • 連携スクリプトのMCP化
    開発初期はスクリプトを直接Cursorに実行させていましたが、途中からスクリプトをMCPでラップして実行するように変更しました。
    MCP化は必須ではありませんが、Cursorとのコミュニケーションがスムーズになる印象です。
    例えばコンパイルチェックでは、スクリプトのままだとCursorがコンパイル完了を待たずに次の処理に進み、エラーなしと誤判定することがありました。
    また、結果の受け渡しが標準出力のみで、整備されていない印象がありました。
    MCP化することで、完了を待機し、結果を辞書型で受け渡せるようになり、スクリプト側もより作りやすくなりました。

  • 「Composer」「yolo」について
    各種記事で見かけるこれらの機能は気にする必要はありません。
    Agentに統合されています。自分も最初CursorIDE上でめっちゃ探しました。

  • TestRunnerのPlayModeテストについて
    VRCSDK環境下ではPlayModeのテストは難しい状況です。
    PlayModeではシーンが消去されるため、ClientSimとの相性が悪いです。自分は諦めました。

  • UdonSharp対応コード生成
    ルールで個別に指示を書いています。
    「ジェネリック型のList<T>は使わずDataListを使え」とか、DataListからの値の取り方とか。
    今思うとDocsでUdonsharpのリファレンスページを読み込ませるのもありだったかもしれません。

  • Unityのドメインリロードは長い
    Unityのドメインリロードはそのままでは長いのでこれをどうにか改善しないとCursorであっても開発は遅いです。
    逆に言うと、指示出してコード生成してコンパイルチェックドメインリロード完了までの間、人間は別作業している前提であれば、CursorのFast requestsが尽きて、コード生成が遅くなってもも大した問題ではないです。

  • 質問の方法
    結合テストから「何があればコードを生成することができるか」を聞くのは結構よいのではないかと思っています。

Discussion