游艺街改版教程 - 添加新的小游戏

游艺街改版教程 - 添加新的小游戏

小猪之最Thepig Lv2

写在前面

这是一个「由Hex-Dragon所制作的《游艺街》 的改版教程,作者是《游艺街 小猪之最改版服》(下简称《LTW派》)的作者小猪之最Thepig。

游艺街也出来一段时间了,但是改版里面目前貌似只有我这个版本还在维护……刚好新的最终轮游戏也做出来了,就先写篇教程解析一下龙猫他们写的数据包吧。本教程以《LTW派》的山丘之王游戏为例讲解。

注意本教程并不负责MC命令和数据包基础知识。

本教程的修改版数据包存放在GitHub 上,包含了我制作的山丘之王小游戏的场景结构。你可以直接将《游艺街》的原版数据包替换为修改后的数据包然后删除原有的山丘之王结构来体验一下这个新的小游戏。白嫖党狂喜

官方的小游戏策划建议

以下内容来自函数文件ltw/functions/main/player_enter.mcfunction内的注释,我将其进行了一定的排版处理。

小游戏最佳实践

  • 需要有玩家交互
    即使是间接交互也比没有好,PVP 击退可能是不错的选项,但最好不要纯 PVP 砍人
  • 必须能体现玩家的技术或策略
    单纯的狂点左键或长按 W 是没有意思的,至少要给玩家发挥的空间
  • 结果不能仅依靠玩家的熟练度
    全靠玩家水平不是说不行,而是这不适合“派对游戏”,跑酷就是一个负面例子
  • 规则应该尽量简单易学
    除非必要,不要增加单个游戏的规则复杂度,这会提高学习成本,让新人难以入门
  • 在制作时考虑给道具留下发挥空间
    玩家有远程攻击,有鞘翅,有金苹果,有护甲和剑,有击退棒,让它们可以得到应用

第零步 - 准备工作

A. 工具准备

  1. 在你的电脑上安装Visual Studio Code
  2. 在VSCode里安装扩展Data-pack Helper Plus
  3. 关闭VSCode,因为我们第二步才会用到它。

B. 策划小游戏

在开始制作之前肯定先要想好你的游戏玩法啦~这里给出山丘之王的规则参考:

比赛场地的中央平台地势较周围高,同时中央平台有一块红色区域,玩家可以通过楼梯到达中央红区。每0.25秒(在《LTW派》中是0.5秒),处在中央红区的玩家会获得1点能量值。当剩余时间小于1分钟时,获得的能量值提升为2点。每位玩家在中央区域都有一个附魔击退II的击退鱼,可用于击退其他玩家。在周围区域时,击退鱼的击退等级会变为IV级,可用于击退中央红区的玩家或阻止玩家到达中央红区。在游戏区域的四角会有奖励物资生成,玩家可以舍弃中央红区的能量值来尝试抢夺生成的奖励物资。

在《LTW派》中,奖励物资大多数情况是能量值,少数才会是全局物品(或积分)。教程中将仅演示生成全局物品(或积分)的奖励物资写法。

第一步 - 小游戏场景的处理

游艺街各个小游戏的场景普遍比较小,并且如果你从主大厅切换旁观者模式朝外边飞,会发现游戏的场景原型都建在主大厅的外面

但是我们在真正玩游戏的时候,并没有在游戏场景周围发现其他游戏的场景或者主大厅。这是因为在每轮游戏开始时,游戏场景都会用结构方块重新加载。从数据包中可以发现,我们游玩每个小游戏的位置都在x=1000z=1000nn为正整数)附近。

因为小游戏中的许多场景内的方块都会在游玩时发生改变(例如地陷圣坛),所以使用结构方块保存并在每局游戏开始前调用结构方块进行加载可以很轻松地使游戏场景恢复到最初的状态。即使你确信你的游戏场景不会被轻易改变,使用结构方块保存仍然可以减少测试中出现的不可抗力因素带来的影响。

你可以先在主大厅的附近建造一个场景,然后使用结构方块保存。保存的结构名称中的命名空间最好为mini,方便管理(例如山丘之王游戏的结构名称为mini:koth)。最后前往你的新游戏地点,例如(1000,y,9000),然后放置结构方块并加载你的地图。

你也可以先在自己的世界把地图建造好并用结构方块保存,然后再前往你的存档文件夹,将generated/structures/mini/xyz.nbt文件复制到游艺街存档内相同的位置。

需要注意的是,原版的结构方块最多只能储存48x48x48的内容。如果你的游戏场景比最大范围还大,那就需要多个结构方块来储存(例如地陷圣坛)。

第二步 - 小游戏基础框架编写

使用VSCode打开游艺街数据包(服务端文件夹/world/datapacks/LTW3)。

看到我们熟悉的data文件夹和pack.mcmeta文件后,展开data文件夹,会发现里面有7个子文件夹(命名空间)。

