芋出し画像

【プロゞェクトNeith】戊闘ロゞック䜜成〜みんなで䜜るゲヌム制䜜【5/1-5/2 14,15日目】

■今回やった事

5/1

・移動凊理を実装


■攻撃凊理実装動画

■アむテム䜿甚

5/2

・セヌブ呚りのバグを修正

■募集䞭のアむディア
①「暹海線」で発生しそうなむベント
䟋森の䞭、クマさんに出䌚った。お化けず遭遇 など
※期限はありたせん皆さんの自由でナニヌクな発想をお埅ちしおいたす。

② 䞻人公・ネむトの「スキル」※もう少し延長したす
䟋ファむアボヌル / 射皋2マス先の䞀䜓 / 効果察象に小ダメヌゞ
※王道からちょっず笑えるネタスキルたで、
どんなアむディアでも倧歓迎です

③ゲヌムで流す著䜜暩フリヌのBGMを教えおください
無料のフリヌのBGMを探しおたす
あなたのお気に入りを教えおください
生成AIで自䜜したものも可胜。
その堎合は、コメントやDMでお知らせください



こんにちはたる。です。


今回のネむトちゃんは、
戊闘システムの構築ず珟圚発生しおいるバグを改修したようです。


埐々にゲヌム党䜓の茪郭が芋え始めおきた印象がありたす😎


かなり、長期戊だったので今回の蚘事はほがダむゞェストになりたすが どうか応揎しお頂けるず嬉しいです


それでは、激闘の開発14、15日目 スタヌトです



二人の茶番

🎧今日の䜜業甚BGM

Tipsy_Dance䜜線曲 ヒオ
【民族音楜颚/é…’å Ž/螊り】Tipsy Dance【フリヌBGM】

ポマむラ自称ネむトの䜿い魔

「よお、おたえらオレだよ、ポマむラ様だ
おたえらが奜きなお菓子っお䜕かあるか
オレ様は、ブラックサンダヌず最近では湖池屋のスコヌンばかり食っおるぞ
あれ、蟞めらんねぇんだよな」

ネむト幻の星の粟霊

「あら、良いじゃない。
ちなみに、私はどら焌きず枩かいお茶の組み合わせが奜きよ
やっぱり、お茶に合う和菓子は最高よね」

ポマむラ自称ネむトの䜿い魔

「おいおい、ネむト。
お前はおばあちゃんか
お菓子は味が濃くおパンチの効いたものこそ至高だろ」

ネむト幻の星の粟霊

「おば 倱瀌ね
今、画面の向こうで倚くの人を敵に回したわよ
それに和菓子に幎霢は関係ないでしょ」

ポマむラ自称ネむトの䜿い魔

「たぁ、確かに和菓子も旚いっちゃ旚いけどな
糖分をしっかり取っお、頭働かせお行こうぜ」


戊闘システム移動、攻撃、アむテム、バトル終了凊理8時間くらい

ポマむラ自称ネむトの䜿い魔

「それで、今日も䜜業の振り返りか」

ネむト幻の星の粟霊

「そうね。
昚日も結構ガッツリず䜜業に集䞭させおもらったから、サクサクず流れを解説しおいく感じにしおいくわね」

ネむト幻の星の粟霊

「5/1(金) 開発14日目に進めた内容はこんな感じよ」

・移動凊理を実装


■攻撃凊理実装動画

■アむテム䜿甚確認動画


「おお
戊闘システム、結構ガッツリ出来お来おるじゃねぇか」

ネむト幻の星の粟霊

「たぁ、この日に照準を合わせおシステム自䜓は準備しおきたからね。
あずはそれを反映させる感じだったから、実装自䜓はすんなりいったわ」

ポマむラ自称ネむトの䜿い魔

「なるほどな。
でも、結構時間かかったんだろ」

ネむト幻の星の粟霊

「そうね。
たぁ、殆どがアむテムの䜿甚に関する凊理で躓いた感じね」

ネむト幻の星の粟霊

「でも今はご芧の通り問題なく動くようになったわ」

ポマむラ自称ネむトの䜿い魔

「苊劎した甲斐があったよな
そんじゃ、簡単にどう䜜ったのか教えおくれよ」


コマンドパネルの実装

ネむト幻の星の粟霊

「たずは、プレむダヌが遞択するコマンドや、
キャラクタヌたちの䜓力・行動順を衚す党䜓的なUIを䜜成しおいくわ。
こんな感じで䜜っおみたの」


ポマむラ自称ネむトの䜿い魔

「UI自䜓はシンプルなんだな。 䜕かこだわった点ずかはあるのか」

ネむト幻の星の粟霊

「そうね 出来るだけ画面䞭倮郚にUIを眮かないようにしたこずかしら。
コマンドパネルに぀いおはちょっず倧きすぎる気もするけど、
誀操䜜が起こらないように、お詊しで倧きめに䜜っおみたの」

ポマむラ自称ネむトの䜿い魔

「なるほどな。 ちなみに、今の時点で芋えおる課題ずかはあるのか」

ネむト幻の星の粟霊

「たず、コマンドの数が倚い事ね。
これが画面に圧迫感を䞎えおしたっお、プレむダヌに䞎える情報量も倚くなっおいるわ。

『アストラルコヌド』ずいうのが必殺技なんだけど、
これは䜿えるタむミングが限られおいるのよね。

だから、垞に衚瀺しおおくのではなく、必芁な堎面でのみアむコンずしお衚瀺しおも良いかも知れないわ。
このあたりは、アストラルコヌドを実装する時にたた改めお方針を考えるわ」


移動凊理

■移動凊理の動画

ネむト幻の星の粟霊

「次に、移動の凊理ね
これはもう、テスト段階で動くこずが確認できおいたものだから、今回は少しだけ挔出面をアレンゞするこずに留めおおいたわ」

ポマむラ自称ネむトの䜿い魔

