U
tab 02Nhân vật & Chiến đấu9 bài — chạm để mở
06 / 09 · 12 phút
Nhân vật & Chiến đấu

Artifact và chỉ số

dự án của bạn tới lúc này

Assets/

Scripts/

CharacterStats.csSkillCaster.csArtifactData.csEquipment.csfinal* stats

Data/

Artifact_ assets

đã 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

Đeo thì 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.
sơ đồ động

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.

add to CharacterStats.cs
[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ó.

ArtifactData.cs
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.

Equipment.cs — put it on the Knight
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.

C# script
//đúng

Làm thử

  1. 1

    Tạo hai artifact qua Assets▸Create▸Game▸Artifact: Crown of Ember (+34 Attack cộng thẳng) và Ring of Haste (+15% Attack, +12 MoveSpeed cộng thẳng).

  2. 2

    Gắn Equipment vào Knight và kéo CharacterStats của nó vào ô tham chiếu.

  3. 3

    Gán phím 7 và 8 cho Equip và Unequip để bật tắt ngay trong lúc chơi.

  4. 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.

đoạn thử, xong thì xoá
[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}");
}