以下为各个文件夹内文件的主要功能(可选择性阅读,我们修改的主要是mini文件夹的内容):

  • backup:备份文件夹。里面有一个压缩包文件slime.zip,储存的是官方废案小游戏《夺命足球》的函数文件。

  • item:与游戏内物品有关的文件夹,例如奖励积分、奖励物品、商店物品和游戏中锁定第九格物品栏等。存放奖励物品的战利品表也在这个文件夹里面。

  • lib:一些常用的函数库。例如random随机函数、sounds常用音效库等。

  • ltw:全局游戏相关文件夹。下面是其中functions文件夹中各个子文件夹和文件的介绍

    • advancement子文件夹表示玩家达成进度时执行的函数,即给予绿宝石+增加统计数据。在之后的进度编写中需要调用这些函数。

    • clock子文件夹内为游戏中周期性执行的函数(tick1、tick2等),但我们要修改的并不是这里的内容。

    • main子文件夹内是一些全局事件函数。

    • state子文件夹里面的子文件夹表示服务器的各个状态:0表示等待中,3表示小游戏预处理,4表示展示小游戏,5表示小游戏进行中,6表示小游戏结束且选择奖励前,7表示选择奖励时。

    • init.mcfunction函数可以重置服务器状态,测试时如果想重置可以输入命令/function ltw:init来重置服务器状态。

  • minecraft:原版相关文件夹。因为游艺街服务器禁用了原版数据包,取消了原版进度和可直接通过获取物品获得合成表的功能,所以需要在minecraft命名空间内添加原版的一些内容。

    • 但是,loot_tables/gameplay/fishing.json似乎并不是原版的,原因你自己拿鱼竿钓一下鱼就知道了。
  • mini:小游戏相关文件夹。里面存放了和各个小游戏相关的文件。我们所做的修改和添加都是围绕这个文件夹进行的。

  • test:测试相关文件夹。提供了测试各个小游戏和奖励物品、跳过剩余时间和将剩余时间设为「无限长」的方法。

A. 新建文件夹!

俗话说的好,当你新建一个文件夹时,项目就正式开始了。

事不宜迟,我们立马建一个,在mini/functions/目录下。

koth为King of The Hill(山丘之王)的简称。在此请为你自己的小游戏取一个相对短而好记的名称。

在正式开始之前,先给你的小游戏取一个编号。小游戏分为三种类型:首轮小游戏、普通小游戏和终轮小游戏。

  • 首轮小游戏的编号为20*(例如核爆刺客为201)
  • 普通小游戏的编号为*(例如地陷圣坛为2,月夜狩猎为5)
  • 终轮小游戏的编号为10*(例如钢铁狂潮为101,贸易专家为102)

如果你需要写一个普通小游戏,编号最好从6开始。当然,官方用过的编号只有1-5、101、102和201,你想定什么编号都可以。在此我将山丘之王的编号定为6。

B. 应该写什么?

一个完整的小游戏应该至少包含以下函数文件:

  • game_end.mcfunction游戏结束执行的方法。至少包含删除强制加载区块的命令。

  • game_init.mcfunction初始化。一般还要有game_init2.mcfunction用来清理残余实体,初始化操作复杂的游戏还要有game_init3game_init4等等。

  • game_start.mcfunction游戏正式开始时执行的方法。

  • give_effect.mcfunction:玩家进入游戏时会被给予的状态效果(夜视、抗性等等,视游戏类型不同而定)。

  • player_enter.mcfunction:玩家进入游戏时,在玩家自己身上执行的方法。这里的玩家既包括游戏开始时服务器内的玩家,也包括游戏中途加入的玩家,因此需要对这两种玩家分别进行处理。

  • player_intro.mcfunction:展示小游戏时,在每个玩家自己身上执行的方法。一般是将玩家传送到游戏场地、给予夜视和使用tellraw命令对玩家展示游戏规则三条命令。

  • tickx.mcfunctionx为1、2、5或20。表示游戏过程中周期性执行的方法,例如tick1为每1游戏刻(约0.05秒)执行一次。一般的周期性命令都放在tick2中,每秒执行的命令放在tick20中。

  • watcher_limit.mcfunction限制旁观者的活动范围。如果没有这个方法,那么旁观者甚至可以在游戏时跑到主大厅去,并且会加载很多不必要的区块,影响服务器性能。

    实测直接用命令/gamerule spectatorsGenerateChunks false进行旁观者加载区块限制会出现bug,某些游戏结束后的区块不能正常加载。

这些只是必须有的文件。除此之外,game文件夹和一些其他的文件一般也会存在,用来封装特定游戏内的事件等。实际上,这些函数都是由mini/main/目录下的同名函数调用的。因此在你的文件夹添加这些函数时,确保在mini/main/目录下的函数内添加了调用这些函数的命令。例如:

data/mini/functions/main/game_init.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 计分板初始化
scoreboard players reset * mini_score
scoreboard players reset * mini_heart
scoreboard players reset * health

# 小游戏初始化
execute if score $mini_type mem matches 1 run function mini:parkour/game_init
execute if score $mini_type mem matches 2 run function mini:tntrun/game_init
execute if score $mini_type mem matches 3 run function mini:hotpm/game_init
execute if score $mini_type mem matches 4 run function mini:colormatch/game_init
execute if score $mini_type mem matches 5 run function mini:phantom/game_init
## 下面一行是你新添加的
execute if score $mini_type mem matches 6 run function mini:koth/game_init

execute if score $mini_type mem matches 201 run function mini:ass/game_init
execute if score $mini_type mem matches 101 run function mini:iron/game_init
execute if score $mini_type mem matches 102 run function mini:trade/game_init

其他文件也类似,把game_init更换为对应的文件名即可。至于main文件夹内的这些函数是如何执行的,你无需多操心,因为龙猫他们已经把调用这些函数的方法写好了。

C. 游戏开始前的操作

首先找到你在第一步中放结构方块的地方,把它的坐标记下来。在我这里是985 4 8985

然后在你的游戏场景附近找到一个与游戏场景大小相等或稍大的正方体,确保把你的游戏场景完全包括进去。记下正方体体对角线上的两个点的坐标,类似于你用WorldEdit等工具划范围。在我这里是985 5 89851016 36 9016

然后编写旁观者范围限制和场景初始化命令。

data/mini/functions/koth/watcher_limit.mcfunction

