
【プロジェクト:Neith】みんなの考えた最強スキル作成〜みんなで作るゲーム制作【5/3 16日目】
■今回やった事
・ネイトのスキルピックシステム作成
・スキルシステム完成
・アルカステラの待機モーション完成
■募集中のアイディア
①「樹海編」で発生しそうなイベント
(例:森の中、クマさんに出会った。お化けと遭遇 など)
※期限はありません!皆さんの自由でユニークな発想をお待ちしています。
② 主人公・ネイトの「スキル」(無期限)
(例:ファイアボール / 射程:2マス先の一体 / 効果:対象に小ダメージ)
※王道からちょっと笑えるネタスキルまで、
どんなアイディアでも大歓迎です!
こんにちは、まる。です!
気が付けば、この企画の期限も残りあと2週間となりました。
ペースとしてはギリギリ……
いや、正直かなり厳しい状況ではありますが、今はとにかく前へ進むのみ!
そして今回は、現在募集しているアイディアの中から、
ついにネイトがスキルを獲得するようです😳
皆さんと一緒に育ててきたネイトちゃんの勇姿を、
どうか最後まで見届けてください!
それでは、進撃の開発16日目……開幕です!
二人の茶番
🎧今日の作業用BGM
10月の彗星、キミと出会う。/作(編)曲 :フリーBGMチャンネル Unique Gear
【フリーBGM】10月の彗星、キミと出会う。

「よお、お前ら!オレだよ、ポマイラ様だ!
最近、めちゃくちゃ暑くねぇか?
今年も夏がやって来るのが早ぇ気がするぜ!
お前らも熱中症には気を付けろよな!」

「…すごいなぁ」

「あん? ……おいネイト!
お前、オレ様が今、有難い話をしてるのに無視してX(旧Twitter)見てるとは良い度胸じゃねぇか!」

「ふふ、ごめんなさい。
つい、タイムラインに流れてきたゲーム開発者さんの動画が面白くて」

「全く、お前は24時間ずっとゲーム開発のこと考えてるよな…
それで、どんな動画なんだよ」

「いっぱいあるわよ。
私と同じようにSRPGを自作している方から、アクション、RPG……とにかく沢山」

「最近、ゲーム開発者増えた気がするよな…
やっぱ、生成AIで気軽に作れるようになったからか?」

「確かに、それは一番大きいわね。
インディーゲーム開発が活気づくのは良いことだわ」

「おいおい、ネイトさんよ。
ちょっと暢気すぎだぜ?
気軽に作れるってことは、それだけライバルが増えるってことじゃねぇか。
現に、今のお前より凄そうなゲーム作ってるやつなんてごまんといるぞ」

「ふふ、そうね。
でもね、ポマイラ。
私、どうしてかそれが嬉しいのよ」

「みんながこれだけ凄いゲームを作れるなら、
私にもまだ可能性があるってことでしょ?
それにね、どうしてか本当に好きな分野では、
自分より凄い作品を見ても『悔しい』って気持ちより『凄いなぁ』って感想しか不思議と出ないのよ」

「私、本当は結構負けず嫌いなのに……なんでだろうね?」

「……まぁ、それだけゲーム開発がお前にとって楽しいものなんだろ。
勝ち負けじゃなく、単純に好きなものだから。
いい作品を愛しちまうんだろうな」

「あら、今日は珍しく良いこと言うのね。
よし!それじゃ、そんな大好きなゲーム開発を始めていきましょうか!」
スキルピックシステムを作成(作業時間:約3時間)

「今日は、これまでみんなから頂いた私のスキル案を実装していくわよ!」

「ついにか…!
それで、どの案を採用するんだ?」

「何言ってるの、ポマイラ。
そんなの『全部』採用するに決まってるじゃない」
…

「はぁあああああああああああああああ!?!?
全部ゥ!? お前、正気か!?
大体、それ前にジェンスパークさんに止められただろうが!!!!」

「そうね。」

「そもそも、ゲームバランスおかしくなるだろ!
それに、スキル組み上げようとも実装の手間やバグのリスクもあるし…
無茶だぜ、そんなの。
新しい機能を追加するんじゃなくて今は仕上げる時期だぜ?ネイト!」

「あら、ジェンスパークさんの言葉そのまんまじゃない」

