Artifact và chỉ số
Assets/
Scripts/
Data/
đã có sẵnbài này thêm
Knight đã có ba skill nhưng chỉ số vẫn y như lúc đầu. Artifact là món đồ bạn đeo vào để mấy con số đó tăng lên.
Crown of Ember: +34 sát thương. Đeo vào, sát thương từ 40 lên 74. Tháo ra, về lại 40. Cả tính năng chỉ có vậy.
Cái bẫy, nói trước khi viết code
Cách viết hiển nhiên nhất là cộng thẳng vào attack của nhân vật. Lần đầu chạy đúng, và từ đó về sau hỏng âm thầm.
✖ Add and subtract
Cộng vào rồi trừ ra
attack += 34, tháo thì attack -= 34. Sót một lần trừ — do đổi đồ, do chết, do load lại cảnh — là Knight mạnh lên vĩnh viễn.✓ Never touch the base
Không đụng vào chỉ số gốc
attack = 40 giữ nguyên mãi mãi. Mỗi lần thay đồ thì vứt tổng cũ đi và cộng lại từ đầu.Nhìn đường nét đứt: phần gốc của thanh không bao giờ dịch chuyển. Chỉ phần màu vàng hiện ra rồi biến mất.
Mỗi chỉ số có hai con số
Vậy CharacterStats cần thêm một bộ trường nữa. Bộ gốc bạn gõ trong Inspector rồi không đổi nữa; bộ cuối do game tự tính lại và đó mới là bộ được dùng khi chơi.
[Header("bạn tự đặt, không ai được sửa")]
public float attack = 40f;
public float defense = 30f;
public float moveSpeed = 5f;
public float maxHealth = 200f;
[Header("tính lại tự động, khi chơi dùng bộ này")]
public float finalAttack;
public float finalDefense;
public float finalSpeed;
public float finalMaxHp;Artifact chỉ là một danh sách phần cộng thêm
Một asset, một danh sách. Crown of Ember chứa ba dòng: +34 sát thương, +18 giáp, +12 tốc độ. Trong file không có gì biết ai sẽ đeo nó.
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; //cộng theo phần trăm
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; //bao nhiêu dòng cũng được
public StatBonus[] bonuses;
}[System.Serializable] trên struct là thứ làm các dòng cộng thêm sửa được trong Inspector. Thiếu nó thì mảng hiện ra rỗng.
Ô trang bị là lý do bạn không đội được hai cái vương miện. Mỗi ô một món, bốn ô, luật túi đồ chỉ có vậy.
Cộng thẳng và cộng phần trăm để riêng là có chủ ý — phần sau giải thích vì sao thứ tự quan trọng.
Tính lại: vứt tổng cũ, cộng lại từ đầu
Đây là hàm duy nhất được phép ghi vào các trường final, và nó luôn bắt đầu lại từ chỉ số gốc. Không có gì cộng dồn, nên không có gì lệch được.
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; // UI lắng nghe
public void Equip(ArtifactData a)
{
worn[a.slot] = a; // thay món cũ trong ô đó
Recalculate();
}
public void Unequip(ArtifactSlot slot)
{
worn.Remove(slot);
Recalculate();
}
void Recalculate()
{
//mỗi chỉ số một ô đựng
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;
}
//gốc + cộng thẳng, RỒI mới nhân phần trăm
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);
}Vì sao cộng thẳng trước, phần trăm sau
Với gốc 40, một món +34 cộng thẳng và một món +15%, hai thứ tự cho hai kết quả khác nhau — và chỉ một trong hai khớp với con số ghi trên tooltip.
//đúngLàm thử
- 1
Tạo hai artifact qua : Crown of Ember (+34 Attack cộng thẳng) và Ring of Haste (+15% Attack, +12 MoveSpeed cộng thẳng).
- 2
Gắn Equipment vào Knight và kéo CharacterStats của nó vào ô tham chiếu.
- 3
Gán phím 7 và 8 cho Equip và Unequip để bật tắt ngay trong lúc chơi.
- 4
Bấm Play, nhìn finalAttack trong Inspector: 40 → 74 → 85.1, rồi tụt về đúng 40.
Phép thử thật sự chứng minh nó đúng
Đeo vào tháo ra cả hai artifact hai mươi lần liên tiếp. Nếu finalAttack không về đúng 40, tức là có chỗ nào đó đang ghi vào chỉ số gốc — đi tìm cho ra.
[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
? "không bị lệch"
: $"DRIFT! {before} → {stats.finalAttack}");
}