1
2
3
4
# 观察者限制飞行范围
## dx dy dz确定范围大小,可用确定的两点对应坐标相减获得这三个值
execute as @a[gamemode=spectator] unless entity @s[x=986.0,y=5.0,z=8986.0,dx=30,dy=31,dz=30] run spectate
execute as @a[gamemode=spectator] unless entity @s[x=986.0,y=5.0,z=8986.0,dx=30,dy=31,dz=30] run tp @s[team=!debugging] 1001.0 14 9001.0 -45 90

data/mini/functions/koth/game_init.mcfunction

1
2
3
4
5
6
7
# 初始化山丘地图
## 将游戏场景区域标记为强制加载,确保初始化操作的顺利进行
forceload add 985 8985 1016 9016
## 放置结构方块,用于生成游戏场景。这里的结构方块posX、posY、posZ配置应该与你在第一步中放置的结构方块一样
setblock 985 4 8985 structure_block[mode=load]{metadata: "", mirror: "NONE", ignoreEntities: 1b, powered: 0b, seed: 0L, rotation: "NONE", posX: 0, posY: 1, posZ: 0, sizeX: 32, sizeY: 32, sizeZ: 32, mode: "LOAD", integrity: 1.0f, showair: 0b, name: "mini:koth"}
## 在结构方块边上的一个方块处放置红石块用于激活结构方块。由于放置后红石块即被游戏场景取代,因此不需要考虑放置失败的问题
setblock 985 5 8985 redstone_block

接下来是对游戏全局的一些操作,加在上述命令的后面。

data/mini/functions/koth/game_init.mcfunction

1
2
3
4
5
6
7
8
9
10
## 设置游戏结束后排名显示中是否显示玩家所获得的小游戏内分数。这里山丘之王小游戏的排名是依照 HUD 右侧计分板显示的能量值排序的,因此游戏结束后可以对所有玩家显示分数(能量值)。若你的游戏为竞速类、死亡淘汰类或者其他不使用自定义分数排名的小游戏,请将该值设为0
scoreboard players set $show_score mem 1
## 重置奖励物品生成的冷却时间
scoreboard players set $new_item_cd mem 0
## 这是一个冒险模式小游戏,因此生存模式的值为0。如果你做的是一个生存模式小游戏,请将该值设为1,以防止系统锁定第九格物品栏。
scoreboard players set $survival mem 0
## 重置每0.1秒减少1的计时器,这条命令仅在你的游戏不需要使用这类计时器的情况下添加
scoreboard players set $countdown_fast mem 0
## 是否启用与遗迹寻宝小游戏中类似的节奏方块,如果你的场景中没有这类方块可以不写该命令
scoreboard players set $tempo_enable mem 0

如果你的游戏还有其他需要初始化的全局变量或玩家变量,一并写在game_init.mcfunction中。例如:

data/mini/functions/koth/game_init.mcfunction

1
2
## 重置能量值计分板
scoreboard players reset * power_count

如果你的游戏中出现了和我一样的原版游艺街没有出现过的计分项(例如power_count),需要在init.mcfunction中添加:

data/mini/functions/koth/init.mcfunction

1
2
3
# 重置计分板
scoreboard objectives remove power_count
scoreboard objectives add power_count dummy "能量值"

data/mini/functions/main/init.mcfunction

1
2
3
4
5
6
7
8
9
10
# 初始化
function mini:tntrun/init
function mini:hotpm/init
function mini:phantom/init
## 山丘之王的初始化方法
function mini:koth/init

function mini:iron/init
function mini:ass/init
function mini:trade/init

接下来是伤害管理:

data/mini/functions/koth/game_init.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 伤害管理
## 官方的函数中总有这一条,原本是用来控制消除金苹果/附魔金苹果/不死图腾产生的伤害吸收效果的,但是这个值设为0或1都会清除效果,所以设置这个值似乎并没有什么意义
scoreboard players set $remove_resistance mem 0
## 是否能够对玩家造成伤害与击退
team modify playing friendlyFire true
## 玩家碰撞设定
team modify playing collisionRule always
## 是否显示玩家的死亡信息
team modify playing deathMessageVisibility never
## 火焰伤害是否启用
gamerule fireDamage false
## 摔落伤害是否启用
gamerule fallDamage false
## 溺水伤害是否启用
gamerule drowningDamage false
## 自然生命恢复是否启用
gamerule naturalRegeneration true

接下来是剩余的初始化操作,一般是清理/生成实体对地图进行一定的初始化,放在上述初始化之后0.75秒进行。在山丘之王小游戏中只需要清理实体即可。

data/mini/functions/koth/game_init.mcfunction

1
schedule function mini:koth/game_init2 15t replace

data/mini/functions/koth/game_init2.mcfunction

1
2
3
# 清理残余实体
## 如果你的游戏中没有出现箭、物品、苦力怕和三叉戟以外的物品,直接调用mini:main中的清理实体方法即可
function mini:main/kill_entity

然后是对你的小游戏进行介绍,可以仿照官方写的介绍进行编写。「免疫击退效果在本轮无效」是无奈之举,一般的游戏需要尽量避免这种情况发生。

data/mini/functions/koth/player_intro.mcfunction

1
2
3
4
5
6
7
# 向单个玩家展示小游戏介绍
## 给予夜视效果
effect give @s night_vision 1000000 0 true
## 传送玩家到游戏场景
tp @s[team=!debugging] 1001.0 14 9001.0 -45 90
## 向玩家展示介绍
tellraw @s ["",{"text":"\n >> 山丘之王 >>\n\n","color":"gold","bold":true}," 保持在红色区域即可 ",{"text":"获得能量值","color":"gold"},"。\n 能量值最多的玩家即可获胜!\n\n",{"text": " <只能击退玩家> ","color":"yellow"},{"text": " <免疫击退效果在本轮无效>","color":"light_purple"},"\n"]

