【随笔】持续伤害的设计模式思考(draft)

README

关于一个伤害数值的计算模拟。

灵感与思考

  • 破败王者之剑的伤害是当前生命值的 y%
  • 在看 som 关于 Path of Achra 这个游戏的录播的时候,让我对持续伤害有了想法。
    • 我其实想到了很多伤害模式:
      • 比如叠毒层数,每秒造成层数的伤害,然后减少一层。但是对于实时战斗游戏来说,太慢了,能不能每过一段时间一次性爆炸性减少一定百分比,并且消耗的层数都累加求和算上。不过这就是另一种数值的构思方法了,我的想法就是更爽一点。
      • 同样的持续性伤害,我觉得有很多可以考虑的方法。
  • 假如设计一种持续伤害,它的伤害是每秒造成当前生命值的 y%,会有什么问题?
  • 我马上就想到了,持续伤害如果一秒一跳,视觉观感太不爽了。
  • 所以我思考了一个数学问题,能不能用一秒 12 帧的频率剩余生命值百分比上海,来模拟一秒一跳的伤害?

数学部分

如何调整每帧的伤害比例 x ,使得在一秒 12 帧的频率下,总的伤害比例还是与每秒造成当前生命值 y% 的持续伤害相同?

形式化地,我们可以使用以下等式来描述这个问题:

( 1 − x ) n = 1 − y (1 - x)^n = 1 - y (1x)n=1y

其中,x 是每帧的伤害比例,n 是每秒的帧数(在这个情况下是 12),y 是每秒的伤害比例。已知 n 和 y,目标是求解 x 的值。

给定等式为:

( 1 − x ) n = 1 − y (1 - x)^n = 1 - y (1x)n=1y

假设 y y y n n n 是已知的值。我们需要解出 x x x 的值。

步骤 1: 将等式两边都取 n n n 次根:

1 − x = ( 1 − y ) 1 / n 1 - x = (1 - y)^{1/n} 1x=(1y)1/n

步骤 2: 重新整理式子,使得等式右侧只有 x x x的项:

− x = ( 1 − y ) 1 / n − 1 - x = (1 - y)^{1/n} - 1 x=(1y)1/n1

步骤 3: x x x移到左边:

x = 1 − ( 1 − y ) 1 / n x = 1 - (1 - y)^{1/n} x=1(1y)1/n

以上就是求解 x x x 的步骤,希望对你有所帮助!

下面是用 Rust 实现的函数:

fn calculate_x(y: f64, n: f64) -> Result<f64, &'static str> {
    if n == 0.0 {
        Err("n can't be zero")
    } else {
        let result = 1.0 - (1.0 - y).powf(1.0 / n);
        Ok(result)
    }
}

这个函数接受两个参数:yn,都是浮点数类型。首先,我们检查 n 是否等于 0,因为在 n n n 等于 0 的情况下,我们不能进行次方运算。如果它是 0,我们就返回一个错误。

然后我们直接使用提供的公式计算 x x x 的值,并将结果返回。powf 函数用于计算一个数的幂。

这样做有什么意义吗?

本质上,这样的做法和每个周期伤害一次其实没什么原理上的区别,设计的目的是为了更方便读者看到每秒的伤害而不是每帧的伤害。因为开根以后每帧的百分比确实会很低,读者也不太可能轻易地心算浮点数幂。

在视觉表现上也可以用伤害数值累加器的形式,增加伤害数值,然后每秒显示一次。

代码部分

fn calculate_x(y: f64, n: f64) -> Result<f64, &'static str> {
    // 确保 y 在范围 (0, 1.0) 内
    if !(0.0..1.0).contains(&y) {
        Err("y 应在范围 (0, 1.0) 内")
    }
    // 确保 n 在范围 [2, 24] 内
    else if !(2.0..=24.0).contains(&n) {
        Err("n 应在范围 [2, 24] 内")
    } else {
        let result = 1.0 - (1.0 - y).powf(1.0 / n);
        Ok(result)
    }
}