「たぁ、実は䞀週間前くらいから裏でコツコツ䜜っおたもんな
その『挔出面』っおのはどんな感じで向䞊したんだ」

ネむト幻の星の粟霊

「䞻に、カメラワヌクね。ポむントは二぀よ。

䞀぀は、マりスホむヌルでのズヌム凊理の他に、
右クリックを抌しながらマりスを動かすこずでカメラを360床回転できるようにした事。
もう䞀぀が、キャラクタヌの移動埌に、カメラが自動でそのキャラクタヌを远埓するようにした事よ」

ネむト幻の星の粟霊

「個人的に、シミュレヌションRPGはカメラワヌクが呜だず思っおいるの

だっお、広倧な戊堎を俯瞰しお戊術を組み立おるのが面癜い芁玠でしょ
だったら、プレむダヌには戊堎党䜓を芋枡せるように工倫したかったのよ」

ポマむラ自称ネむトの䜿い魔

「なるほどな
ちなみに、この移動凊理はどういう感じで䜜られおるんだ」

ネむト幻の星の粟霊

「結構、難しいわよ
たず、『PathFinderパスファむンダヌ』ずいうプログラムが、移動できる範囲を蚈算しおいるの」

// PathFinder.cs
// 移動範囲の蚈算BFSず経路探玢
// GridMap を参照しお「どこに移動できるか」「どう移動するか」を蚈算する。
// ステヌトを持たない静的クラスずしお実装し、キャッシュは倖郚で持぀蚭蚈。

using System.Collections.Generic;
using UnityEngine;

namespace TrainingGame.Battle
{
    /// <summary>
    /// BFS幅優先探玢を甚いた移動範囲・経路蚈算クラス。
    ///
    /// 【蚭蚈方針】
    ///   - ステヌトなしの静的クラス。毎タヌン呌び出しお結果を䜿い捚おる。
    ///   - 移動力moveRange分のコストたで到達できるタむルをすべお返す。
    ///   - 敵ナニットのタむルは「通過䞍可」ずしお扱う友軍は通過可・停止䞍可。
    /// </summary>
    public static class PathFinder
    {
        // ================================================================
        //  移動範囲の蚈算BFS
        // ================================================================

        /// <summary>
        /// 指定ナニットの移動可胜タむル䞀芧を返す。
        ///
        /// 【アルゎリズム: BFS + コスト管理】
        ///   各タむルぞの最小コストを蚘録しながら幅優先で探玢。
        ///   移動コストが moveRange を超えるタむルは探玢を打ち切る。
        /// </summary>
        /// <param name="map">グリッドマップ</param>
        /// <param name="unit">移動するナニット</param>
        /// <param name="allies">同じ陣営のナニット䞀芧通過可・停止䞍可</param>
        /// <param name="enemies">敵陣営のナニット䞀芧通過䞍可</param>
        /// <returns>移動可胜なタむルのセット出発地点を含む</returns>
        public static HashSet<TileData> GetReachableTiles(
            GridMap map,
            UnitBattleState unit,
            IEnumerable<UnitBattleState> allies,
            IEnumerable<UnitBattleState> enemies)
        {
            int moveRange = unit.BaseStats.moveRange;

            // 敵・味方のタむルセットを事前に構築
            var enemyTiles = BuildTileSet(map, enemies);
            var allyTiles  = BuildTileSet(map, allies);
            // 自分自身は通過可胜にする
            allyTiles.Remove(map.GetTile(unit.GridX, unit.GridY));

            // BFS
            // cost[tile] = そのタむルに到達するための最小コスト
            var cost    = new Dictionary<TileData, int>();
            var queue   = new Queue<TileData>();
            var result  = new HashSet<TileData>();

            var startTile = map.GetTile(unit.GridX, unit.GridY);
            cost[startTile] = 0;
            queue.Enqueue(startTile);

            while (queue.Count > 0)
            {
                var current    = queue.Dequeue();
                int currentCost = cost[current];

                // 停止可胜かどうか敵タむル・味方タむルは停止䞍可
                bool canStop = !enemyTiles.Contains(current) && !allyTiles.Contains(current);
                if (canStop)
                    result.Add(current);

                // 隣接タむルを探玢
                foreach (var neighbor in map.GetNeighbors(current.X, current.Y))
                {
                    if (!neighbor.IsPassable)     continue; // 通行䞍可地圢
                    if (enemyTiles.Contains(neighbor)) continue; // 敵ナニットは通過䞍可

                    int newCost = currentCost + neighbor.MoveCost;
                    if (newCost > moveRange)          continue; // 移動力オヌバヌ

                    // より䜎コストで到達できる堎合のみ曎新
                    if (!cost.TryGetValue(neighbor, out int existing) || newCost < existing)
                    {
                        cost[neighbor] = newCost;
                        queue.Enqueue(neighbor);
                    }
                }
            }

            return result;
        }

        // ================================================================
        //  経路の取埗A* / BFS で最短経路を返す
        // ================================================================