D. 游戏开始时的操作

主要包含三个方法。首先是游戏开始时对小游戏整体的修改。

data/mini/functions/koth/game_start.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 游戏结束倒计时,我这里为120秒
scoreboard players set $countdown mem 120

# HUD,通常为玩家所看到的游戏相关信息
scoreboard objectives setdisplay list power_count
scoreboard objectives setdisplay belowName power_count
scoreboard objectives setdisplay sidebar power_count
## boss栏颜色,1,2,3,4分别代表红、黄、绿、蓝,0代表按照每个玩家在bossbar_color的计分项内的分数对每个玩家显示不同颜色的boss栏(例如炸弹狂魔里面获得炸弹与未获得炸弹的玩家看到的boss栏不一样)
scoreboard players set $bossbar_color mem 4
## boss栏倒计时的最大值
scoreboard players set $countdown_max mem 120
## boss栏类型,0,1,2分别代表关闭boss栏、显示快速倒计时(0.1秒减少1单位)和显示标准倒计时(1秒减少1单位)
scoreboard players set $bossbar_type mem 2
## 调用显示boss栏方法
function lib:bossbar/show
## 更改每种颜色的boss栏的名字
bossbar set mini:red name "剩余时间"
bossbar set mini:yellow name "剩余时间"
bossbar set mini:blue name "剩余时间"

接下来是每个玩家进入游戏时的方法。这里的玩家有三种:参与游戏的玩家team=playing,tag=!rejoining)、未参与游戏的旁观者team=watching)和中途退出而后又加入游戏的玩家team=playing,tag=rejoining)。

data/mini/functions/koth/player_enter.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 单个玩家开始小游戏时执行
## 对自己播放音乐
function lib:sounds/music/mini_middle
## 清理标题显示区
title @s clear
title @s actionbar ""

# 给予状态效果
effect clear @s
function mini:koth/give_effect

# 设置玩家生命
attribute @s generic.max_health base set 20

# 传送玩家
## 此处依据你设定的场景坐标而改。在我的游戏中玩家会出生在空中,因此会有以下这一条给予缓降效果的命令。传送玩家
effect give @s[team=playing] slow_falling 3
tp @s[team=playing] 1001.0 30.0 9001.0

# 调整模式
## 将调试者以外的玩家调整为旁观模式
gamemode spectator @s[team=!debugging]
## 将参与游戏而非中途重新加入的玩家调整为冒险模式
gamemode adventure @s[team=playing,tag=!rejoining]

# 解除免疫击退效果
attribute @s[team=playing,tag=!rejoining] generic.knockback_resistance modifier add e0edf3eb-5aea-4a18-9b9e-1bde9df27ab5 "" -1.0 multiply

解除免疫击退效果的影响是永久的,因此需要在玩家进入游戏时先取消免疫击退的限制,然后如果需要限制免疫击退才会执行上面的最后一条命令。如果你的游戏中没有这一项则不需要进行任何操作。

data/ltw/functions/koth/player_enter.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
# 存储UUID
execute store result score @s UUID run data get entity @s UUID[0]

# 解除限制免疫击退
## 注意是在这里添加
attribute @s[team=playing,tag=!rejoining] generic.knockback_resistance modifier remove e0edf3eb-5aea-4a18-9b9e-1bde9df27ab5

# 如果是旁观者或局号不对头:开始旁观
execute if entity @s[team=!playing] run function ltw:main/player_enter_watcher
execute if entity @s[team=playing] unless score @s game_id = $ game_id run function ltw:main/player_enter_watcher
# 如果不是旁观且局号正确:游戏中掉线
execute if entity @s[team=playing] if score @s game_id = $ game_id run function ltw:main/player_enter_rejoin

最后是给予玩家状态效果:

data/mini/functions/koth/give_effect.mcfunction

1
2
3
4
5
6
7
8
# 状态效果
## 饱和
effect give @s saturation 1000000 0 true
## 夜视
effect give @s night_vision 1000000 0 true
## 抗性
effect give @s resistance 1000000 4 true
## 如果还有其它状态效果,加在下面即可

E. 游戏结束时的操作

结束时的操作相对简单,只有一个game_end.mcfunction函数。

data/mini/functions/koth/game_end.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
13
# 当前游戏结束时触发
## 取消在game_init.mcfunction定义的强制加载区域
forceload remove 985 8985 1016 9016

# 计算积分
## 游艺街默认使用玩家淘汰的顺序来决定最终的排名,即每有一人淘汰时给剩余还在场上的玩家的mini_score分数加1。如果想要使用自定义的分数进行排名,直接把玩家对应的分数赋值给mini_score即可
## 如果你在game_init.mcfunction设置了$show_score为1,在游戏结束后玩家的mini_score值会在排名列表里显示出来
scoreboard players set @a[team=playing] mini_score 0
execute as @a[team=playing] run scoreboard players operation @s mini_score += @s power_count

# 解除限制免疫击退
## 和之前一样,需要解除玩家的免疫击退限制,让玩家可以在其他游戏中正常使用免疫击退的道具(最好还是加上这句)
execute as @a run attribute @s generic.knockback_resistance modifier remove e0edf3eb-5aea-4a18-9b9e-1bde9df27ab5

第三步 - 小游戏事件编写

