我一直在 stackoverflow 和其他网站上阅读有关游戏中的碰撞检测的内容。 他们中的很多人都在谈论 BSP、边界椭圆、集成等。然而,在 NES 上,他们成功地在游戏中进行了地板和墙壁碰撞检测,我很难相信他们做了很多计算来检测墙壁碰撞。
我想我的问题是,考虑到一个仅由瓷砖组成的关卡,他们如何检测像《马里奥》和《洛克人》这样处理能力很少的游戏中与墙壁和地板的碰撞?
- 他们是否沿着运动路径确定了最近的连接块? (一点搜索)(先验)
- 他们是否确定了与地板的碰撞,然后找出调整角色的最佳方法? (后验)这对于可变的时间步长来说是有风险的,如果你足够快,你可以跳过一块瓷砖。 尽管我假设 NES 游戏的时间步长与电视的刷新率同步。
- 当你在地面上时,重力总是会影响你的角色吗? 或者当您决定在瓷砖上行走时,您是否只是“将其关闭”? 当你走下悬崖边缘时会怎样? 否则你需要某种方法来确定你下面的瓷砖。
- 如果您与一块瓷砖相撞,您是否会找到该瓷砖的边缘并将您的角色移动到它的一侧(取决于行进方向)?
- 像《超级银河战士》和《马里奥》那样的倾斜瓷砖怎么样?
- 可以从底部跳到顶部的“平台”怎么样? 如果您“事后”处理这些瓷砖的碰撞,您将如何处理?
我编写了一些基本上是“先验”的碰撞代码,因为它会搜索您将在某个方向上碰到的第一个图块。 我只是想知道是否有更好的方法。 (也许只是使用事后碰撞检测)
例如,检查向下移动的图块碰撞的代码(我检查垂直然后水平移动):
def tile_search_down(self, char, level):
y_off = char.vert_speed
assert y_off > 0
# t_ are tile coordintes
# must be int.. since we're adding to it.
t_upper_edge_y = int( math.ceil((char.y+char.h) / self.tile_height ) ) #lowest edge
while (t_upper_edge_y*self.tile_height) < (char.y+char.h+y_off): # lowest edge + offset
t_upper_edge_x = int( math.floor(char.x/self.tile_width) )
while (t_upper_edge_x*self.tile_width) < (char.x+char.w):
t_x = t_upper_edge_x
t_y = t_upper_edge_y
if self.is_tile_top_solid(t_x, t_y, plane):
char.y = t_y*self.tile_height - char.h
char.vert_speed = 0.0
char.on_ground = True
return
t_upper_edge_x += 1
t_upper_edge_y += 1
char.y += y_off
I have been reading about collision detection in games on stackoverflow and other sites. A lot of them talk about BSPs, bounding elipses, integration etc. However, on the NES, they managed to do floor and wall collision detection in games and I find it hard to believe that they did many calculations to detect wall collisions.
I guess my question is, given a level made up of just tiles, how did they detect collisions with walls and floors in games like Mario and Megaman which had little processing power?
- Did they follow the path of motion and determine the closest connecting tile? (a bit of searching) (priori)
- Did they determine a collision with the floor and then figure out the best way of adjusting the character? (posteriori) This is risky with variable timesteps, you could jump through a tile if you were fast enough. Although I assume NES games timesteps were synced with the tv's refresh rate.
- Is gravity always affecting your character when you're on the ground? Or do you just 'turn it off' when you're determined to be walking on a tile? What about when you walk off an edge of the cliff? You'd need some sort of way of determining tiles underneath you otherwise.
- If you've collided with a tile, would you just find the edge of that tile and move your character to the side of it (depending on the direction of travel)?
- what about sloping tiles like in super metroid and mario?
- What about 'platforms' where you can jump through the bottom and land on top. How would you deal with collisions with these tiles if you were doing it 'posteriori'?
I have written some collision code that is basically 'priori' as it searches for the first tile you will hit in a certain direction. I'm just wondering if there's a better way. (just using after-the-fact collision detection instead maybe)
eg, code to check for tile collisions for moving downward (I check vert then horizontal movement):
def tile_search_down(self, char, level):
y_off = char.vert_speed
assert y_off > 0
# t_ are tile coordintes
# must be int.. since we're adding to it.
t_upper_edge_y = int( math.ceil((char.y+char.h) / self.tile_height ) ) #lowest edge
while (t_upper_edge_y*self.tile_height) < (char.y+char.h+y_off): # lowest edge + offset
t_upper_edge_x = int( math.floor(char.x/self.tile_width) )
while (t_upper_edge_x*self.tile_width) < (char.x+char.w):
t_x = t_upper_edge_x
t_y = t_upper_edge_y
if self.is_tile_top_solid(t_x, t_y, plane):
char.y = t_y*self.tile_height - char.h
char.vert_speed = 0.0
char.on_ground = True
return
t_upper_edge_x += 1
t_upper_edge_y += 1
char.y += y_off
发布评论
评论(4)
对于您所谈论的 NES 时代游戏类型,一切都是 2D。 仅此一点就简化了很多事情。
那个时代的一些机器(特别是带有硬件精灵的机器,如 Commodore 64)具有硬件碰撞检测功能。 大多数不依赖于硬件碰撞检测的游戏会使用边界框或命中蒙版(精灵的 1 位位图)。
无论哪种方式,碰撞检测通常是“后验”完成的,除了世界边缘等特殊情况。 有些游戏实际上确实存在错误,当你击中某物时移动太快可能会导致你穿过它。 (事实上,对 80 年代早期游戏的评论经常会评论碰撞检测的精确程度。)
对于平台游戏,您通常会在施加重力之前检查角色是否“接地”。
事后处理单向平台问题并不太难,因为您知道精灵的速度矢量,因此您可以使用它来确定是否应该记录碰撞。
For the types of NES-era games you're talking about, everything was 2D. That alone simplifies many things.
Some machines of that era (particularly ones with hardware sprites, like the Commodore 64) had hardware collision detection. Most games that weren't relying on hardware collision detection would either use a bounding box or a hit mask (1-bit bitmap of the sprite).
Either way, collision detection was usually done "a posteriori", except for special cases like the edges of the world. Some games actually did have bugs where moving too fast when you hit something could cause you to pass through it. (In fact, reviews of early 80's games would often comment on how precise the collision detection was.)
For platformers, you'd typically check to see if the character is "grounded" before applying gravity.
The one-way platforms thing isn't too hard to deal with after the fact since you know the sprite's velocity vector, so you can use that to determine whether or not the collision should register.
这里有一篇文章深入研究了任天堂娱乐系统(NES)“平台游戏”的编程。
我可能没有正确搜索,因为我以前没有偶然发现过这篇文章。
There is article here that is an in-depth look at programming a Nintendo Entertainment System (NES) "platform game".
I may not have been googling right because I haven't stumbled upon this article before.
对于《超级马里奥世界》(SNES) 等游戏,游戏以内存格式存储关卡,这样可以轻松获取马里奥的 X/Y 位置,将其转换为图块地址,然后立即检查该地址周围的图块。 由于关卡始终是固定宽度(尽管您可以查看的区域不同),因此它使寻址更易于管理,因为它始终与马里奥的位置有固定的偏移量,例如,地址 + 1 表示马里奥旁边的图块,地址 + 0x300 表示马里奥旁边的图块。在他下面平铺瓷砖,等等。
For games such as Super Mario World (SNES), the game stored the levels in a memory format that made it easy to take Mario's X/Y location, convert it to a tile address, and then check the tiles immediately around that address. Since the levels were always a fixed width (though the area you could view varied), it made addressing easier to manage since it was always a fixed offset from mario's position, e.g. Address + 1 for the tile beside mario, Address + 0x300 for the tile below him, etc.
在较旧的游戏中,碰撞检测通常不够完美,以牺牲准确性来换取性能。
例如,在《超级马里奥兄弟》中,与敌人的碰撞仅每隔一帧检查一次。 每 27 帧仅发生一次与关卡结束旗杆的碰撞。 检查碰撞的最大对象数量也有限制,众所周知,这可以让你在游戏结束时通过浏览器的一些攻击而不会死亡。
另一个例子是 Gradius 的 PC Engine 端口。 它没有使用更昂贵的边界框命中检测,而是使用平铺系统。 每个对象都被简化为一个图块编号,由四舍五入到 8 的倍数并连接成一个数字的 X 和 Y 位置组成。 如果两个物体占据相同的 8x8 方块,则认为它们发生了碰撞。 它不太准确,但往往对玩家有利,因此提供了一个可以接受且有趣的折衷方案。
In older games collision detection was often less than perfect, trading accuracy for performance.
For example, in Super Mario Bros. collision with enemies is only checked every other frame. Collision with the end-of-level flag pole is done only once every 27 frames. There is also a limit on the maximum number of objects checked for collisions, which famously allows you to pass through some of Browser's attacks at the end of the game without dying.
Another example is the PC Engine port of Gradius. Rather than using more expensive bounding box hit detection, it uses a tile system. Each object is reduced to a tile number, consisting of the X and Y position rounded to a multiple of 8 and concatenated into a single number. If two objects occupy the same 8x8 tile they are deemed to have collided. It's less accurate but tends to favour the player, so presents an acceptable and fun compromise.