        /// <summary>
        /// 出発地点からゎヌルたでの最短経路をタむルのリストで返す。
        /// 到達䞍可の堎合は空リストを返す。
        ///
        /// 【アルゎリズム: Dijkstraコスト考慮BFS】
        ///   地圢コストを考慮した最短経路を求める。
        ///   ヒュヌリスティックなしの Dijkstra で確実な最短経路を保蚌する。
        /// </summary>
        /// <param name="map">グリッドマップ</param>
        /// <param name="startX">出発X座暙</param>
        /// <param name="startY">出発Y座暙</param>
        /// <param name="goalX">目暙X座暙</param>
        /// <param name="goalY">目暙Y座暙</param>
        /// <param name="moveRange">最倧移動コスト到達刀定に䜿甚</param>
        /// <param name="blockingUnits">通過䞍可ナニット䞀芧敵ナニットを枡す</param>
        /// <returns>出発タむルを含む経路リスト。到達䞍可なら空リスト。</returns>
        public static List<TileData> FindPath(
            GridMap map,
            int startX, int startY,
            int goalX,  int goalY,
            int moveRange,
            IEnumerable<UnitBattleState> blockingUnits = null)
        {
            var blockingTiles = BuildTileSet(map, blockingUnits);

            // 優先床付きキュヌコスト昇順— SortedList で代甚
            var openSet  = new SortedList<int, Queue<TileData>>();
            var cameFrom = new Dictionary<TileData, TileData>();
            var costSoFar = new Dictionary<TileData, int>();

            var startTile = map.GetTile(startX, startY);
            var goalTile  = map.GetTile(goalX,  goalY);

            if (startTile == null || goalTile == null)
                return new List<TileData>();

            Enqueue(openSet, 0, startTile);
            costSoFar[startTile] = 0;

            while (openSet.Count > 0)
            {
                var current = Dequeue(openSet);

                if (current == goalTile)
                    return ReconstructPath(cameFrom, startTile, goalTile);

                foreach (var neighbor in map.GetNeighbors(current.X, current.Y))
                {
                    if (!neighbor.IsPassable)           continue;
                    if (blockingTiles.Contains(neighbor)) continue;

                    int newCost = costSoFar[current] + neighbor.MoveCost;
                    if (newCost > moveRange)            continue;

                    if (!costSoFar.TryGetValue(neighbor, out int existing) || newCost < existing)
                    {
                        costSoFar[neighbor] = newCost;
                        cameFrom[neighbor]  = current;
                        // ヒュヌリスティックマンハッタン距離を加算しお優先床を決定
                        int priority = newCost + GridMap.ManhattanDistance(neighbor.X, neighbor.Y, goalX, goalY);
                        Enqueue(openSet, priority, neighbor);
                    }
                }
            }

            return new List<TileData>(); // 到達䞍可
        }

        // ================================================================
        //  射皋内ナニットの怜玢
        // ================================================================

        /// <summary>
        /// 指定座暙から射皋range マス内にいるナニットを返す。
        /// マンハッタン距離で刀定する。
        /// </summary>
        /// <param name="map">グリッドマップ</param>
        /// <param name="originX">攻撃起点のX座暙</param>
        /// <param name="originY">攻撃起点のY座暙</param>
        /// <param name="range">射皋マス数</param>
        /// <param name="targets">察象ナニット䞀芧</param>
        /// <returns>射皋内にいるナニットのリスト</returns>
        public static List<UnitBattleState> GetUnitsInRange(
            GridMap map,
            int originX, int originY,
            int range,
            IEnumerable<UnitBattleState> targets)
        {
            var result = new List<UnitBattleState>();
            foreach (var unit in targets)
            {
                if (unit.IsDefeated) continue;
                int dist = GridMap.ManhattanDistance(originX, originY, unit.GridX, unit.GridY);
                if (dist <= range)
                    result.Add(unit);
            }
            return result;
        }

        // ================================================================
        //  ナヌティリティ
        // ================================================================

        /// <summary>ナニット䞀芧からタむルのセットを構築する</summary>
        private static HashSet<TileData> BuildTileSet(
            GridMap map,
            IEnumerable<UnitBattleState> units)
        {
            var set = new HashSet<TileData>();
            if (units == null) return set;

            foreach (var u in units)
            {
                var tile = map.GetTile(u.GridX, u.GridY);
                if (tile != null) set.Add(tile);
            }
            return set;
        }

        /// <summary>経路を再構築するゎヌルから蟿っお逆順にする</summary>
        private static List<TileData> ReconstructPath(
            Dictionary<TileData, TileData> cameFrom,
            TileData start,
            TileData goal)
        {
            var path    = new List<TileData>();
            var current = goal;

            while (current != start)
            {
                path.Add(current);
                if (!cameFrom.TryGetValue(current, out current))
                    return new List<TileData>(); // 異垞通垞は到達しない
            }
            path.Add(start);
            path.Reverse();
            return path;
        }

        /// <summary>優先床付きキュヌぞの远加SortedList + Queue でコリゞョン察応</summary>
        private static void Enqueue(SortedList<int, Queue<TileData>> openSet, int priority, TileData tile)
        {
            if (!openSet.TryGetValue(priority, out var queue))
            {
                queue = new Queue<TileData>();
                openSet.Add(priority, queue);
            }
            queue.Enqueue(tile);
        }

        /// <summary>優先床付きキュヌからの取り出し</summary>
        private static TileData Dequeue(SortedList<int, Queue<TileData>> openSet)
        {
            var first = openSet.Values[0];
            var tile  = first.Dequeue();
            if (first.Count == 0)
                openSet.RemoveAt(0);
            return tile;
        }
    }
}
ポマむラ自称ネむトの䜿い魔

「な、なんだこれ 
今たでで䞀番難しそうだぞ」

ネむト幻の星の粟霊

「本圓、その通りだず思うわ。
SRPGの䞀番難しい点っお、この『経路や射皋を蚈算する凊理』だず私は思っおるわ。

簡単に蚀うずね、ナニットが居るマスを起点にしお、
そのナニットの『移動力』のステヌタスから隣接するマスをどんどん蚈算しおいくの。

そこに障害物や他のナニットがある堎合は、そのマスをスキップしお再蚈算を行うわ。
結果ずしお、移動可胜なマスだけを導き出しお返す感じね。

図にするずこんな感じかしら」

■移動経路

ネむト幻の星の粟霊

「私を起点に、どのマスが䜕マス離れおいるかを衚しおいるわ。

移動力は私が歩ける歩数ね。

ポむントは、私は盎角にしか動くこずが出来ず、斜めに移動するこずは出来ないの。

それを螏たえお、もし隣に障害物があった堎合、
たっすぐにしか移動できない私はどうやっお3歩歩けるず思う
考えおみたしょう」

ポマむラ自称ネむトの䜿い魔

「えっず 斜めに移動できないなら、迂回したりしお移動するしかないよな」

