这可能需要一些时间来解释——在你阅读这篇文章的时候去吃点零食。
我正在为 C++ 中的 Gameboy Advance 开发一个 2D 益智平台游戏(我是一个相当新的程序员)。直到昨晚,我一直在制作一个物理引擎(只是一些轴对齐的边界框的东西),我正在使用 GBA 屏幕大小的级别进行测试。但是最终的游戏需要一个大于屏幕大小的关卡,所以我尝试实现一个系统,让GBA的屏幕跟随玩家,结果我必须绘制所有东西在屏幕上相对于屏幕的偏移量。
但是,当我显示可以在关卡中拾取和操作的立方体时,我遇到了麻烦。每当玩家移动时,屏幕上立方体的位置似乎会偏离它们在关卡中的实际位置。就像绘制立方体的位置是一个不同步的帧 - 当我在玩家移动时暂停游戏时,框会显示在完全正确的位置,但是当我取消暂停时,它们会漂移到玩家停止的位置再次移动。
我的类的简要描述 - 有一个名为 Object 的基类,它定义了 (x, y) 位置和宽度和高度,有一个继承自 Object 并添加速度分量的 Entity 类,以及一个继承自 Entity 的 Character 类并增加运动功能。我的播放器是一个角色对象,而我要拾取的立方体是一组实体对象。player 和 cubes 数组都是 Level 类的成员,它也继承自 Object。
我怀疑问题出在最后一个代码示例中,但是,为了完全理解我正在尝试做的事情,我以稍微更合乎逻辑的顺序排列了示例。
以下是 Level 的截断标题:
class Level : public Object
{
private:
//Data
int backgroundoffsetx;
int backgroundoffsety;
//Methods
void ApplyEntityOffsets();
void DetermineBackgroundOffsets();
public:
//Data
enum {MAXCUBES = 20};
Entity cube[MAXCUBES];
Character player;
int numofcubes;
//Methods
Level();
void Draw();
void DrawBackground(dimension);
void UpdateLevelObjects();
};
...和实体:
class Entity : public Object
{
private:
//Methods
int GetScreenAxis(int &, int &, const int, int &, const int);
public:
//Data
int drawx; //Where the Entity's x position is relative to the screen
int drawy; //Where the Entity's y position is relative to the screen
//Methods
void SetScreenPosition(int &, int &);
};
以下是我的主要游戏循环的相关部分:
//Main loop
while (true)
{
...
level.MoveObjects(buttons);
level.Draw();
level.UpdateLevelObjects();
...
}
由于暂停时精灵显示在正确位置的方式,我很确定问题不在于MoveObjects()
,它决定了玩家和立方体在关卡中相对于关卡的位置。所以留下Draw()
和UpdateLevelObjects()
。
好的,Draw()
。如果不是我的立方体显示不正确,而是它们所在的关卡和平台,我会提供这个(我认为这不是问题,但可能)。Draw()
只调用一个相关函数,DrawBackground()
:
/**
Draws the background of the level;
*/
void Level::DrawBackground(dimension curdimension)
{
...
//Platforms
for (int i = 0; i < numofplatforms; i++)
{
for (int y = platform[i].Gety() / 8 ; y < platform[i].GetBottom() / 8; y++)
{
for (int x = platform[i].Getx() / 8; x < platform[i].GetRight() / 8; x++)
{
if (x < 32)
{
if (y < 32)
{
SetTile(25, x, y, 103);
}
else
{
SetTile(27, x, y - 32, 103);
}
}
else
{
if (y < 32)
{
SetTile(26, x - 32, y, 103);
}
else
{
SetTile(28, x - 32, y - 32, 103);
}
}
}
}
}
}
这不可避免地需要一些解释。我的平台以像素为单位,但显示为 8x8 像素的图块,因此我必须为这个循环划分它们的大小。SetTile()
首先需要一个屏幕块编号。我用来显示平台的背景层是 64x64 瓦片,因此需要 2x2 个屏幕块,每个屏幕块 32x32 瓦片才能全部显示。屏幕块编号为 25-28。103 是我的瓷砖地图中的瓷砖编号。
这是UpdateLevelObjects()
:
/**
Updates all gba objects in Level
*/
void Level::UpdateLevelObjects()
{
DetermineBackgroundOffsets();
ApplyEntityOffsets();
REG_BG2HOFS = backgroundoffsetx;
REG_BG3HOFS = backgroundoffsetx / 2;
REG_BG2VOFS = backgroundoffsety;
REG_BG3VOFS = backgroundoffsety / 2;
...
//Code which sets player position (drawx, drawy);
//Draw cubes
for (int i = 0; i < numofcubes; i++)
{
//Code which sets cube[i] position to (drawx, drawy);
}
}
这些REG_BG
位是 GBA 的寄存器,允许背景层垂直和水平偏移多个像素。这些偏移量首先计算在DetermineBackgroundOffsets()
:
/**
Calculate the offsets of screen based on where the player is in the level
*/
void Level::DetermineBackgroundOffsets()
{
if (player.Getx() < SCREEN_WIDTH / 2) //If player is less than half the width of the screen away from the left wall of the level
{
backgroundoffsetx = 0;
}
else if (player.Getx() > width - (SCREEN_WIDTH / 2)) //If player is less than half the width of the screen away from the right wall of the level
{
backgroundoffsetx = width - SCREEN_WIDTH;
}
else //If the player is in the middle of the level
{
backgroundoffsetx = -((SCREEN_WIDTH / 2) - player.Getx());
}
if (player.Gety() < SCREEN_HEIGHT / 2)
{
backgroundoffsety = 0;
}
else if (player.Gety() > height - (SCREEN_HEIGHT / 2))
{
backgroundoffsety = height - SCREEN_HEIGHT;
}
else
{
backgroundoffsety = -((SCREEN_HEIGHT / 2) - player.Gety());
}
}
需要明确的是,width
是指以像素为单位的关卡宽度,而SCREEN_WIDTH
指的是GBA屏幕宽度的恒定值。另外,对于懒惰的重复感到抱歉。
这是ApplyEntityOffsets
:
/**
Determines the offsets that keep the player in the middle of the screen
*/
void Level::ApplyEntityOffsets()
{
//Player offsets
player.drawx = player.Getx() - backgroundoffsetx;
player.drawy = player.Gety() - backgroundoffsety;
//Cube offsets
for (int i = 0; i < numofcubes; i++)
{
cube[i].SetScreenPosition(backgroundoffsetx, backgroundoffsety);
}
}
基本上,当玩家位于关卡中间时,它会在屏幕上居中,并在屏幕碰到关卡边缘时允许它移动到边缘。至于立方体:
/**
Determines the x and y positions of an entity relative to the screen
*/
void Entity::SetScreenPosition(int &backgroundoffsetx, int &backgroundoffsety)
{
drawx = GetScreenAxis(x, width, 512, backgroundoffsetx, SCREEN_WIDTH);
drawy = GetScreenAxis(y, height, 256, backgroundoffsety, SCREEN_HEIGHT);
}
请耐心等待 - 我稍后会解释 512 和 256。这是GetScreenAxis()
:
/**
Sets the position along an axis of an entity relative to the screen's position
*/
int Entity::GetScreenAxis(int &axis, int &dimensioninaxis, const int OBJECT_OFFSET,
int &backgroundoffsetaxis, const int SCREEN_DIMENSION)
{
int newposition;
bool onawkwardedgeofscreen = false;
//If position of entity is partially off screen in -ve direction
if (axis - backgroundoffsetaxis < dimensioninaxis)
{
newposition = axis - backgroundoffsetaxis + OBJECT_OFFSET;
onawkwardedgeofscreen = true;
}
else
{
newposition = axis - backgroundoffsetaxis;
}
if ((newposition > SCREEN_DIMENSION) && !onawkwardedgeofscreen)
{
newposition = SCREEN_DIMENSION; //Gets rid of glitchy squares appearing on screen
}
return newposition;
}
OBJECT_OFFSET
(512 和 256)是 GBA 特有的东西 - 将对象的 x 或 y 位置设置为负数不会正常执行您想要的操作 - 它会弄乱用于显示它的精灵。但是有一个技巧:如果你想设置一个负的 X 位置,你可以在负数上加上 512,精灵就会出现在正确的位置(例如,如果你打算将它设置为 -1,那么将它设置为512 + -1 = 511)。同样,添加 256 适用于负 Y 位置(这都是相对于屏幕,而不是级别)。最后一个 if 语句将立方体显示在屏幕之外,如果它们通常显示得更远的话,因为试图将它们显示得太远会导致出现故障方块,这也是 GBA 特定的东西。
如果您已经阅读了所有内容,那么您就是绝对的圣人。如果您能找到可能导致漂移的立方体的原因,我将非常感激。此外,任何一般改进我的代码的提示都将不胜感激。
编辑:GBA对象更新设置播放器和立方体位置的方式如下:
for (int i = 0; i < numofcubes; i++)
{
SetObject(cube[i].GetObjNum(),
ATTR0_SHAPE(0) | ATTR0_8BPP | ATTR0_REG | ATTR0_Y(cube[i].drawy),
ATTR1_SIZE(0) | ATTR1_X(cube[i].drawx),
ATTR2_ID8(0) | ATTR2_PRIO(2));
}