手把手使用Minestom带你解剖Minecraft的击退机制
——实现精准可控的PVP击退效果
读前须知
- 该教程相对于其他的击退,或许(此处表示也许)更加通俗易懂(仅仅对于我而言)
- 初中生来了也能懂,但是你如果初中没有学习向量的话可以稍微了解一点,稍微一点就会
- 我代码写的很烂,别喷,能看懂就行,这里主要是计算
- 该教程也是看着MinestomPVP总结出来的
- 此处不包含任何MoJang代码,依赖与Minestom
核心概念
在Minecraft中,击退(Knockback,简称KB)的本质是 通过方向向量和速度合成实现的物理效果 要实现优秀的PVP击退,需掌握以下核心要素:
- 击退方向: 由攻击者的面朝角度(Yaw)决定
- 击退力度: 由武器属性和状态效果调节
- 速度合成: 结合目标当前速度和击退力
零、代码展示
0.1 takeKnockback(对目标执行一个击退)
@JvmStatic
fun executeKnockback(
target: LivingEntity,
//source也许是一个空的
source: Entity?,
strength: Float,
dx: Double,
dz: Double,
type: Type,
attacker: Entity,
verticalLimit: Double = 0.4
): Boolean {
val event = EntityKnockbackEvent(target, source ?: attacker, type ,strength, verticalLimit).apply {
EventDispatcher.call(this)
}
if (event.isCancelled) return false
takeKnockback(target, event.strength, dx, dz, event.verticalLimit)
return true
}
/*
*空中连击为什么难?因为limit参数卡死了上升速度!
*在20tick服务器,Y轴速度最大=0.4×20=8格/秒——
*这就是你无法把对手打成卫星的根本原因!
*
* */
@JvmStatic
fun takeKnockback(entity: LivingEntity, str: Float, x: Double, z: Double, limit: Double = 0.4) {
var strength = str
if (strength > 0.0f) {
strength *= ServerFlag.SERVER_TICKS_PER_SECOND.toFloat()
//这是做了个归一化,如果不做归一化的话,可能会使击退数据出现异常,增加或降低
val velocityModifier = Vec(x, z).normalize().mul(strength.toDouble())
//在20tick服务器,Y轴速度最大=limit×20=8格/秒——
val verticalLimit = limit * ServerFlag.SERVER_TICKS_PER_SECOND.toDouble()
entity.velocity = Vec(
entity.velocity.x() / 2.0 - velocityModifier.x(), if
(entity.isOnGround) min(verticalLimit, entity.velocity.y() / 2.0 + strength.toDouble())
else entity.velocity.y(), entity.velocity.z() / 2.0 - velocityModifier.z()
)
}
}
外部参数名称 |
类型 |
默认值 |
作用域 |
功能描述 |
entity: LivingEntity |
一只富有活力实体 |
- |
函数入参 |
被施加击退效果的目标实体(玩家/生物) |
str: Float |
浮点数 |
- |
函数入参 |
基础击退强度(受武器、附魔、药水等影响) |
x: Double |
双精度浮点 |
- |
函数入参 |
击退方向的 X 轴分量(东西方向) |
z: Double |
双精度浮点 |
- |
函数入参 |
击退方向的 Z 轴分量(南北方向) |
limit: Double |
双精度浮点 |
0.4 |
函数入参 |
垂直速度上限(格/秒) |
内部变量名称 |
类型 |
默认值 |
作用域 |
功能描述 |
strength |
浮点数 |
- |
函数内部变量 |
经过 Tick 率换算后的实际击退强度 |
velocityModifier |
Vec 向量 |
- |
函数内部变量 |
归一化后的击退方向向量 × 强度 |
verticalLimit |
双精度浮点 |
- |
函数内部变量 |
根据服务器 Tick 率换算的垂直速度上限 |
0.2 attackKnockback(执行一个攻击的击退)
private const val DEFAULT_KNOCKBACK_FACTOR = 0.5f
private const val DEFAULT_VERTICAL_LIMIT = 0.4
override fun processKnockback(
attacker: Entity,
target: LivingEntity,
knockbackStrength: Int
): Boolean {
if (knockbackStrength <= 0) return false
return with(attacker.position) {
val horizontalStrength = knockbackStrength * DEFAULT_KNOCKBACK_FACTOR
val (dx, dz) = calculateAttackDirectionComponents(yaw)
executeKnockback(
target = target,
source = attacker,
strength = horizontalStrength, dx = dx, dz = dz, type = Type.ATTACK, attacker = attacker, DEFAULT_VERTICAL_LIMIT
)
.also {
success -> if (success && attacker is GamePlayer) attacker.afterMoveAttack()
}
}
}
private fun calculateAttackDirectionComponents(yaw: Float): Pair<Double, Double> {
val radianYaw = Math.toRadians(yaw.toDouble())
return sin(radianYaw) to -cos(radianYaw)
}
一、坐标系与基础参数
1.1 世界坐标系
- X轴:东(+) ↔ 西(-)
- Z轴:南(+) ↔ 北(-)
- Y轴:垂直方向(上+,下-)
1.2 面朝角度(Yaw)
- 0°:正南(-Z方向)
- 90°:西(-X方向)
- 180°:正北(+Z方向)
- 270°:东(+X方向)
二、击退方向计算
2.1 基础公式
击退方向由攻击者的Yaw角度通过三角函数计算:
//将角度转为弧度制
val radianYaw = Math.toRadians(yaw.toDouble())
//计算生成一个Pair<Double,Doblue>的一对数值
return sin(radianYaw) to -cos(radianYaw)
关于2.1
你或许会有的疑问:
为什么dx不带负号而dz带了呢?:
我们前面说过面朝的角度(Yaw)与方向的关系我们可以知道Yaw角度与方向的关系:
Z轴和南北有关
X轴与东西有关
第一步:理解 Minecraft 的角度系统
这与数学中的标准角度系统(0° 为东,逆时针旋转)不同,Minecraft 的 yaw 是 以正南为起点顺时针旋转
可见我们需要调整三角函数的计算方式
第二步:击退方向的反向逻辑
当攻击者挥剑时,击退方向与攻击者的面朝方向相反(类似“向后推”)例如:
攻击者面朝 正南(+Z) → 击退方向是 正北(-Z)
攻击者面朝 东(+X) → 击退方向是 西(-X)
可见为了反向,代码中需要将面朝方向的分量取反
第三步:X 和 Z 分量的推导
1. X 轴(东西方向)的分量:x = sin(yaw)
在MC中,sin(θ) 对应 东西方向的分量
当玩家面朝 东(yaw=270°) 时:sin(270°) = -1 → 击退方向为 西(-X)
当玩家面朝 西(yaw=90°) 时:sin(90°) = 1 → 击退方向为 东(+X)
2. Z轴(南北方向)的分量:z = -cos(yaw)
cos(yaw) 原本对应 南北方向的分量,但需要反向
当玩家面朝 南(yaw=0°) 时:cos(0°) = 1 → -cos(0°) = -1 → 击退方向为 北(-Z)
当玩家面朝 北(yaw=180°) 时:cos(180°) = -1 → -cos(180°) = 1 → 击退方向为 南(+Z)
第四步:举例子
例1:攻击者面朝正南(yaw=0)
x = sin(0°) = 0 → 无东西方向分量
z = -cos(0°) = -1 → 击退方向为 北(-Z)
✅ 符合预期(面朝南,击退向北)
例2:攻击者面朝东(yaw=270°)
x = sin(270°) = -1 → 击退方向为 西(-X)
z = -cos(270°) = 0 → 无南北方向分量
✅ 符合预期(面朝东,击退向西)
例3:攻击者面朝西北(yaw=135°)
x = sin(135°) ≈ 0.707 → 击退方向为 东南(+X)
z = -cos(135°) ≈ 0.707 → 击退方向为 东南(+Z)
✅ 合成为 东南方向,与面朝西北相反
你还可以这么想,通过诱导公式可以得出以下公式
sin(-α) = -sinα
cos(-α) = cosα
这组诱导公式得到的sin都是相反的,但是cos不是,想要得到相反的直接加负号不就好了
2.2 方向验证表
面朝方向 |
Yaw |
dx |
dz |
击退方向 |
正南 |
0° |
0.0 |
-1.0 |
正北 |
正东 |
270° |
-1.0 |
0.0 |
正西 |
东北 |
45° |
0.707 |
0.707 |
西南 |
三、归一化处理
3.1 问题描述
原始方向向量的长度不固定(如东北方向长度为√2≈1.414),直接使用会导致:
3.2 解决方案
将方向向量转换为单位向量(长度=1):
原代码(Kotlin):
val velocityModifier = Vec(dx, dz).normalize().mul(strength.toDouble())
实际表达的意思(Java):
// 计算向量长度
double length = Math.sqrt(dx * dx + dz * dz);
// 归一化
if (length > 0) {
dx /= length;
dz /= length;
}
3.3 效果对比
原始向量 |
归一化结果 |
(3, 4) |
(0.6, 0.8) |
(1, 1) |
(0.707, 0.707) |
四、速度合成算法
4.1 计算公式
原代码:
// 水平速度
val newVelX = entity.velocity.x() / 2.0 - velocityModifier.x()
val newVelZ = entity.velocity.z() / 2.0 - velocityModifier.z()
// 垂直速度
val newVelY = if (entity.isOnGround) min(verticalLimit, entity.velocity.y() / 2.0 + strength.toDouble()) else entity.velocity.y()
entity.velocity = Vec(newVelX, newVelY, newVelZ)
4.2 参数说明
- entity.velocity:目标当前速度矢量
- velocityModifier:处理后的速度矢量,实际上就是原速度矢量*strength
- /2.0:模拟惯性衰减
五、实现优秀PVP击退的配置指南
5.1 参数推荐值
战斗风格 |
strength |
适用场景 |
竞技精准 |
0.6~0.8 |
1v1决斗 |
快速连击 |
0.4~0.6 |
连招Combo |
爆发控制 |
1.0~1.2 |
群体PVP |
5.2 进阶优化技巧
-
地面限制增强
// 增强地面单位的垂直击退
val newVelY = if (entity.isOnGround) min(verticalLimit + 0.2, entity.velocity.y() / 2.0 + strength.toDouble() * 1.2) else entity.velocity.y()
-
空中击退补偿
// 对空中单位施加20%额外水平击退
if (!entity.isOnGround) entity.velocity = Vec(newVelX * 1.2, newVelY, newVelZ * 1.2)
-
方向随机扰动(防预判,同时这也会为将要讲的DamageKnockback埋下伏笔)
// 添加±5°随机偏移
val randomOffset = ThreadLocalRandom.current().nextDouble(-5.0, 5.0)
//这里的yaw是processKnockback里的那个calculateAttackDirectionComponents(yaw)里面的这个yaw
val adjustedYaw = yaw + randomOffset
//在processKnockback里的calculateAttackDirectionComponents(yaw)改成calculateAttackDirectionComponents(adjustedYaw)
六、调试与验证方法
6.1 调试工具
- F3调试界面:实时查看实体坐标和速度
- Replay Mod:录制并回放击退轨迹
- 自定义ActionBar:显示方向向量和力度数值
6.2 验证流程
- 面朝正南攻击,确认目标向北移动(Z坐标递减)
- 使用45°方向攻击,验证击退距离 = strength × √2
- 连续攻击空中目标,检查垂直速度是否符合预期
七、常见问题解决方案
问题现象 |
排查重点 |
解决方案 |
击退方向相反 |
检查dz是否使用-cos(yaw) |
修正符号 |
斜向击退距离异常 |
确认是否执行归一化 |
添加归一化处理 |
空中单位无击退效果 |
检查onGround判断逻辑 |
移除不必要的条件限制 |
连击时速度指数增长 |
验证速度衰减是否使用/2.0 |
检查速度合成公式 |
通过以上步骤的系统化实施,你将能够精确控制Minecraft的击退机制,打造出既符合物理直觉又具备竞技深度的PVP体验
如果还有问题请加入我们