一个小游戏只有基础框架是没有任何实际内容的,甚至连游戏都无法正常结束。因此需要为每个小游戏编写自己的事件,这些事件一般会在tick2.mcfunction(每0.1秒执行)、tick20.mcfunction(每秒执行)等周期性执行的函数中触发。本教程只提供山丘之王小游戏的事件编写,但其中的某些内容(例如倒计时检测、奖励物品生成)也可用于其它小游戏。

A. 剩余10s时的倒计时显示

众所周知,大部分规定了剩余时间的游戏都会在剩余时间≤10秒时在subtitle副标题处显示倒计时。这个倒计时的写法直接照搬现成的即可。

data/mini/functions/koth/tick20.mcfunction

1
2
3
4
5
# 显示倒计时
execute if score $countdown mem matches ..10 run title @a times 3 14 2
execute if score $countdown mem matches ..10 run title @a subtitle {"score":{"name":"$countdown","objective":"mem"}}
execute if score $countdown mem matches ..10 run title @a title [""]
execute if score $countdown mem matches ..10 as @a at @s run function lib:sounds/hit2

注意你不需要在tick20里面写倒计时减少的命令,因为龙猫已经在data/ltw/functions/clock/tick20.mcfunction里面给你写好了。

B. 倒计时事件

山丘之王的倒计时事件有以下三个

  1. 剩余90秒时提示玩家30秒后开启双倍能量值。
  2. 剩余60秒时中央红区给予的能量值翻倍,变为2点。
  3. 剩余0秒时游戏结束。

于是我们在tick20.mcfunction里加上各个事件的代码,加在倒计时显示的后面。

data/mini/functions/koth/tick20.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 90s:双倍提示
execute if score $countdown mem matches 90 run title @a subtitle {"text":"⚠ 双倍能量将在三十秒后开启! ⚠","color":"yellow"}
execute if score $countdown mem matches 90 run title @a times 1 90 3
execute if score $countdown mem matches 90 run title @a title ""
## 更改bossbar颜色为黄色,下同
execute if score $countdown mem matches 90 run scoreboard players set $bossbar_color mem 2
## bossbar颜色更改需要重新调用show方法,下同
execute if score $countdown mem matches 90 run function lib:bossbar/show

# 60s:双倍开启
## 这里不直接调整能量值的给予量,给予1点还是2点能量值会在tick5里面判定
## 更改BGM
execute if score $countdown mem matches 61 as @a run function lib:sounds/music/mini_fast
execute if score $countdown mem matches 60 run title @a subtitle {"text":"⚠ 双倍能量已开启! ⚠","color":"red"}
execute if score $countdown mem matches 60 run title @a times 1 120 3
execute if score $countdown mem matches 60 run title @a title ""
execute if score $countdown mem matches 60 run scoreboard players set $bossbar_color mem 1
execute if score $countdown mem matches 60 run function lib:bossbar/show
## 播放音效
execute if score $countdown mem matches 60 run playsound minecraft:entity.ender_dragon.growl player @a 0 900000 0 900000 1.5

# 0s:游戏结束
execute if score $countdown mem matches 0 run function mini:main/game_end

如果你的游戏还有别的倒计时事件,需要一并在该函数里加上

C. 能量值的给予

每0.25秒给予游戏内的玩家1点(双倍能量时为2点)能量值。

data/mini/functions/koth/tick5.mcfunction

1
2
3
# 给予红区玩家能量值
execute if score $countdown mem matches 61.. as @a[team=playing,tag=mini_running] at @s if block ~ 9 ~ red_mushroom_block run scoreboard players add @s power_count 1
execute if score $countdown mem matches ..60 as @a[team=playing,tag=mini_running] at @s if block ~ 9 ~ red_mushroom_block run scoreboard players add @s power_count 2

注意这里用了相对坐标与绝对坐标结合的写法,原因是我的中央红区方块全部是在y=9的位置,且中央红区方块为红色蘑菇块。

游戏中的玩家分为team=playing,tag=!mini_runningteam=playing,tag=mini_running两种。前者表示已经淘汰或结束游戏的玩家(一般为旁观者模式),后者表示正在游玩的玩家。这里如果不对玩家进行限定会出现旁观者获得能量值的情况。

D. 游戏物品的给予

游戏内需要用到的物品一般不会多于1个,且均放在快捷栏第9格。这是一个需要高频触发的事件,因为需要时刻检测玩家的快捷栏第9格是否是对应的物品(击退鱼),以及击退等级是否符合要求等等。

除此之外,如果玩家快捷栏第9格放入了其他的物品,我们还需要将该物品返还给玩家。幸运的是,这个复杂的返还方法龙猫已经帮我们写好了,但我们仍然需要调用这个方法来实现物品返还。

由于它比较复杂,我们通常把游戏物品的给予放在tick2.mcfunction里面,且用一个专门的函数处理该事件。

data/mini/functions/koth/tick2.mcfunction

1
2
3
# 给予击退鱼
execute as @a[tag=mini_running] at @s if block ~ 9 ~ red_mushroom_block unless data entity @s Inventory[{Slot:8b,id:"minecraft:cod",tag:{game_item:1b,powerful:0b}}] run function mini:koth/game/give_fish
execute as @a[tag=mini_running] at @s unless block ~ 9 ~ red_mushroom_block unless data entity @s Inventory[{Slot:8b,id:"minecraft:cod",tag:{game_item:1b,powerful:1b}}] run function mini:koth/game/give_fish

在这里,我们使用了powerful这个自定义标签来区分击退鱼等级是否正确。当然你也可以直接用tag:{game_item:1b,Enchantments:[{id:"minecraft:knockback",lvl:4}]}来区分不同等级的击退鱼,但这样相对耗性能并且不利于平衡调整(你需要把give_fish里面的鱼和tick2里面的鱼都改一下)。