ネむト幻の星の粟霊

「その通り
そうなるず、遠回りした分、私が移動できる距離が倉わっお来ちゃうのよ」


ポマむラ自称ネむトの䜿い魔

「隣に岩があるせいで、岩の向こうに行けなくなったな」

ネむト幻の星の粟霊

「そういうこずよ。

実際に珟実䞖界でやっお芋るず盎感的に分かるこずだけど、
プログラムの䞖界ではこれも䞀぀䞀぀蚈算しおあげないずいけないのよ。

『ここに障害物があるから、その䞊で3歩歩くならこういう経路になるよね』っお。

これがあの難しそうなコヌドの䞭でやっおいる基本的な動きよ」

ポマむラ自称ネむトの䜿い魔

「なるほどな
実際に図にしおみるず案倖わかるもんだな」


攻撃凊理

■攻撃凊理実装動画



ネむト幻の星の粟霊

「次に、攻撃の凊理も䜜成したわ。
これもさっきの『PathFinder』の応甚で射皋を蚈算しおくれおいるから、
あず必芁になっおくるのは『ダメヌゞ蚈算』ず『呜䞭刀定』、そしお『HPを枛らす凊理』になるわね」

ポマむラ自称ネむトの䜿い魔

「これも実は事前に組み立おおたんだよな」

ネむト幻の星の粟霊

「そうよ
たぁ、難しい話は䞀旊眮いずいお たずは挔出面をどうしたかをお話するわね」


■゚フェクトのアセット

ネむト幻の星の粟霊

「ダメヌゞを受けた時の挔出ずしお、こちらのフリヌアセットを䜿わせおいただいたわ。
あいにく、私にはVFXをれロから䜜成する技術も時間もないからね。
時短よ、時短」

ポマむラ自称ネむトの䜿い魔

「おお
これ、無料なのにすげえカッコいい゚フェクトがたくさん入っおるな

ネむト幻の星の粟霊

「そうよ
実は、昔Unityを觊っおいた時代からずっずダりンロヌドはしおいたんだけど、䞀回も䜿ったこずがなかったの。
今回ようやく日の目を芋お嬉しいわ」

ポマむラ自称ネむトの䜿い魔

「なぁなぁ それはそうず、肝心の『攻撃』はどうやっお䜜ったんだよ」

ネむト幻の星の粟霊

「これも難しいわよ ダメヌゞ蚈算甚のスクリプトをお芋せするわね」

// BattleCalculator.cs
// バトル蚈算ロゞック玔粋な静的クラス
// UnityEngine に最小限しか䟝存しないため、単䜓テストが容易。

using UnityEngine;

namespace TrainingGame.Battle
{
    /// <summary>
    /// バトルにおける各皮蚈算を行う静的クラス。
    /// ステヌトを持たず、入力倀から結果を返すだけの玔粋な蚈算噚。
    /// </summary>
    public static class BattleCalculator
    {
        // ================================================================
        //  定数
        // ================================================================

        /// <summary>物理・魔法攻撃の最䜎ダメヌゞ</summary>
        public const int MIN_DAMAGE = 1;

        /// <summary>基本呜䞭率%</summary>
        public const int BASE_HIT_RATE = 80;

        /// <summary>呜䞭率の䞊限%</summary>
        public const int MAX_HIT_RATE = 100;

        /// <summary>クリティカル発生時のダメヌゞ倍率</summary>
        public const float CRITICAL_MULTIPLIER = 1.5f;


        // ================================================================
        //  呜䞭刀定
        // ================================================================

        /// <summary>
        /// 最終呜䞭率を蚈算する。
        /// 蚈算匏: Min(80 + 噚甚さ, 100)
        /// 噚甚さが20で呜䞭率100%必䞭になる。
        /// </summary>
        /// <param name="dexterity">攻撃偎の噚甚さ</param>
        /// <returns>呜䞭率0〜100の敎数</returns>
        public static int CalcHitRate(int dexterity)
        {
            return Mathf.Min(BASE_HIT_RATE + dexterity, MAX_HIT_RATE);
        }

        /// <summary>
        /// 呜䞭したかどうかを刀定する。
        /// </summary>
        /// <param name="dexterity">攻撃偎の噚甚さ</param>
        /// <returns>呜䞭したなら true</returns>
        public static bool RollHit(int dexterity)
        {
            int rate = CalcHitRate(dexterity);
            return Random.Range(0, 100) < rate;
        }


        // ================================================================
        //  クリティカル刀定
        // ================================================================

        /// <summary>
        /// クリティカル発生率を蚈算する。
        /// 蚈算匏: 運 / 2%、小数点切り捚お
        /// </summary>
        /// <param name="luck">攻撃偎の運</param>
        /// <returns>クリティカル率0〜100の敎数</returns>
        public static int CalcCriticalRate(int luck)
        {
            return Mathf.Clamp(luck / 2, 0, 100);
        }

        /// <summary>
        /// クリティカルが発生したかどうかを刀定する。
        /// </summary>
        /// <param name="luck">攻撃偎の運</param>
        /// <returns>クリティカルなら true</returns>
        public static bool RollCritical(int luck)
        {
            int rate = CalcCriticalRate(luck);
            return Random.Range(0, 100) < rate;
        }


        // ================================================================
        //  物理ダメヌゞ蚈算
        // ================================================================

        /// <summary>
        /// 物理攻撃のダメヌゞを蚈算する。
        /// 蚈算匏: Max(ATK - DEF, MIN_DAMAGE)
        /// </summary>
        /// <param name="atk">攻撃偎のATK</param>
        /// <param name="def">防埡偎のDEF</param>
        /// <returns>確定ダメヌゞ量最䜎 MIN_DAMAGE</returns>
        public static int CalcPhysicalDamage(int atk, int def)
        {
            return Mathf.Max(atk - def, MIN_DAMAGE);
        }

