关于一个伤害数值的计算模拟。
如何调整每帧的伤害比例 x ,使得在一秒 12 帧的频率下,总的伤害比例还是与每秒造成当前生命值 y% 的持续伤害相同?
形式化地,我们可以使用以下等式来描述这个问题:
( 1 − x ) n = 1 − y (1 - x)^n = 1 - y (1−x)n=1−y
其中,x 是每帧的伤害比例,n 是每秒的帧数(在这个情况下是 12),y 是每秒的伤害比例。已知 n 和 y,目标是求解 x 的值。
给定等式为:
( 1 − x ) n = 1 − y (1 - x)^n = 1 - y (1−x)n=1−y
假设 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} 1−x=(1−y)1/n
步骤 2: 重新整理式子,使得等式右侧只有 x x x的项:
− x = ( 1 − y ) 1 / n − 1 - x = (1 - y)^{1/n} - 1 −x=(1−y)1/n−1
步骤 3: 将 x x x移到左边:
x = 1 − ( 1 − y ) 1 / n x = 1 - (1 - y)^{1/n} x=1−(1−y)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)
}
}
这个函数接受两个参数:y
和 n
,都是浮点数类型。首先,我们检查 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 的(时间/效果)这样的基础上继承更多的特性。
比如:
总之就可以挖掘出一种基础模板:
持续 w 秒,每秒触发 n 次,每层造成 {某种效果或伤害} 消耗 k 层。
我可以思考一下如何围绕这个模板来设计,七步必杀的效果:
持续无数秒,每秒触发满帧次,每层造成计算累积位移距离,如果没有达到要求消耗 0 层,达到了就击杀,然后消耗 1 层。
有了这些模式,完全可以用 GPT 生成一大堆的效果,自己慢慢挑选。
毒素蔓延: 持续 5 秒,每秒触发 3 次,每次造成 50 的伤害并在目标周围产生毒雾,消耗 1 层。毒雾会对周边单位造成额外的 10 点伤害。
烈焰燃烧: 持续 4 秒,每秒触发 2 次,每次造成当前生命值 5% 的伤害并降低目标攻击力 20%,消耗 2 层。
深度冻结: 持续 6 秒,每秒触发 1 次,每层都会使目标移动速度和攻击速度降低 10%,消耗一半层数。
能量剥离: 持续 8 秒,每秒触发 2 次,每层都会从目标身上剥离出 10 点能量,转化为攻击者的生命值或者魔法值,消耗 3 层。
七步必杀: 持续无限秒,每秒触发满帧次,每层造成计算累积位移距离,如果没有达到 7 米要求消耗 0 层,达到了就击杀,然后消耗 1 层。
电流穿透: 持续 6 秒,每秒触发 4 次,每次造成 40 点电伤害,并减少目标 15% 的防御力,消耗 2 层。
灵魂束缚: 持续 10 秒,每秒触发 1 次,每次造成当前生命值 3% 的伤害并让目标定身,消耗 2 层。
虚空震颤: 持续 7 秒,每秒触发 3 次,每次造成 60 点伤害,如果目标的生命值低于 30%,则伤害增加 50%,消耗 1 层。
生命吸取: 持续 6 秒,每秒触发 1 次,每次从目标身上吸取等于其当前生命值 4% 的生命值,消耗 3 层。
噩梦诅咒: 持续 8 秒,每秒触发 2 次,每次使目标失去 50 点魔法值,并降低其 10% 的技能伤害,消耗 1 层。
甚至可以整理成表格:
序号 | 名称 | 持续时间(秒) | 每秒触发次数 | 伤害或效果描述 | 层数消耗 |
---|---|---|---|---|---|
1 | 毒素蔓延 | 5 | 3 | 每次造成 50 点伤害并在目标周围产生毒雾 | 1 |
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 |
本文主要是围绕作者自己过往观察的随笔,写作目的有:
(存档在 CSDN 未来会转私有)