在我的游戏中,我想要一个在大地图(Overworld)中进行的战斗系统。
到目前为止,我已经解决了大部分功能,除了真正与敌人交互的攻击功能;我不确定如何实现这一点。
有人知道如何让玩家在大地图中击中敌人,并对敌人产生影响吗?
简而言之,在大地图中,我希望玩家能够按下“攻击”键(在这种情况下是 Z 键),这将执行一个“攻击”动作。当攻击接触到敌人时,根据敌人的类型和玩家使用的攻击方式,会触发不同的效果(例如敌人是否被瞬间击倒、是否被击退、是否只是受到伤害等)。
在我的游戏中,我想要一个在大地图(Overworld)中进行的战斗系统。
到目前为止,我已经解决了大部分功能,除了真正与敌人交互的攻击功能;我不确定如何实现这一点。
有人知道如何让玩家在大地图中击中敌人,并对敌人产生影响吗?
简而言之,在大地图中,我希望玩家能够按下“攻击”键(在这种情况下是 Z 键),这将执行一个“攻击”动作。当攻击接触到敌人时,根据敌人的类型和玩家使用的攻击方式,会触发不同的效果(例如敌人是否被瞬间击倒、是否被击退、是否只是受到伤害等)。
顶一下
Z 已经在 MZ 中映射为“确定”键,因此你可以使用一个并行的公共事件,通过“条件分支 → 按钮 → 确定键被触发”来检测按键,无需额外设置来读取按键状态。
至于攻击判定本身,我建议跳过尝试检测碰撞箱的做法,改为直接打开一个窗口。按下 Z 键时,将名为类似“正在攻击”的开关设为 ON,在玩家身上播放出拳动画,等待攻击持续的时间,然后再将其设为 OFF。在将其设为 OFF 之前,设置一个变量来记录使用了哪种攻击。
敌人事件是处理反应的地方。给它设置一个“事件接触”页面的触发条件,无论玩家走到敌人身上还是敌人走到玩家身上都会触发,并在顶部根据上述开关进行分支。如果“正在攻击”为 ON,表示敌人被击中,那么伤害、击退或即死效果应放在该分支中,并在敌人死亡时触发一个自我开关。如果“正在攻击”为 OFF,则表示普通的碰撞,此时玩家应受到伤害。在同一个击中分支中读取攻击类型变量,这样可以让一个敌人对拳攻击免疫而对踢击受伤,而另一个敌人则相反。
拳头需要延伸到玩家前方的格子,还是说在挥击过程中接触到玩家就足够了?上述内容都是接触判定版本。如果拳头需要延伸到前方,则需要将玩家的 X、Y 坐标和方向提取到变量中,并与每个敌人的 X、Y 坐标进行比较,这是可行的,但实现方式不同,因此在搭建系统前明确你需要的是哪种版本是值得的。
是的,按钮相关的功能都已经处理好了,按钮也能正常工作。我就是前几天和你讨论过增加更多按钮的那个人,哈哈。我只是开了一个新帖子来讨论一个相关的问题。
这个想法非常巧妙!问题在于,敌人事件只能有一个页面,因为它会根据战斗前设置的某些变量自动设定自己的精灵/属性/攻击集。如果创建新页面,所有的代码都会丢失,导致其再次变为空白且无法运行。实际情况比这更复杂一些,如果需要我可以进一步解释,但基本上,我认为我只能在这个地方维持使用一个页面。
敌人事件是一个并行处理(Parallel Process),当所有内部计算完成后,其代码末尾会无限循环。它会检查可以使用哪些攻击,然后检查其 HP 是否为 0 以跳出循环。我可以在这个循环中的某个地方放置攻击反应吗?
是的,攻击确实需要到达玩家面前的格子。我之前有过相关的概念,但还不确定效果如何。我在屏幕外有另一个并行处理,它不断将另一个不可见事件设置为始终位于玩家上方。这个不可见事件本身就是攻击的判定框(hitbox),根据玩家面对的方向和按下的按键,它会被迅速移动到玩家面前一格,然后再缩回。
是的,而且不需要第二个页面。我建议把它放在你已经写好的 HP 归零检查之前,这样致命一击会在同一帧中断循环,而不是等到下一帧。
我要补充的是一个守卫逻辑,因为并行循环会在碰撞框停留在该格子的每一帧重复注册相同的命中。使用自开关可以在不改动你页面设置的情况下处理这个问题:
◆条件分支:攻击状态为开
◆条件分支:自开关 B 为关
◆变量操作:#0031 命中 X = 角色 [碰撞框] 地图 X
◆变量操作:#0032 命中 Y = 角色 [碰撞框] 地图 Y
◆变量操作:#0033 我的 X = 角色 [本事件] 地图 X
◆变量操作:#0034 我的 Y = 角色 [本事件] 地图 Y
◆条件分支:命中 X = 变量 [我的 X]
◆条件分支:命中 Y = 变量 [我的 Y]
(伤害,读取攻击类型变量,击退)
◆控制自开关:B = 开
◆条件分支结束
◆条件分支结束
◆条件分支结束
◆否则
◆控制自开关:B = 关
◆条件分支结束
选择你尚未使用的任意字母。它仅表示“已计算过此次攻击”,而“否则”分支会在攻击结束时将其清除。
如果你保留碰撞框事件,设置事件位置会直接将其放置在那里,而不是通过移动方式到达,因此它不会被阻挡,也不会像移动路线那样晚到几帧。
另一种思路是放弃碰撞框事件,让敌人自行判断所在的格子。获取玩家地图 X、玩家地图 Y 和玩家方向,并根据方向进行偏移(2 是 y+1,4 是 x-1,6 是 x+1,8 是 y-1),然后将其与敌人自身的 X 和 Y 进行比较。这与你原本会进行的比较相同,只是没有任何需要每帧重新定位的东西。
结构看起来没问题,为坐标使用自变量在处理地图上的多个副本时是个好主意。
需要修改的一点是 Else 分支。它将 B 设置为 ON,但应该设置为 OFF。该分支在 ATTACKING 关闭时运行,因此它是清除标志并为敌人准备下一次攻击的分支。按照目前的写法,第一次攻击会将 B 设置为 ON,且之后没有任何操作将其关闭,因此敌人在剩余的战斗中只会记录一次命中。
: Else
◆控制自变量:B = OFF
注释块的位置没问题,只需注意这三个分支都位于“自变量 B 为 OFF”的检查内部,因此你在其中构建的逻辑每次攻击只运行一次,而不是每帧都运行。这对击退效果尤为重要,因为“直到无法移动”应该是一个移动路线或一个能自行结束的循环,而不是每次遍历都重新触发的东西。
哎呀,这么小的细节居然漏掉了,所以我才问得这么仔细嘛,哈哈。总是这些小问题让我栽跟头。
在当前的情境下,“设置移动路线=向后走,直到无法移动(撞到墙)”这个指令不是应该能生效吗?
移动路线中没有“撞到墙为止”这样的终止条件,因此最接近的替代方案是设置固定数量的“后退”步骤,并勾选“无法移动时跳过”。当碰到墙壁时,剩余步骤会被跳过,路线自然结束。
在当前上下文中可能产生问题的部分是并行处理中的“等待完成”。事件自身的循环会停留在这个命令上,直到路线报告完成。如果未勾选“无法移动时跳过”,受阻的步骤不会推进路线,而是会重试。因此,如果敌人被挤在墙边,它将停止检查其他任何事项,包括自身的生命值。
敌人被击中时是否面向玩家?“后退”是相对于其当前朝向的反方向移动,而不是远离击中它的对象。因此,一个正在巡逻中的敌人可能会根据其路线最后指向的位置,被推向侧面。
对,我就是这个意思——“如果无法移动则跳过”,然后移动路径会一直运行直到撞到墙。
所以你的意思是,在并行处理中放置“等待移动完成”命令的问题在于,该命令会暂时获得并行处理中所有其他事件的优先级,直到“等待移动完成”命令执行完毕?
如果敌人只有在撞到墙后才被击败,我觉得这样看起来会很酷。
还是说,如果有其他敌人挡住了去路,它会一直卡在那里直到那个敌人移动?这确实是个合理的观点——或许在击退事件开始时更改优先级会有所帮助。
敌人必须面向玩家才能发动攻击,但如果玩家在敌人背对时击中它们,这确实是个问题。我不确定如何根据敌人面向的位置,让敌人相对于玩家被击退。
仅针对此事件。每个并行事件都会运行自己的解释器,因此“等待移动完成”只会暂停它所在的那个事件。地图上的其他所有事件仍会继续运行。
开启“无法移动时跳过”后,受阻的步骤会被跳过而不是重试,因此路径上的其他敌人也不会导致其挂起。路线会执行剩余的所有步骤并报告完成。挂起情况仅在关闭跳过时出现。关于互相穿过,并没有干净利落的方法。优先级仅影响玩家,事件之间会互相阻挡,而移动路线中的“通过”开关也会忽略墙壁,这会破坏靠墙停止的效果。在开启跳过时,在阻挡物前停下是无害的结果,因此我建议保持原样。
关于方向,移动路线列表中有一个“远离玩家转向”命令。将其放在最前面,并在其后添加“向前移动”,无论敌人被击中时面向哪个方向,推动都会使其远离玩家。这会使它们在结束时背对玩家,因此如果它们的朝向影响其他内容,请在最后一步添加“面向玩家转向”。
好吧,算了,这其实挺明显的。我把 ATTACKING=ON 放错了位置,导致它一直开着,所以攻击碰撞体始终粘在玩家身上。
不过紧接着又出现了新问题:有时候,当你反复按下 Z 键时,攻击碰撞事件可能会被遗留下来,完全不再跟随玩家。当玩家再次按下 Z 键时,它会重新吸附到玩家身上,但把攻击判定框留在原地仍然是一个……问题。有什么想法吗?
嗯,在继续之前,我有几个问题。Attack(攻击)碰撞事件在其选项中勾选了“通过”(Through)吗?那两个“设置移动路线”(Set Movement Routes)事件是否勾选了“无法移动时跳过”(Skip If Cannot Move)?
我之所以这么问,是因为移动路线是遵循碰撞检测的。如果前方的图块是墙壁或者有其他事件,那么“前进 1 步”就会失败。如果关闭了“无法移动时跳过”,失败的移动会在每一帧重试,而不是跳过,导致公共事件卡在“等待”状态,且“攻击中”(ATTACKING)标志仍然处于开启状态。“攻击中”开启正是阻止碰撞事件跟随的条件,所以它会停在原地,直到阻挡它的物体移开。在走动时不断按下 Z 键意味着挥击最终会朝向墙壁或敌人,这可能会触发“有时”(Sometimes)分支。一种检查方法是看 Z 键在卡住时是否有任何反应。如果是这种情况,在你离开前方的障碍物之前,它不会有任何反应。
无论如何,我都建议将 Attack 碰撞事件的“通过”(Through)选项勾选上。它必须能够移动到敌人所在的图块以判定命中,而且事件之间会互相阻挡,无论优先级如何。如果不勾选“通过”,攻击敌人的挥击反而会被阻挡。
另一个选择是取消移动路线,直接放置事件。碰撞事件中已经有了四个朝向分支,所以可以将“设置事件位置”(Set Event Location)设为玩家 X 和 Y 坐标,并在朝向方向上偏移一格,保持足够挥击持续的帧数,然后让正常的跟随逻辑将其拉回。这样就不需要移动路线,无需等待,也不会被阻挡。
这个问题已经修复了!攻击碰撞现在会始终跟随玩家。
至于下一个问题(抱歉,由于系统非常复杂,后续可能还会有不少问题,所以随时可以退出 lol)
普通攻击除了造成主要击退效果外,还附带一个小型的击退指令。然而,似乎敌人必须完成当前正在执行的步骤,才能真正被击退。这会导致出现这样的情况:敌人开始向新方块移动,刚起步就受到攻击,但直到完全踏入该方块时才会被击退,这中间可能延迟高达半秒。
简而言之,敌人没有被立即击退。
我能想到的唯一解决办法是,让敌人以某种方式强制将自己定位到游戏判定中当前“站立”的方块中央,但我不确定这是否是正确的思路。如果我表述清楚了,有什么建议吗?
太棒了!
你说得对,强制移动路线只有在角色完成当前所在的步骤后才会开始处理,因此中途设定的击退效果会先等待当前行走步骤的剩余部分结束。在默认的移动速度下,一个完整步骤耗时略超过半秒,这与你观察到的情况相符。
你的修复思路也是正确的,而且你已经有了所需的数值。引擎会在步骤开始的一瞬间将事件的地图 X 和 Y 坐标移动到目标瓦片,因此你在命中检测中获取的 SV: My X 和 My Y 就是它即将前往的瓦片坐标。在击退路线执行之前,立即使用“设置事件位置:此事件”指令,将该事件的位置设置为这两个变量,这样事件会瞬间瞬移到目标瓦片,从而立即结束当前步骤,使得路线在下一帧立即开始运行。
代价是,如果命中发生在步骤中途,视觉上会出现最多半个瓦片的瞬移效果,因为行走动作会在单帧内完成。
这确实大大缓解了问题。然而,在某些情况下,似乎完全没有触发击退效果,甚至在被击中时还会向前弹出一个格子的距离?我猜测最近提交的代码导致了这个问题,你觉得呢?
“前跳”是指打击命中时恰好发生在一步开始时的瞬间弹射。此时事件地图的 X 和 Y 坐标已经是目标瓦片,因此会被拉前整整一个瓦片,而不是我之前说的半个瓦片。缺失的击退效果则是从另一侧观察到的同一现象:向玩家方向弹射一个瓦片,再击退一个瓦片,最终位置与视觉起始位置重合,因此看起来仿佛什么都没发生。
改为弹射到精灵视觉上最接近的瓦片,而不是逻辑瓦片,即可修复这两个问题。真实位置并未作为事件命令暴露出来,但可以通过“控制变量”命令,将操作数设置为“脚本”,用一行代码获取:
◆控制变量:[Snap X] = Math.round(this.character(0)._realX)
◆控制变量:[Snap Y] = Math.round(this.character(0)._realY)
◆设置事件位置:本事件,({Snap X},{Snap Y})
将其放在击退路线之前,且在命中判定已通过之后。这样,前跳最多只减少半个瓦片(在较短的方向上),而击退则始终从敌人看起来所在的位置开始,移动整整一个瓦片。
如果你更倾向于不使用脚本,那么它来源的瓦片是其逻辑瓦片减去朝向方向的一步,因此使用本事件的“方向”和四个分支即可实现相同的效果。
另外,检查一下“没有任何效果”的情况,伤害是否仍然生效?如果没有生效,那就是另一个问题了:敌人离开了命中区域的瓦片,导致其逻辑位置从一开始就没有匹配。
第二行脚本中使用了 _realX,而实际上应该使用 _realY,因此 My Y 获取的是 X 坐标,而 Set Event Location 将坐标设置为了 (x, x)。这会导致角色陷入虚空。
◆Control Variables: #0195 SV: My Y = Math.round(this.character(0)._realY)
该代码块中的其他部分读取都是正确的。