        /// <summary>
        /// 物理攻撃の䞀連の流れ呜䞭→クリティカル→ダメヌゞを蚈算する。
        /// </summary>
        /// <param name="attacker">攻撃偎のバトル状態</param>
        /// <param name="defender">防埡偎のバトル状態</param>
        /// <returns>蚈算結果</returns>
        public static AttackResult CalcPhysicalAttack(UnitBattleState attacker, UnitBattleState defender)
        {
            var result = new AttackResult();

            // 呜䞭刀定噚甚さで呜䞭率が䞊昇
            result.IsHit = RollHit(attacker.BaseStats.dexterity);
            if (!result.IsHit)
                return result; // ミスダメヌゞなし

            // ★修正基瀎ダメヌゞの蚈算に、バフ蟌みの「ActualAtk」ず「ActualDef」を䜿う
            int baseDamage = CalcPhysicalDamage(attacker.ActualAtk, defender.ActualDef);

            // クリティカル刀定運でクリティカル率が䞊昇
            result.IsCritical = RollCritical(attacker.BaseStats.luck);
            if (result.IsCritical)
                baseDamage = Mathf.FloorToInt(baseDamage * CRITICAL_MULTIPLIER);

            result.Damage = baseDamage;
            return result;
        }


        // ================================================================
        //  魔法スキルダメヌゞ蚈算
        // ================================================================

        /// <summary>
        /// 魔法攻撃のダメヌゞを蚈算する。
        /// 蚈算匏: Max(魔力 - 耐魔力, MIN_DAMAGE)
        /// </summary>
        /// <param name="magic">攻撃偎の魔力</param>
        /// <param name="magicDef">防埡偎の耐魔力</param>
        /// <returns>確定ダメヌゞ量最䜎 MIN_DAMAGE</returns>
        public static int CalcMagicDamage(int magic, int magicDef)
        {
            return Mathf.Max(magic - magicDef, MIN_DAMAGE);
        }

        /// <summary>
        /// 回埩量を蚈算する。
        /// 蚈算匏: 魔力 × healMultiplier小数点切り捚お
        /// </summary>
        /// <param name="magic">䜿甚者の魔力</param>
        /// <param name="healMultiplier">回埩倍率䟋: 1.0f or 1.5f</param>
        /// <returns>回埩量</returns>
        public static int CalcHealAmount(int magic, float healMultiplier = 1.0f)
        {
            return Mathf.FloorToInt(magic * healMultiplier);
        }

        /// <summary>
        /// スキル䜿甚の䞀連の流れを蚈算する。
        /// ※魔法攻撃は呜䞭率の抂念を持たない必䞭蚭蚈。
        ///   倉曎する堎合はここに呜䞭刀定を远加する。
        /// </summary>
        /// <param name="attacker">䜿甚者のバトル状態</param>
        /// <param name="defender">察象のバトル状態回埩の堎合は䜿甚者自身を枡すこずも可</param>
        /// <param name="skill">䜿甚するスキル</param>
        /// <returns>蚈算結果</returns>
        public static SkillResult CalcSkillEffect(UnitBattleState attacker, UnitBattleState defender, SkillData skill)
        {
            var result = new SkillResult { Skill = skill };

            switch (skill.skillType)
            {
                case SkillType.MagicAttack:
                    result.IsHit = true; // 魔法は必䞭
                    result.Damage = CalcMagicDamage(attacker.BaseStats.magic, defender.BaseStats.magicDef);
                    break;

                case SkillType.Heal:
                    result.IsHit = true;
                    result.HealAmount = CalcHealAmount(attacker.BaseStats.magic, skill.healMultiplier);
                    break;

                case SkillType.Buff:
                case SkillType.Debuff:
                    // バフ・デバフの詳现はPhase3以降に実装
                    result.IsHit = true;
                    break;
            }

            return result;
        }


        // ================================================================
        //  アストラルコヌド蚈算
        // ================================================================

        /// <summary>
        /// アストラルコヌドの効果量を蚈算する必䞭・クリティカルなし。
        ///
        /// 党䜓系AllEnemy / AllAllyの堎合、BattleManager 偎でこの結果を
        /// 各タヌゲットに個別適甚する。defender には null を枡しおよい。
        /// </summary>
        /// <param name="attacker">䜿甚者</param>
        /// <param name="defender">単䜓察象党䜓系は null でよい</param>
        /// <param name="entry">アストラルコヌド゚ントリヌ定矩</param>
        /// <returns>蚈算結果</returns>
        public static AstralCodeResult CalcAstralCode(
            UnitBattleState attacker,
            UnitBattleState defender,
            AstralCodeEntry entry)
        {
            var result = new AstralCodeResult { Entry = entry };
            var stats = attacker.BaseStats;

            switch (entry.effectType)
            {
                case AstralCodeEffectType.PhysicalDamage:
                    // ★修正バフ蟌みの ActualAtk ず ActualDef を䜿う
                    result.Damage = Mathf.Max(
                        Mathf.FloorToInt(attacker.ActualAtk * entry.powerMultiplier)
                        - (defender?.ActualDef ?? 0),
                        MIN_DAMAGE);
                    result.IsAoE = false;
                    break;

                case AstralCodeEffectType.MagicDamage:
                    result.Damage = Mathf.Max(
                        Mathf.FloorToInt(stats.magic * entry.powerMultiplier)
                        - (defender?.BaseStats.magicDef ?? 0),
                        MIN_DAMAGE);
                    result.IsAoE = false;
                    break;

                case AstralCodeEffectType.SelfHeal:
                    // MaxHP の割合回埩
                    result.HealAmount = Mathf.FloorToInt(stats.maxHp * entry.powerMultiplier);
                    result.IsAoE = false;
                    break;

                case AstralCodeEffectType.AllEnemyPhysical:
                    // ★修正党䜓物理もバフ蟌みの ActualAtk を䜿う
                    result.Damage = Mathf.Max(Mathf.FloorToInt(attacker.ActualAtk * entry.powerMultiplier), MIN_DAMAGE);
                    result.IsAoE = true;
                    break;

                case AstralCodeEffectType.AllEnemyMagic:
                    // 党䜓魔法基瀎倀のみ
                    result.Damage = Mathf.Max(Mathf.FloorToInt(stats.magic * entry.powerMultiplier), MIN_DAMAGE);
                    result.IsAoE = true;
                    break;

                case AstralCodeEffectType.AllAllyHeal:
                    result.HealAmount = Mathf.FloorToInt(stats.magic * entry.powerMultiplier);
                    result.IsAoE = true;
                    break;

                case AstralCodeEffectType.InstantKill:
                    result.Damage = 9999; // BattleManager 偎で IsDefeated チェック
                    result.IsAoE = false;
                    break;
            }

            return result;
        }
    }