「…とにかく!
オレ様は反対だからな!
大人しく三つくらいにしておけって!」

「私も、この決断はかなり無茶をしていると思ってるわ。
…でもね」

「私の権能『世界を編み上げる力』は、画面の向こうにいるみんなと私を繋ぐ、大切な力なの。
私はこの自分の能力に誇りを持っているわ。
だから、みんなのアイディアは一つも無駄にしたくないの」
※権能とは?
アルカステラが個別に持つ特別な力。
ネイトの権能『世界を編み上げる力』は、異なる世界の声を力に変えて、
世界のルールを書き換えるという強力な力なのだ。

「…お前にとって、その権能はプロジェクトが破綻するリスクを冒してでも守りたいものなのかよ」

「ええ。」

「だったら、オレ様はもう止めねぇよ。
思う存分、その世界ってやつをみんなの力で編み上げてやれ!ネイト!」

「…ふふ、ありがとう。ポマイラ。」
スキルピックシステムを作成

「それで、ジェンスパさんに逆らうからには算段はあるんだろうな?」

「そうね。
まずは、これを見てもらおうかしら」
■ネイトのスキルピックシステム作成 動画

「お、おいおい…マジかよ。
もう組み上げちまってる」

「まぁ、ほとんどがClaudeさんのお陰だけどね!
今回は前回の反省を活かして、しっかりと指示を出しながら一つずつ確認して実装していったわ」

「…ところで、これはどんな仕様のシステムなんだ?」

「ヴァンサバ(Vampire Survivors)のような、ローグライク系のベーシックなスキル獲得システムね。
獲得していないスキル一覧の中からランダムに3つ抽選して、そこからスキルを一つ獲得するシステムよ。
スキルには『重み(Weight)』という値があって、強いスキルほど出にくい仕様になっているの」
・実際の抽選処理のコード(NateSkillPool.cs)
// NateSkillPool.cs
// 抽選対象となる全スキルを管理する ScriptableObject。
//
// 【使い方】
// 1. Unity メニュー → Assets → Create → TrainingGame → NateSkillPool でアセット作成
// 2. Inspector の skills リストに NateSkillData アセットを登録する
// 3. NateSkillManager の skillPool フィールドにこのアセットをアサインする
//
// 【抽選の重み付け】
// 各 NateSkillData の weight フィールドが大きいほど出やすくなる。
// 例: 通常スキル weight=10、レアスキル weight=3
using System.Collections.Generic;
using UnityEngine;
namespace TrainingGame
{
[CreateAssetMenu(menuName = "TrainingGame/NateSkillPool", fileName = "NateSkillPool")]
public class NateSkillPool : ScriptableObject
{
[Tooltip("抽選対象のスキル一覧。weight が大きいほど抽選されやすい。")]
public List<NateSkillData> skills = new List<NateSkillData>();
/// <summary>
/// 重み付き抽選で重複なく count 件のスキルを返す。
/// 既所持スキルを除外してから抽選する。
/// プール内の候補が count 未満の場合は候補を全件返す。
/// </summary>
/// <param name="ownedSkillIds">既に所持しているスキルIDのセット(除外対象)</param>
/// <param name="count">抽選件数(通常は3)</param>
/// <returns>抽選されたスキルのリスト</returns>
public List<NateSkillData> DrawRandom(HashSet<string> ownedSkillIds, int count = 3)
{
// 候補を絞る(未所持スキルのみ)
var candidates = new List<NateSkillData>();
foreach (var skill in skills)
{
if (skill == null) continue;
if (string.IsNullOrEmpty(skill.skillId)) continue;
if (ownedSkillIds != null && ownedSkillIds.Contains(skill.skillId)) continue;
candidates.Add(skill);
}
var result = new List<NateSkillData>();
int draw = Mathf.Min(count, candidates.Count);
for (int i = 0; i < draw; i++)
{
// 総重みを計算
int totalWeight = 0;
foreach (var c in candidates)
totalWeight += c.weight;
if (totalWeight <= 0) break;
// ランダムに1件選ぶ
int roll = Random.Range(0, totalWeight);
int cumulative = 0;
NateSkillData chosen = null;
foreach (var c in candidates)
{
cumulative += c.weight;
if (roll < cumulative)
{
chosen = c;
break;
}
}
if (chosen == null) break;
result.Add(chosen);
candidates.Remove(chosen); // 同じスキルが重複して出ないよう除外
}
return result;
}
/// <summary>skillId から NateSkillData を検索して返す。見つからなければ null。</summary>
public NateSkillData FindById(string skillId)
{
if (string.IsNullOrEmpty(skillId)) return null;
foreach (var skill in skills)
{
if (skill != null && skill.skillId == skillId)
return skill;
}
return null;
}
}
}


