Skip to content
是封面

从 /stopwatch 开始: 与时间检测有关的一些胡思乱想

摘要

本文通过比较不同计时方式在不同状态下的区别,提供了能够对玩家暂停游戏、玩家电脑休眠以及退出世界等行为进行时间监测的方案。

25w41a的最新 /stopwatch 命令可以创建一个秒表用于计时,实际使用下来,这个指令有一些令人惊喜之处,但也有十分令人沮丧的特性。

不过仔细想来,这一指令确确实实提供了时间检测的新方式,这一方式可以和其他方式并行使用达成互补。 所以,笔者觉得是时候在此简单总结一下数据包中所有可以用于检测时间的方案了。

一、计时方式

1.1 命令系统依赖型计时

这些计时方式依赖命令系统的运行,命令系统暂停时这些计时方式会停止。

1.1.1 计分板自增

这种计时方式非常简单直接: 取一个计分板项,初始化后每刻将其自增1。该自增指令可以由tick函数或循环命令方块完成。 也可以在计分板中为每个玩家分别自增,此时可以使用标签或目标选择器技巧完成特定玩家状态的筛选。

1.1.2 游戏时间 gametime

gametime 记录了游戏从开始运行以来经过的游戏刻数,可以通过/time query gametime查询并返回至计分板。

1.2 玩家依赖型计时

这些计时方式依赖玩家运行,玩家未在世界中时这些计时方式会停止。

1.2.1 游玩时间 play_time

计分板准则 minecraft.custom:play_time 自动记录每一位玩家的游玩时间,精确到游戏刻。

1.2.2 世界时间 total_world_time

计分板准则 minecraft.custom:total_world_time 在每一位玩家的计分板中记录对该玩家来说该世界实际打开的时间,精确到游戏刻。对服务器玩家而言该项始终与上一项相等。

1.3 命令方块时间

该方式必然会对世界产生改变。放置命令方块执行任意命令,其输出值会返回一个时间,精确到秒。可以使用字符串切片以及宏的方式将其转存至计分板完成分析。

1.4 秒表 stopwatch

新版本的 /stopwatch 指令可以创建秒表,以毫秒精度完成计时,并且可以以毫秒精度在/execute if stopwatch进行匹配。

不过令人十分沮丧的一点是,/stopwatch query始终向计分板返回1,并且这是官方有意为之(见MC-302701)。因此,我们如果需要得到秒表的现有时间,则仍然需要放置命令方块并取得其输出值(以double形式存储,精确到毫秒),因此仍然会对世界产生改变。

补注

本文发布前一天的官方快照版本25w42a中,/stopwatch query指令已经可以返回当前的时间值(整数)且可以进行比例缩放,因此现在不再需要取得命令方块输出以取用精确到毫秒的秒表读数。

1.5 (补)worldborder计时

可能会有读者好奇25w41a官方更新日志中介绍/stopwatch时在Developer's Note处提及的/worldborder计时方式是什么样子的。

由于笔者没有使用相关计时方式的经验,笔者请教了巨佬 @小豆8593 ,得到回答如下: (略有改动)

世界边界始终以毫秒为单位进行扩张/收缩,使用命令/worldborder set <distance> [<time>]可以控制该扩张/收缩的进程,而使用命令/worldborder get可以获取目前世界边界的距离数。
因此,通过合适地设定世界边界的扩张/收缩速度,我们可以通过作差方式获取前后两次操作的边界距离差值,除以速度后转换为时间差值。
例如,用1000秒的时间扩张1000000格世界边界,即扩张速度为 1格/毫秒/worldborder set 1000000 1000)时,前后两次获取的世界边界差值即为精确到毫秒的时间差值。

以下为一个例子:

mcfunction
# 瞬间设置好初始位置
worldborder set 1000000 0
# 开始测时
worldborder set 5000000 4000
# 测时的命令上下文
...
# 恢复边界
worldborder set 5000000 0

在25w41a版本前,该计时方式基于现实时间,即可以将其计时区间视为与/stopwatch相同。但根据Developer's Note,之后的版本中worldborder将基于游戏刻运行,即其计时区间和精度变更为与命令系统依赖型计时方式相同。

二、时间状态

2.1 运行的游戏刻内