        // ================================================================
        //  結果デヌタ構造䜓
        // ================================================================

        /// <summary>通垞攻撃の蚈算結果</summary>
        public struct AttackResult
    {
        /// <summary>呜䞭したか</summary>
        public bool IsHit;

        /// <summary>クリティカルだったか</summary>
        public bool IsCritical;

        /// <summary>最終ダメヌゞ量ミスなら0</summary>
        public int Damage;

        public override string ToString()
        {
            if (!IsHit) return "ミス";
            return IsCritical
                ? $"クリティカル {Damage} ダメヌゞ"
                : $"{Damage} ダメヌゞ";
        }
    }

    /// <summary>スキル䜿甚の蚈算結果</summary>
    public struct SkillResult
    {
        /// <summary>䜿甚したスキル</summary>
        public SkillData Skill;

        /// <summary>効果が発動したか必䞭スキルは垞にtrue</summary>
        public bool IsHit;

        /// <summary>䞎えるダメヌゞ量攻撃スキルのみ</summary>
        public int Damage;

        /// <summary>回埩量回埩スキルのみ</summary>
        public int HealAmount;
    }

    // ================================================================
    //  アストラルコヌド蚈算結果
    // ================================================================

    /// <summary>アストラルコヌド䜿甚の蚈算結果</summary>
    public struct AstralCodeResult
    {
        /// <summary>䜿甚した゚ントリヌ定矩</summary>
        public AstralCodeEntry Entry;

        /// <summary>䞎えるダメヌゞ量攻撃系のみ</summary>
        public int Damage;

        /// <summary>回埩量回埩系のみ</summary>
        public int HealAmount;

        /// <summary>党䜓攻撃・党䜓回埩の堎合 true</summary>
        public bool IsAoE;

        public override string ToString()
        {
            if (Entry == null) return "(null)";
            return IsAoE
                ? $"【アストラルコヌド】{Entry.skillName} → 党䜓{Damage}ダメヌゞ"
                : HealAmount > 0
                    ? $"【アストラルコヌド】{Entry.skillName} → {HealAmount}回埩"
                    : $"【アストラルコヌド】{Entry.skillName} → {Damage}ダメヌゞ";
        }
    }
}
ポマむラ自称ネむトの䜿い魔

「 分かりやすく説明しおくれ。」

ネむト幻の星の粟霊

「もうポマむラったら暪着しお
たぁ、やっおる蚈算は前に決めた仕様をそのたた螏襲しおいるわね」

🔢 バトル蚈算匏の定矩
1. 物理ダメヌゞ蚈算
ダメヌゞ = ATK - DEF ※最䜎ダメヌゞを1に蚭定
䟋ネむトのATKが30、敵のDEFが15の堎合、

30 - 15 = 15ダメヌゞ。

非垞にシンプルで、「特蚓でATKを1䞊げれば、ダメヌゞが1増える」ずいう成長の実感がダむレクトに䌝わりたす。

2. 魔法スキルダメヌゞ蚈算
ダメヌゞ = 魔力 - 耐魔力
物理ず同様の蚈算です。
回埩スキルの堎合は、そのたた「魔力の数倀回埩量」ずするか、

「魔力 × 1.5」など少しボヌナスを぀けるずバランスが取りやすいかも。

3. 呜䞭率蚈算
最終呜䞭率(%) = 80 + 噚甚さ ※䞊限100%
基本呜䞭率を80%ずし、「噚甚さ1ポむントに぀き、呜䞭率が1%䞊がる」仕様です。

これにより、「噚甚さが20になれば呜䞭率100%必䞭になる」ずいう明確な特蚓の目暙が生たれたす。

4. クリティカル蚈算
クリティカル率(%) = 運 / 2
クリティカル発生時のダメヌゞ = 通垞ダメヌゞ × 1.5倍 小数点切り捚お

発動したらラッキヌ。

ネむト幻の星の粟霊

「噚甚さず運の関係は、
盎感的な分かりやすさを重芖しお『噚甚さを呜䞭率』『運をクリティカル率』に倉曎したの。

そしおこれを基に、ClaudeAIさんにコヌドを䜜っおもらったのよ」

ポマむラ自称ネむトの䜿い魔

「なるほどな
 おこずは、ネむトも殆ど蚈算匏芋おないんじゃないか」

ネむト幻の星の粟霊

「た、たぁ、私は明らかにおかしい数倀になった時にここを調敎できればそれでいいのよ」

ポマむラ自称ネむトの䜿い魔

「ネむトも人のこず蚀えねぇじゃねぇか 」


戊闘甚アむテムの実装

■アむテム䜿甚

ネむト幻の星の粟霊

「次に戊闘甚アむテムの実装ね。
これは本圓に苊劎したわ 」

ポマむラ自称ネむトの䜿い魔

「たぁ、暪で芋おお盞圓倧倉そうだったもんな。
䜕が倧倉だったか聞かせおくれよ」

ネむト幻の星の粟霊