「獲得していないスキルの中からランダムに3つ抽選するの。
そのスキルは、重みっていう値があって強いスキルほど出にくい仕様になってるのよ。」


「リストに載るアイテムは、いつもの『ScriptableObject(スクリプタブルオブジェクト)』で管理しているわ。
これをスキル単位で細かく設定して、さっきのリストに追加していくのよ。
……スキルの作り方は話すと長くなっちゃうから、また今度にさせてね」


「あと、それぞれのスキルにはアイコンを用意したわ!
背景の色やフレームの豪華さで、レアリティや効果内容を何となく分類させてるの。
とは言え、全部生成AIに作成してもらったから雰囲気の統一感は……あまりないのだけど」


「『宴』からスキルピック画面を呼び出して、この画面からスキルをチョイスするのよ。
そうしたら、私がスキルを獲得してまた宴のアドベンチャーパートに戻るって仕組み!」


「あと、私のステータス画面にも獲得したスキルを表示するように改修したわ!
ちなみに、頂いたアイディアのスキルは全部実装したから、
追加のアイディアをまだまだ待ってるわよ!」

「……本当に全部作りやがったよ。
実装、苦労しただろうに。ネイト、お前の執念は筋金入りだな」

「ふふん、私の諦めの悪さを甘く見てもらったら困るわ!」
スキルシステムの作成&待機モーション(8時間くらい)

「…よし、仲間のスキルも完成。
あとは、戦闘シーンにこのスキルのコマンドを反映させていくだけね」
■スキルシステム完成動画

「こうしてみると、この二日間で戦闘システムも大分良い感じになって来たな」

「そうね。
とは言え、まだ私たちのターンの処理ばかり作成してるからやることはいっぱいあるのだけどね」

「スキル効果処理は…作業時間8時間くらいか。
やっぱ結構大変だったのか?」

「そうね……AOE(範囲攻撃)の処理と、一部の特殊なスキルの処理周りを実装するのは大変だったわ。
どうしても自分を攻撃するような動きになっちゃったりして……。
ちょっと、スキル周りの処理はかなり複雑になってきたから、
出来ればもう二度とコードを読みたくないわ……」

「とは言え、ここまで出来たらあとは殆どのスキルは使いまわしでいけるんだろ?」

「まぁ、まだ状態異常の数も少ないし、思いつく限りでは他にも実装したい効果処理はあるのだけどね……。
とはいえ、ベースのシステムとしては多分これで大丈夫……な、はず」

「おいおい、そんなに弱気で大丈夫か?」

「うぅ……だって、本当に色んなスクリプトファイルを見なきゃいけないから、複雑すぎて自信ないのよ」

「まぁ、細かい調整は置いといて…
今は動いたことを喜ぼうぜ!」

「そうね!
これでやっと次に進めるわ!」
待機モーション作成

「そう言えば一つ気になったんだが、ユニットが動くようになったんだな」

「そうなの! と言っても、まだ待機モーションだけだけどね。
でも、これで少しはSRPGらしくなってきたんじゃないかしら?」

「確かに動きがあると臨場感が違うぜ!
それで、どうやって作ったんだ?」

「まず、Gemini先生にスプライトシート形式(アニメーションのコマ割り画像)で、待機モーションのドット絵を描いてもらったわ」


「フレーム(コマ)数は多い方が滑らかに動くんだけど……
そうすると、上手く意図した画像が生成されないパターンが多かったから、今回は3フレームにしたの」


