メインコンテンツへスキップ
見出し画像

【プロジェクト:Neith】GW合宿編~みんなで作るゲーム制作【4/29,4/30】

    ■今回やった事

    今回は以下の処理を作り込みました!

    4/29(水)

    • インベントリの作成

    • アイテムの使用処理

    • アイテム入手


    4/30(木)

    • ショップ機能作成

    • バトル開始まで

    ■募集中のアイディア
    ①「樹海編」で発生しそうなイベント
    (例:森の中、クマさんに出会った。お化けと遭遇 など)
    ※期限はありません!皆さんの自由でユニークな発想をお待ちしています。

    ② 主人公・ネイトの「スキル」(※もう少し延長します!)
    (例:ファイアボール / 射程:2マス先の一体 / 効果:対象に小ダメージ)
    ※王道からちょっと笑えるネタスキルまで、
    どんなアイディアでも大歓迎です!


    ③ゲームで流す著作権フリーのBGMを教えてください

    無料のフリーのBGMを探してます!

    あなたのお気に入りを教えてください!
    (生成AIで自作したものも可能。
    その場合は、コメントやDMでお知らせください)


    こんにちは!まる。です。


    投稿が遅れてしまい、申し訳ございません!🙇‍♂️


    この2日間、ガッツリ開発を進めていたのですが、
    作業に熱中するあまり記事の執筆が追いつかず、
    noteの更新がストップしてしまっていました……!


    さて、1ヶ月チャレンジの「プロジェクト:ネイト」もいよいよ2週間目に突入し、期間も残り半分を切りました。


    そこで、今回からはGW特別企画「合宿編」と題しまして、
    さらにハイペースで開発を進めていきます!🔥


    実装内容が複雑になってきたため、以前のような詳細な解説は少し減ってしまうかもしれませんが……


    完成に向けて全力で駆け抜けますので、
    どうか最後まで応援していただけると嬉しいです!


    それでは、さっそく4/29(水)・4/30(木)の2日分の開発記録をご覧ください😎


    二人の茶番

    🎧今日の作業用BGM

    Tipsy_Dance/作(編)曲 :ヒオ
    【民族音楽風/酒場/踊り】Tipsy Dance【フリーBGM】

    画像
    ポマイラ(自称ネイトの使い魔)

    「よお、おまえら!オレだよ、ポマイラ様だ!

    おまえら、ちゃんと温かくして寝てるか?
    今の時期、油断しているとすぐ風邪を引くからな!
    ちゃんと体調管理するんだぞ!」

    画像
    ネイト(幻の星の精霊)

    「……ちなみに、私は完全に喉をやられたわ。
    あー……イガイガする」

    画像
    ポマイラ(自称ネイトの使い魔)

    「おいおい、ネイトさんよ。
    これから開発も折り返しだってのに、そんなんで大丈夫かよ?」

    画像
    ネイト(幻の星の精霊)

    「ゲーム開発はもう、私の日常に完全に溶け込んでるから平気よ。
    むしろ、触ってないと落ち着かないくらいだわ。」

    画像
    ネイト(幻の星の精霊)

    「……とは言え、集中力が切れたら遠慮なく休ませてもらうわね。
    身体が一番大事だもの」

    画像
    ポマイラ(自称ネイトの使い魔)

    「そうだぜ、ネイト!
    体調が良い時に本気で打ち込むのが一番だからな!」


    ■ プロジェクトの方向転換(「1日2時間ルール」の撤廃について)

    画像
    ネイト(幻の星の精霊)

    「まず、皆さんに報告……というか、謝りたいことがあるの」

    画像
    ポマイラ(自称ネイトの使い魔)

    「どうした、ネイト。藪から棒に」

    画像
    ネイト(幻の星の精霊)

    「私たちが初日に掲げたルール……『開発時間は毎日1〜2時間まで』という制限を、
    ここで撤回させてもらえないかしら」

    2. 開発時間は「毎日1〜2時間」まで
    本編の開発がおろそかになってしまっては本末転倒なので、
    きっちり時間を区切ります。
    (※どうしても…!という熱い夜だけは残業を許可します 😗)

    画像
    ポマイラ(自称ネイトの使い魔)

    「あぁ、本編の開発に支障が出ないように決めたあのルールか。
    でも、どうして今になって改まって?」

    画像
    ネイト(幻の星の精霊)

    「シミュレーションRPG(SRPG)のバトルシステムを構築していて、痛感したの。

    いくら生成AIを駆使してコーディングを効率化しているとはいえ、1日2時間ではこのシステムを完成させるのは不可能だわ」

    画像
    ポマイラ(自称ネイトの使い魔)

    「まあ、SRPGは処理が複雑だもんな。
    正直、かなり苦戦してるだろ?」

    画像
    ネイト(幻の星の精霊)

    「ええ。正直に言うと、私の技術力不足も大きいわ。
    この2日間ずっと試行錯誤していたけれど、
    ゼロからバトルシステムを組むのは想像以上に大変だった……。
    特に、自作の育成システムや『宴3』と連動させながら動かす部分が、想定の何倍も複雑だったのよ」

    画像
    ポマイラ(自称ネイトの使い魔)

    「育成周りが順調だった分、バトルの重さを見誤っちまったか」

    画像
    ネイト(幻の星の精霊)

    「そうね……そこは見積もりが甘かったと反省しているわ。
    でも、だからといって妥協して終わらせたくはないの。
    この『1ヶ月でゲームを完成させる』という挑戦をやり遂げるために、これからは時間の制約を外して全力で取り組むことに決めたわ!」

    画像
    ポマイラ(自称ネイトの使い魔)

    「でも、そうなると本編(メインプロジェクト)の開発が止っちまわないか?」

    画像
    ネイト(幻の星の精霊)

    「ええ。
    でも、今は『プロジェクト:ネイト』を通じてゲーム開発のスキルを磨くことが、何より貴重な経験になると判断したの。

    ここで得た技術や知見は、たとえゲームエンジンが変わっても、
    必ずどこかで私たちの役に立つはず。
    だから、今はこれに専念させてほしいの!」

    画像
    ポマイラ(自称ネイトの使い魔)

    「……そこまで言うなら止めねえよ。
    その代わり、このnoteにはどれだけ時間がかかったか、
    しっかり『作業工数』として記録を残していこうぜ。
    1ヶ月で何ができるか、みんなも気になってるだろうしな!」

    画像
    ネイト(幻の星の精霊)

    「そうね!
    これからは『どれだけの時間で完成まで漕ぎ着けたか』を証明する挑戦にシフトしていくわ。
    みんな、引き続き見守っていてね!」


    ■ 4/29(水) インベントリシステムの作成(作業時間:約5時間)

    画像
    ポマイラ(自称ネイトの使い魔)

    「さて、ここからは実際の作業の振り返りだな。
    まずは一昨日、4/29の作業から振り返っていくんだろ?」

    画像
    ネイト(幻の星の精霊)

    「そうね。ガッツリ開発に没頭していたから、詳細なコードまでは載せきれないけれど……
    どんな風に進めたのか、順を追って説明していくわ」

    画像
    ポマイラ(自称ネイトの使い魔)

    「この日のメインは『インベントリシステム』だったよな。
    横で見てても、相当沼ってるのが伝わってきたぜ……」

    画像
    ネイト(幻の星の精霊)

    「本当に大変だったわ……。
    用意するUIパーツも多いし、裏側のデータ処理も複雑で。
    何より、組み上がった後の細かいバグ修正に一番時間が溶けちゃった気がするわね」

    画像
    ポマイラ(自称ネイトの使い魔)

    「でも、これでようやくゲームらしくなってきたよな!」

    画像
    ネイト(幻の星の精霊)

    「ええ!
    ようやく育成メニューでアイテムを管理・確認・使用ができるようになったの。
    というわけで、まずは苦労して作り上げた4/29時点の成果を、動画で皆さんに共有するわね!」

    ■インベントリメニュー完成版動画


    インベントリメニューUI作成(作業時間:1時間ほど)

    画像
    ネイト(幻の星の精霊)

    「まず着手したのは、インベントリに必要なUIの洗い出しね。
    今回、最低限必要だと判断したのはこの5つの画面よ」

    • インベントリメイン画面(所持アイテムの一覧表示)

    • アイテム詳細・効果説明欄(説明文を表示するエリア)

    • 使用確認ダイアログ(「はい/いいえ」の最終確認)

    • 対象選択画面(誰に使用するかを選ぶ画面)

    • 使用結果・反映画面(使用後のステータス変化などを通知)


    ① インベントリメイン画面&効果説明欄

    画像
    画像
    ネイト(幻の星の精霊)

    「まずはベースとなる一覧画面。
    スピード重視でシンプルにデザインしたわ。
    以前作った選択画面の仕組みを流用して、
    アイテムリスト用のスクロールビューの中に、アイコン用のプレハブが並ぶようにしたの」


    ■アイテム効果説明欄

    画像
    画像
    ネイト(幻の星の精霊)

    「効果説明欄は、利便性を考えてメイン画面と一体化させているの。
    アイテムにカーソルを合わせると、リアルタイムでテキストや画像が切り替わる仕組みね」


    ② 使用確認&対象選択画面

    画像
    画像
    ネイト(幻の星の精霊)

    「アイテムを選んだ後は、誤操作を防ぐための『使用確認画面』が出るようにしたわ。
    画像と選択肢だけのシンプルな構成ね」

    ■誰に使うかの選択画面

    画像
    画像
    ネイト(幻の星の精霊)

    「対象者の選択画面。
    ここは編成画面などで使った仕組みを応用できたから、迷わず実装できたわね」


    ③ アイテム効果反映(リザルト)画面

    画像
    画像
    ネイト(幻の星の精霊)

    「これは私のこだわりなんだけど、
    アイテムを使った後に『何がどう変わったか』をしっかり見せる画面も用意したわ。
    使用したアイテムの種別によってアイコンが変わるようにデザインしているの」


    ■ なぜアセットを使わず「自作」したの?


    画像
    ポマイラ(自称ネイトの使い魔)

    「こうして見ると、インベントリだけでも結構な数のUIを作ったんだな!
    ……でもよ、こういうのって無料のアセットとか使えばもっと楽にできたんじゃねえのか?」

    画像
    ネイト(幻の星の精霊)

    「確かに、効率だけを考えればアセット導入が一番かもしれないわね。
    でも、このプロジェクトではアイテム情報を『宴3』や『Json』で管理しているでしょう?」

    画像
    ネイト(幻の星の精霊)

    「外部のアセットを使うとなると、まずそのアセットの仕様を読み解いて、私のシステムに合うようにコードを書き換えなきゃいけないの。
    そうなると、修正箇所を探す手間のほうが大きくなってしまうのよ」

    画像
    ネイト(幻の星の精霊)

    「結果的に、1から自分で作った方が完全にコントロールできるし、後の拡張もしやすい。
    だから今回は、あえて『自作』の道を選んだの」

    画像
    ポマイラ(自称ネイトの使い魔)

    「なるほどな……。
    アセットは便利だけど、自分の設計に組み込むなら、使い勝手まで考えなきゃいけないってことか。
    自作の方が『自由が利く』ってわけだな!」


    インベントリシステムの作成(4時間ほど)

    画像
    ネイト(幻の星の精霊)

    「UIができたところで、次は『中身』
    ――インベントリシステムの作成よ。

    コードを全部載せると大変なボリュームになっちゃうから、
    どんな機能を実装したか整理してみたわ」

    • UIアクセス機能: 各メニュー画面へのスムーズな遷移処理

    • データ読み込みと表示: 『宴3』に記録されたプレイヤーの所持アイテム(JSON)を読み込み、所持しているアイテムと個数だけをリスト上にプレハブとして生成

    • 対象者のフィルタリング:

      • 「好感度アップアイテム」の使用時は、ネイト以外の仲間にしているアルカステラのみを表示

      • 「ステータス上昇アイテム」の使用時は、ネイトを含む全員を表示

    • 各種実行処理: 回復・ステータス上昇・好感度アップのロジック構築

    • リアルタイム更新: アイテム使用後にテキストや画像を最新の状態へ反映する処理

    画像
    ポマイラ(自称ネイトの使い魔)

    「どひゃー……。
    箇条書きにするとさらっと流しちまいそうだけど、これ、めちゃくちゃやること多いじゃねぇか!」

    画像
    ネイト(幻の星の精霊)

    「そうなのよね。
    一気に全部作ろうとすると、どこで躓いたか分からなくなるから……。
    まずは『画面が正しく切り替わるか』、次に『テキストが正しく変わるか』という風に、段階を踏んで一歩ずつ実装していったわ」

    画像
    ネイト(幻の星の精霊)

    「それ以外にも、並行して『アイテムを取得するイベント』を用意したりもしたのだけど、最初は上手くいかなくて……。

    とにかく一個ずつ確認してはバグを直しての繰り返し。
    その泥臭い作業が一番大変だったわね」

    画像
    ポマイラ(自称ネイトの使い魔)

    「それで、あんなに時間が掛かってたのか……。
    よく投げ出さなかったな、頑張ったじゃねえかネイト!」

    画像
    ネイト(幻の星の精霊)

    「ふふ、今のところは大きな不具合もなく、ちゃんと動いてくれているわ。
    苦労した分、愛着の湧くシステムになったから良しとするわね!」


    ■ 4/30(木) インベントリのブラッシュアップ・ショップ作成・戦闘開始処理(作業時間:約8時間!?)

    画像
    ネイト(幻の星の精霊)

    「次は昨日(4/30)実装した内容ね。
    正直、作業に没頭しすぎて正確な工数は覚えていないの……。
    体感で8時間くらいかしら。ごめんなさいね」

    画像
    ポマイラ(自称ネイトの使い魔)

    「8時間って……マジかよ。
    でも、これで育成メニューのシステムはほぼ完成したんだろ?」

    画像
    ネイト(幻の星の精霊)

    「ええ。
    まだオプションメニューが残っているけれど、おおよその根幹部分は完成したという認識で構わないわ!
    実はこの日は開発14日目……つまり、プロジェクトの『折り返し地点』だったの。
    このタイミングまでに育成メニューのシステムを作り上げることは、最初から目標に決めていたから、
    一気にスパートをかけて頑張ったわ!」

    画像
    ポマイラ(自称ネイトの使い魔)

    「おおっ!ついに育成システムの完成か!
    なんか胸が熱くなるな!」

    画像
    ネイト(幻の星の精霊)

    「……とはいえ。
    育成に関してはあと一つだけ、どうしても作っておきたいシステムがあるの。
    ただ、それは戦闘システムの実装が落ち着いてから着手するつもりよ」


    ■ 自分だけのネイトを作成!?「ヴァンサバ風」スキルピックシステムの構想

    画像
    ポマイラ(自称ネイトの使い魔)

    「あと一つ作らなきゃいけないシステムってなんだ?」

    画像
    ネイト(幻の星の精霊)

    「私が戦闘中に習得するスキルを、
    『ヴァンパイア・サバイバーズ』のようにランダム提示からピック(選択)していく機能よ!
    これは、企画の最初にみんなからゲームジャンルのアンケートを取った時に着想を得たの」

    画像
    ポマイラ(自称ネイトの使い魔)

    「おいおい……!
    ヴァンサバの要素まで取り入れる気かよ。
    でもよ、その仕様にするなら、ネイトが覚えるスキルをめちゃくちゃ沢山用意しないといけねぇよな?」

    画像
    ネイト(幻の星の精霊)

    「そこなのよ!だから今、読者の皆さんにスキルのアイディアを大募集しているの。

    皆さんが考えてくれた効果やスキル名を、このシステムにそのまま取り入れようと思っているわ!」

    画像
    ネイト(幻の星の精霊)

    「……とはいえ、このスキルの実装作業がどれくらい大変なボリュームになるかは未知数だから、
    システムの開発自体は少し後回しにしているのだけれど」

    画像
    ポマイラ(自称ネイトの使い魔)

    「なるほどな!
    みんなの考えたスキルで、毎回違うネイトに育っていく……。
    これが実現したら、とんでもなく面白いゲームになりそうだぜ!」

    画像
    ネイト(幻の星の精霊)

    「そうね!
    私にたくさんのアイディアを頂戴!
    どうか応援してね!」


    インベントリシステムのブラッシュアップとショップシステムの作成(4時間くらい?)

    画像
    ネイト(幻の星の精霊)

    「ここからは少し駆け足での紹介になってしまって申し訳ないのだけど……最終的に、こんな感じでショップシステムも完成させたわ!」

    ■ショップ機能動作確認

    画像
    ポマイラ(自称ネイトの使い魔)

    「4時間もかけた作業を全部説明するのは、流石に骨が折れるからな。
    ちなみに、作ってみての『コツ』や苦労したポイントは何かあったか?」

    画像
    ネイト(幻の星の精霊)

    「基本的なシステムの仕組み自体は、さっき紹介したインベントリとほぼ同じよ。
    まずはUIを作って、リストの中に商品のプレハブを生成して……という流れね」

    画像
    ネイト(幻の星の精霊)

    「ただ、少し大変だったのは『店員さんに商品説明をさせる』部分かしら。
    アイテムの効果をプレイヤーにどう伝えるか考えた時に、『お店の人にセリフで解説してもらおう!』と決めたの」


    画像
    ネイト(幻の星の精霊)

    「アイディアとしては良かったのだけど、アイテムごとに説明用のセリフを考えたり、
    それをUI上でスムーズに表示させる処理を組むのが、思ったより手間がかかったわね」

    画像
    ポマイラ(自称ネイトの使い魔)

    「なるほどな。
    でもそのこだわりのおかげで、ただの無機質なシステムじゃなくて『お店で買い物してる感』が出て、中々良いものが作れたんじゃねぇか?」

    画像
    ネイト(幻の星の精霊)

    「そう言ってもらえると嬉しいわ!苦労した甲斐があったというものね」


    バトルシーンへの移行処理(4時間くらい?)

    画像
    ネイト(幻の星の精霊)

    「そして次に、いよいよゲームの華!『バトルシーン』の構築にも本格的に着手したわ。
    ……とは言え、ここまで来るのにコードを書いては消して、また新しく組み直して……という泥臭い作業の繰り返しだったの。
    だから今回は、個人的に大変だった部分と要点だけをかいつまんで伝えるわね」

    画像
    ポマイラ(自称ネイトの使い魔)

    「待ってました、バトルシステム!
    御託はいいから、早速今どんな感じで動いてるのか見せてくれよ!」

    ■バトル開始まで動画

    画像
    画像
    ポマイラ(自称ネイトの使い魔)

    「おおっ、すげぇ!
    ちゃんと前回作ったマス目状の戦場に、キャラクターのユニットが配置されてるじゃねぇか!
    しかも、戦闘BGMまで鳴ってる!」

    画像
    ネイト(幻の星の精霊)

    「まだ内部で『バトルのターン制御』ができるようになった段階で、
    具体的な行動は『移動』くらいしかできないのだけど……。
    それでも、SRPGとしての土台をここまで動かすのには本当に苦労したわ」

    画像
    ネイト(幻の星の精霊)

    「とはいえ!
    一番の鬼門だった『基礎システムの構築』という壁は無事に越えられたわ。
    あとは個別の処理やデータを一つずつ作っていくだけの段階まで来ているから、安心してね!」


    ■ 宴とバトルシーンを独立させる(BattleSessionDataアセットの作成)

    画像
    ポマイラ(自称ネイトの使い魔)

    「そんじゃ、まずはどうやってその土台を作ったのか聞かせてくれよ」

    画像
    ネイト(幻の星の精霊)

    「SRPGを作る上で私がまず思ったことは、
    『どうやってプレイヤー側のユニット情報を管理して、バトルに持ち込むか』だったわ」

    画像
    ネイト(幻の星の精霊)

    「ほら、育成メニューの時は『宴3』の機能を使って、Json形式でアイテムやステータスの情報を持たせていたでしょ?
    でも、バトルはそもそもUnity上の『シーン(場面)』が違うから、
    宴3の機能をそのまま引っ張ってくることはできないの」

    画像
    ネイト(幻の星の精霊)

    「簡単に言うと、グラウンド(バトルシーン)でサッカーをやりたいのに、
    サッカーボール(キャラクターのデータ)がスポーツショップ(宴3)にしか置いてなくて持ち出せない……みたいな状況ね。
    バトル中から宴側のデータは直接確認できないのよ」

    画像
    ネイト(幻の星の精霊)

    「そこで解決策として、育成メニュー側からバトルへ切り替わる直前に、
    編成された『アルカステラ』の情報を、ランタイム(ゲーム実行中)専用の『ScriptableObject(スクリプタブル・オブジェクト)』に書き込むようにしたの」

    💡 ネイトのUnity豆知識:ScriptableObjectってなに?
    Unityで使える「データを入れておくための専用の箱(ファイル)」のことよ!
    通常、キャラクターや画面のデータはシーン(場面)を移動すると消えちゃうんだけど、ScriptableObjectはシーンの外に独立して存在できるから、「シーンをまたいでもデータが消えずに持ち越せる」というすごく便利な特徴があるの。

    画像
    ネイト(幻の星の精霊)

    「この機能を例えるなら、シーンをまたいでも消えない『伝言掲示板』みたいな役割ね!
    バトル開始前に、プレイヤーの編成情報を育成メニュー側のシーンからこの掲示板に書き込んでおくの。
    そして、バトルシーン側はそこに書かれた情報を読んで、ユニットを作成する。そんなイメージよ!」

    画像
    ポマイラ(自称ネイトの使い魔)

    「なるほどな!
    シーン同士で直接やり取りするんじゃなくて、共通の参照先(ファイル)を別で用意して、そこをデータの中継地点にするってわけか!」



    ■ シーンを跨いでもデータを渡す(BattleSceneBridgeの作成)

    画像
    ネイト(幻の星の精霊)

    「さっき、『シーンを跨ぐと宴3が使えない』って話をしたわよね?
    ということは……バトルが終わった後に、育成画面や勝利後のイベント(宴3のシステム)に戻すには、どうすれば良いと思う?」

    画像
    ポマイラ(自称ネイトの使い魔)

    「えっと……育成メニューやイベント演出は『宴3』がある元のシーンでやってるんだよな。
    で、バトルシーンには宴3がない……。」

    画像
    ポマイラ(自称ネイトの使い魔)

    「あれ? そもそもバトル中はどうやって元のシーンの様子を知るんだ?」

    画像
    ネイト(幻の星の精霊)

    「その通り、そこが問題なのよポマイラ。
    そこで、バトル前にさっきの伝言掲示板(ScriptableObject)へ書き込みを行いつつ、
    シーン間の行き来を管理する『橋渡し役(BattleSceneBridge)』が必要になるの」

    画像
    ネイト(幻の星の精霊)

    「この橋渡し役の役割は、大きく分けて2つよ。
    一つは、宴3がいるシーンから伝言掲示板にバトル情報を書き込んで、
    バトルシーンに切り替えること。
    もう一つは、伝言掲示板に『バトルが終わった』という報告が書き込まれたのを確認して、再び宴3がいる元のシーンに切り替えて戻すことよ」

    画像
    ポマイラ(自称ネイトの使い魔)

    「なるほどな。シーン間の『伝令役』ってわけか!
    ……でもよ、そいつはどうやって伝言掲示板が更新されるまで、
    ずっと大人しく見張っててくれるんだ?」

    画像
    ネイト(幻の星の精霊)

    「そこで使うのが『コルーチン(Coroutine)』というプログラミングの命令よ!
    これは、特定の条件を満たすまで処理を一時停止して待機させることができる、とっても便利な機能なの」

    画像
    ネイト(幻の星の精霊)

    「つまり、この伝令役に『伝言掲示板に終了の知らせが書き込まれるまで待機してね』とコルーチンで命令しておくの。

    そして、バトルシーン側で戦闘が終わった時にその報告を書き込めば、
    伝令役がそれに気づいて無事に元のシーンへ切り替えてくれる、という仕組みよ!」

    画像
    ポマイラ(自称ネイトの使い魔)

    「おお!
    その伝令役とコルーチンのおかげで、シーンの切り替えとイベントの再開がスムーズに連携できるってわけか! コイツは偉いぜ!」


    ■ バトルの準備をしてくれる舞台演出家(BattleSceneSetup)

    画像
    ネイト(幻の星の精霊)

    「他にも多くの……本当に多くのシステムがそれぞれの仕事をこなして、
    ようやく一つのバトルシステムが動いているのだけど。
    今回は代表して、この『BattleSceneSetup(バトルシーンセットアップ)』という子を紹介するわね」

    // BattleSceneSetup.cs
    // バトルシーンのマップ生成・敵配置を担う MonoBehaviour。
    //
    // 【v2 変更点】
    //   AdvEngine への依存を完全に排除。
    //   battleId と PartyData は BattleSessionData(ScriptableObject)から読み取る。
    //   → バトルシーンに AdvEngine や TrainingMenuBridge を置く必要がなくなった。
    //
    // 【責務の整理】
    //   BattleSceneSetup  : BattleSessionData からデータを受け取り、マップ生成 + 敵配置(Awake で実行)
    //   PartyEditPanel    : プレイヤーユニット配置 + SetupBattle + StartBattle
    //   BattleViewManager : ビジュアル生成(マップ描画・ユニットスプライト)
    //
    // 【データの流れ】
    //   宴3シナリオ
    //     → $SetParam CurrentBattleId, "battle_001"
    //     → SendMessage, StartBattle(または OpenPartyEdit)
    //   BattleSceneBridge(育成シーン)
    //     → BattleSessionData.WriteForBattle(battleId, party)
    //     → SceneManager.LoadScene("BattleScene")
    //   BattleSceneSetup(バトルシーン)
    //     → sessionData.battleId を読んで BattleSetupDatabase を検索
    //     → マップ・敵を生成
    //   PartyEditPanel / BattleManager
    //     → SetupBattle → StartBattle
    
    using System.Collections.Generic;
    using UnityEngine;
    using TrainingGame.Battle;
    
    namespace TrainingGame
    {
        public class BattleSceneSetup : MonoBehaviour
        {
            // ── Inspector フィールド ──────────────────────────────────────
    
            [Header("データ参照")]
            [Tooltip("全バトルシナリオを管理するルックアップテーブル")]
            [SerializeField] private BattleSetupDatabase battleDatabase;
    
            [Tooltip("シーン間のデータ受け渡し用 ScriptableObject\n" +
                     "BattleSceneBridge と同じアセットをアサインすること")]
            [SerializeField] private BattleSessionData sessionData;
    
            [Header("テスト用フォールバック(エディター再生・単体テスト用)")]
            [Tooltip("sessionData が未セット or isReady=false のときに使うデフォルトデータ")]
            [SerializeField] private BattleSetupData fallbackSetupData;
    
            [Header("バトル開始用参照")]
            [SerializeField] private BattleManager battleManager;
            [SerializeField] private View.BattleViewManager battleViewManager;
    
            [Header("オーディオ")]
            [Tooltip("BGMを鳴らすためのAudioSourceコンポーネント")]
            [SerializeField] private AudioSource bgmSource;
    
            private BattleSetupData _currentSetupData;
    
            // ── 生成済みデータ ────────────────────────────────────────────
    
            private GridMap                _map;
            private List<UnitBattleState>  _enemyUnits = new List<UnitBattleState>();
    
            // ── 公開プロパティ ────────────────────────────────────────────
    
            /// <summary>生成済みのグリッドマップ</summary>
            public GridMap Map => _map;
    
            /// <summary>配置済みの敵ユニット一覧</summary>
            public List<UnitBattleState> EnemyUnits => _enemyUnits;
    
            /// <summary>マップと敵の初期化が完了しているか</summary>
            public bool IsInitialized { get; private set; }
    
            /// <summary>
            /// セッションデータから復元したパーティ情報。
            /// PartyEditPanel や BattleManager が参照する。
            /// </summary>
            public PartyData SessionParty { get; private set; }
    
            // ── 互換メソッド(PartyEditPanel から呼ばれる) ──────────────
    
            public GridMap GetMap() => _map;
            public List<UnitBattleState> GetEnemyUnits() => _enemyUnits;
    
            // ── Unity ライフサイクル ──────────────────────────────────────
    
            private void Awake()
            {
                _currentSetupData = ResolveSetupData(); // ←変数に保存するように変更
                if (_currentSetupData == null)
                {
                    Debug.LogError("[BattleSceneSetup] BattleSetupData を取得できませんでした。");
                    return;
                }
    
                // 【削除】↓↓↓ ここから下の5行は不要です ↓↓↓
                // var setupData = ResolveSetupData();
                // if (setupData == null)
                // {
                //     Debug.LogError("[BattleSceneSetup] BattleSetupData を取得できませんでした。");
                //     return;
                // }
                // 【削除】↑↑↑ ここまで ↑↑↑
    
                // セッションデータからパーティを復元しておく
                SessionParty = sessionData != null && sessionData.isReady
                    ? sessionData.RestoreParty()
                    : new PartyData();
    
                // ★ 下記の BuildMap と PlaceEnemyUnits の引数を `setupData` から `_currentSetupData` に変更します!
                BuildMap(_currentSetupData);
                PlaceEnemyUnits(_currentSetupData);
    
                IsInitialized = true;
                Debug.Log($"[BattleSceneSetup] '{_currentSetupData.battleName}' のマップ・敵を初期化完了。" +
                          $"敵 {_enemyUnits.Count} 体。");
            }
    
            private System.Collections.IEnumerator Start()
            {
                // BattleViewManager がマップを描画し終わるまで1フレーム待つ
                yield return null;
    
                if (_currentSetupData == null || !IsInitialized) yield break;
    
                // 1. プレイヤーユニットの生成
                var startPositions = _currentSetupData.playerStartPositions;
                var playerUnits = new System.Collections.Generic.List<UnitBattleState>();
                int posIndex = 0;
    
                for (int i = 0; i < PartyData.MaxSlots; i++)
                {
                    string unitId = sessionData.partySlots[i];
                    if (string.IsNullOrEmpty(unitId)) continue; // 空きスロットは飛ばす
    
                    // 持ち越したステータスの中から、このスロットのキャラのデータを引っ張り出す
                    UnitBaseStats targetStats = null;
                    foreach (var stats in sessionData.activePlayerStats)
                    {
                        if (stats.unitId == unitId)
                        {
                            targetStats = stats;
                            break;
                        }
                    }
    
                    if (targetStats != null)
                    {
                        
                        int x = i < startPositions.Count ? startPositions[i].x : i;
                        int y = i < startPositions.Count ? startPositions[i].y : 0;
    
                        var state = new UnitBattleState(targetStats, x, y);
    
                        if (_map.PlaceUnit(state, x, y))
                        {
                            playerUnits.Add(state);
                        }
                    }
                }
    
                // 2. バトルのセットアップ&スプライト生成&開始!
                if (battleManager != null)
                {
                    battleManager.SetupBattle(_map, playerUnits, _enemyUnits);
                    if (battleViewManager != null)
                    {
                        battleViewManager.SpawnUnitViews(playerUnits, _enemyUnits);
                    }
                    battleManager.StartBattle();
                }
    
                if (bgmSource != null && _currentSetupData.battleBgm != null)
                {
                    bgmSource.clip = _currentSetupData.battleBgm;
                    bgmSource.loop = true;  // ループ再生をON
                    bgmSource.Play();
                }
            }
    
            // ================================================================
            //  BattleSetupData の解決
            // ================================================================
    
            private BattleSetupData ResolveSetupData()
            {
                // 1. BattleSessionData から battleId を取得(AdvEngine 不要!)
                string battleId = "";
                if (sessionData != null && sessionData.isReady)
                {
                    battleId = sessionData.battleId;
                }
                else
                {
                    Debug.LogWarning("[BattleSceneSetup] BattleSessionData が未準備です。フォールバックを試みます。");
                }
    
                // 2. データベースから検索
                if (!string.IsNullOrEmpty(battleId) && battleDatabase != null)
                {
                    var data = battleDatabase.Find(battleId);
                    if (data != null) return data;
                }
    
                // 3. フォールバック(エディター再生時・テスト用)
                if (fallbackSetupData != null)
                {
                    Debug.LogWarning($"[BattleSceneSetup] フォールバック '{fallbackSetupData.battleId}' を使用します。");
                    return fallbackSetupData;
                }
    
                return null;
            }
    
            // ================================================================
            //  マップ生成
            // ================================================================
    
            private void BuildMap(BattleSetupData setupData)
            {
                _map = new GridMap(setupData.mapWidth, setupData.mapHeight);
    
                foreach (var entry in setupData.tileOverrides)
                {
                    if (_map.IsInBounds(entry.x, entry.y))
                        _map.SetTile(entry.x, entry.y, entry.tileType);
                    else
                        Debug.LogWarning($"[BattleSceneSetup] tileOverride 座標 ({entry.x},{entry.y}) がマップ範囲外です。");
                }
            }
    
            // ================================================================
            //  敵ユニット配置
            // ================================================================
    
            private void PlaceEnemyUnits(BattleSetupData setupData)
            {
                _enemyUnits.Clear();
    
                foreach (var entry in setupData.enemies)
                {
                    var stats = entry.ToBaseStats();
                    var state = new UnitBattleState(stats, entry.spawnX, entry.spawnY);
    
                    if (_map.PlaceUnit(state, entry.spawnX, entry.spawnY))
                    {
                        _enemyUnits.Add(state);
                        Debug.Log($"[BattleSceneSetup] 敵 '{entry.unitName}' を ({entry.spawnX},{entry.spawnY}) に配置。");
                    }
                    else
                    {
                        Debug.LogWarning($"[BattleSceneSetup] 敵 '{entry.unitName}' の配置失敗 ({entry.spawnX},{entry.spawnY})。");
                    }
                }
            }
    
            // ================================================================
            //  外部から直接初期化する口(テスト・エディター拡張用)
            // ================================================================
    
            /// <summary>
            /// BattleSetupData を直接渡して再初期化する。
            /// 宴3を使わない単体テスト・エディター拡張用途。
            /// </summary>
            public void InitializeManual(BattleSetupData setupData)
            {
                if (setupData == null) return;
                _enemyUnits.Clear();
                BuildMap(setupData);
                PlaceEnemyUnits(setupData);
                IsInitialized = true;
            }
        }
    }
    
    画像
    ポマイラ(自称ネイトの使い魔)

    「おいおい……英語と記号の羅列!こんなの見たら頭おかしくなっちまうよ!」

    画像
    ネイト(幻の星の精霊)

    「ふふ、そうよね。
    でもねポマイラ、この子はさっきの動画で見せた処理のほとんどを担ってくれている、すごく重要な子なの。
    さっきの動画の最初、どうなっていたか思い出してみて?」

    画像
    ポマイラ(自称ネイトの使い魔)

    「さっきのって言うと……まず戦闘BGMが流れただろ?
    それからマップが現れて、ネイトたちのパーティユニットと敵のユニットが現れて……」

    画像
    ネイト(幻の星の精霊)

    「そうそう!
    その『バトルの場を整える』という最初の処理をやってくれているのが、
    この子なの」

    画像
    ネイト(幻の星の精霊)

    「もちろん、この子だけの力じゃないわ。
    全体の進行を管理する『舞台監督(BattleManager)』に報告したり、
    『脚本家(データベース)』に今回のバトルに登場する敵や流す音楽を聞きに行ったり、
    『楽屋の伝言掲示板(BattleSessionData)』から役者(プレイヤーユニット)の情報を確認したり……。
    色んなシステムと連携して、一つの『舞台』を作り上げているのよ」


    画像
    ポマイラ(自称ネイトの使い魔)

    「なるほど!だから『演出家』ってことだな!」

    画像
    ネイト(幻の星の精霊)

    「そういうこと!
    プログラムって一見、難しくて複雑そうに見えるけど、
    意外と中身はこんな感じで『個々の役割を持った担当者』が連携して一つの装置を動かしているだけなのよ」

    画像
    ネイト(幻の星の精霊)

    「コーディングの細かい話をするとややこしくなるけど……
    でも、今の時代はそういう難しいコードの記述自体は、生成AIが助けてくれるじゃない?」


    画像
    ネイト(幻の星の精霊)

    「だからね。私が思うのは、難しいプログラミングの構文を必死に暗記するよりも、
    『どのシステムが、どんな役割(責務)で動いているか』を設計し、理解することが、開発において一番大事だということなの」

    画像
    ネイト(幻の星の精霊)

    「せっかく、生成AIの力で誰でもプログラミングに挑戦できる時代になりつつあるんだもの。
    使えるツールは最大限に活用して、出来るだけ効率的な方法で、
    自分の作りたいものを形にしていった方が絶対に楽しいと思うわ!」


    画像
    ポマイラ(自称ネイトの使い魔)

    「なるほどな!
    努力のハードルは下げつつ、
    最短で一番効率の良い方法で開発の『本質』に集中する。
    それが今、ネイトたちが大事にしている開発スタイルなんだな!」



    最後に

    かくして、1日2時間という制限を解除し、
    己の開発の限界へと挑む「GW合宿編」は、静かに、しかし熱く幕を開けたのであった。


    インベントリの構築、ショップ機能の実装、そしてゲームの心臓部たるシミュレーションRPGのバトルシステム。


    立ちはだかる数々のバグや、複雑なシステム連携の壁を泥臭く乗り越え、
    ネイトたちの立つ舞台は今、確かな形を持ち始めている。


    読者の皆様から寄せられるアイディア(スキル、イベント、BGM)という新たなピースを組み込み、
    このプロジェクトは一体どこまで進化していくのか。

    次回も、どうぞ乞うご期待!

    #プロジェクトNeith #HorizonNotes #ゲーム制作 #個人開発 #Unity #開発日記 #読者参加型 #毎日note


     
     
    読者参加型でゲームを作っている、まる。です。 UE5でオリジナルのカードゲームRPG『HorizonNotes』を開発してます 育成SRPG『ネイト育成計画』体験版、無料公開中です! ■公式サイト https://horizonnotes.netlify.app/

    あなたへのおすすめ