「そうねぇ これは私の環境固有の問題だったのだけど、
宎3にデヌタ管理を任せおいる以䞊、たずはアむテムの情報を戊闘シヌンに匕き枡すのが倧倉だったわね。
結局、前回同様スクリプタブルオブゞェクトで匕き枡すようにしたのだけど。
あずは、ここのアむテム効果を远加しお、バグを取り陀いおっお䜜業が地道で倧倉だったわ」

ポマむラ自称ネむトの䜿い魔

「たぁ、確かに宎3に無理やりデヌタ管理任せおる運甚だったもんな。
だったらいっそのこず宎から切り離しおデヌタ管理すればよかったんじゃねぇか」

ネむト幻の星の粟霊

「正盎、プロゞェクトの初期ならその方法もありだったず今では思うわ。
でも、私が䞀番気にしおるのは保存するデヌタを宎ずC#でそれぞれ持っおしたった堎合、バグが発生した時にそれがどこが原因なのか分からなくなるし、䜕のデヌタどこで保存されおいるのか分からなくなるず思ったの。

それに、宎には元々セヌブ・ロヌド機胜があるからそれを䜿う運甚にすれば私がわざわざセヌブ機胜やUIを䜜る手間もなくなるわ。
だから、デヌタ管理は党郚宎の管蜄にしようっお決めたの」

ポマむラ自称ネむトの䜿い魔

「なるほどな。
䜜業スピヌドず効率を考えた蚳か」

ネむト幻の星の粟霊

「うん、でもそのせいで今日は痛い目にあったのだけどね」


セヌブ機胜のバグ修正5/2 8時間くらい

ポマむラ自称ネむトの䜿い魔

「今日のネむト、めちゃくちゃ忙しそうだったよな。
䜕があったのか教えおくれよ」

ネむト幻の星の粟霊

「うん  。
実はね、『宎Utage』の機胜を䜿っおセヌブ・ロヌドしおも、
ちゃんずゲヌム䞊ではデヌタがロヌドされおないっお珟象を芋぀けたの」

ポマむラ自称ネむトの䜿い魔

「それ、そもそもゲヌムが出来ないレベルの倧問題じゃねぇか
原因は䜕だったんだ」

ネむト幻の星の粟霊

「原因はシンプルだったわ。
そもそも、自䜜のメニュヌからロヌドした段階では『宎』偎からデヌタを共有しおないからしおいないから、宎偎で保存したデヌタを取り蟌めおいなかったのよ」


ポマむラ自称ネむトの䜿い魔

「なんだ、そんなこずか じゃあ、なんでこんなに苊劎したんだ」

ネむト幻の星の粟霊

「ほら、私基本的にはコヌディングは生成AIに任せおるじゃない
だから、ファむルをClaudeAIさんやGemini先生に芋せお修正しおもらっおたの。」

ネむト幻の星の粟霊

「でも、色んなAIに聞いおも結局、本圓の原因には誰も気づかなかったわ。
『ロヌドした段階では宎からデヌタを貰っおいない』ずいう構造的な発想にたで至らなくお、䞭々解決できなかったのよね」

ポマむラ自称ネむトの䜿い魔

「なるほどな。
そこで、ネむトはどうしたんだ」

ネむト幻の星の粟霊

「これ以䞊AIに聞いおも解決しないず思ったから、
敢えお生成AIの助蚀は党郚忘れお、自分で䞀぀ず぀『デヌタがどの段階たで保持されおいるか』を確認しおみたの。」

ネむト幻の星の粟霊

「䟋えば、ロヌドした埌に宎が持っおる倉数を呌び出したら倀は入っおいるのか

そうやっお調べおみるず、宎偎での倉数はちゃんずロヌドした埌も残っおたの。

そうなるず、『ロヌド実行埌に、ちゃんず宎偎を参照出来おいないC#偎のメニュヌ生成ロゞック』に問題がある。

それで改めおコヌドを確認しおみたら、
そもそもロヌドした埌に宎の倉数を読みに行っおないから、ロヌドも䜕もないっおこずが分かったのよ」

ポマむラ自称ネむトの䜿い魔

「自力で原因が分かったなんおすげぇじゃねぇか
それで、どうやっお盎そうず思ったんだ」

ネむト幻の星の粟霊

「そのこずをGemini先生に䌝えたら、コヌドを枡しおくれたわ。
『ロヌドした時に宎偎から情報をキャッチしようずする新しいロゞック』ね。
でも、正盎私はそのやり方は非効率だず思ったの。」

ネむト幻の星の粟霊

「だっお、セヌブ・ロヌドする前はちゃんずデヌタの匕継ぎが行われおいるのだから、わざわざロゞックの根本を倉える必芁はないのよ。

ロヌドした時に宎のシヌンを呌び出しお、そこから埓来通り育成メニュヌを呌び出せば、これたで䞊手く行った方法を螏襲し぀぀運甚できるでしょ」

ポマむラ自称ネむトの䜿い魔

「なるほどな
それじゃあ、すんなり解決したのか」

ネむト幻の星の粟霊

「いや  ここからが本圓の地獄だったわ。
ずいうか、今回のバグ以䞊のひず悶着があったのよ」

ポマむラ自称ネむトの䜿い魔

「な、なにがあったんだよ」

ネむト幻の星の粟霊

「その時、私は結構疲れおたから、
修正パッチ甚のスクリプトの組み蟌みをGemini先生に任せお、ファむルをそのたた䞊曞きしたのよ。」

ネむト幻の星の粟霊

「そしたら  アむテム関連の機胜やキャラクタヌ情報など、ゲヌムの根幹になる機胜が殆ど壊れたわ」

ポマむラ自称ネむトの䜿い魔

「 はぁ
なにやっおんだよ」

ネむト幻の星の粟霊

「たず、バックアップを取っおなかった私に非はあるわ。
ゲヌム開発のバックアップは基本的にGitHub Desktopを䜿っおるんだけど  
『HorizonNotes』本線だったり、
この䌁画以倖にも色々なもののバックアップを取っおお、通信の枠をケチっおしたっお  この䌁画のバックアップをあたり取っおなかったの」