游戏物品拥有一个game_item:1b的NBT标签,但由于鳕鱼这个物品不在LTW设定的游戏物品列表里,因此我们需要在游戏物品列表中把它加上,来保证游戏物品在游戏结束之后能够正常被清除

data/mini/tags/game_item.json

1
2
3
4
5
6
7
8
{
"values": [
"......(省略前面的游戏物品列表)",
"minecraft:soul_soil",
"minecraft:tnt",
"minecraft:cod"
]
}

接下来就是给予击退鱼的方法了。

data/mini/functions/koth/game/give_fish.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
# 清除玩家已有的击退鱼
clear @s cod{game_item: 1b}

# 返还格子上的物品
## 此处设定需要返还的物品栏位,快捷栏第九格为8
execute if data entity @s Inventory[{Slot: 8b}] run scoreboard players set @s item_slot 8
## 此处调用返还物品方法,没有物品时不会返还任何东西
execute if data entity @s Inventory[{Slot: 8b}] at @s run function item:pop_slot/return_item

# 依据玩家位置给予击退鱼
execute if block ~ 9 ~ red_mushroom_block run item replace entity @s hotbar.8 with cod{game_item: 1b, powerful: 0b, display: {Name: '{"text":"击退鱼","color":"aqua","italic":false}'}, Enchantments: [{id: "knockback", lvl: 2}], AttributeModifiers: [{UUID: [I; 2, 0, 2, 1], Amount: 15, AttributeName: "generic.attack_damage", Name: "attack", Slot: "mainhand", Operation: 0}], HideFlags: 63}
execute unless block ~ 9 ~ red_mushroom_block run item replace entity @s hotbar.8 with cod{game_item: 1b, powerful: 1b, display: {Name: '{"text":"强力击退鱼","color":"light_purple","italic":false}'}, Enchantments: [{id: "knockback", lvl: 4}], AttributeModifiers: [{UUID: [I; 2, 0, 2, 1], Amount: 15, AttributeName: "generic.attack_damage", Name: "attack", Slot: "mainhand", Operation: 0}], HideFlags: 63}

由于盾牌在防击退方面表现十分出色,因此我们需要让击退鱼有一定的破盾功效(暴力破盾)。你可以删掉击退鱼的AttributeModifiers标签。

其他物品的给予方法和这里的击退鱼类似,只需要替换你需要用到的游戏物品即可。

E. 奖励物品的生成与处理

在LTW中,生成奖励物品需要一定的条件。例如在色彩迷阵中,需要当前场上没有物品才会开始计算生成物品的冷却。冷却到15秒后需要存活玩家数大于等于3死亡玩家数小于等于2才生成新的奖励物品。但山丘之王中由于玩家不会死亡,所以只需要判定当前场上没有物品,然后计算冷却即可。

是否符合奖励物品的生成条件每秒判断一次。注意这里是在不符合条件时将#new_item设置为1,而不是在符合条件时设置为0。

data/mini/functions/koth/tick20.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
13
# 判断是否符合刷出新物品的条件:0 为可以,其他为不可以
scoreboard players set #new_item mem 0

# 当前场地上没有物品
execute if entity @e[tag=bonus_item,x=985.0,y=4.0,z=8985.0,dx=32,dy=32,dz=32] run scoreboard players set #new_item mem 1

# 如果没有物品,则计算冷却
execute if score #new_item mem matches 0 run scoreboard players add $new_item_cd mem 1
# 已经超过 15s 冷却时间
execute if score $new_item_cd mem matches ..14 run scoreboard players set #new_item mem 1

# 如果上述条件均满足,则刷出新物品
execute if score #new_item mem matches 0 run function mini:koth/game/new_item

奖励物品的生成方法如下。由于山丘之王有四个物品生成点,每次生成只有2个生成点有物品,因此需要随机选择。生成奖励物品的方法比较常用,因此可以直接调用现成的,只需要更改物品生成的位置即可。

data/mini/functions/koth/game/new_item.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 重置冷却时间
scoreboard players set $new_item_cd mem 0

# 随机选择生成组
scoreboard players set $random_min mem 0
scoreboard players set $random_max mem 1
function lib:random

# 生成物资
execute if score $random mem matches 0 positioned 1014.5 16 8987.5 run function item:bonus_item/gameparty/auto/low
execute if score $random mem matches 0 positioned 987.5 16 9014.5 run function item:bonus_item/gameparty/auto/low
execute if score $random mem matches 1 positioned 1014.5 16 9014.5 run function item:bonus_item/gameparty/auto/low
execute if score $random mem matches 1 positioned 987.5 16 8987.5 run function item:bonus_item/gameparty/auto/low
execute as @e[tag=bonus_item] run data modify entity @s NoGravity set value 0b

# 显示提示
title @a subtitle {"text":"❇ 奖励物资已在空中部署 ❇","color":"aqua"}
title @a times 1 60 3
title @a title ""
tellraw @a ["",{"text": ">> ","color":"aqua","bold": true},{"text":"奖励物资:","color":"aqua"},{"selector": "@e[tag=bonus_item]"}]
playsound minecraft:entity.player.levelup player @a 0 1000000 0 1000000 2

山丘之王的物品是在y=16生成的,我们需要让物品缓慢下落到某个位置停下来,但又不是完全不下落。由于物品会受到空气阻力,所以当物品不受重力时下落速度会逐渐减小。所以我们可以判定物品的下落速度和物品的y坐标来确定物品是否要受到重力影响以下落。这需要非常高频的判定,因此命令需要写在tick1里面。