「そして、出力された画像を『GIMP(画像編集ソフト)』を使って細かく分割したわ」
■ ユニットを動かすスクリプト(UnitAnimator)
// UnitAnimator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TrainingGame.Battle;
namespace TrainingGame.View
{
public class UnitAnimator : MonoBehaviour
{
[Header("フレーム間隔(秒)")]
[SerializeField] private float _frameInterval = 0.3f;
private SpriteRenderer _spriteRenderer;
private Sprite[] _frames;
private Coroutine _idleCoroutine;
private void Awake()
{
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
}
/// <summary>
/// BattleViewManager.SpawnUnit() からユニットデータを渡して初期化する。
/// unitSprite をフレーム0として、idleFrames をその後に続ける。
/// </summary>
public void Initialize(UnitBaseStats stats)
{
var frameList = new List<Sprite>();
if (stats.unitSprite != null)
frameList.Add(stats.unitSprite);
if (stats.idleFrames != null)
frameList.AddRange(stats.idleFrames);
_frames = frameList.ToArray();
// フレーム0を初期表示
if (_frames.Length > 0 && _spriteRenderer != null)
_spriteRenderer.sprite = _frames[0];
}
/// <summary>
/// HandleTurnChanged から呼ぶ。このユニットがアクティブかどうかを渡す。
/// </summary>
public void SetActiveUnit(bool isActive)
{
if (isActive)
StartIdleAnimation();
else
StopIdleAnimation();
}
private void StartIdleAnimation()
{
if (_idleCoroutine != null) StopCoroutine(_idleCoroutine);
// フレームが1枚しかない場合はアニメーション不要
if (_frames == null || _frames.Length <= 1) return;
_idleCoroutine = StartCoroutine(PlayIdle());
}
private void StopIdleAnimation()
{
if (_idleCoroutine != null)
{
StopCoroutine(_idleCoroutine);
_idleCoroutine = null;
}
// フレーム0(通常立ち)に戻す
if (_frames != null && _frames.Length > 0 && _spriteRenderer != null)
_spriteRenderer.sprite = _frames[0];
}
private IEnumerator PlayIdle()
{
int index = 0;
while (true)
{
_spriteRenderer.sprite = _frames[index];
index = (index + 1) % _frames.Length;
yield return new WaitForSeconds(_frameInterval);
}
}
}
}
「あとは、ユニットの差分(コマごとの画像)をこの子に登録すれば、
行動可能なユニットの待機モーションアニメーションは完成よ!」
private IEnumerator PlayIdle()
{
int index = 0;
while (true)
{
_spriteRenderer.sprite = _frames[index];
index = (index + 1) % _frames.Length;
yield return new WaitForSeconds(_frameInterval);
}
}
「特にこの部分で、フレームごとにスプライトを順番にコマ送り表示させているの」

「なるほどなぁ。 ちなみに、攻撃とか他のモーションも作るのか?」

「作りたいけど……今は後回しにしてるわ。
だって、モーションを一つ増やすごとに、全ユニット分の差分画像を作成する必要があるでしょ?
今の開発スケジュールじゃ、時間的に少し厳しいのよ」

「とはいえ! 戦闘モーションはSRPGの華よね!
まずは戦闘システムの構築を完了させて、少し落ち着いたらスキルのエフェクトといった演出面と合わせて必ず検討してみるわ!」
最後に
かくして、進撃の開発16日目は幕を閉じた。
読者の皆様から寄せられた大切なアイディアは、
試行錯誤の末、無事に「スキルピックシステム」として実装され、ネイトの新たな力となった。
8時間にも及ぶコードとの死闘の末、
範囲攻撃や特殊効果といった複雑な処理を乗り越え、ついに戦闘の要であるスキルシステムが産声を上げたのである。
さらに、ドット絵に命を吹き込むことで、戦場に立つキャラクターたちは「待機モーション」という確かな鼓動を手に入れた。
止まっていた画面の時間が動き出し、
SRPGとしての輪郭がまた一つ、色濃く浮かび上がってきたのだ。
だが、安堵している暇はない。
企画のタイムリミットは、残りわずか2週間。
次なる試練は敵ユニットの思考処理か、それとも立ちはだかる未知のバグか。
ネイトたちの往く手には、まだまだ険しい開発の道が続いている……。
果たして、期限内にゲームは完成するのか!?
次回も、どうぞ乞うご期待!
#プロジェクトNeith #HorizonNotes #ゲーム制作 #個人開発 #Unity #開発日記 #読者参加型 #毎日note