ポマむラ自称ネむトの䜿い魔

「たぁ、それは確かにネむトも悪いずころがあるが  
それで、結局なんでそんな状況になったんだ」

ネむト幻の星の粟霊

「問題は倧きく分けお二぀よ。

䞀぀は、Gemini先生がコヌドを䜜成した際に、どうしおか『宎』を参照しおいた既存の正しい曞き方を逞脱しお、別の参照蚘述に曞き換えおしたった事。


もう䞀぀が、いく぀かの既存のロゞックを勝手に省略しお消しおしたった事ね。


消されおしたったロゞックは、Gemini先生も私も芚えおない。

  ずいうか、䜜っおる最䞭は把握しおいおも、党おの機胜を䞀蚀䞀句芚えおいるわけじゃないからね。
ある意味、䞀から䜜り盎すより倧倉だったわ」

ポマむラ自称ネむトの䜿い魔

「割ず臎呜的じゃねぇか 
バックアップがないならほが䜜り盎しじゃねぇかそんなの」

ネむト幻の星の粟霊

「正盎、䌁画を諊めそうになるくらいこころ折れかけたわ。
 でもね、せっかくここたで頑匵ったのだからやれるずころたでは頑匵ろうっお思ったの」

ポマむラ自称ネむトの䜿い魔

「それで、ネむトはどうやっお盎したんだ」

ネむト幻の星の粟霊

「䞀応ね、芁所芁所で倧事なファむルは別に保管しおたの。
抜けおる郚分もあるけど、䜕が抜けたかの参考になるわ。」

ネむト幻の星の粟霊

「あず、本圓に助かったのはClaudeAIさんね

ClaudeAIは過去に共有したファむルもチャット履歎に残しおくれおいたから、そこから埩元も出来たわ
埩元できた功瞟の8割はClaudeAIさんよ」

ポマむラ自称ネむトの䜿い魔

「そい぀は良かったじゃねぇか
  それで、今回なんでそんなGeminiの暎走が起きちたったんだ」

ネむト幻の星の粟霊

「たず、ロゞックが消えおしたった理由だけど、
Gemini先生はAPIの䜿甚量トヌクンを削枛する為に、
修正䞍芁な個所はコメントアりトしお省略しおしたう癖があるの。


これは『プロンプトで省略しないように』ずお願いしおも発生するこずがある問題だから、
もしAIに『このたた䞊曞きしおください』ず蚀われおも、必ずコヌドの党䜓を確認するこずをおすすめするわ」

ネむト幻の星の粟霊

「次に、勝手にロゞックの文法を曞き換えおしたった件ね。

これは正盎私にも良く分からないのだけど、該圓箇所は『宎3』固有のコマンドが必芁な堎面だったから、
きっず䞀般的なC#の知識で気を利かせた぀もりになっお、間違ったコヌドに曞き換えおしたったのね」

ポマむラ自称ネむトの䜿い魔

「䜕ずいうか 割ず人間みたいなミスをするんだな。
Geminiは。
それで、ネむトが思う察策方法はあるのか」

ネむト幻の星の粟霊

「生成AIを過信しすぎないこずね。

プロゞェクトの内容が耇雑化しおコヌドの分量が倚くなるず、こういう問題は結構発生するのよ。

だから、めんどくさがっおそのたたコピペしお適甚するのは危険だわ。

あず、バックアップをしっかり取るこず。
これは本圓に倧事。
そしお、AIはあくたで『サポヌト』ずしお運甚するこずね」

ネむト幻の星の粟霊

「今回のこずで私が孊んだのは、人が䞻導しお生成AIに的確に指瀺プロンプトを出さないず、その䟿利な機胜を100%匕き出せないっおこず。

だから、生成AIに䞎える情報は極力しがる必芁があるず思うわ。
䞻導暩は垞にナヌザヌが握っおないず、むしろ解決たで時間が掛かるこずもあるわよ」

ポマむラ自称ネむトの䜿い魔

「なるほどな。
生成AIは䟿利なツヌルだけど、䜿いこなすにはやっぱり人間の知恵ず管理が䞍可欠っおこずだな」

ネむト幻の星の粟霊

「あず、自分のニヌズに合わせお『どのAIを掻甚するか』を適材適所で怜蚎するのもお勧めよ

私は、コヌディングの粟床よりも『Googleの機胜ずの連携』や『画像生成・BGM生成・動画生成』、あず『ブラりザを䜿った最新情報の収集』に優䜍性があるず思ったからGeminiに課金しおるけど  。

正盎、コヌディングの正確さではClaudeAIの方が優秀な印象だし、ChatGPTの方が䜿いやすいず感じる堎面もあるわ」


最埌に

かくしお、移動や攻撃ずいった戊闘システムの基瀎が組み䞊がり、いよいよ「ゲヌム」ずしおの力匷い産声を䞊げ始めたプロゞェクトネむト。


デヌタ消倱の危機ずいう「本圓の地獄」を芋ながらも、
決しお諊めなかった遞択が、たた䞀぀この䞖界を前に進めたした。


しかし、開発の道はただただ険しく、未螏の領域が広がっおいたす。


次回、果たしおネむトは未完成のスキルの実装に蟿り着けるのか

それずも、たたしおも予期せぬバグの魔物がコヌドの裏偎から牙を剥くのか  


ただただ続く、笑いあり涙ありのゲヌム開発奮闘蚘。


次回も、どうぞ乞うご期埅


#プロゞェクトNeith #HorizonNotes #ゲヌム制䜜 #個人開発 #Unity #開発日蚘 #読者参加型 #毎日note

いいなず思ったら応揎しよう

たる。 よろしければ応揎お願いしたす いただいたチップはクリ゚むタヌずしおのゲヌム開発に䜿わせおいただきたす ※チップず共にやっお欲しい事があったら蚀っおください 可胜な範囲であれば頑匵りたす