Skip to content

Numeric Component

Note

The content of this document about the numeric component mostly comes from the numeric component section of ET Book.

Introduction

The numeric component is an important functional component in the ET framework, providing a complete numeric value management solution. The main features include:

  • Basic arithmetic operations for numeric values
  • Event listening and distribution for numeric value changes

Design Approach

Note

In complex game systems (such as MMORPG, MOBA), skill systems need a flexible numeric structure for support. A character may include multiple attributes: movement speed, strength, rage, energy, focus, magic, health, etc. These attributes interact with each other and can be affected by buffs (absolute value increases, percentage increases, etc.).

Traditional Solutions and Limitations

csharp
class Numeric
{
    public int Hp;
    public int MaxHp;
    public int Speed;
    public int Energy;
    public int MaxEnergy;
    public int Mp;
    public int MaxMp;
}

This design has the following problems:

  1. Different classes may use different attribute systems (mages use magic value, rogues use energy value)
  2. Inheritance schemes lead to complex class hierarchies
  3. Attribute calculations need to consider multiple influencing factors

For example, speed calculation needs to consider:

csharp
class Numeric
{
    public int Speed;        // Final speed value
    public int SpeedInit;    // Initial speed value
    public int SpeedAdd;     // Speed increase value
    public int SpeedPct;     // Speed percentage increase value
}

Calculation formula:

Speed = (SpeedInit + SpeedAdd) * (100 + SpeedPct) / 100

The main problems with this approach:

  1. Bulky class structure
  2. Repeated calculation formulas
  3. Inflexible attribute configuration

ET Framework Solution

The ET framework uses a Key-Value form to store numeric attributes:

csharp
public enum NumericType
{
    Max = 10000,

    Speed = 1000,
    SpeedBase = Speed * 10 + 1,
    SpeedAdd = Speed * 10 + 2,
    SpeedPct = Speed * 10 + 3,
    SpeedFinalAdd = Speed * 10 + 4,
    SpeedFinalPct = Speed * 10 + 5,

    Hp = 1001,
    HpBase = Hp * 10 + 1,

    MaxHp = 1002,
    MaxHpBase = MaxHp * 10 + 1,
    MaxHpAdd = MaxHp * 10 + 2,
    MaxHpPct = MaxHp * 10 + 3,
    MaxHpFinalAdd = MaxHp * 10 + 4,
    MaxHpFinalPct = MaxHp * 10 + 5,
}

Core implementation:

csharp
public class NumericComponent: Component
{
    public readonly Dictionary<int, int> NumericDic = new Dictionary<int, int>();

    public void Update(NumericType numericType)
    {
        if (numericType > NumericType.Max)
        {
            return;
        }
        int final = (int) numericType / 10;
        int bas = final * 10 + 1; 
        int add = final * 10 + 2;
        int pct = final * 10 + 3;
        int finalAdd = final * 10 + 4;
        int finalPct = final * 10 + 5;

        // final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100;
        this.NumericDic[final] = ((this.GetByKey(bas) + this.GetByKey(add)) * (100 + this.GetByKey(pct)) / 100 + this.GetByKey(finalAdd)) * (100 + this.GetByKey(finalPct)) / 100;
        Game.EventSystem.Run(EventIdType.NumbericChange, this.Entity.Id, numericType, final);
    }
}

Advantages:

  1. Flexible attribute management: Different classes can have different attributes
  2. Unified calculation formula: All attributes use the same calculation logic
  3. Event system support: Attribute changes can trigger corresponding events

Practical Application Example

Monitor HP value changes:

csharp
namespace MH
{
    [NumericWatcher(SceneType.Main, NumericType.Hp)]
    public class NumericWatcher_HpChange : INumericWatcher
    {
        public void Run(Unit unit, NumbericChange args)
        {
            if (unit == null || unit.IsDisposed)
                return;
            var numericComponent = unit.GetComponent<NumericComponent>();
            var currentHp = numericComponent[NumericType.Hp];
            var maxHp = numericComponent[NumericType.MaxHp];
            if (currentHp > maxHp)
                numericComponent.SetNoEvent(NumericType.Hp, maxHp);
            if (currentHp < 0)
                numericComponent.SetNoEvent(NumericType.Hp, 0);
            var hpBarComponent = unit.GetComponent<HpBarComponent>();
            hpBarComponent.SetHpSliderHandler(unit);
        }
    }
}

Technical Support

Get Help

  • 💬 Join QQ group for discussion (ET Framework Group) : 474643097
  • ⭐ Follow the project on GitHub for the latest updates

Released under the MIT License.