装备与属性
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 里填好后就不再改;最终那套由游戏每次重算,也是实际用上的。
[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;神器不过是一串加成列表
一个 asset,一个列表。Crown of Ember 里有三行:攻击 +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 里编辑。没有它,数组显示为空。
槽位决定了你戴不了两顶王冠。每槽一件、共四个槽 —— 背包规则就这么多。
故意把 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,一件 +34 flat、一件 +15%,两种顺序给出两个结果 —— 对得上 tooltip 承诺数字的只有一个。
// flat first
// percent first动手试试
- 1
通过 创建两个 artifact:Crown of Ember(+34 flat Attack)和 Ring of Haste(+15% Attack,+12 flat MoveSpeed)。
- 2
给 Knight 挂上 Equipment,并把它的 CharacterStats 拖进引用槽。
- 3
把按键 7 和 8 绑到 Equip 和 Unequip,游戏运行时就能随时开关。
- 4
按 Play,盯着 Inspector 里的 finalAttack:40 → 74 → 85.1,然后一分不差地退回 40。
真正能证明它对的测试
两个 artifact 连续戴上摘下二十次。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}");
}