data/mini/functions/koth/tick1.mcfunction

1
2
3
4
# 调整物品:仅在纵向速度 < -0.015 且 y > 12 时,才将 NoGravity 设置为 false
execute as @e[tag=bonus_item] store result score @s temp run data get entity @s Motion[1] 1000
execute as @e[tag=bonus_item] if entity @s[x=985.0,y=12.0,z=8985.0,dx=32,dy=32,dz=32,scores={temp=-15..}] run data merge entity @s {NoGravity: 0b}
execute as @e[tag=bonus_item] unless entity @s[x=985.0,y=12.0,z=8985.0,dx=32,dy=32,dz=32,scores={temp=-15..}] run data merge entity @s {NoGravity: 1b}

至于y大于几时才让物品受到重力影响,可以采用地面坐标y值+3的方法来确定这个y值(例如山丘之王奖励物品对应的地面坐标y值为9)。这样物品停止时会悬浮在地面上1-2个方块左右的位置。

第四步 - 初步测试

在确保你的data/mini/functions/koth下的模板函数(即你在第二步编写的函数以及tick1tick2tick5还有tick20)均在data/mini/functions/main中的同名函数有对应的调用方法后(参照第二步B),就可以开始初步测试你的新游戏了。

首先让我们复制粘贴一个测试方法,记得把$mini_type的值改为你的游戏编号。

data/test/functions/mini/koth.mcfunction

1
2
3
4
5
6
7
8
9
10
11
# 测试用:快速开始山丘之王
team join playing @a[team=watching]
clear @a[team=!debugging]
function ltw:state/0/start_game
scoreboard players set $round mem 5
scoreboard players set $mini_type mem 6
function mini:main/game_init
function ltw:state/4/state_enter
say 已开始单个测试游戏!

schedule function test:countdown/quick 20t

先别急着在服务器里输入/reload命令!因为游艺街内置一个游戏结束判定,即人数等于1时游戏会自动结束(遗迹寻宝除外)。因此如果你是在一个人测试这个新游戏,那么游戏就会在开始之后的一瞬间结束!为了防止这种现象的发生,我们需要更改游戏结束的判定代码。在原有的run前面加上一句unless score $mini_type mem matches 6,这样你的游戏就不会自动结束了。

data/mini/functions/main/check_game_end.mcfunction

1
2
3
4
# 检查游戏是否可以结束

function mini:main/update_player_count
execute if score $state mem matches 5 if score $player_alive mem matches ..1 unless score $mini_type mem matches 1 unless score $mini_type mem matches 6 run function mini:main/game_end

好了,现在可以/reload了。重载数据包之后,记得再输入/function ltw:init命令,确保你的能量值记分板添加成功。(我就是因为经常忘记init导致写的东西老出bug)

输入/function test:mini/koth来开始测试你的游戏。下面给一张测试时的初始化图。

经测试游戏运行逻辑正确、奖励物品可以正常生成并且游戏可以正常结束后,初步测试就完成了。

第五步 - 将新游戏加入随机池

你写的新游戏目前还不能被随机到。但好在我们写的是一个普通小游戏,只需要更改一个数字即可实现将小游戏加入随机池。

data/ltw/functions/init.mcfunction

1
2
3
4
5
6
7
# 常量与变量初始化
## 把这里的5改成6
scoreboard players set #mini_total mem 6
scoreboard players set #-1 mem -1
scoreboard players set #0 mem 0
scoreboard players set #1 mem 1
scoreboard players set #2 mem 2

然后输入/function ltw:init即可。现在当游戏正常开始时,你的游戏也有概率被轮换到了。注意如果你还要添加第7个、第8个普通小游戏,那么在初步测试之后也要更改这里的#mini_total值。

如果你做的不是一个普通小游戏,而是首轮/终轮小游戏,那么你需要更改的函数不是上面这个,是状态3(小游戏预处理)的状态进入函数。下面是你需要改的原本的代码。

data/ltw/functions/state/3/state_enter.mcfunction

1
2
3
4
5
6
7
8
9
# 前置/后置小游戏
# 如果为第 1 轮,则有一半概率选用刺客,一半概率不变
# 如果为第 5 轮,则有一半概率选用钢铁,一半概率选用贸易
scoreboard players set $random_max mem 1
scoreboard players set $random_min mem 0
function lib:random
execute if score $round mem matches 1 if score $random mem matches 0 run scoreboard players set $mini_type mem 201
execute if score $round mem matches 5 if score $random mem matches 0 run scoreboard players set $mini_type mem 101
execute if score $round mem matches 5 if score $random mem matches 1 run scoreboard players set $mini_type mem 102

在我的《LTW派》中,是这么改的。(游戏编号202为掘一死箭,103为钻石风暴,104为富翁之路)

data/ltw/functions/state/3/state_enter.mcfunction

1
2
3
4
5
6
7
8
9
10
11
12
# 前置/后置小游戏
# 如果为第 1 轮,则有 40% 概率选用刺客,40% 概率选用掘战,20% 概率不变
# 如果为第 6 轮,则有 20% 概率选用钢铁,20% 概率选用钻石,20% 概率选用贸易,40% 概率选用富翁之路
scoreboard players set $random_max mem 5
scoreboard players set $random_min mem 1
function lib:random
execute if score $round mem matches 1 if score $random mem matches 1..2 run scoreboard players set $mini_type mem 201
execute if score $round mem matches 1 if score $random mem matches 2..3 run scoreboard players set $mini_type mem 202
execute if score $round mem matches 6 if score $random mem matches 1 run scoreboard players set $mini_type mem 101
execute if score $round mem matches 6 if score $random mem matches 2 run scoreboard players set $mini_type mem 102
execute if score $round mem matches 6 if score $random mem matches 3 run scoreboard players set $mini_type mem 103
execute if score $round mem matches 6 if score $random mem matches 4..5 run scoreboard players set $mini_type mem 104

