『博客日志 & 大事祭』
25.6.1 emm 好久好久没写博客了删了点东西就当重新开始吧 😉
25.10.11 压缩了博客的大部分图片,提高响应速度
25.11.9
- permalinks 更新,由
:year/:month/:day/:title转为posts/:title - per_page 由每页 8 篇变为每页 5 篇文章
加载过慢请开启缓存 浏览器默认开启
25.6.1 emm 好久好久没写博客了删了点东西就当重新开始吧 😉
25.10.11 压缩了博客的大部分图片,提高响应速度
25.11.9
:year/:month/:day/:title 转为 posts/:title⚠️硬核警告⚠️
英译版见『Minimum KB Click Frequency Lower Bound Analysis』.
本文包含大量数学公式和代码分析,建议具备一定理论基础阅读。
欢迎各位大佬指正🥰。
嗯……AI 帮了点小忙,还是挺好看出来的,对吧。
$$\frac{dv}{dt} = a - fv \tag{1.1.1}$$
$$v = v_0 e^{-ft} + \frac{a}{f} (1 - e^{-ft}) \tag{1.1.2}$$
$$\frac{v}{v_0} = \lim\limits_{t_0 \to 0} (1 - f t_0)^{\frac{t}{t_0}}, a = 0 \tag{1.1.3}$$
公式 (1.1.1) 描述了物体在恒定驱动力和正比于速度的阻力共同作用下,速度从变化逐渐达到稳定平衡的动态过程。这也是后续所有公式推导的基础。
公式 (1.1.2) 是通过求解公式 (1.1.1) 得到的精确解(通解)。其描述了在任何初始速度 $v_0$ 和任何驱动力 $a$ 的情况下,速度随时间变化的完整规律。
公式 (1.1.3) 则是将驱动力 $a=0$ 时的特殊情况代入公式 (1.1.2) 得到的结果。表明物体仅在阻力作用下,速度从初值 $v_0$ 开始指数衰减到零的过程。
其中,$f$ 为阻力系数。
但是,Minecraft 是以 tick(后续将称为 gt)为单位进行离散更新,即 $dt$ 和 $t_0$ 的最小值为 1gt,不能无限趋近于 0。因此这一规则在游戏中不能精确实现。
以下是实体在水平和空中运动源代码简化版。已删去不必要和不相关的代码。
在 net/minecraft/entity/EntityLivingBase.java 中:
public void moveEntityWithHeading(float strafe, float forward) {
/**
* 环境检测与移动类型判断
* isInWater()、isInLava()、isFlying
* 水、岩浆、梯子等特殊状态移动处理(else if)
*/
float f4 = 0.91F; // 惯性系数
if (this.onGround) {
f4 = this.worldObj.getBlockState(
new BlockPos(
MathHelper.floor_double(this.posX),
MathHelper.floor_double(this.getEntityBoundingBox().minY) - 1,
MathHelper.floor_double(this.posZ)
)
)
.getBlock()
.slipperiness * 0.91F;
} // 获取下方方块滑度(后续将 slipperiness 称为滑度系数)
float f = 0.16277136F / (f4 * f4 * f4);
/**
* (0.6*0.91)^3 = 0.16277136
* 移动系数
*/
float f5;
if (this.onGround) f5 = this.getAIMoveSpeed() * f;
/**
* f5 = this.landMovementFactor * f;
* landMovementFactor 基础情况下为 0.1,疾跑时*1.3,受药水效果影响
*/
else f5 = this.jumpMovementFactor;
/**
* jumpMovementFactor = 0.02;
* 疾跑时*1.3,与药水效果无关
*/
this.moveFlying(strafe, forward, f5); // 速度计算
// 重新计算 f4
// 嗯···屎山代码这一块(
this.moveEntity(this.motionX, this.motionY, this.motionZ); // 实体移动
// 未加载区块重力处理
this.motionY -= 0.08D; // 重力
this.motionY *= 0.9800000190734863D; // 空气阻力
this.motionX *= (double)f4; this.motionZ *= (double)f4; // 摩擦力(水平)
// 玩家肢体动画更新
}
方块的滑度系数在 net/minecraft/block/Block.java 等文件中定义:
// Block.java
public class Block {
// 其它方块属性
public float slipperiness;
public Block(Material blockMaterialIn, MapColor blockMapColorIn) {
// 其它属性初始化
this.slipperiness = 0.6F;
}
}
// BlockIce.java
public class BlockIce extends BlockBreakable {
public BlockIce() {
this.slipperiness = 0.98F;
}
} // 冰
//BlockPackedIce.java
public class BlockPackedIce extends Block {
public BlockPackedIce() {
this.slipperiness = 0.98F;
}
} // 浮冰
// BlockSlime.java
public class BlockSlime extends BlockBreakable {
public BlockSlime() {
this.slipperiness = 0.8F;
}
} // 史莱姆块
滑度系数将用 $f_s$ 在后续的公式推导中来表示。
特别地,蓝冰在 1.13 被加入,滑度为 0.989。此处仅作补充。
实体在地面的移速(通过 getAIMoveSpeed() 函数返回,本质上是变量 landMovementFactor 的值)在文件 net/minecraft/entity/EntityLivingBase.java 定义:
public abstract class EntityLivingBase extends Entity {
private float landMovementFactor;
public float getAIMoveSpeed() {
return this.landMovementFactor;
}
}
其基本值在 net/minecraft/entity/player/PlayerCapabilities.java 给出:
public class PlayerCapabilities {
private float walkSpeed = 0.1F;
}
疾跑状态的修饰在 net/minecraft/entity/EntityLivingBase.java 中定义:
public abstract class EntityLivingBase extends Entity {
private static final AttributeModifier sprintingSpeedBoostModifier =
(new AttributeModifier(
sprintingSpeedBoostModifierUUID,
"Sprinting speed boost",
0.30000001192092896D,
2
)).setSaved(false);
}
疾跑速度则是通过 net/minecraft/entity/ai/attributes/ModifiableAttributeInstance.java 中的方法实现:
private double computeValue() {
double d0 = this.getBaseValue(); // 属性基本值获取
for (AttributeModifier attributemodifier : this.func_180375_b(0)) {
d0 += attributemodifier.getAmount();
}
double d1 = d0;
for (AttributeModifier attributemodifier1 : this.func_180375_b(1)) {
d1 += d0 * attributemodifier1.getAmount();
}
for (AttributeModifier attributemodifier2 : this.func_180375_b(2)) {
d1 *= 1.0D + attributemodifier2.getAmount();
}
// 三种类型修饰叠加
return this.genericAttribute.clampValue(d1);
}
实体在空中的移速(jumpMovementFactor)在 net/minecraft/entity/EntityLivingBase.java 中定义:
public abstract class EntityLivingBase extends Entity {
public float jumpMovementFactor = 0.02F;
}
疾跑时在 net/minecraft/entity/player/EntityPlayer.java 中更新:
public abstract class EntityPlayer extends EntityLivingBase {
public void onLivingUpdate() {
if (this.isSprinting()) {
this.jumpMovementFactor = (float)(
(double)this.jumpMovementFactor +
(double)this.speedInAir * 0.3D
);
}
}
}
屎山代码这两种运动实现还不一样 ):
实体的「motion」在 net/minecraft/entity/Entity.java 中更新:
public void moveFlying(float strafe, float forward, float friction) {
float f = strafe * strafe + forward * forward;
if (f >= 1.0E-4F) {
f = MathHelper.sqrt_float(f); // 模长为 1 的单位向量
if (f < 1.0F) {
f = 1.0F; // 避免斜向速度异常
}
f = friction / f;
strafe = strafe * f;
forward = forward * f;
// 摩擦系数应用
float f1 = MathHelper.sin(this.rotationYaw * (float)Math.PI / 180.0F);
float f2 = MathHelper.cos(this.rotationYaw * (float)Math.PI / 180.0F);
this.motionX += (double)(strafe * f2 - forward * f1);
this.motionZ += (double)(forward * f2 + strafe * f1);
// 2D 旋转矩阵
}
}
strafe 和 forward 变量在 net/minecraft/util/MovementInputFromOptions.java 中获取按键输入更新:
public void updatePlayerMoveState() {
this.moveStrafe = 0.0F;
this.moveForward = 0.0F;
if (this.gameSettings.keyBindForward.isKeyDown()) {
++this.moveForward;
}
if (this.gameSettings.keyBindBack.isKeyDown()) {
--this.moveForward;
}
if (this.gameSettings.keyBindLeft.isKeyDown()) {
++this.moveStrafe;
}
if (this.gameSettings.keyBindRight.isKeyDown()) {
--this.moveStrafe;
}
// 其它按键事件
/**
* 潜行事件
* moveStrafe 和 moveForward 乘以 0.3
*/
}
通过分析上述代码实现可以得知:
实体在水平方向上的「motion 属性」,在数值上等于当前速度向量在各坐标轴的分量乘以对应的阻力系数(垂直方向上的「motion」计算略有不同)。
若将 $t_0$ 时间内的「motion」视为平均速度 $M$,那么「motion」与阻力系数的乘积在物理意义上表示在该时间段内阻力对实体产生的冲量。
实体的「motion 属性」本质上是实体运动计算过程中的中间量,仅在游戏执行
moveFlying()函数计算后,「motion 属性」可被视为实体的速度(或该时间段内的平均速度近似值)。
为了方便理解公式,在这里定义一些必要参数:
由此推导得到实体运动的递推关系(主要参考 Minecraft Parkour Wiki):
地面水平速度公式:$$v_{H,t} = \underbrace{v_{H,t-1} \times f_{s, t-1} \times 0.91} _ \text{动量保留} + \underbrace{0.1 \times \left( \frac{0.6}{f_{s,t}}\right)^3 \times k_M \times k_E} _ \text{加速度} \tag{1.3.1}$$
空中水平速度公式:$$v_{H,t} = \underbrace{v_{H,t-1} \times 0.91} _ \text{动量保留} + \underbrace{0.02 \times k_M} _ \text{加速度} \tag{1.3.2}$$
垂直速度公式:$$v_{Y,t} = ( v_{Y,t-1} - \mathop{0.08}\limits_\text{重力} ) \times \mathop{0.98}\limits_\text{阻力} \tag{1.3.3}$$
考虑到实际应用场景,上述推导中大部分系数或乘数均使用特殊值。
在后续『最优 kb 的点击频率下限分析』章节中,将加入点击行为对速度的影响机制。
经测试,鼠标双击的触发延迟主要分布在 $[16, 27]\ \mathrm{ms}$ 区间范围内,受硬件性能、驱动程序、系统环境等影响,在不同的测试环境下略有差异。
若两次点击分别落在相邻的 tick 中,则称为「有效双击」。
设第一次点击的时间为 $T$,服从区间 $[0,50]$ 上的均匀分布:$$T \sim \mathcal{U}(0,50)$$
双击延迟为 $D$,服从区间 $[16,27]$ 上的均匀分布:$$D \sim \mathcal{U}(16,27)$$
则「有效双击」的概率:$$P(T + D \geq 50)$$
$T$ 和 $D$ 相互独立,其联合概率密度函数为:
$$f_{T,D}(t,d) = \frac{1}{50} \times \frac{1}{11} = \frac{1}{550},\ t \in [0,50],\ d \in [16,27]$$
展开积分并计算所求概率:
$$P(T + D \geq 50) = \iint\limits_{t + d \geq 50} f_{T,D}(t, d)\ dt\ dd = \int_{16}^{27} \int_{50 - d}^{50} \frac{1}{550}\ dt\ dd = \frac{473}{1100} = \frac{43}{100}$$
即「有效双击」的触发概率为 43%.
如果你不想阅读这部分略微令人头皮发麻的积分还可以通过条件期望来推导该概率:
$$P(T + D \geq 50) = P(T \geq 50 - D) = E[P(T \geq 50 - D \mid D)]$$
对于固定的 $D,\ T \sim \mathcal{U}(0, 50)$,且 $50 - D \in [23, 34] \subseteq [0, 50]$,有:
$$P(T \geq 50 - D \mid D) = \frac{50 - (50 - D)}{50} = \frac{D}{50}$$
得到:
$$P(T + D \geq 50) = E\left[\frac{D}{50}\right] = \frac{1}{50} E[D] = \frac{1}{50} \times \frac{16 + 27}{2} = \frac{43}{100}$$
与积分计算的结果一致。
🧠☠️
当玩家攻击时调用 net/minecraft/entity/player/EntityPlayer.java 文件中的 attackTargetEntityWithCurrentItem() 函数处理该过程:
public abstract class EntityPlayer extends EntityLivingBase {
public void attackTargetEntityWithCurrentItem(Entity targetEntity) {
/**
* 可攻击检查
* 基础伤害计算
* 附魔伤害计算
*/
int i = 0;
i = i + EnchantmentHelper.getKnockbackModifier(this);
if (this.isSprinting()) {
++i;
}
// 击退效果计算
/**
* 伤害有效性检查
* 暴击判断
* 火焰附加
*/
double d0 = targetEntity.motionX;
double d1 = targetEntity.motionY;
double d2 = targetEntity.motionZ;
boolean flag2 = targetEntity.attackEntityFrom(DamageSource.causePlayerDamage(this), f);
// 伤害应用
if (flag2) {
if (i > 0) {
targetEntity.addVelocity(
(double)(-MathHelper.sin(
this.rotationYaw * (float)Math.PI / 180.0F) * (float)i * 0.5F),
0.1D,
(double)(MathHelper.cos(
this.rotationYaw * (float)Math.PI / 180.0F) * (float)i * 0.5F)
); // 第二阶段击退计算
this.motionX *= 0.6D;
this.motionZ *= 0.6D;
this.setSprinting(false);
// 攻击者减速并取消疾跑状态
}
if (targetEntity instanceof EntityPlayerMP && targetEntity.velocityChanged) {
((EntityPlayerMP)targetEntity).playerNetServerHandler.sendPacket(
new S12PacketEntityVelocity(targetEntity)
);
targetEntity.velocityChanged = false;
targetEntity.motionX = d0;
targetEntity.motionY = d1;
targetEntity.motionZ = d2;
} // 避免重复叠加击退
/**
* 伤害成功后续处理
* 攻击失败处理
*/
}
}
// net/minecraft/entity/EntityLivingBase.java
public abstract class EntityLivingBase extends Entity {
public boolean attackEntityFrom(DamageSource source, float amount) {
/**
* 检查实体是否对特定伤害源免疫
* 客户端检查
* 死亡检查
* 特殊伤害免疫(抗火)
* 装备减伤
* 伤害刻(特别地,该时间段内更高的伤害会覆盖原有伤害)
* 攻击者判断
* 视觉更新(伤害动画)
*/
double d1 = entity.posX - this.posX;
double d0;
for(d0 = entity.posZ - this.posZ;
d1 * d1 + d0 * d0 < 1.0E-4D;
d0 = (Math.random() - Math.random()) * 0.01D) {
d1 = (Math.random() - Math.random()) * 0.01D;
} // 距离过近的随机击退
this.attackedAtYaw = (float)(
MathHelper.atan2(d0, d1) * 180.0D / Math.PI
- (double)this.rotationYaw
);
this.knockBack(entity, amount, d1, d0); // 执行击退
// 其它操作
}
public void knockBack(Entity entityIn, float amount, double d0, double d1) {
if (this.rand.nextDouble() >= this.getEntityAttribute(
SharedMonsterAttributes.knockbackResistance).getAttributeValue()) {
this.isAirBorne = true;
float f = MathHelper.sqrt_double(d0 * d0 + d1 * d1);
float f1 = 0.4F;
this.motionX /= 2.0D;
this.motionY /= 2.0D;
this.motionZ /= 2.0D;
// 受击者速度衰减
this.motionX -= d0 / (double)f * (double)f1;
this.motionY += (double)f1;
this.motionZ -= d1 / (double)f * (double)f1;
// 第一阶段击退计算
if (this.motionY > 0.4000000059604645D) {
this.motionY = 0.4000000059604645D;
} // 垂直击退上限
}
}
}
// net/minecraft/entity/Entity.java
public abstract class Entity implements ICommandSender {
protected void setBeenAttacked() {
this.velocityChanged = true;
}
public boolean attackEntityFrom(DamageSource source, float amount) {
if (this.isEntityInvulnerable(source)) {
return false;
} else {
this.setBeenAttacked();
return false;
}
}
}
为了更清晰地理解击退机制,在这里定义以下参数:
horizontal:基础水平击退。对应 knockBack() 函数中的 f1 变量,默认值为 0.4vertical:基础垂直击退。其数值与基础水平击退相同,为 0.4horizontalExtra:额外水平击退。仅考虑疾跑的因素,即 i = 1,值为 0.5verticalExtra:额外垂直击退。addVelocity() 函数中的叠加固定额外垂直击退,默认值为 0.1verticalLimit:垂直击退上限。motionY 的最大值 0.4friction:暂无合适的译法。表示玩家受击时各轴的速度衰减(区别于方块的 friction),默认值为 2.0基于上述代码分析可知玩家受到的击退效果主要分为两个阶段:
本小节重点讨论与原版击退算法不同之处。
MMC 引入了以下新的或修改的参数:
totalHorizontal = 0.8835d // 总水平击退
totalVertical = 0.9055d // 总垂直击退
rangeFactor = 0.035d // 距离影响系数
maxReduction = 0.4d // 最大距离减免
startRange = 3.0d // 距离减免起始计算距离
idleReduction = 0.6d // 基础击退倍率
attackBuffer = 1 // 攻击缓存
// 计算后的实际值
horizontal = totalHorizontal * idleReduction // 基础水平击退
horizontalExtra = totalHorizontal * (1 - idleReduction) // 额外水平击退
vertical = totalVertical * 0.4d // 基础垂直击退
verticalExtra = 0.0d // 额外垂直击退
verticalLimit = 0.4d // 垂直击退上限
attackerSlowdown = 0.6d
friction = 0.0d
不难看出:
idleReduction 决定了基础水平击退和额外水平击退的大小,并与 totalHorizontal 的值紧密相关。verticalExtra 的值归零,即玩家的垂直击退固定。attackBuffer 提供伤害刻结束前的攻击窗口。rangeFactor、maxReduction、startRange 参数用于减免由于延迟造成的远距离攻击注册的击退。特别地,当
friction = 0.0时,受击者的动量归零,即击退完全覆盖原速度。
第一阶段击退算法:
void firstStage(EntityLiving attacker, EntityLiving victim) {
// 省略部分伪代码
double distance = Math.sqrt(distanceX * distanceX + distanceZ * distanceZ);
double rangeReduction = calculateRangeReduction(distance)
double modifiedHorizontal = horizontal - rangeReduction // 远距离击退衰减
double magnitude = Math.sqrt(distanceX * distanceX + distanceZ * distanceZ)
victim.motX -= (distanceX / magnitude) * (modifiedHorizontal * 0.5d)
victim.motZ -= (distanceZ / magnitude) * (modifiedHorizontal * 0.5d)
double yaw = Math.toRadians(attacker.yaw)
victim.motX += -Math.sin(yaw) * (modifiedHorizontal * 0.5d)
victim.motZ += Math.cos(yaw) * (modifiedHorizontal * 0.5d)
}
与原版不同的是,MMC 算法中第一阶段的击退同时与玩家的相对位置和攻击者的偏航角相关(占比各 50%)。并增加了 calculateRangeReduction() 函数用于减免远距离击退。
第二阶段击退算法与第一阶段相似:
void secondStage(EntityLiving attacker, EntityLiving victim, int knockbackEnchantLevel) {
// 省略部分伪代码
if (extraKBMult > 0) {
double distanceX = attacker.locX - victim.locX
double distanceZ = attacker.locZ - victim.locZ
double distance = Math.sqrt(distanceX * distanceX + distanceZ * distanceZ)
double modifiedExtraHorizontal = horizontalExtra * extraKBMult // 额外击退
double magnitude = Math.sqrt(distanceX * distanceX + distanceZ * distanceZ)
victim.motX -= (distanceX / magnitude) * (modifiedExtraHorizontal * 0.5d)
victim.motZ -= (distanceZ / magnitude) * (modifiedExtraHorizontal * 0.5d)
double yaw = Math.toRadians(attacker.yaw)
victim.motX += -Math.sin(yaw) * (modifiedExtraHorizontal * 0.5d)
victim.motZ += Math.cos(yaw) * (modifiedExtraHorizontal * 0.5d)
}
}
额外水平击退由 horizontalExtra 参数和 extraKBMult 共同决定。并保持与第一阶段相同的 50/50 分配。
How MMC Knockback Actually Works
对我的世界中PVP击退的研究报告 - LSeng, CarmJos
关于修正并改进 xwj 的 MC 实体运动公式 —— 适用于MC中所有实体 - Bio-Hazard
Minecraft 实体运动研究与应用 - lovexyn0827
The ULTIMATE Guide to PING - sceyna
25.11.30 - 发布已基本写完的三章
之前写过一篇『录制 & 渲染配置分享(OBS & Blur)』
后来调录制参数一直没调好过(
120fps 下的素材跑 blur 之后就会变得非常非常糊 🤬,但是提高帧速率会带来更高的占用、游戏的卡顿以及可能卡炸的素材
很难去找到一个平衡点
Lunar 最近更新的新模组 Rewind,一定程度上解决了上述的问题:
HOW TO USE REWIND ON LUNAR CLIENT
Lunar Client | Introducing Rewind

不要在意上面这张图左上角的 logo 为什么这么奇怪,扒不到高清的图拿 AI 跑的超清像素
Rewind 是一款功能全面的剪辑、录制和编辑软件,并完全集成于 Lunar Client。无需任何外部软件即可录制 Minecraft 游戏内容
Rewind 提供广泛的功能。与大多数编辑软件一样,可以导入视频媒体,并进行组织和修剪。该 mod 专为 Minecraft 和 Lunar Client 量身定制
启动界面进入 Rewind Editor:

选择你刚才的 Recording,创建项目
分辨率(Resolution)建议选择你玩游戏时的分辨率,不然 ui 大小会变,非常难受 ):):):
帧率(framerate)建议直接拉满,毕竟后续还要变速

