c++ - 玩家移动时,GameBoy Advance对象未显示在正确的位置

标签 c++ oop graphics 2d gba

这可能需要一段时间才能解释-在阅读本文时,请先品尝小吃。

我正在为C ++中的Gameboy Advance开发2D益智平台游戏(我是一个相当新的程序员)。直到昨晚,我一直在制作物理引擎(只是一些与轴对齐的边界框东西),我正在使用GBA屏幕大小的水平进行测试。但是,最终游戏将要求其级别大于屏幕大小,因此我尝试实现一种系统,该系统允许GBA的屏幕跟随玩家,因此,我必须绘制所有内容在屏幕上相对于屏幕的偏移量。

但是,当我显示可以在关卡中拾取和操纵的多维数据集时遇到麻烦。每当玩家移动时,屏幕上的多维数据集的位置似乎就会偏离其在关卡中的实际位置。这就像绘制多维数据集的帧是一个不同步的帧-当我在玩家移动时暂停游戏时,这些框会显示在正确的位置,但是当我取消暂停时,它们会错位直到玩家停止再次移动。

我的类的简要说明-有一个称为Object的基类,它定义(x,y)位置和宽度和高度,还有一个Entity类,它从Object继承并添加速度分量,而Character类则从Entity继承并增加了运动功能。我的播放器是一个Character对象,而我要拾取的多维数据集是Entity对象的数组。 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)。同样,为负Y位置添加256也可以(这都是相对于屏幕,而不是水平)。如果通常将多维数据集显示在更远的地方,则最后一个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));
}

最佳答案

我将解释这个答案,按位运算符是如何工作的,一个数字如何表示一个可能值为0到255(256个组合)的字节,它包含所有GBA Control印刷机。这类似于您的X / Y位置问题。

控件

Up - Down - Left - Right - A - B - Select - Start

这些是GameBoy Color控件,我认为GameBoy Advanced具有更多控件。
因此共有8个控件。
每个控件都可以被按下(按住)或不被按下。
这意味着每个控件应仅使用数字10
由于10仅占用1位信息。在一个字节中,您最多可以存储8个不同的位,适合所有控件。

现在您可能正在考虑如何通过添加或其他方式将它们组合在一起?是的,您可以这样做,但是这会使您的理解变得非常复杂,并且会给您带来这个问题。

假设您有半杯空的水,并且向其中添加了更多的水,并且想要将新添加的水与旧水分离。.您就是不能这样做,因为水都变成了一种水,无法取消此操作(除非我们标记每个水分子,而且我们还不是外星人。..哈哈)。

但是对于按位运算,它使用数学运算来找出整个位流(列表)中的哪个位确切是10

因此,您要做的第一件事就是将每一点都交给控件。
每个位都是二进制的2的倍数,因此您只需将值加倍即可。

Up - Down - Left - Right - A - B - Select - Start
1 - 2 - 4 - 8 - 16 - 32 - 64 - 128

同样,按位运算不仅用于确定哪个位是10,还可以使用它们将某些内容组合在一起。控件可以很好地完成此操作,因为您可以一次按住多个按钮。

这是我用来找出被按下或未被按下的代码。

我不使用C / C ++,所以这是javascript我在我的Gameboy模拟器网站上使用了它,字符串部分可能是错误的,但实际的按位代码在几乎所有编程语言上都是通用的,我所看到的唯一区别是Visual Basic cc>在此处称为&

function WhatControlsPressed(controlsByte) {
    var controlsPressed = " ";
    if (controlsByte & 1) {
        controlsPressed = controlsPressed + "up "
    }
    if (controlsByte & 2) {
        controlsPressed = controlsPressed + "down "
    }
    if (controlsByte & 4) {
        controlsPressed = controlsPressed + "left "
    }
    if (controlsByte & 8) {
        controlsPressed = controlsPressed + "right "
    }
    if (controlsByte & 16) {
        controlsPressed = controlsPressed + "a "
    }
    if (controlsByte & 32) {
        controlsPressed = controlsPressed + "b "
    }
    if (controlsByte & 64) {
        controlsPressed = controlsPressed + "select "
    }
    if (controlsByte & 128) {
        controlsPressed = controlsPressed + "start "
    }
    return controlsPressed;
}


如何设置要按下的单个控件?好吧,您必须记住您使用了哪个按位数字来控制什么,我会做出这样的事情

#DEFINE UP 1
#DEFINE DOWN 2
#DFFINE LEFT 4
#DEFINE RIGHT 8


假设您同时按下ANDUp所以您按下了A1

您制作一个包含所有控件的字节,可以说

unsigned char ControlsPressed = 0;


现在什么也没按下,因为它是0。

ControlsPressed |= 1; //Pressed Up
ControlsPressed |= 16; //Pressed A


