画布游戏:我的蛇吃自己
Posted
技术标签:
【中文标题】画布游戏:我的蛇吃自己【英文标题】:Canvas Game: My snake eats itself 【发布时间】:2011-12-22 03:52:09 【问题描述】:自从我的另一个错误得到解决后,我将针对这个错误发布一个新问题。
我制作了一个 Snake 画布游戏,但是当您同时按下两个按钮时,我的蛇往往会吃掉自己。我不知道如何正确解释,但会发生这种情况:
假设我的蛇向左移动,我按下 + 右,它会吃掉自己并触发游戏结束。当它向右移动时也一样:向下+向左和砰,死了。不过,当蛇上下移动时,我似乎无法重现该错误..
这是改变方向的代码:
bindEvents = ->
keysToDirections =
37 : LEFT
38 : UP
39 : RIGHT
40 : DOWN
$(document).keydown (e) ->
key = e.which
newDirection = keysToDirections[key]
if newDirection
setDirection newDirection
e.preventDefault()
setDirection = (newDirection) ->
# TODO: there's some bug here; try pressing two buttons at the same time..
switch Snake.direction
when UP, DOWN
allowedDirections = [LEFT, RIGHT]
when LEFT, RIGHT
allowedDirections = [UP, DOWN]
if allowedDirections.indexOf(newDirection) > -1
Snake.direction = newDirection
我认为编译的 JS 有问题,因为我的 switch 语句在最后一个 case
上没有 break
;但是,我尝试将else return
添加到咖啡脚本文件中,但这并没有改变任何东西。我在这里完全迷路了,所以我希望有人能够发现我哪里出错了。
似乎它正确地使用了 keydown 事件,但是当你按得太快时它们会被覆盖。如果这有意义吗?就像..当蛇向右移动时,您会向上按,但是在它实际上有机会向上移动之前您按向左,然后它就向左移动。那里乱七八糟的句子,我建议你玩一会儿,如果你和我一样感兴趣,我建议你尝试重现这个:(
我不知道如何正确调试它。当我执行console.log
时,游戏循环往往会向我发送垃圾邮件。
可以在here找到现场演示和指向我的 github 存储库的链接
提前致谢。
【问题讨论】:
【参考方案1】:问题是如果你按键足够快,可能会在一帧内多次触发事件回调。因此,如果蛇向下,它可以在同一帧中右转然后向上,从而反转方向并吃掉自己。我会建议两种方法来解决这个问题:
首先是在方向改变时设置一个标志,即:
if allowedDirections.indexOf(newDirection) > -1 and !turnedThisFrame
Snake.direction = newDirection
turnedThisFrame = true
然后,在每帧运行的代码中,将 turnedThisFrame
设置为 false
。
第二个是重新设计处理按键的方式。这通常是我采取的方法。保留按下了哪些键的映射(例如,keysDown
),并绑定一个将keysDown[e.which] = true
设置为keydown 的函数和另一个将keysDown[e.which] = false
设置为keyup 的函数。然后,您可以检查在运行每一帧的代码中按下了哪些键,并采取相应措施。
以下是有关我如何在当前项目中实施第二个解决方案的一些详细信息。以下代码出现在我的 onload 处理程序中:
do ->
keysDown = []
$(document).keydown (e) ->
keysDown.push e.which
$(document).keyup (e) ->
keysDown = _.without keysDown, e.which
window.isPressed = (keyCode) -> keyCode in keysDown
do ->
构造用于创建一个函数并立即调用它,这具有使处理程序和isPressed
保持keysDown
封闭的有益效果,同时避免污染onload
的主要范围回调。
然后,在我的 tick
函数(每帧运行一次并处理游戏逻辑、绘制屏幕等)的开头,我会得到这样的东西:
switch Snake.direction
when UP, DOWN
allowedDirections = [LEFT, RIGHT]
when LEFT, RIGHT
allowedDirections = [UP, DOWN]
for direction in allowedDirections
if isPressed(directionToKey[direction])
Snake.direction = newDirection
break
地图directionToKey
与您的keysToDirections
正好相反。
请注意,这意味着allowedDirections
中列出的第一个键将具有优先权,即如果您向右移动并在同一帧中同时按下向上和向下,则无论哪个先按下都会向上。
第二种方法的附加优势:在菜单屏幕和游戏之间切换时,您不必更改键处理程序回调。您只是有一个不同的tick
函数来检查按下的内容。
【讨论】:
非常感谢。我现在提出了第一个解决方案,但是一旦我把头绕过去,我肯定会检查第二种方式。我认为,我必须重写我的代码才能做到这一点。非常感谢! 我详细介绍了如何实现第二种方法。改变它真的不需要太多的工作,我更喜欢它,因为我的帖子中提到的原因。 非常感谢。我一定会看看的!【参考方案2】:您必须保留蛇实际移动的最后一个方向,并根据该方向确定 allowedDirection。按下一个键只代表向那个方向移动的意图,但按下键时它实际上并没有移动,而是根据游戏的速度,我猜。
【讨论】:
【参考方案3】:你的蛇吃本身表明你的命中检测(和命中检测处理)代码存在问题。如果你打蛇,游戏应该结束。蛇不是苹果! 没关系,显然我错过了游戏为你结束的部分。
如果您选择允许像素粒度(我看不到您的演示,我的工作网络已中断..),您无法真正让 U 变“安全”,就像使用 hex-粒度方法。不是说你的选择不好,只是告诉你选择你的战斗,有些你赢不了。
【讨论】:
【参考方案4】:我有同样的问题,但我通过实现一个队列解决了它;每次按键都会设置蛇的方向,并且(新)方向被推送到一个数组中。然后,在我真正在游戏循环中移动蛇之前,我检查我的方向队列中是否有任何元素。如果是这样,我通过 Array.shift 将其移动一次。返回值是队列的第一个元素和我的蛇的新方向。 Array.shift 也会从队列中移除那个元素,这正是我们想要的。如果几乎同时按下两个键,第一个和第二个方向变化存储在我们的队列中,我们前面提到的例程会处理其余的,首先在下一个可用滴答中应用第一个方向变化,然后应用第二个方向变化在此后的下一个滴答声中。
希望这有意义吗? :-)
【讨论】:
以上是关于画布游戏:我的蛇吃自己的主要内容,如果未能解决你的问题,请参考以下文章