fn simulate_damage(dmg_rate_per_sec: f64, frames: usize, initial_hp: f64) {
    // 根据每秒造成的伤害率、总帧数和初始生命值模拟伤害效果

    // 计算每帧伤害比例 x:
    let dmg_rage_per_frame = match calculate_x(dmg_rate_per_sec, frames as f64) {
        Ok(val) => val,
        Err(_) => unreachable!(),
    };

    // 打印计算得到的每帧伤害比例 x 的值,保留两位小数的百分比%
    println!("每帧伤害比例: {:.2}%", dmg_rage_per_frame * 100.0);

    let mut hp = initial_hp;

    for _ in 0..frames {
        // 计算当前帧的伤害
        let dmg = hp * dmg_rage_per_frame;

        // 计算剩余生命值
        let hp_left = hp - dmg;

        // 打印当前帧的伤害和剩余生命值
        println!("伤害: {:.2}, 剩余生命值: {:.2}", dmg, hp_left);

        hp = hp_left;
    }
}

fn main() {
    let dmg_rate_per_sec = 0.30; // 每秒造成当前生命值 * dmg_rate_per_sec 的伤害
    let frames = 12; // 总共的帧数为 12
    let initial_hp = 1000.0; // 初始生命值为 1000

    simulate_damage(dmg_rate_per_sec, frames, initial_hp);
}

写完代码后,可以看出当前计算持续伤害的方法存在一些误差。首先,我们假设在伤害过程中生命值不会发生变化,这是一个不准确的假设。其次,我们还没有考虑到浮点数运算带来的舍入误差。

为了更准确地描述这样的持续伤害效果,我认为我们应该使用更恰当的语言表达:

每秒造成 {y} % 剩余生命值的伤害(分为每秒 n 次 {x} % 剩余生命值的伤害)。

保底伤害

在上面的实现中可以看出,残血的时候伤害会很地,所以肯定要设计一个最低伤害的数值。首先要有一个每秒最低伤害传入,然后计算出每帧最低伤害:

fn calculate_x(y: f64, n: f64) -> Result<f64, &'static str> {
    if !(0.0..1.0).contains(&y) {
        Err("y 应在范围 (0, 1.0) 内")
    } else if !(2.0..=24.0).contains(&n) {
        Err("n 应在范围 [2, 24] 内")
    } else {
        let result = 1.0 - (1.0 - y).powf(1.0 / n);
        Ok(result)
    }
}

fn simulate_damage(dmg_rate_per_sec: f64, frames: usize, initial_hp: f64, min_dmg_per_sec: f64) {
    let dmg_rate_per_frame = match calculate_x(dmg_rate_per_sec, frames as f64) {
        Ok(val) => val,
        Err(_) => unreachable!(),
    };

    let min_dmg_per_frame = min_dmg_per_sec / frames as f64;

    println!("每帧伤害比例: {:.2}%", dmg_rate_per_frame * 100.0);

    let mut hp = initial_hp;

    for _ in 0..frames {
        let dmg = (hp * dmg_rate_per_frame).max(min_dmg_per_frame);
        let hp_left = hp - dmg;

        println!("伤害: {:.2}, 剩余生命值: {:.2}", dmg, hp_left);

        hp = hp_left;
    }
}

fn main() {
    let dmg_rate_per_sec = 0.30;
    let frames = 12;
    let initial_hp = 1800.0;
    let min_dmg_per_sec = 300.0;

    simulate_damage(dmg_rate_per_sec, frames, initial_hp, min_dmg_per_sec);
}

抽象思考

我是否能用一个更好的对象来描述持续伤害这个过程呢?

我觉得层数和时间应该用频率来控制关联,可以是 1:1 的,也可以是更高频率的。

然后伤害效果的结算主要与层数关联,也就是每层对应一定伤害。

由此可以总结出三个关键的属性:时间、层数和每层效果。

并且这个模型可以套用在所有 buff 上,或者说,应该在 buff 的(时间/效果)这样的基础上继承更多的特性。