所以是的,16现在将保留数字ControlsPressed,您可能只是在想17这就是它的工作原理,但是是的,您无法将其恢复到基本值的原因首先使用基本数学。

但是,是的,您可以将1+16更改为17,然后放开向上箭头并按住16按钮,就可以了。

但是,当您按住很多按钮时,值会变得很大,可以说。
A = 1+4+16+128

因此,您不记得所加的内容,但您知道值是149现在如何取回密钥?好吧,这是很容易的,是的,就是开始减去最高的数字,您可以找到控件使用的最高数字,该数字低于149;如果您减去它的数值较大,那么它不会被按下。

是的,此时您想的是,我可以进行一些循环并执行此操作,但是无需任何操作即可完成,内置命令可以即时执行此操作。

这是您按下任何按钮的方式。

ControlsPressed = ControlsPressed AND NOT (NEGATE) Number


在C / C ++ / Javascript中,您可以使用类似这样的内容

ControlsPressed &= ~1; //Let go of Up key.
ControlsPressed &= ~16; //Let go of A key.


还有什么要说的,就是关于按位知识的所有知识。

编辑:

我没有解释按位移位运算符149<<,我真的不知道如何在基本层面上对此进行解释。

但是当你看到这样的东西

int SomeInteger = 123;
print SomeInteger >> 3;


上面有一个右移运算符,在那里被使用,它向右移3位。

它的实际作用是将2除以3的幂。
因此,在基础数学中,它实际上就是这样做的

SomeInteger = 123 / 8;


因此,现在您知道向右移>>与将值除以2的幂是一回事。
现在向左移动>>在逻辑上意味着您将值乘以2的幂。

移位通常用于将2种不同的数据类型打包在一起,并在以后提取它们。 (我认为这是移位的最常见用法)。

假设您的游戏中有X / Y坐标,则每个坐标只能达到一个限定值。
(这只是一个例子)

X: (0 to 63)
Y: (0 to 63)


而且您还知道X,Y必须存储在一些小的数据类型中。我认为包装非常紧密(没有空隙)。

(这可能需要一些逆向工程才能准确地找到,或者只是阅读手册)。
用于保留位或某些未知信息的位置可能存在间隙。

但是沿着这里移动,这样两个都可以容纳总共64种不同的组合。

因此,<<X各自占用6位,而两者总共占用12位。
因此,每个字节总共节省了2位。 (总共节省了4位)。


 X         |       Y

  
  [1 2 4 8 16 32] | [1 2 4 8 16 32]
  [1 2 4 8 16 32 64] [128 1 2 4 8 [16 32 64 128]


因此,您需要使用移位来正确存储信息。

这是它们的存储方式

int X = 33;
int Y = 11;


由于每个坐标占用6位,这意味着您必须将每个数字向左移6。

int packedValue1 = X << 6; //2112
int packedValue2 = Y << 6; //704
int finalPackedValue = packedValue1 + packedValue2; //2816


所以是的,最终值将是Y

现在,您可以从2816返回值,并在相反的方向进行相同的移位。

2816 >> 6 //Gives you back 44. lol.


所以是的,水的问题再次发生,您有44(33 + 11),没有办法取回水,这一次您不能依靠2的幂来帮助您。

我使用了非常凌乱的代码,向您展示了为什么您以后必须故意使它复杂化以防止错误。

无论如何要回到每个坐标6位以上,您需要做的就是将6加到那里。

所以现在有了28166

int packedValue1 = X << 6; //2112
int packedValue2 = Y << 12; //45056
int finalPackedValue = packedValue1 + packedValue2; //47168


因此,是的,最终值现在更大了47168。但是,至少现在,您完全可以毫无问题地恢复到这些值。唯一要记住的事情是,您必须首先在相反的方向进行最大的移动。

47168 >> 12; //11


现在,您必须找出11的数字是多少,因此将其向左移12次。

11 << 12; //45056


减去原始金额

//47168 - 45056 = 2112


现在您可以右移6。

2112 >> 6; //33


您现在又恢复了两个值。

使用上面的按位命令将控件加在一起,您可以更轻松地完成打包部分。

int finalPackedValue = (X << 6) | (Y << 12);

关于c++ - 玩家移动时,GameBoy Advance对象未显示在正确的位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23718832/

相关文章:

python - Unicode解码错误: 'ascii' codec can't decode byte 0xea in position 8: ordinal not in range(128)

graphics - 延迟着色中的全屏方 block 有名称吗?

c++ - 看不见的地形

c++ - 使用自定义模式序列化和使用 Boost 随机访问

c++ - XP 滚动条在 Windows7/Vista 中乱七八糟

c++ - 为什么 C++ 标准库没有具有连续内存的双端队列版本?

c++ - 删除别名指针

ruby-on-rails - 得墨忒耳法则 : create delegates to access attributes on associated objects or not?

php - 使用 PHP 中的扩展方法更改输出

java - android - 带有随机图片的动画