アーティファクトとステータス
Assets/
Scripts/
Data/
すでにあるものこのレッスンで追加
ナイトはスキルを三つ持っていますが、ステータスは開始時のままです。アーティファクトは、その数値を大きくするために装備するアイテムです。
Crown of Ember:攻撃 +34。装備すると攻撃力が 40 から 74 に上がり、外すと 40 に戻ります。機能はそれだけです。
コードを書く前に知っておくべき罠
一番素直な書き方は、キャラクターの attack に直接足すことです。最初は動きますが、その後は静かに壊れ続けます。
✖ Add and subtract
attack += 34、解除時に attack -= 34。引き算を一回でも忘れる—— 装備の入れ替え、死亡、シーンの読み込み——それでナイトは永遠に強くなります。✓ Never touch the base
attack = 40 を永遠に変更しないでおきます。装備が変わるたびに 合計を捨て、最初からすべて計算し直します。点線を見てください:バーのベース部分は絶対に動きません。現れたり消えたりするのは琥珀色の部分だけです。
ステータスごとに数値を二つ持つ
そこで CharacterStats にフィールドをもう一セット追加します。ベース側は Inspector に入力したら二度と変えず、final 側はゲームが計算し直し、実際に使われるものです。
[Header("Base — you set these, nothing else may")]
public float attack = 40f;
public float defense = 30f;
public float moveSpeed = 5f;
public float maxHealth = 200f;
[Header("Final — recalculated, use these in gameplay")]
public float finalAttack;
public float finalDefense;
public float finalSpeed;
public float finalMaxHp;アーティファクトは単なるボーナスのリスト
アセット 1 つにつきリスト 1 つ。Crown of Ember は 3 行を持ちます:攻撃 +34、防御 +18、速度 +12。ファイルの中に、それを誰が装備するかを知る情報は一切ありません。
using UnityEngine;
public enum StatType { Attack, Defense, MoveSpeed, MaxHealth }
public enum ArtifactSlot { Crown, Chest, Boots, Ring }
[System.Serializable]
public struct StatBonus
{
public StatType stat;
public float flat; // +34
public float percent;
}
[CreateAssetMenu(fileName = "Artifact_", menuName = "Game/Artifact")]
public class ArtifactData : ScriptableObject
{
public string displayName = "Crown of Ember";
public Sprite icon;
[Range(1, 5)] public int rarity = 4;
public ArtifactSlot slot = ArtifactSlot.Crown; // one item per slot
public StatBonus[] bonuses;
}struct に付けた [System.Serializable] があるからこそ、ボーナス行を Inspector で編集できます。これが無いと配列は空で表示されます。
スロットがあるから王冠を二つ同時に装備できません。スロットごとにアーティファクト 1 つ、スロットは 4 つ——これがインベントリのルール全部です。
flat と percent をあえて分けています——順序がなぜ重要かは次のセクションで説明します。
再計算:捨てて、もう一度数え上げる
final フィールドへ書き込むのはこのメソッドだけで、毎回必ずベースの値から計算します。積み上がりがないので、ズレることもありません。
using System.Collections.Generic;
using UnityEngine;
public class Equipment : MonoBehaviour
{
[SerializeField] private CharacterStats stats;
private readonly Dictionary<ArtifactSlot, ArtifactData> worn = new();
public System.Action OnChanged; // the UI listens
public void Equip(ArtifactData a)
{
worn[a.slot] = a; // replaces whatever was in that slot
Recalculate();
}
public void Unequip(ArtifactSlot slot)
{
worn.Remove(slot);
Recalculate();
}
void Recalculate()
{
// one bucket per stat
int n = System.Enum.GetValues(typeof(StatType)).Length;
float[] flat = new float[n];
float[] pct = new float[n];
foreach (ArtifactData a in worn.Values)
foreach (StatBonus b in a.bonuses)
{
flat[(int)b.stat] += b.flat;
pct[(int)b.stat] += b.percent;
}
// base + all the flat, THEN multiply by the percent
stats.finalAttack = Apply(stats.attack, flat, pct, StatType.Attack);
stats.finalDefense = Apply(stats.defense, flat, pct, StatType.Defense);
stats.finalSpeed = Apply(stats.moveSpeed, flat, pct, StatType.MoveSpeed);
stats.finalMaxHp = Apply(stats.maxHealth, flat, pct, StatType.MaxHealth);
OnChanged?.Invoke();
}
static float Apply(float baseValue, float[] flat, float[] pct, StatType s)
=> (baseValue + flat[(int)s]) * (1f + pct[(int)s] / 100f);
}なぜ flat が先で percent が後か
ベース 40 に flat +34 と percent +15% を足すとき、順序が違えば答えも変わります——そしてツールチップが約束した数値と一致するのはそのどちらか一方だけです。
// flat first
// percent first試してみよう
- 1
アーティファクトを で二つ作成します:Crown of Ember(+34 flat Attack)と Ring of Haste(+15% Attack、+12 flat MoveSpeed)。
- 2
ナイトに Equipment を追加し、その CharacterStats をスロットへドラッグします。
- 3
キー 7 と 8 を Equip と Unequip に割り当て、プレイ中に装備の着脱を切り替えられるようにします。
- 4
Play を押し、Inspector の finalAttack を見てください:40 → 74 → 85.1、そしてぴたりと 40 に戻ります。
本当に動作を証明できるテスト
二つのアーティファクトの装備と解除を 20 回連続で繰り返します。finalAttack がぴったり 40 に戻らないなら、どこかがベースの値に書き込んでいる証拠です——見つけてください。
[ContextMenu("Stress test equip")]
void StressTest()
{
float before = stats.finalAttack;
for (int i = 0; i < 20; i++)
{
Equip(crown); Equip(ring);
Unequip(ArtifactSlot.Crown); Unequip(ArtifactSlot.Ring);
}
Debug.Log(before == stats.finalAttack
? "OK — no drift"
: $"DRIFT! {before} → {stats.finalAttack}");
}