第三人称游戏都有一个由引擎控制的第三人称控制器,那么这在Minecraft中能否实现?
只能说达到一半。
第三人称控制器(Third Person Controller)是角色控制器的一种,较比第一人称控制器可以反映更多信息包括角色的建模/动画/行为,以及更加多样的操作模式。第三人称控制器最近的一个例子就是Minecraft中的F5视角,即第三人称视角。
当然比起像Minecraft这种可以直接依赖实时演算的第三人称控制器,这篇帖子里所讲的是对于目前Minecraft原版来说依靠命令方块力所能及的控制器。具体来讲,就是没有进行三维旋转的功能。原因很简单,一是无法使用相关函数,二是穷举即使精度为5°的三维旋转也特别消耗资源。
总体来说,Minecraft原版最大限度可以还原一款2.5D游戏。
所以这篇帖子讨论的第三人称控制器是基于玩家视角不动,但是能通过自身操作操作非关联自身的实体角色(什么叫关联自身其实很明白,就是利用execute指令对玩家的原始坐标相对操作。例如你可以使用/execute @p ~ ~ ~ tp @e[tag=Character] ~ ~-5 ~制作一个最简单的关联自身的控制器)
一个gif展示:(15.7M,耐心加载)
Demo截图:
基本实现了角色移动,角色切换。
点击按钮进入游戏
F1关闭游戏GUI以获得最佳体验
WASD移动
绿柱用于切换角色
物品栏中单击下图的Exit退出游戏
按键操作事件的监听
- 大前提
在正式开始讲之前,我们先要来了解一下原理。
而在了解原理之前,我们需要了解一个概念:事件监听
拿一个按钮作为例子,比如你的登录器,你点击"Play"按钮,这个按钮就会发生这个叫做OnClick的事件。事件监听就是内部检测这个事件。进行事件监听并监听到该事件的发生即可关联或调用相关操作。
你的鼠标按左键也有三种事件(OnMouseDown()鼠标刚按下、OnMouse()鼠标按下的状态、OnMouseUp()鼠标刚松开),这些事件都可以被内部监听。
而要在Minecraft中做到各种事件监听,要结合各种各样的方法:
- WASD移动事件
假设玩家在其立足点坐标中心,也就是(x.500,y.000,z.500),面向z+方向
检测按下'W'(向前/x+方向),就是检测玩家对于前方的蓝色方块距离是否缩小,也就是要相对玩家向前检测前方距离<0.5是否为蓝色方块:
- /execute @a[tag=Controlling] ~ ~ ~ detect ~ ~ ~0.49999999999995 wool 11
- /execute @a[tag=Controlling] ~ ~ ~ detect ~ ~ ~-0.49999999999995 wool 4
- /execute @a[tag=Controlling] ~ ~ ~ detect ~0.49999999999995 ~ ~ wool 5
- /execute @a[tag=Controlling] ~ ~ ~ detect ~-0.49999999999995 ~ ~ wool 1
除了上述逐个检测的方法外,我们可以更加简化地,检测玩家向各方向的顶部是否为空气:
(四个彩块都代表空气)拿前进为例,由于头顶上是一个存在的方块,玩家没有前进时检测前方空气距离<0.5为空气不成立,只有玩家向前移动后与前方空气的距离才会<0.5。以此类推,各方向都可以直接靠检测空气实现监听。
对应的指令为:
- execute @a[tag=Controlling] ~ ~ ~ detect ~ ~2 ~0.49999999999995 air 0
- execute @a[tag=Controlling] ~ ~ ~ detect ~ ~2 ~-0.49999999999995 air 0
- execute @a[tag=Controlling] ~ ~ ~ detect ~0.49999999999995 ~2 ~ air 0
- execute @a[tag=Controlling] ~ ~ ~ detect ~-0.49999999999995 ~2 ~ air 0
有关detect之后的操作将会在后面"移动操作"中进一步讲解
- Space空格事件
- execute @a[score_control_min=1] ~ ~ ~ detect ~ ~2.99999 ~ air 0
为什么分开来讲?因为在第三人称控制中,Space不止作为移动的跳跃键,也可以是一个功能键,具体detect后的操作在后面"其他操作"中进一步讲解
- Shift事件
点击这篇帖子查看原理讲解
同样检测后的操作在后面"其他操作"中进一步讲解
移动操作
- 基本操作
- execute @a[tag=Controlling] ~ ~ ~ detect ~ ~2 ~0.49999999999995 air 0 execute @e[tag=character] ~ ~ ~ detect ~ ~1 ~0.9 air 0 tp @e[tag=character] ~ ~ ~0.6
- execute @a[tag=Controlling] ~ ~ ~ detect ~ ~2 ~0.49999999999995 air 0 execute @e[tag=character] ~ ~ ~ tp @e[tag=character] ~ ~ ~ 0 ~
- execute @a[tag=Controlling] ~ ~ ~ detect ~ ~2 ~-0.49999999999995 air 0 execute @e[tag=character] ~ ~ ~ detect ~ ~1 ~-0.9 air 0 tp @e[tag=character] ~ ~ ~-0.6
- execute @a[tag=Controlling] ~ ~ ~ detect ~ ~2 ~-0.49999999999995 air 0 execute @e[tag=character] ~ ~ ~ tp @e[tag=character] ~ ~ ~ 180 ~
- execute @a[tag=Controlling] ~ ~ ~ detect ~0.49999999999995 ~2 ~ air 0 execute @e[tag=character] ~ ~ ~ detect ~0.9 ~1 ~ air 0 tp @e[tag=character] ~0.6 ~ ~
- execute @a[tag=Controlling] ~ ~ ~ detect ~0.49999999999995 ~2 ~ air 0 execute @e[tag=character] ~ ~ ~ tp @e[tag=character] ~ ~ ~ -90 ~
- execute @a[tag=Controlling] ~ ~ ~ detect ~-0.49999999999995 ~2 ~ air 0 execute @e[tag=character] ~ ~ ~ detect ~-0.9 ~1 ~ air 0 tp @e[tag=character] ~-0.6 ~ ~
- execute @a[tag=Controlling] ~ ~ ~ detect ~-0.49999999999995 ~2 ~ air 0 execute @e[tag=character] ~ ~ ~ tp @e[tag=character] ~ ~ ~ 90 ~
- 重置玩家视角和坐标
首先建立一个moved计分板/dummy,并将其用stats关联到实体:
- /scoreboard players set @e[tag=character] moved 0
- stats entity @e[tag=character] set AffectedEntities @e[tag=character] moved
接下来就是重置的工作了
- tp @a[tag=Controlling] ~ ~ ~ [dx] [dy]
- execute @e[tag=character,score_moved_min=1] ~ ~ ~ tp @a[tag=Controlling] [x] ~ [z]
- cond:scoreboard players set @e[tag=character] moved 0
- 执行顺序
并且移动操作监听其实并非并列进行,这是由于链式模块都有单向顺序,刷新周期0.05s,而Minecraft中实际的玩家移动是Update同步进行,模块执行和实际玩家移动存在时间差。总的来说如果使用了不恰当的玩家移动监听顺序,某些方向的移动监听也许会失去效果。
最后的恰当顺序请看"最终指令"。
其他操作
- Space操作
- execute @a[tag=Controlling] ~ ~ ~ detect ~ ~2.99999 ~ air 0 execute @p ~ ~ ~ detect [x] [y] [z] air 0 setblock [x] [y] [z] redstone_block 0 replace
作为一个Minecraft的移动键,同样也受执行顺序影响。最后的恰当顺序请看"最终指令"。
- Shift操作
- /tp @e[name=sneak] ~ ~-300 ~
- /kill @e[name=sneak]
- /summon minecraft:rabbit ~ ~ ~ {NoAI:true,Silent:true,Invulnerable:true,ActiveEffects:[{Id:14,Duration:111110,ShowParticles:false}],Age:-1000000,CustomName:sneak}
- /execute @p ~ ~ ~ /tp @e[name=sneak] @a[score_control_min=1]
- /tp @e[name=sneak] ~0.01 ~ ~
- /execute @a[score_control_min=1,score_sneak_min=1] ~ ~ ~ setblock [x] [y] [z] minecrat:redstone_block 0 replace
- /scoreboard players reset @a sneak
- /scoreboard objectives add sneak stat.sneakTime
- 另外各种操作
例如右键可以靠胡萝卜钓竿的使用监听,左键可以靠对实体的伤害监听;
数字键123456789可以靠NBT-SelectedItemSlot;
还有等等等等都可以靠各种方法监听。
优化
- 视角卡顿
其实这个视角卡顿就是因为模块执行和实际玩家移动检测存在时间差,因此在你移动后因为又被tp会原始点,你的视角将疯狂摇晃。
解决方法就是降低玩家移动速度,这就是我们需要强行让玩家穿上一个靴子:
- /replaceitem entity @p slot.armor.feet diamond_boots 1 0 {Unbreakable:1,display:{Name:"Exit"},HideFlags:63,AttributeModifiers:[{AttributeName:"generic.movementSpeed",Name:"generic.movementSpeed",Amount:-0.099999,Operation:0,UUIDMost:84265,UUIDLeast:167246,Slot:"feet"}]}
- 视角变小
- 实体消失范围
- 关于GUI
- /effect @p[tag=controller] invisibility 1 1 true
你也可以选择让玩家F1隐藏GUI,提升游戏体验性。
- 退出游戏
最终指令
Start 游戏开始部分:
- icb:/scoreboard players tag @p add controller
- *给玩家添加一个控制者标签
- /replaceitem entity @p slot.armor.feet diamond_boots 1 0 {Unbreakable:1,display:{Name:"Exit"},HideFlags:63,AttributeModifiers:[{AttributeName:"generic.movementSpeed",Name:"generic.movementSpeed",Amount:-0.099999,Operation:0,UUIDMost:84265,UUIDLeast:167246,Slot:"feet"}]}
- *让玩家穿上前面防卡顿的靴子
- /scoreboard objectives add moved dummy
- *增加moved计分板
- //重置结束
- /setblock ~-3 ~1 ~2 minecraft:air 0
- /scoreboard objectives add control dummy
- /summon minecraft:实体名 [x] [y] [z] {Tags:["character"],NoAI:1,CustomName:"Character",CustomNameVisible:false,Invulnerable:1,Silent:1,Rotation:[180.0f,0.0f]}
- *生成角色
- /tp @p [x] [y] [z] [dx] [dy]
- *将玩家传送到控制器的中心点(也就是"移动监听"中用玩家头颅代表的玩家立足点中心)
- /scoreboard players set @e[tag=character] moved 0
- *初始化stats
- stats entity @e[tag=character] set AffectedEntities @e[tag=character] moved
- *stats关联到实体
- /gamemode a @p
- *设置游戏模式
Control 事件监听和操作:
- rcb:scoreboard players set @a control 0
- scoreboard players set @a control 1 {Inventory:[{id:"minecraft:diamond_boots",Slot:100b,Damage:0s,Count:1b,tag:{display:{Name:"Exit"}}}]}
- *检测玩家没有选择离开游戏,给玩家control计分板设为1,表示玩家正在操作
- testfor @a[tag=controller,score_control=0,c=1]
- cond:testforblock ~-3 ~1 ~4 air 0
- cond:setblock ~-4 ~1 ~4 redstone_block 0
- *检测玩家退出后,激活End()模块
- tp @a[score_control_min=1] ~ ~ ~ [dx] [dy]
- execute @e[tag=character,score_moved_min=1] ~ ~ ~ tp @a[score_control_min=1] [x] ~ [z]
- cond:scoreboard players set @e[tag=character] moved 0
- *固定和重置玩家视角和坐标
- execute @a[score_control_min=1] ~ ~ ~ detect ~ ~2 ~-0.49999999999995 air 0 execute @e[tag=character] ~ ~ ~ detect ~ ~1 ~-0.9 air 0 tp @e[tag=character] ~ ~ ~-0.6
- execute @a[score_control_min=1] ~ ~ ~ detect ~0.49999999999995 ~2 ~ air 0 execute @e[tag=character] ~ ~ ~ tp @e[tag=character] ~ ~ ~ -90 ~
- execute @a[score_control_min=1] ~ ~ ~ detect ~0.49999999999995 ~2 ~ air 0 execute @e[tag=character] ~ ~ ~ detect ~0.9 ~1 ~ air 0 tp @e[tag=character] ~0.6 ~ ~
- execute @a[score_control_min=1] ~ ~ ~ detect ~ ~2 ~0.49999999999995 air 0 execute @e[tag=character] ~ ~ ~ detect ~ ~1 ~0.9 air 0 tp @e[tag=character] ~ ~ ~0.6
- execute @a[score_control_min=1] ~ ~ ~ detect ~ ~2 ~-0.49999999999995 air 0 execute @e[tag=character] ~ ~ ~ tp @e[tag=character] ~ ~ ~ 180 ~
- *在Space操作之前移动操作可使用的顺序
- //Space事件
- execute @a[score_control_min=1] ~ ~ ~ detect ~ ~2.99999 ~ air 0 execute @p ~ ~ ~ detect [x] [y] [z] air 0 setblock [x] [y] [z] redstone_block 0 replace
- //Space事件结束
- execute @a[score_control_min=1] ~ ~ ~ detect ~ ~2 ~0.49999999999995 air 0 execute @e[tag=character] ~ ~ ~ tp @e[tag=character] ~ ~ ~ 0 ~
- execute @a[score_control_min=1] ~ ~ ~ detect ~-0.49999999999995 ~2 ~ air 0 execute @e[tag=character] ~ ~ ~ detect ~-0.9 ~1 ~ air 0 tp @e[tag=character] ~-0.6 ~ ~
- execute @a[score_control_min=1] ~ ~ ~ detect ~-0.49999999999995 ~2 ~ air 0 execute @e[tag=character] ~ ~ ~ tp @e[tag=character] ~ ~ ~ 90 ~
- *在Space操作之后移动操作可使用的顺序
- //Shift事件
- /tp @e[name=sneak] ~ ~-300 ~
- /kill @e[name=sneak]
- /summon minecraft:rabbit ~ ~ ~ {NoAI:true,Silent:true,Invulnerable:true,ActiveEffects:[{Id:14,Duration:111110,ShowParticles:false}],Age:-1000000,CustomName:sneak}
- /execute @p ~ ~ ~ /tp @e[name=sneak] @a[score_control_min=1]
- /tp @e[name=sneak] ~0.01 ~ ~
- /execute @a[score_control_min=1,score_sneak_min=1] ~ ~ ~ setblock [x] [y] [z] minecrat:redstone_block 0 replace
- /scoreboard players reset @a sneak
- /scoreboard objectives add sneak stat.sneakTime
- //Shift事件结束
- //可选配置项目
- /effect @p[tag=controller] invisibility 1 1 true
End 游戏结束部分:
- icb:/clear @a diamond_boots 0 10 {display:{Name:"Exit"}}
- /scoreboard objectives remove moved
- /scoreboard objectives remove control
- *各种重置
- /setblock ~-3 ~1 ~-2 minecraft:air
- *清除Start()模块开头的红石块
- /stats entity @e[tag=character] clear AffectedEntities
- /gamemode c @a
- *继续各种重置
- /tp @a [x] [y] [z] [dx] [dy]
- *返回大厅
- /tp @e[tag=character] ~ -100 ~
- /kill @e[tag=character]
- /scoreboard players tag @a remove controller
- *再各种重置
如果你使用上述指令,那么模块的摆放也有相应要求,具体看"Demo&源码资源"
Demo&源码资源
Demo地址:百度网盘
.pcb文件打包地址:百度网盘
关于.pcb转为OOC后的摆放顺序:
此贴实际研究时间数个月前,而且感谢地图《A Day to Remember》给予的2.5D第三人称操作灵感。[groupid=546]Command Block Logic[/groupid]