进入后在 Properties 功能区中将 Speed 选项拉到 0.25(根据情况而定,拉的越低后续渲染会更舒服,但是导出时间也会更长)

电脑配置:
开启性能模式
相同 15s 素材,1440p
| 时间线帧率 | 慢放倍数 | 时间 |
|---|---|---|
| 120fps | 0.2 | 1 : 42 |
| 240fps | 0.4 | 1 : 38 |
| 240fps | 0.25 | 2 : 55 |
120fps 与 240fps 导出时间的误差可以忽略不计


output timescale 根据你原素材慢放倍数进行调整
注意需要一条没有变速的素材的音频轨道
左 960 -> 60,右 600 -> 60

渲染效果 Render Test #7 ( Recorded by Rewind ) - Revethere
自己摸索摸索吧,这次更新的这个 mod 真的挺好 $\phantom{其实是懒(}$
甚至能做绿幕❗
文章中所有的定义(以及一些生僻概念)仅为了规范与统一文章的内容,不代表圈内叫法👌🏼
文章中所有涉及的操作均通过 dbc 点击方式实现,同时您也需要熟练使用间隔点
大部分技巧讲简述原理,少数从底层机制分析
部分技巧因适用于多个场景会重复出现
为了方便表示,大部分 click 都用单数
原本想试试图文的但是图片太难做了,受限于样式这种做视频其实会好点
本文内容仅代表个人观点❗❗❗
wtap / 重置疾跑
严格意义上是两个技巧,但在这里不作区别
需注意一点:wtap 可以增大对方的 kb 同时自己的 kb 也会增大,不要一味地叠 wtap,根据情况 hold w 也可以获得非常优秀的 kb
stap
分为 s-tap 和 ws-tap,s-tap 是松开 w 按 s(会有较明显的后退),ws-tap 则是 hold w 并 tap s(有点类似急停)
s-tap 适用于完全预判对手 hitselect,ws-tap 则相对保守,更多见『End Game』章节
据说打的时候下落阶段按 s 可以更快下落但是我真没研究明白(按理来说不应该啊
staper 是真重,也是真稳
hitselect ( hs )
分为 light hs 和 hard hs,在这里将 light hs 定义为玩家收到上升 kb 的时间内(大约达到最大高度的 60% ~ 80%)进行 hs,hard hs 则是在上述时间之后到下次受到击退前
建议在距离 3.5blocks 的时候开始点击,你的 kb 会在攻击被注册之前受到,wtap 的节奏会好把控一点
先手也没关系,只要对方不是长后手第二下 trade 回来就没问题
hard hs 一般称为长后手,更适合打一个出其不意
midtrade ( mt )
典中典啊,可以说这个技巧是 sumo 的必修课
在这里定义玩家进行 midtrade 时上次结束到下次攻击被注册为一个 trade
同样分为 light mt 和 hard mt,对于时机的区别与上述 hs 的相同。light mt 对于较依赖 wtap 节奏的玩家作用不言而喻,hard mt 的作用则与 hard hs 相似
掌握 midtrade 需大量练习,难以言传,多打即可 o.O
关于 mt 时机及更多 mt 相关技巧详见『Early Game』- 『衍生技巧』与『End Game』-『mt 节奏』章节
jumpreset ( jr )
虽然这是作为萌新相较于 wtap 更先听说的技巧,但对于我来说 jr 更贴近被动技巧。一般是用来脱离 combo,或是拥有良好 timing 把控在 mt 被创飞的一瞬间 jr 重置 kb 继续对刀(被创飞的第一刀落地再 tap space会有较高的成功率)
当然也有将其作为主要技巧的且打得也非常厉害的玩家,至少我不行(
相关 jr 重置 kb 实例(主要是水平 kb)计算如下:
$\phantom{找不到数据,Wiki 上的东西太杂了,绝对不是我懒(确信}$
$\phantom{后面如果补这部分的话应该会新开一篇}$
⬆空白?打开 F12 看 awa
run
是的,就是当 runner。
不过也需要注意一下与台边的距离别逃的时候掉了
建议是在与台边比较远的时候被 combo 再逃,一般距离还是 jr 算了
strafe
很多 sumo 玩家往往专注与去拼体重而忘记了走位发挥的作用。除了偏移击退以外,还可以干扰瞄准减少对方注册的点击
一般建议在距离对方 3.5blocks 的时候走位或是贴近后在自己受到击退前开始走位
负 kb
仅仅依靠间隔点实现的极小 kb + 间隔期间前进的位移实现的负 kb
一般适用于对方 wtap 频率不高,同样对于 lowping 的 sumo 对局(50ms 以下)一直卡 3blocks 的对刀有一定的效果
mt 偏移
指在玩家之间相互 midtrade 的间隙通过走位干扰对方瞄准并在下次攻击时改变对方击退方向,一般是在本次攻击之后玩家之间距离拉近或是穿身时使用(这个就需要多打打判断了)
release w
这个技巧归类到心里博弈会好点,这里单独提出来,会累赘一点但是问题不大。
在玩家之间相互 midtrade 时判断本次攻击之后会穿身因此在本次攻击时松开 w 并在对方的攻击注册时重新按下 w 并攻击(在平台边缘并且处于优势位置效果最佳,毕竟要是没处理好穿身后自己就被创下去了)
一般对方会以为穿身往后瞄准(但事实上并没有)被创下台或者进入 combo,另一种就是避免自己进入劣势局面
midtrade 反制(mt cm)
不会。
会了再补 o.O
其实也不算完全会
反制手段要么就是 trade 回去,相互保持 trade(对于不会直接创下台的情况);或者是 spam click(trade 节奏较长的时候)
都是等对面失误
那什么高低位,不懂(
air jump
非常猎奇的我觉得应该不能称之为技巧的技巧,我一般对其解释为在服务端玩家被判定为 onGround 但在客户端玩家视角是在空中进行跳跃重置。非常有用,但是很吃 timing(似乎 1.8 更容易触发?)
mmc 这个赛季(Season 9) air jump 触发的手感挺奇怪的,tap space 到触发 air jump 之间有延迟而且成功概率挺大,感觉 combo 时只要 3combos 没问题基本对面逃不了
backtrack
此 backtrack 非彼此 backtrack,其实就是 misplace 刀
当你和对手的 ping 处于一个暧昧的关系时,你 / 对手会莫名其妙在推开之后老远再摸到一刀(具体怎么触发不知道)
总之当你能触发就能恶心对面,对面触发容易被打红温(
而且一旦能够触发那大概率一长段时间内都能恶心对面(
奇妙😱
在这里将两指分别敲击一次鼠标(一般为 4cps)的操作称为 1tc;一般地,8cps 间隔点简称为 2tc,12cps 为 3tc
这只是一种叫法,与有没有触发双倍点无关
如果能够做到在短时间内的 4tc 点击,那你的 kb 将会进化到下一个等级
2tc 和 3tc 的点击建议放长一点,不然点击之间间隙太大容易创飞
忘记从哪看的聊天记录了简单来说就是在 1gt 内你的点击无限接近于 12cps 并且不超过这个值(还是 13cps 来着我忘了),能够触发 double hit
真实性待考证,仅基于不可靠理论推测
(不太合法的)减 kb 的新思路
在解释这个思路之前,补充一点理论:
Minecraft 中所有可运动实体都有一个 motion 的属性,这个属性可以认为是一个存储速度及运算其中间量和一个三维向量的容器;速度属性被表示为在一段时间内表示坐标值变化量的三维向量。但是此处不对这两个概念进行区分,后文不管是用 motion 还是速度可以相互转换
三个非常重要的公式
$\frac{dv}{dt} = a - fv$
$\frac{v}{v_0} = \lim\limits_{t_0 \to 0} (1 - f t_0)^{\frac{t}{t_0}}, a = 0 $
$v = v_0 e^{-ft} + \frac{a}{f} (1 - e^{-ft})$
其中 $f$ 是阻力系数(这里仅考虑空气阻力和点击受到的减速效果的阻力,这里称为点击阻力),$t_0 = 1 gt$
虽然都很重要但最重要的是公式三,其描述了物体从初始到任意的整个时间 $t$ 内,速度的完整变化过程。但最重点的不是这个😶🌫️
最最重要但很显然的是,在 Minecraft 中这一设定无法精准实现,Minecraft 中最小的时间单位是 1gt,$dt$ 和 $t_0$ 显然无法小于 1gt。游戏中对于这一运算的实现简单来讲则是通过将受阻力的各轴对应的 motion 减去对应轴上以 $gt^{-1}$ 为单位的阻力系数的乘积;从物理上来说,实体在 $t_0$ 内的 motion 可以看作一个平均值 M,那么 motion 与阻力系数的乘积可视为其在 $t_0$ 时间内的冲量,即:
$M_{作用后} = M_{作用前} - f t_0 M_{作用前} = (1 - f t_0)M_{作用前}$
其中 $(1 - f t_0)$ 在这里称为速度乘数,记作 $k$。也就是说,实体受到阻力的本质就是 motion 乘以了这个数。这也就是为什么你刷到的有些减 kb 的分析很多是说速度乘以 0.6❗
接下来该进入正题了
这里暂且不考虑击退刻的问题,假设玩家的 1s 内一直都受到击退(当然实际情况不是这样)
对于分布均匀的 dbc 20cps,放到时间轴上大概长这样(左键可放大):

玩家的 double click 每次只落在了一个 gt 上
其 motion 受到阻力作用的时间(只考虑点击阻力)只有 10gt
如果将点击偏移一下(大概半个 gt 左右):

double click 的第一下落在上一 gt 末,第二下落在下一 gt 的开始,两次 click 分别落在了两个 gt 上。这样玩家在 1s(20gt)内都能受到阻力的作用
恭喜你成为了胖子🥳
当然在现实中你的点击肯定不会这么均匀
真实的点击大概长这样:

注意到 16cps 时受到阻力作用的时间只有 8gt(情况好点可以到 9gt)
20cps 基本可以稳定 10gt(同样,情况好点能到 11gt 或更多)
24cps(在上述片段中)阻力作用时间为 13gt(实际情况下应该是在 12gt 到 14gt 之间)
显然,16cps 的体重与 24cps 的体重完全不在一个水平线上
但我们讨论的重点不在这里
是否可以写一个 mod,在客户端接收到 double click 时候,自动将点击修正到两个 2gt 之间?
虽然说 Minecraft 每秒只有 20gt,也就是说我们的修正操作只能按 gt 进行。但是我们完全可以判断当前 gt 是否存在 double click,若存在则将 double click 的第二下在下一 gt 开始的时候发包出去(我这里写的可能不是很清楚,我没做过 mod,客户端与服务端之间的通信了解的也不多,这里只是提供一个思路,不保证完全可以实现)
修正后的点击:

但是修正后点击太均匀可能会触发 ac 检测(随机触发就好了)
注意在模拟的 24cps 点击图中第 6gt 末的点击是在第 7gt 才触发。最后一个 gt 中较后的点击将会移到下一秒(即在点击之后处理)
逻辑有点乱,大概思路是这样
毫无疑问有一个默认打法然后基于此打法进行衍生是极其重要的(听君一席话,如听一席话😶🌫️
建议你的默认打法满足以下几点:
比如我现在的默认打法就是接近时(大于 3blocks)开始点击(3tc),如果是先手第二下 mt,之后的对刀 wtap + 2tc / 3tc(根据情况交替)
当然这只是一个参考,第一刀同样可以 wstap 进行试探再根据对手打法调整
阅前提醒:对于「特定场景」显然无法全覆盖,所以在这里仅举例一些(或许算是)典型的场景(慢慢填)
在默认打法下,对方比自己重(kb 相对较小),且无其它转换先后手的技巧(或此类技巧极少)
增加间隔点频率,尽可能多的 3tc 点击,在保证自己 kb 不会乱飘的同时适当增加 3tc 点击密度(尽量保持 3tc 本身的点击次数)
根据距离选择性 wtap,离得近就 wtap 增加对方受到的 kb,反之亦然
对手打法上总体偏向后手,先手刀较少
尝试比对面更长的后手(出其不意的后手),预判后手穿身或者 release w + hs。在 kb 上势均力敌那就抓推开后第一刀的失误
面对这类玩家第一刀十分重要
对手点击间隙较短(或大部分是 spam click),不要尝试放长 hs 或是长 mt,因为在第三刀后你大概率会莫名其妙空刀然后被推飞
慢节奏的 3tc 或 4tc 的点击去拼 kb 就行,spam click 的体重一般是拼不过间隔点的
面对 lowping 的对手一定要 first hit + spam click,不然那距离太抽象了
如果对方开局就是 wa 或者 wd 那大概率是短后手,wstap + spam click 基本可以保证开局不被推飞,并且能够在接近 3blocks 的距离下有较好的先后手转换容错
如果对方是接近时 strafe 那就顺着对方的方向按 a / d,预判错了页面也没关系,spam click 可以保证第二下不会推太远
如果感觉对面的 kb 突然变小或是垂直 kb 突然变高那大概可以推断对方 jr,可以尝试放短 mt
对方特别重并且 holdw 能够获得负 kb,可以 wtap 硬推或者对刀按 ad 飘对面
关于 mt 技巧的推测见下一 part 『End Game』-『mt 节奏』
only hs
几乎每一刀都是 hs,这对于稍微有些经验的对手很容易识别并 stap 反制
预判型 jr
此处指的是将 jr 作为主要技巧并且预判 jr 的占比比较大的玩家
mt 预判失败直接就飞了
pinger ( over 130ms )
在有加速 ip 或者加速器的情况下请不要用高 ping 玩❗❗❗虽然高延迟玩的少但是据我了解第一刀打法主要就是侧身 + 长 hs 很容易反制,同时这样的 ping 在对刀中节奏非常不好把控
130ms 左右的延迟都还算可以接受(比如 boxing 圈有 BBDL 这样非常强势的 130ms 玩家),再高真的不行了
其实 90ms 以上的延迟打起来就已经很难受了
更多关于延迟的讲解见下一部分『关于延迟』
trader
对刀严重依赖 mt,具体表现为每一刀的攻击节奏慢于正常节奏 $\phantom{比如某个lowA Tier}$
很难说这个习惯是好是坏,trade 节奏不好很容易失误(但是又很难定义「不好的 trade 节奏」,大概就是你和对面相互 trade 但是先后手基本没变或是同时,然后你 spam click 不再 trade 拼几刀后对面会被推走,对面就是「不好的 trade 节奏」)
瞄准不好的话被对面 mt 偏移直接被创飞
不想写了,有空再填这个坑(🤐
The ULTIMATE Guide to PING - sceyna
现在好讨厌打 lowping 的对局,尤其是在 wihar,小的 kb 拼不过,技巧在 lowping 的作用很小😕 $\phantom{kb 还很猎奇}$
但是 wihar 新赛季(Season 4)的水平 kb 好像变小了,可以直接推了(
众所周知 ranked sumo 是 ft3,因此在第一局中判断对手打法以及在比分 2 : 2 时稳定心态十分重要
如何心平气和地打 ranked sumo如何快速破防红温💀:
被黑客飘
被疯狂打出 misplace 刀
遇上 elo 核弹被吃满 elo
开局预判对手先后手失败直接创飞,0 : 1(微红❗
开局预判对手先后手失败直接创飞,2 : 2(破防❗❗
开局预判对手先后手失败直接创飞,2 : 3(虾波❗❗❗
🤬🤬🤬
但是说真的,ranked 打多了还真不太容易被整红温😗
建议整个不在意数据的小号打打
嗯很好地水了一小章
根据对手的习惯一般分为两类 trade 节奏:持续性 trade 和反 trade
持续性 trade 节奏
双方保持相互 trade,即很少情况 spam click 转变对刀节奏
经典的反制手段是在三次 trade 后 spam click 加快对刀节奏,对手点击间隔长的话会直接被创飞(更多时候需要根据 trade 间隔时间判断)
另一种是叠 4tc 点击拼体重(对于体重较重的)
反 trade 节奏
本质上是对手为了转变先后手而进行 mt,可以理解为间歇性 mt
要么就是你 trade 一下我 trade 一下看谁先失误
要么就让着对面后手拼体重
最好的办法是推开之后抓第一刀失误
待补
在高手玩家对局中想要得到以下两类观赏性的 sumo clip 几乎不可能(偏向于技巧上的观赏,但是对于圈外显然无法 get 到其观赏性),所以更多会是演员或者有点技术但不多的小趴菜 o.O
不保证说法完全正确,大概意思没问题就行(
circle combo ( + style )
关于环绕 combo,国外有非常高质的创作者,见 iusehuzuni 的频道(这家伙一年没更新了www~)
combo style 则是在环绕 combo 的过程中进行 style,大致可以理解为 boxing style,但相比于 boxing style,sumo 的 style 对瞄准、控距要求更高,节奏也与 boxing 差异很大
hitting style
在对刀中的 style,对瞄准及注册到的点击要求较高(此处注册到的点击指的是你的 hits 注册到实体的数量),需要对方较低的 wtap 率和几乎没有的技巧
特别适合跟那些没脑子的 ac 晃悠
建议在尝试观赏性 sumo 之前,多打打 boxing,当你的 boxing style 观赏性足够高的时候 sumo 的观赏性也不会差🥰
国内目前几乎没有观赏性较高的 sumo 玩家,尤其是 hitting style。嗯对除了我 QAQ
原本是想把这个放到打法优化那个 part 写的,在这里单独列出来
很久以后再补吧 QAQ
Minecraft 实体运动研究与应用 - lovexyn0827
The only Minecraft sumo guide you’ll ever need - crracky
How Minecraft PvP really works - luzis
The ULTIMATE Guide to PING - sceyna
25.10.6 -(主体)完结撒花🥰🥳🎉
超级省流版:



分辨率一般建议缩放为 1080p,略微提高分辨率后期做视频的容错更高,酌情选择
录制帧数 120fps 在进行渲染后基本够用,B 站压缩后与 240fps 的渲染差别应该不大
如果你的内存足够用的话选择 H.264 编码器也无妨。
240fps、1080p 的录制规格下 H.264 的视频码率大约在 100mbps 左右?
240fps、2k 的录制规格下 H.265 编码的视频码率大约在 45mbps 左右,以此类推
目标质量设为 16 为视觉无损,大部分情况也够用,更高的质量带来的视频观感的提升并不明显


blur-weighting 建议选择 vegas(目前观感最好)
插针倍数(interpolated fps)不建议超过 6x,建议使用 svp 而不是 rife 模式进行插针(否则您将会体验到非常慢的速度以及报错的视频)
AI 补帧(pre-interpolation)视个人喜好而定,开启画面会有一种粘腻感。倍数越高,渲染速度越慢
质量(quality)拉到 16 差不多就够了,似乎是因为软件没有进行多次编码导致输出文件过于庞大(如果您不在意内存消耗可以掠过)
如果你遇到了与该 issues 相同的问题,尝试关闭 GPU 编码,如果还是不行,请重置渲染设置
更多详见 README