5.6Numerical component design.md 8.0 KB

Similar to world of warcraft, moba such skills are extremely complex, flexibility requires a very high skill system, must need a set of its flexible numerical structure to match. Numerical structure is well designed, the realization of the skill system will be very simple, otherwise it is a disaster. For example, in World of Warcraft, a character has many numerical attributes, such as movement speed, strength, anger, energy, concentration value, magic value, blood, maximum blood, physical attack, physical defense, spell attack, spell defense, etc. There are dozens of attributes. Attributes and attributes affect each other, buffs will add absolute value to attributes, increase the percentage, or some kind of buff will come back to you after counting all the increased value and doubling it.

Common practice:

The general is to write a value class.

class Numeric
{
    public int Hp;
    public int MaxHp;
    public int Speed;
    // Energy
    public int Energy;
    public int MaxEnergy;
    // Magic
    public int Mp;
    public int MaxMp;
    .....
}

On second thought, I'm a thief using energy why should I have a value of Mp? I am a mage using magic why should there be a field for energy? I'm not sure what to do with this, just pretend you didn't see it? I can not, I come to an inheritance?

// Mage values
calss MageNumeric: Numeric
{
    // magic
    public int Mp;
    public int MaxMp;
}

// Thief value
calss RougeNumeric: Numeric
{
    // Energy
    public int Energy;
    public int MaxEnergy;
}
````
10 races, each race 7, 8 kinds of heroes, just these values class inheritance relationship, you have to be confused it. Object-oriented is difficult to adapt to the needs of this flexible and complex.

And look at the Numeric class, each value can not just design a field, for example, I have a buff will increase 10 points Speed, and a kind of buff to increase 50% of the speed, then I must add at least three secondary attribute fields
```c#
class Numeric
{
    // speed final value
    public int Speed;
    // Speed initial value
    public int SpeedInit;
    // Speed increase value
    public int SpeedAdd;
    // Speed increase percentage value
    public int SpeedPct;
}

After SpeedAdd and SpeedPct are changed, a calculation is performed to calculate the final speed value. buff only needs to go to modify SpeedAdd and SpeedPct on the line.

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

Each property may have several indirect effects on the value, you can think about how large this class is, a rough estimate of more than 100 fields. The trouble is that the formula is basically the same, but just can not be unified into a function, such as MaxHp, also has a buff effect

class Numeric
{
    public int Speed;
    public int SpeedInit;
    public int SpeedAdd;
    public int SpeedPct;
    
    public int MaxHp;
    public int MaxHpInit;
    public int MaxHpAdd;
    public int MaxHpPct;
}

Also have to write a formula for calculating Hp

MaxHp = (MaxHpInit + MaxHpAdd) * (100 + MaxHpPct) / 100

Dozens of properties, you have to write dozens of times, and each secondary property changes to correctly call the corresponding formula calculation. Very troublesome! This design also has a big problem, buff configuration table to fill the corresponding attribute field is not very good to fill, for example, sprint buff (increase speed 50%), how to configure the buff table to make the program simple to find and operate the SpeedPct field? Not a good idea.

ET framework uses the Key Value form to save the value of the property

Using System.Collections.Generic;

Generic; namespace Model
Generic; namespace Model {
    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,
	}

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

		public void Awake()
		{
			// initialize base value here
		}

		public float GetAsFloat(NumericType numericType)
		{
			return (float)GetByKey((int)numericType) / 10000;
		}

		public int GetAsInt(NumericType numericType)
		{
			return GetByKey((int)numericType);
		}

		public void Set(NumericType nt, float value)
		{
			this[nt] = (int) (value * 10000);
		}

		public void Set(NumericType nt, int value)
		{
			this[nt] = value;
		}

		public int this[NumericType numericType]
		{
			get
			{
				return this.GetByKey((int) numericType);
			}
			set
			{
				int v = this.GetByKey((int) numericType);
				if (v == value)
				{
					return;
				}

				NumericDic[(int)numericType] = value;

				Update(numericType);
			}
		}

		private int GetByKey(int key)
		{
			int value = 0;
			This.NumericDic.TryGetValue(key, out value);
			return value;
		}

		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;

			// A value may be affected by a variety of circumstances, such as speed, adding a buff may increase the speed of the absolute value of 100, but also some buffs increase the speed of 10%, so a value can be controlled by 5 values of the final result
			// final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100;
			this.NumericDic[final] = ((this.GetByKey(base) + 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);
		}
	}
}
  1. values are saved with key value, key is the type of value, defined by NumericType, value are integers, float type can also be converted to integers, for example, multiply by 1000; key value to save properties will become very flexible, for example, mage no energy properties, then initialize the mage object does not add energy key value It's fine. Thieves do not have mana, no spell damage, etc., the initialization will not need to add these.

  2. world of warcraft, a value by 5 values to influence, you can unify the use of a formula.

    final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100;
    

    For example, the speed value speed, there is an initial value speedbase, there is a buff1 to increase the absolute speed by 10 points, then buff1 will add 10 to speedadd when it is created, buff1 minus 10 to speedadd when it is deleted, buff2 increases the speed by 20%, then buff2 adds to speedpct when it is created The 5 values are changed and the corresponding properties can be recalculated by using the Update function in a unified way. buff configuration is quite simple. If the corresponding NumericType is filled in the buff configuration, the program can easily manipulate the corresponding value.

  3. Changes in properties can be uniformly thrown to other modules to subscribe to the event, writing a property change monitor becomes very simple. For example, the achievement module needs to develop an achievement life value over 1000, will get the achievement of longevity master. Then the person developing the achievement module will subscribe to the HP changes as follows.

    	/// Monitor hp value changes
    	[NumericWatcher(NumericType.Hp)
    	public class NumericWatcher_Hp : INumericWatcher
    	{
    		public void Run(long id, int value)
    		{
    		    if (value > 1000)
    		    {
    		        // get achievement longevity master achievement
    		    }
    		}
    	}
    

    Similarly, recording an exception log for a gold change greater than 10,000 at a time, etc. can be done this way.

With this numerical component, a moba skill system can be said to be half complete.