由此可见,只需要更改$random的最大值,并且按照概率计算$random取何值时应该将$mini_type设置为你的新游戏即可。

第六步 - 添加进度

进度系统允许玩家在达成一定的条件后获得绿宝石奖励。需要注意的是,进度一般要在你的游戏经过多人确认几乎没有影响游戏平衡的bug后才添加。

《游艺街》的所有可见进度均存放在data/ltw/advancements/文件夹中。分为blood(金铁交鸣)、parkour(飞檐走壁)、story(荣誉之路)和vs(百般绝学)四个子文件夹。你可以根据你的游戏主要玩法来选择进度需要存放的文件夹位置。山丘之王的进度将会存放在vs子文件夹中。

大部分的游戏包含α、β、γ和Ω四个进度,α进度给予1个绿宝石,β、γ进度分别给予2个绿宝石,Ω进度为隐藏进度,在有的游戏中不存在。我们只需要编写各个进度的描述以及达成条件即可,给予绿宝石的方法龙猫已经帮我们写好了。

这里的进度达成条件与《LTW派》中的不同,原因是《LTW派》中的山丘之王有给予能量值的奖励物品,以及中央红区的能量值增长速度较教程中慢一倍。

如果你想copy and paste,你需要更改的进度的名称(koth改为你的小游戏英文代号),parent也需要一并更改。此外你通常需要更改display里面的icontitledescription三个键的值。达成进度的条件均写impossible,我们会在函数文件中写给予进度的代码。

data/ltw/advancements/vs/koth1.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"parent": "ltw:vs/root",
"display": {
"icon": {
"item": "minecraft:cod"
},
"title": {
"text": "山丘之霸 · α "
},
"description": [
{"text": "【山丘之王】获得 100 能量值\n", "color":"gray"},
{"text": "奖励: ","color":"green"},
{"text": "1 绿宝石","color":"white"}
],
"frame": "task",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"rewards": {
"function": "ltw:advancement/bonus_1"
},
"criteria": {
"a":{
"trigger": "minecraft:impossible"
}
}
}

data/ltw/advancements/vs/koth2.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"parent": "ltw:vs/koth1",
"display": {
"icon": {
"item": "minecraft:salmon"
},
"title": {
"text": "山丘之霸 · β "
},
"description": [
{"text": "【山丘之王】获得 180 能量值\n", "color":"gray"},
{"text": "奖励: ","color":"green"},
{"text": "2 绿宝石","color":"white"}
],
"frame": "task",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"rewards": {
"function": "ltw:advancement/bonus_2"
},
"criteria": {
"a":{
"trigger": "minecraft:impossible"
}
}
}

data/ltw/advancements/vs/koth3.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"parent": "ltw:vs/koth2",
"display": {
"icon": {
"item": "minecraft:tropical_fish"
},
"title": {
"text": "山丘之霸 · γ "
},
"description": [
{"text": "【山丘之王】获得 250 能量值\n", "color":"gray"},
{"text": "奖励: ","color":"green"},
{"text": "2 绿宝石","color":"white"}
],
"frame": "challenge",
"show_toast": true,
"announce_to_chat": true,
"hidden": false
},
"rewards": {
"function": "ltw:advancement/bonus_3"
},
"criteria": {
"a":{
"trigger": "minecraft:impossible"
}
}
}

data/ltw/advancements/vs/koth4.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"parent": "ltw:vs/koth3",
"display": {
"icon": {
"item": "minecraft:pufferfish",
"nbt": "{Enchantments:[{id:\"minecraft:protection\",lvl:4s}]}"
},
"title": {
"text": "山丘之霸 · Ω "
},
"description": [
{"text": "【山丘之王】获得 320 能量值\n", "color":"gray"},
{"text": "究极隐藏挑战!","color":"dark_gray","italic": true}
],
"frame": "challenge",
"show_toast": true,
"announce_to_chat": true,
"hidden": true
},
"rewards": {
"function": "ltw:advancement/bonus_4"
},
"criteria": {
"a":{
"trigger": "minecraft:impossible"
}
}
}

接下来在每次给予玩家能量值时,判定玩家的能量值是否符合获得进度的条件。你无需担心当玩家已经获得了进度后还会获得重复的进度。

data/mini/functions/koth/tick5.mcfunction

1
2
3
4
5
6
7
8
# 给予红区玩家能量值
## 此处代码省略

# 给予进度
advancement grant @a[team=playing,scores={power_count=100..}] only ltw:vs/koth1
advancement grant @a[team=playing,scores={power_count=180..}] only ltw:vs/koth2
advancement grant @a[team=playing,scores={power_count=250..}] only ltw:vs/koth3
advancement grant @a[team=playing,scores={power_count=320..}] only ltw:vs/koth4

第七步 - 大功告成!

输入/reload命令,你的小游戏就大功告成了!并且进度也确实在正常地工作。

教程结束,期待更多的《游艺街》改版会因此产生!

  • 标题: 游艺街改版教程 - 添加新的小游戏
  • 作者: 小猪之最Thepig
  • 创建于 : 2023-04-10 23:39:26
  • 更新于 : 2024-05-06 23:45:18
  • 链接: https://www.pigest.top/2023/ltwtutor-e1/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论