对单人模式而言,运行的游戏刻是不在tick freeze状态且不在暂停界面的状态。(单人模式下除暂停菜单、暂停类对话框和F3+Esc暂停之外的其他UI界面都会使游戏继续运行。) 对于多人或局域网模式而言,则暂停界面下游戏仍会继续运行且算作该玩家仍在游玩。

这一状态下游戏本身以及指令系统都会以默认20tick/s的速度运行,因此以上六种计时方式全部正常运行。

2.2 运行但未在游玩的游戏刻内

该状态存在于无玩家登入的服务器中,玩家依赖型计时不可用,其他方式仍然可用。

2.3 游玩但未在运行的时间内

使用/tick freeze可以达到无正在运行游戏刻的游玩状态,此时命令系统依赖型计时会暂停,但由于玩家仍在世界内,玩家依赖型计时方式将全部继续。

2.4 既未在运行又未在游玩但仍打开世界的时间内

单人模式下暂停游戏会进入该状态(无论是否tick freeze)。此时前三种计时方式暂停,后三种继续。

2.5 掉刻或电脑休眠时间内

虽然前四种计时方式或依赖或不依赖运行游戏刻,这些方式全部依赖非运行游戏刻而非系统时间进行计时。 也就是说,在暂停状态下掉刻(世界仍然打开)时,前四种方式全都不会运行。 电脑睡眠/休眠会被服务端记录为掉刻,且该掉刻数值可能达到数万甚至数十万刻。

这种掉刻由服务端登记,发生时会在日志中打印一行消息。

2.6 关闭世界或关闭游戏的时间

由于命令方块方式直接获得的是现实时间,我们可以借此追踪在24小时范围内的世界关闭到重新打开的时长。此时前五种方式均不运行。

2.7 总结

游戏状态计分板自增/time query gametimeplay_timetotal_world_time/stopwatch命令方块
单人模式正常运行
单人模式下暂停游戏
单人模式下/tick freeze
多人模式正常运行(有玩家)
多人模式玩家进入暂停菜单
多人模式正常运行(无玩家)
掉刻或电脑休眠
退出世界

三、时间比较

综合以上结论,我们可以使用不同计时方式之间的差异追踪一些命令系统本身难以触及到的时间段。

3.1 单人模式玩家暂停

单人模式下暂停游戏的总时长可以通过仍然在运行的计时方式(如total_world_time/stopwatch)追踪,此时前三种计时方式不会运行。因此,只需比较前三种计时方式与后三种计时方式在某一特定期间的差值即可确定暂停时长。示例代码如下:

mcfunction
# 初始化 (load)
scoreboard objectives add test0 minecraft.custom:total_world_time
scoreboard objectives add test1 minecraft.custom:play_time
scoreboard objectives add test00 dummy
scoreboard objectives add test11 dummy
scoreboard objectives add test01 dummy

# 在玩家处每刻执行 (tick)
scoreboard players operation @s test01 = @s test0
scoreboard players operation @s test01 -= @s test00
scoreboard players operation @s test01 -= @s test1
scoreboard players operation @s test01 += @s test11

scoreboard players operation @s test00 = @s test0
scoreboard players operation @s test11 = @s test1

运行后当单人模式游戏暂停后,玩家的test01计分板值将存储其以刻为单位的暂停时长。(未暂停时始终为0)

当然,也可以在命令系统每次运行时重置stopwatch来检测这一差值,但操作上不算非常方便。

3.2 单人模式玩家电脑睡眠/休眠

电脑休眠与游戏暂停的差值体现在total_world_time计分板中。因此,我们可以通过比较total_world_time计时方式与stopwatch计时方式的差值即可确定电脑休眠时长。

由于前述原因,stopwatch计时方式需要放置命令方块来获取其对应时间,略微麻烦,此处不提供示例代码。

3.3 在24小时内重新进入世界

通过持续检测命令方块时间的方式,我们能够获取世界未打开的具体时间。为避免命令系统未运行的情况干扰,我们将把该时间差值与stopwatch计时方式进行比较,此时可以直接将两者合并在一个循环命令方块中执行(命令方块持续执行/stopwatch query,在输出位置可以同时取得两种计时方式所需的数据)。

当然,由于该方式只会返回时间而无法返回日期,世界关闭24小时以上时该方式也会出现误差。

四、总结

笔者实在也不太清楚什么样的地图/需求会需要检测玩家休眠或者退出世界的时间的,不过既然有这一可行性存在,也就算是给各位提供一个灵感吧。

Powered by Vitepress and Github Pages