比如:

  1. 最多持续 w 秒,每秒触发 n 次,造成层数 * e 的伤害,消耗 1。
  2. 最多持续 w 秒,每秒触发 n 次,造成剩余生命值 x% 的伤害,消耗 2。
  3. 最多持续 w 秒,每秒触发 n 次,每层造成,当前层数到一半层数的累加值伤害,消耗一半层数。
    1. 但是这种设计会要求很高的层数,所以可以是与常规持续伤害的组合,比如每 a 秒触发 1 次。或者长时间每加毒的时候,就进入快速解毒的状态。

总之就可以挖掘出一种基础模板:

持续 w 秒,每秒触发 n 次,每层造成 {某种效果或伤害} 消耗 k 层。

我可以思考一下如何围绕这个模板来设计,七步必杀的效果:

持续无数秒,每秒触发满帧次,每层造成计算累积位移距离,如果没有达到要求消耗 0 层,达到了就击杀,然后消耗 1 层。

有了这些模式,完全可以用 GPT 生成一大堆的效果,自己慢慢挑选。

  1. 毒素蔓延: 持续 5 秒,每秒触发 3 次,每次造成 50 的伤害并在目标周围产生毒雾,消耗 1 层。毒雾会对周边单位造成额外的 10 点伤害。

  2. 烈焰燃烧: 持续 4 秒,每秒触发 2 次,每次造成当前生命值 5% 的伤害并降低目标攻击力 20%,消耗 2 层。

  3. 深度冻结: 持续 6 秒,每秒触发 1 次,每层都会使目标移动速度和攻击速度降低 10%,消耗一半层数。

  4. 能量剥离: 持续 8 秒,每秒触发 2 次,每层都会从目标身上剥离出 10 点能量,转化为攻击者的生命值或者魔法值,消耗 3 层。

  5. 七步必杀: 持续无限秒,每秒触发满帧次,每层造成计算累积位移距离,如果没有达到 7 米要求消耗 0 层,达到了就击杀,然后消耗 1 层。

  6. 电流穿透: 持续 6 秒,每秒触发 4 次,每次造成 40 点电伤害,并减少目标 15% 的防御力,消耗 2 层。

  7. 灵魂束缚: 持续 10 秒,每秒触发 1 次,每次造成当前生命值 3% 的伤害并让目标定身,消耗 2 层。

  8. 虚空震颤: 持续 7 秒,每秒触发 3 次,每次造成 60 点伤害,如果目标的生命值低于 30%,则伤害增加 50%,消耗 1 层。

  9. 生命吸取: 持续 6 秒,每秒触发 1 次,每次从目标身上吸取等于其当前生命值 4% 的生命值,消耗 3 层。

  10. 噩梦诅咒: 持续 8 秒,每秒触发 2 次,每次使目标失去 50 点魔法值,并降低其 10% 的技能伤害,消耗 1 层。

甚至可以整理成表格:

序号名称持续时间(秒)每秒触发次数伤害或效果描述层数消耗
1毒素蔓延53每次造成 50 点伤害并在目标周围产生毒雾1
2烈焰燃烧42每次造成当前生命值 5% 的伤害并降低目标攻击力 20%2
3深度冻结61每层都会使目标移动速度和攻击速度降低 10%半层数
4能量剥离82每层都会从目标身上剥离出 10 点能量3
5七步必杀无限满帧计算累积位移距离,如果没有达到 7 米要求消耗 0 层,达到了就击杀1
6电流穿透64每次造成 40 点电伤害,并减少目标 15% 的防御力2
7灵魂束缚101每次造成当前生命值 3% 的伤害并让目标定身2
8虚空震颤73每次造成 60 点伤害,如果目标的生命值低于 30%,则伤害增加 50%1
9生命吸取61每次从目标身上吸取等于其当前生命值 4% 的生命值3
10噩梦诅咒82每次使目标失去 50 点魔法值,并降低其 10% 的技能伤害1

小结

本文主要是围绕作者自己过往观察的随笔,写作目的有:

  • 复习一点数学计算,防止脑子生锈
  • 排出脑内想法,防止过渡思考
  • 为未来的自己提供有趣的参考
  • 试验语言模型在辅助创意性思考中的作用

(存档在 CSDN 未来会转私有)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值