在游戏中渲染 2D 地图 - 裁剪和滚动
好的。 我本质上是在尝试对 NES 硬件做出自己的解释,这样我就可以制作一款理想情况下类似于 NES 游戏的游戏。我目前陷入如何调整如何将游戏场绘制到视频内存(后缓冲区)的问题上。
BACKBUFFER
NES 对名称表或后备缓冲区有限制。对于我的示例,后缓冲区大小为 256x240 像素,或者一个屏幕的大小。如果我把它画到电视上,它会完美地填满屏幕。现在,如果我以 X=5 的偏移量将其绘制到屏幕上,则整个图像将发生移动并环绕屏幕。
例如......
ORIGINAL, NO OFFSET: DRAWN WITH OFFSET OF X=5:
ABCDEFGHIJK GHIJKABCDEF
DRAWN WITH OFFSET OF X=-5:
FGHIJKABCDE
屏幕被分成每个 8x8 像素的正方形,总共 32x30 行和列。 (256x240 像素)。偏移量以像素表示,而不是列;所以理论上,我可以将屏幕偏移 5 个像素,并且每列都会向右移动 5 个像素。
关卡设计
我的舞台由屏幕组成,其中包含以 16x15 行和列表示的数据,每行和列由 16x16 像素表示。这是为了模拟有多少 NES 游戏存储了关卡数据 - 每个图块都包含有关每个 8x8 块中应包含内容的信息。
例如:
AA
BC with A,B,C,D representing what 8x8 graphic should go where
关卡设计也由此表示,每个数字都是不同的屏幕,并且 - 没有任何意义,为空。
-----
-123-
---4-
--54-
-----
角色放置 这很容易。我已经知道如何根据角色的绝对位置确定角色所在的表、行和列。我还可以确定屏幕内的相对位置。
有了这些信息,我可以很容易地找出哪些列位于角色的左侧和右侧(如果有的话)(如果角色位于屏幕 1 的左侧,则左侧不再有任何级别)
所以这是问题敦敦敦 如何将关卡绘制到屏幕上,以便它们从一个关卡滚动到下一个关卡。 一次绘制一个全屏相对容易,当角色到达边缘时只需翻转到另一个屏幕。
然而,我在概念上遇到的问题是我需要将关卡数据“流”到屏幕上。假设角色向右移动 24 个像素。 BG 也需要移动。
因此,我需要将后台缓冲区的滚动调整 24 像素。然而,仅单独滚动就会导致屏幕覆盖,从而覆盖关卡的旧部分。因此,在滚动时,我需要确保将关卡的新部分绘制到后台缓冲区。但在此之前,我需要弄清楚需要先绘制哪些图块。如果该人向左移动 24 像素,我需要在后缓冲区上重新绘制图块并相应地更改偏移量。
我们不要忘记,如果角色向右移动并且没有更多的关卡数据要绘制,则不应有偏移,而是角色会更靠近屏幕的一侧(而不是通过X 轴)。
基本上,我现在脑海中浮现出很多不同的数字和价值观——试图让我的头脑理解许多概念,它们让我的大脑变得粘稠。有人对我如何处理这个问题有任何看法吗?
编辑:使用VB.Net。 C# 也适用(我都用这两种语言编程)
OK.
I'm in essense trying to make my own interpretation of the NES hardware, so I can make a game that ideally would resemble what a NES game would look like. I'm currently stuck with how to adjust how to draw the playfield to the video memory (back buffer).
BACKBUFFER
The NES had restrictions on the nametable, or backbuffer. For my example, the backbuffer is 256x240pixels big, or the size of one screen. If I drew this to the TV, it would fill up the screen perfectly. Now, if I drew this to the screen with an offset of X=5, the entire image would be shifted and would wrap around the screen.
For example.....
ORIGINAL, NO OFFSET: DRAWN WITH OFFSET OF X=5:
ABCDEFGHIJK GHIJKABCDEF
DRAWN WITH OFFSET OF X=-5:
FGHIJKABCDE
The screen is split up into squares 8x8 pixels each, totalling 32x30 rows and columns. (256x240 pixels). The offset is represented in pixels - not columns; so in theory, I could offset the screen by 5 pixels and every column would be shifted to the right by five pixels.
LEVEL DESIGN
My stages are made up of screens, containing data that is represented in 16x15 rows and columns, each by 16x16 pixels. This is to emulate how many NES games stored level data - each tile holds info about what should be in each 8x8 block.
E.g.:
AA
BC with A,B,C,D representing what 8x8 graphic should go where
Level design is represented by this as well, with each number being a different screen and - meaning nothing, null.
-----
-123-
---4-
--54-
-----
CHARACTER PLACEMENT
This is easy. I already know how I can determine which table, row, and column my characters are on based on their absolute positioning. I can also determine the relative positioning within the screen.
With this info, I can easily figure out which columns are to the left and right of the character, if any (if the character is at the left side of screen 1, thered be no more level left)
SO HERE'S THE QUESTION DUN DUN DUN
How do I draw my levels to the screen, so that they scroll from one to the next.
It would be relatively easy to draw one full screen at a time, and when the character gets to the edge just flip to another screen.
However, the problem I'm conceptually having is that I need to 'stream' the level data onto the screen. That, lets say the character moves 24 pixels to the right. The BG needs to move as well.
So, I need to adjust the scrolling of the backbuffer by 24 pixels. However, just scrolling alone will cause the screen to wrap over, diplsaying old portion of the level. So, while scrolling I need to make sure to draw new pieces of the level to the back buffer. But before I can do that, I need to figure out which tiles need to get drawn first. And if the person goes 24 pixels to the left, I need to redraw tiles onto the backbuffer and change the offset accordingly as well.
And let's not forget that if the character moves right and there's no more level data to be drawn, there should be no offset but instead the character gets closer to the side of the screen (instead of being centered in the middle of the screen via the X axis).
Basically, I just have a lot of different numbers and values stuck in my mind right now - trying to wrap my head around many concepts and they caused my brain to turn into goo. Anyone have any perspective on how I can approach this?
EDIT: Using VB.Net. C# is applicable as well ( I program in both)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
NES 的做法是使用 4 个名称表,每个名称表为 256x240。当您将其中一个滚出屏幕时,相邻的一个将会滚动。要获得大于 512x480 的“世界”,您需要更新屏幕外位置的名称表。
一般来说,卡带在 ROM 中有一个包含所有关卡数据的巨大表格,并使用 1 或 2 个 RAM 变量跟踪总体位置。然后,当游戏滚动时,它会更新 RAM 变量,使用这些变量来查找关卡数据表,并将新的关卡数据复制到屏幕外的名称表部分。
所以实际上你的后台缓冲区应该是 512x480,而你只显示其中的 256x240 部分。
您是否看过任何具有大型滚动世界的 NES 游戏的反汇编?我认为 Metroid 的部分反汇编就在某个地方......
是的,在 romhacking.net
银河战士反汇编;评论不太好,但只要稍加努力,就不难弄清楚。
另一个反汇编,注释得更好。
编辑:在上面发布的第二个反汇编中,查看 MetroidGameEnginePage.txt 中的“SetupRoom”例程和“DrawRoom”例程;标签 LEB4D 处的例程还显示了在一种情况下如何更新名称表。另请参阅“GetNameTable”例程上方的大量注释。一般来说,在整个文档中对“姓名表”进行文本搜索将为您带来更多信息。
编辑编辑:也在 romhacking.net; SMB 内存映射可能有用。
The way the NES did it was with 4 name tables, each 256x240. When you scrolled one off the screen, the adjacent one would scroll on. To get "worlds" bigger than 512x480, you update the name table at the positions that are offscreen.
Generally, a cartridge would have a giant table in ROM with all of the level data, and would track the overall position with 1 or 2 RAM variables. Then when the game scrolled, it would update the RAM variables, use those to lookup the level data table and copy in the new level data into the parts of the name table that were offscreen.
So actually your back buffer should be 512x480, and you only show a 256x240 portion of it.
Have you looked at the disassembly for any NES games that have large scrolling worlds? I think a partial disassembly of Metroid is out there somewhere...
...yeah, found some docs at romhacking.net
Metroid disassembly; not real well commented but with a little effort it shouldn't be too hard to figure it out.
Another disassembly that is commented better.
EDIT: In the 2nd disassembly posted above, look in MetroidGameEnginePage.txt at the "SetupRoom" routine and the "DrawRoom" routine; also the routine at label LEB4D shows how the name table is updated in one case. Also look at the extensive comments above the "GetNameTable" routine. In general, a text search for "name table" throughout the document will get you lots more.
EDIT EDIT: Also at romhacking.net; the memory map for SMB might be useful.
我将设置一个图像控件网格,并从数据库加载级别,该数据库存储每张图片相对于级别的列和行。一旦代表游戏角色的图像距离屏幕右侧足够远(您必须将其位置存储在某些模块级别变量中),您将加载下一列图像(同时将所有其他图像移动到向左并删除第一列)。
I would set up a grid of image controls and load the levels from a database that stores what column and row each picture is relative to the level. Once the image that represents the game's character gets far enough to the right (you would have to store its location in some module level variables) of the screen you would load the next column of images (while shifting all of the other images one to the left and removing the first column).