2
\$\begingroup\$

I want to create a function for a game stat of attack speed, but I cannot figure out how to write a math function for this.

My formula should work in a way, if attack speed is 200%, it does 4 hits per second., 100% does 2 hits per second, 1000% does 10 hits per second etc..

These are the expected results:

 speed       attack_speed
  100000%  = 1000
  10000%   = 100
  2000%    = 20
  1000%    = 10
  200%     = 4
  100%     = 2
  50%      = 1.5
  10%      = 1.1
  1%       = 1.01
  0%       = 1
 -1%       = 0.99
 -10%      = 0.9
 -50%      = 0.75
 -100%     = 0.5
 -200%     = 0.25
 -1000%    = 0.1
 -2000%    = 0.05
 -10000%   = 0.01
 -100000%  = 0.001

For example, If I give a speed value of -100%, it should return an attack_speed of 0.5

I was using the formula att_speed = 1 / (1 + speed / 100), but it didn't return correct results.

\$\endgroup\$
2
  • \$\begingroup\$ i use base attack speed set at 1, various bonuses increase or decrease this factor by various percentages so for example 1 attack speed for me equals default animation speed for all types of attacks... but 2 attack spead means all animations are twice as fast... easier to calculate since the formula is literally : attacks per second * attack speed factor, most games infact use this formula, league of legends for example \$\endgroup\$
    – Cei
    Jul 13, 2023 at 20:12
  • 1
    \$\begingroup\$ @Basic try plotting the values. The graph bends, so no single affine function (y = mx + b) will fit the whole range. \$\endgroup\$
    – DMGregory
    Jul 14, 2023 at 19:15

2 Answers 2

4
\$\begingroup\$

The target values you show in the table don't follow one consistent rule, so the best we can do is a piecewise approximation, using different formulas for different ranges.

I've highlighted in blue the ranges where each constituent formula fits well. The two rightmost sets show different ways of combining these parts to get a formula for the whole range.

Spreadsheet calculations showing constituent formulas and outputs

  • For high speed values (>= 10), attack_speed is equal to speed

    (taking 100% = 1.0 as is standard convention - if your code stores this as 100 instead, then divide it by 100 first for all formulas in this answer)

  • For values close to zero, attack_speed is equal to speed + 1

  • For low values, the relationship is approximately hyperbolic. There's no hyperbola that matches the entire range, but for simplicity, 1 / (1.0 - speed) is a close approximation.

(I've included the results of Bogdan's answer, which is identical to the formula I show below for speed <= 200%, but retains some small error for higher speed values)

We can glue these three ranges together into a single formula as in the following pseudo-code:

float get_attacks_per_second(float speed) {

    if (speed >= 0) {
        // Linear segments:

        // Smoothly ramp bias down from 1.0 for low speeds (<= 2)
        // to 0.0 for high speeds (>= 10).
        float bias = 1.25f - speed / 8.0f;

        // Clamp the bias between zero and one.
        bias = clamp(bias, 0, 1);

        return speed + bias;
    } else {
        // Hyperbolic segment:
        return 1.0f / (1.0f - speed);
    }
}

You can see this gives quite a good match across the whole range, with the exception of the point (2, 4) where the formula gives an output value of 3 instead. Looking at the graph though, it looks like that point at speed = 200% breaks the trend of the points on either side of it, and may be an outlier or error. We'd have to put a pretty severe kink in the curve to pass through that point.

Graph comparing formula outputs to targets

\$\endgroup\$
1
  • \$\begingroup\$ Thanks, thats a nice explanation! \$\endgroup\$
    – mansim
    Jul 15, 2023 at 14:03
0
\$\begingroup\$
 void Update()
    {
        Debug.Log($"100000 = {GetAttcksCount(100000)}");
        Debug.Log($"10000 = {GetAttcksCount(10000)}");
        Debug.Log($"2000 = {GetAttcksCount(2000)}");
        Debug.Log($"1000 = {GetAttcksCount(1000)}");
        Debug.Log($"200 = {GetAttcksCount(200)}");
        Debug.Log($"100 = {GetAttcksCount(100)}");
        Debug.Log($"50 = {GetAttcksCount(50)}");
        Debug.Log($"10 = {GetAttcksCount(10)}");
        Debug.Log($"1 = {GetAttcksCount(1)}");
        Debug.Log($"0 = {GetAttcksCount(0)}");
        Debug.Log($"-1 = {GetAttcksCount(-1)}");
        Debug.Log($"-10 = {GetAttcksCount(-10)}");
        Debug.Log($"-100 = {GetAttcksCount(-100)}");
        Debug.Log($"-200 = {GetAttcksCount(-200)}");
        Debug.Log($"-1000 = {GetAttcksCount(-1000)}");
        Debug.Log($"2000 = {GetAttcksCount(-2000)}");
        Debug.Log($"10000 = {GetAttcksCount(-10000)}");
        Debug.Log($"100000 = {GetAttcksCount(-100000)}");
    }
    float GetAttcksCount(int attackSpeed)
    {
        if (attackSpeed >= 0)
        {
            return 1f + 0.01f * attackSpeed;
        }
        else
        {
            return 1/GetAttcksCount(Mathf.Abs(attackSpeed));
        }
    }

enter image description here

\$\endgroup\$
1
  • \$\begingroup\$ The expected results is almost similar to your ones. But your data a little bit wrong so 200=3 and there is plus 1 in some cases but I think it is not a problem for your task. \$\endgroup\$
    – Bogdan
    Jul 15, 2023 at 21:45

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .