当玩家移动时,GameBoy Advance 对象未显示在正确的位置 [英] GameBoy Advance objects not being displayed in correct place when player is moving

查看:28
本文介绍了当玩家移动时,GameBoy Advance 对象未显示在正确的位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这可能需要一段时间来解释 - 阅读本文时去吃点零食.

我正在用 C++ 为 Gameboy Advance 开发一个 2D 拼图平台游戏(我是一个相当新的程序员).直到昨晚,我一直在制作一个物理引擎(只是一些与轴对齐的边界框的东西),我正在使用 GBA 屏幕大小的级别进行测试.然而,最终的游戏需要一个比屏幕尺寸更大的关卡,所以我试图实现一个让GBA的屏幕跟随玩家的系统,结果我必须绘制所有东西在屏幕上相对于屏幕的偏移量.

但是,当我显示可以在关卡中拾取和操作的立方体时,我遇到了问题.每当玩家移动时,屏幕上立方体的位置似乎会偏离它们在关卡中的实际位置.这就像绘制立方体的地方是一个不同步的单帧 - 当我在玩家移动时暂停游戏时,框会显示在完全正确的位置,但是当我取消暂停时,它们会偏离原位直到玩家停止又搬家了.

我的类的简要描述 - 有一个名为 Object 的基类,它定义了 (x, y) 位置和宽度和高度,有一个从 Object 继承并添加速度分量的 Entity 类,以及一个 Character 类,它定义了 (x, y) 位置以及宽度和高度继承自 Entity 并添加了移动功能.我的播放器是一个 Character 对象,而我想要拾取的立方体是一个 Entity 对象数组.player 和cubes 数组都是Level 类的成员,该类也继承自Object.

我怀疑问题出在最后一个代码示例中,但是,为了完全理解我想要做的事情,我以稍微更合乎逻辑的顺序布置了示例.

以下是截断的 Level 标题:

class Level : 公共对象{私人的://数据int backgroundoffsetx;int backgroundoffsety;//方法无效 ApplyEntityOffsets();无效确定BackgroundOffsets();民众://数据枚举 {MAXCUBES = 20};实体立方体[MAXCUBES];角色球员;int numofcubes;//方法等级();无效绘制();无效的绘制背景(尺寸);void UpdateLevelObjects();};

...和实体:

class Entity : public Object{私人的://方法int GetScreenAxis(int &, int &, const int, int &, const int);民众://数据int drawx;//Entity的x位置相对于屏幕的位置int drawy;//实体的y位置相对于屏幕的位置//方法void SetScreenPosition(int &, int &);};

以下是我的主要游戏循环的相关部分:

//主循环而(真){...level.MoveObjects(按钮);水平.Draw();level.UpdateLevelObjects();...}

由于暂停时精灵显示在正确位置的方式,我很确定问题不在于 MoveObjects(),它决定了玩家和立方体的位置水平相对于水平.这样就剩下 Draw()UpdateLevelObjects().

好的,Draw().如果不是我的立方体显示不正确,而是它们所在的级别和平台(我认为这不是问题,但可能是问题),我将提供此信息.Draw() 只调用一个相关的函数,DrawBackground():

/**绘制关卡的背景;*/void Level::DrawBackground(dimension curdimension){...//平台for (int i = 0; i 

这不可避免地需要一些解释.我的平台以像素为单位,但以 8x8 像素的图块显示,所以我必须为这个循环划分它们的大小.SetTile() 首先需要一个屏幕块编号.我用来显示平台的背景层是 64x64 平铺,因此需要 2x2 个 32x32 平铺的屏幕块来显示它们.屏幕块编号为 25-28.103 是我的瓦片地图中的瓦片编号.

这是UpdateLevelObjects():

/**更新关卡中的所有 gba 对象*/void Level::UpdateLevelObjects(){确定背景偏移();ApplyEntityOffsets();REG_BG2HOFS = 背景偏移量;REG_BG3HOFS = backgroundoffsetx/2;REG_BG2VOFS = 背景偏移;REG_BG3VOFS = 背景偏移/2;...//设置玩家位置的代码(drawx,drawy);//绘制立方体for (int i = 0; i 

REG_BG 位是 GBA 的寄存器,它允许背景层垂直和水平偏移多个像素.这些偏移量首先在 DetermineBackgroundOffsets() 中计算:

/**根据玩家在关卡中的位置计算屏幕的偏移量*/void Level::DetermineBackgroundOffsets(){if (player.Getx()  width - (SCREEN_WIDTH/2))//如果玩家离关卡右墙小于屏幕宽度的一半{backgroundoffsetx = 宽度 - SCREEN_WIDTH;}else//如果玩家在关卡中间{backgroundoffsetx = -((SCREEN_WIDTH/2) - player.Getx());}如果 (player.Gety() < SCREEN_HEIGHT/2){背景偏移 = 0;}否则如果 (player.Gety() > 高度 - (SCREEN_HEIGHT/2)){backgroundoffsety = 高度 - SCREEN_HEIGHT;}别的{backgroundoffsety = -((SCREEN_HEIGHT/2) - player.Gety());}}

需要说明的是,width是指以像素为单位的关卡宽度,而SCREEN_WIDTH是指GBA屏幕宽度的常数值.另外,抱歉懒惰的重复.

这是ApplyEntityOffsets:

/**确定使玩家保持在屏幕中间的偏移量*/void Level::ApplyEntityOffsets(){//玩家偏移player.drawx = player.Getx() - backgroundoffsetx;player.drawy = player.Gety() - backgroundoffsety;//立方体偏移for (int i = 0; i 

基本上,当玩家位于关卡中间时,这将使其在屏幕上居中,并允许当屏幕撞到关卡边缘时移动到边缘.至于立方体:

/**确定实体相对于屏幕的 x 和 y 位置*/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():

/**设置沿实体轴相对于屏幕位置的位置*/int Entity::GetScreenAxis(int &axis, int &dimensioninaxis, const int OBJECT_OFFSET,int &backgroundoffsetaxis, const int SCREEN_DIMENSION){新职位;bool onawkwardedgeofscreen = false;//如果实体的位置在-ve方向上部分离开屏幕如果(轴 - 背景偏移轴 < 维度轴){newposition = 轴 - 背景偏移轴 + OBJECT_OFFSET;onawkwardedgeofscreen = true;}别的{newposition = 轴 - 背景偏移轴;}if ((newposition > SCREEN_DIMENSION) && !onawkwardedgeofscreen){新位置 = SCREEN_DIMENSION;//摆脱出现在屏幕上的毛刺方块}返回新位置;}

OBJECT_OFFSET(512 和 256)是 GBA 特定的东西 - 将对象的 x 或 y 位置设置为负数不会做你通常想要的 - 它弄乱了以前的精灵显示它.但是有一个技巧:如果你想设置一个负的 X 位置,你可以在负数上加上 512,精灵就会出现在正确的位置(例如,如果你要把它设置为 -1,那么将它设置为512 + -1 = 511).同样,添加 256 适用于负 Y 位置(这都是相对于屏幕,而不是水平).最后一个 if 语句将立方体显示在屏幕上,如果它们通常显示在更远的地方,因为试图将它们显示得太远会导致出现故障方块,同样是 GBA 特定的东西.

如果你读了这么多,你是绝对的圣人.如果您能找到可能导致立方体漂移的原因,我将不胜感激.此外,如果您有任何一般改进我的代码的提示,我们将不胜感激.

<小时>

更新GBA对象以设置玩家和立方体位置的方式如下:

for (int i = 0; i 

解决方案

我将解释这个答案按位运算符的工作原理以及一个数字如何让可能值为 0 到 255(256 种组合)的字节包含所有 GBA控制压力机.这类似于您的 X/Y 位置问题.

控件

上 - 下 - 左 - 右 - A - B - 选择 - 开始

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

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

假设你有一杯半空的水,你往里面加了更多的水,你想把新加的水和旧的水分开……你不能这样做,因为水都变成了一个水没有办法撤销这个(除非我们给每个水分子贴上标签,而且我们还不是外星人……哈哈).

但是对于按位运算,它使用数学来确定在整个位流(列表)中哪个位是 10.

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

上 - 下 - 左 - 右 - A - B - 选择 - 开始
1 - 2 - 4 - 8 - 16 - 32 - 64 - 128

此外,按位运算不仅用于确定哪个位是 10,您还可以使用它们将某些东西组合在一起.控件在这方面做得很好,因为您可以一次按住多个按钮.

这是我用来确定按下或未按下的代码.

我不使用 C/C++ 所以这是 javascript 我在我的游戏男孩模拟器网站上使用了这个字符串部分可能是错误的但实际的按位代码在几乎所有编程语言上都是通用的,只有我看到的不同之处在于 Visual Basic,& 在那里将被称为 AND.

function WhatControlsPressed(controlsByte) {var controlsPressed = " ";if (controlsByte & 1) {控制按下 = 控制按下 + 向上"}if (controlsByte & 2) {控制按下 = 控制按下 + 向下"}如果(控制字节和 4){控制按下 = 控制按下 + 左"}if (controlsByte & 8) {控件按下 = 控件按下 + 右"}if (controlsByte & 16) {控件按下 = 控件按下 + "a "}如果(控制字节和 32){控件按下 = 控件按下 + "b "}如果(控制字节和 64){控件按下 = 控件按下 + 选择"}如果(控制字节和 128){控制按下 = 控制按下 + 开始"}返回控件已按下;}

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

#DEFINE UP 1#DEFINE DOWN 2#DFFINE 左 4#定义正确 8

假设你同时按下 UpA 所以你按下了 116

你制作了一个包含所有控件的字节

unsigned char ControlsPressed = 0;

所以现在没有按下任何东西,因为它是 0.

ControlsPressed |= 1;//按下ControlsPressed |= 16;//按下A

所以是的 ControlsPressed 现在将持有数字 17 你可能认为只是 1+16 这正是它所做的,哈哈但是,是的,您无法将水恢复到最初使用基本数学构成的基本价值观.

但是,是的,您可以将 17 更改为 16,然后松开向上箭头并按住 A 按钮.

但是当你按住很多按钮时,价值会变得如此之大.1+4+16+128 = 149

所以你不记得你加起来了什么,但你知道值是 149 你现在如何取回钥匙?嗯,这很简单,是的,只需开始减去您可以找到的控件使用的最高数字,该数字低于 149,如果减去它时它更大,则它不会被按下.

是的,此时您认为是的,我可以进行一些循环并执行这些操作,但这一切都不需要完成,因为内置命令可以即时执行此操作.

这就是您按下任何按钮的方式.

ControlsPressed = ControlsPressed AND NOT (NEGATE) 数字

在 C/C++/Javascript 中你可以使用这样的东西

ControlsPressed &= ~1;//松开向上键.ControlsPressed &= ~16;//松开A键.

还有什么要说的,就是关于按位的东西你需要知道的一切.

我没有解释按位移位运算符 <<>> 我真的不知道如何在基本层面上解释这一点.

但是当你看到这样的东西

int SomeInteger = 123;打印 SomeInteger >>3;

在那里使用了一个右移运算符,它向右移动了 3 位.

它实际上做的是除以 2 的 3 次方.所以在基础数学中它真的是这样做的

SomeInteger = 123/8;

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

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

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

X: (0 到 63)是:(0 到 63)

而且您也知道 X,Y 必须存储在一些小数据类型中.我假设非常紧凑(没有间隙).

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

但是在这里移动,所以两者都可以容纳总共 64 种不同的组合.

所以 XY 各占 6 位,总共 12 位.所以每个字节总共保存了 2 位.(总共节省了 4 位).

<块引用>

<代码> X |是

[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;整数 Y = 11;

由于每个坐标需要 6 位,这意味着您必须为每个数字左移 6.

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

所以是的,最终值将是 2816

现在你从 2816 取回值,在相反的方向做同样的转变.

2816 >>6//还给你 44.lol.

所以是的,水的问题再次发生了,你有 44 (33+11) 并且没有办法把它找回来,这次你不能依靠 2 的力量来帮助你.

我使用了非常凌乱的代码来向您展示为什么您必须故意将其复杂化以防止将来出现错误.

无论如何回到每个坐标 6 位以上,您必须做的是取 6 并将其添加到那里.

所以现在你有 66+6=12.

intpackedValue1 = X <<<;6;//2112intpackedValue2 = Y <<12;//45056int 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);

This might take a while to explain - go grab a snack while you're reading this.

I am developing a 2D puzzle platforming game for the Gameboy Advance in C++ (I'm a fairly new programmer). Up until last night, I have been making a phyics engine (just some axis aligned bounding box stuff) which I was testing using a level which was the size of the GBA's screen. However, the final game will demand having a level which is bigger than the size of the screen, and so I have tried to implement a system which allows the screen of the GBA to follow the player, and as a result I have to draw everything on screen relative to the screen's offsets.

However, I am having trouble when I display cubes which can be picked up and manipulated in the level. Whenever the player moves, the locations of the cubes on screen appear to drift away from their actual positions in the level. It's like where the cubes are drawn is a single frame out of sync - when I pause the game when the player is moving, the boxes are displayed in exactly the right position, but when I unpause, they drift out of place until the player stops moving again.

A brief description of my classes - there is a base class called Object which defines (x, y) position and a width and height, there is an Entity class which inherits from Object and adds velocity components, and a Character class which inherits from Entity and adds movement functions. My player is a Character object, while the cubes I want to pick up are an array of Entity objects. Both the player and cubes array are members of the Level class, which also inherits from Object.

I suspect the problem lies in the last code sample, however, for full comprehension of what I am trying to do I have laid out the samples in a slightly more logical order.

Here are the truncated headers of 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();
};

...and Entity:

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 &);
};

Here are the relevant parts of my main game loop:

//Main loop
while (true)
{
    ...

    level.MoveObjects(buttons);
    level.Draw();
    level.UpdateLevelObjects();

    ...
}

Because of the way sprites are displayed in the correct places when paused, I'm pretty sure the problem does not lie in MoveObjects(), which determines the poitions of the player and cubes in the level relative to the level. So that leaves Draw() and UpdateLevelObjects().

Ok, Draw(). I'm providing this in the event that it is not my cubes that are being displayed incorrectly, but the level and platforms upon which they sit (I don't think this is the problem, but possibly). Draw() only calls one relevant function, 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);
                    }
                }
            }
        }
    }
}

This inevitably requires some amount of explaining. My platforms are measured in pixels, but displayed in tiles of 8x8 pixels, so I have to divide their sizes for this loop. SetTile() firstly requires a screenblock number. The background layer I am using to display the platforms is 64x64 tiles, and so requires 2x2 screenblocks of 32x32 tiles each to display them all. The screenblocks are numbered 25-28. 103 is the tile number in my tilemap.

Here's 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);
    }
}

The REG_BG bits are the registers of the GBA which allow the background layers to be offset vertically and horizontally by a number of pixels. Those offsets are first calculated in 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());
    }
}

Just to be clear, width refers to the width of the level in pixels, while SCREEN_WIDTH refers to the constant value of the width of the GBA's screen. Also, sorry for the lazy repetition.

Here's 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);
    }
}

Basically this centres the player on the screen when it is in the middle of the level, and allows it to move to edges when the screen bumps against the edge of the level. As for the cubes:

/**
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);
}

Bear with me - I will explain the 512 and 256 in a moment. Here's 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 (the 512 and 256) is a GBA specific thing - setting an object's x or y position to a negative number won't do what you intend normally - it messes up the sprite used to display it. But there's a trick: if you want to set a negative X position, you can add 512 to the negative number, and the sprite will appear in the right place (e.g. if you were going to set it to -1, then set it to 512 + -1 = 511). Similarly, adding 256 works for negative Y positions (this is all relative to the screen, not the level). The last if statement keeps the cubes displayed fractionally off the screen if they would normally be displayed further away, as trying to display them too far away results in glitchy squares appearing, again GBA specific stuff.

You are an absolute saint if you have come this far having read everything. If you can find what potentially might be causing the drifting cubes, I will be VERY grateful. Also, any tips to generally improve my code will be appreciated.


Edit: The way the GBA's objects are updated for setting player and the cubes' positions is as follows:

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));
}

解决方案

I will explain this answer how bitwise operators work and how one number lets say a byte with a possible value of 0 to 255 (256 combinations) holds all the GBA Control presses. Which is similar to your X/Y position problem.

The controls

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

Those are the GameBoy Color controls I think GameBoy Advanced has more controls. So a total of 8 controls. Each control can either be pressed (held down) or not pressed. That would mean the each control should only be using a number 1 or 0. Since 1 or 0 takes only 1 bit of information. In one byte you can store up to 8 different bits, which fits all the controls.

Now you may be thinking how can I combine them together by adding or something? yes you can do that but it makes it very complicated to understand and it gives you this problem.

Say you have a glass of water that's half empty and you add more water into it and you want to separate the newly added water from the old water.. you just can't do that because the water all became one water with no way to undo this (unless we label each water moleclue and we ain't aliens yet.. lol).

But with Bitwise operations it uses math to figure out which bit exactly is a 1 or 0 in the whole stream (list) of bits.

So first thing you do is you give each bit to a control. Each bit is in binary a multiple of 2, so you just keep doubling the value.

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

Also bitwise operations are not only used to figure out which bit is a 1 or 0 you could also use them to combine certain things together. Controls do this well since you can press and hold multiple buttons at once.

Here is the code I use to figure out which is pressed or not pressed.

I don't use C/C++ so this is javascript I used this for my gameboy emulator website the string part may be wrong but the actual bitwise code is universal on nearly all programing languages, only difference I seen is Visual Basic the & would be called AND there.

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;
}

How do you set a individual control to be pressed? well you have to remember which bitwise number you used for what control I would make something like this

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

So lets say you press down Up and A at once So you pressed 1 and 16

You make 1 byte that holds all the controls lets say

unsigned char ControlsPressed = 0;

So nothing is pressed now because it's 0.

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

So yeah the ControlsPressed will now be holding the number 17 you may be thinking just 1+16 which is exactly what it does lol but yeah the water thing you can't get it back to it's basic values that made it up in the first place using basic math.

But yeah you could change that 17 to 16 and bam you let go off the Up arrow and just holding down the A button.

But when you holding down lots of buttons the value gets so big lets say. 1+4+16+128 = 149

So you don't remember that what you added up but you know the value is 149 how will you get back the keys now? well it's pretty easy yeah just start subtracting the highest number you can find your controls use that is lower then 149 and you if it's bigger when you subtract it then it's not pressed down.

Yeah at this point you thinking yeah I could make some loops and do this stuff but it's all no needed to be done there is built-in commands that do this on the fly.

This is how you unpress any of the buttons.

ControlsPressed = ControlsPressed AND NOT (NEGATE) Number

In C/C++/Javascript you can use something like this

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

What else to say that's about everything you need to know about the bitwise stuff.

EDIT:

I didn't explain the bitwise shifting operators << or >> I really don't know how to explain this on a basic level.

But when you see something like this

int SomeInteger = 123;
print SomeInteger >> 3;

That up there is a shift right operator getting used there and it's shifting 3 bits to the right.

What it actually does is divide by 2 to the power of 3. So in basic math it's really doing this

SomeInteger = 123 / 8;

So now you know that shifting to the right >> is the same thing as dividing the value by powers of 2. Now shifting to the left << would logically mean you are multiplying the value by the powers of 2.

Bit shifting is mostly used to pack 2 different datatypes together and extract them later. (I think this is the most common use of the bit shift).

Say you have X/Y Coordinates in your game each coordinate can only go to a limited value. (This is just a example)

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

And you also know that X,Y must be stored into some small datatype. I assume very tightly packed (no gaps).

(this may take some reverse engineering to figure out exactly or just reading manuals). There could be gaps in there used for reserved bits or some unknown information.

But moving along here so both can hold a total of 64 different combinations.

So both X and Y each take 6 bits, 12 bits in total for both. So a total of 2 bits are saved for each byte. (4 bits saved in total).

 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]

So you need to use bitshifting to store information properly.

Here is how you store them

int X = 33;
int Y = 11;

Since each coordinate takes 6 bits that would mean you have to shift left by 6 for each number.

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

So yeah the final value will be 2816

Now you get the values back from 2816 doing the same shift in the opposite direction.

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

So yeah the problem with the water happened again you have 44 (33+11) and no way to get it back and this time you can't rely on the powers of 2 to help you out.

I used very messy code to show you why you must complicate it on purpose to fend of bugs in the future.

Anyways back to above its 6 bits per coordinate what you must do is take the 6 and add it there.

so now you have 6 and 6+6=12.

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

So yeah the final value is now bigger 47168.. But atleast now you will have no problems at all getting back the values. Only thing to remember you must do it in opposite direction biggest shift first.

47168 >> 12; //11

Now you have to figure out what big number 11 is made of so you shift it back left 12 times.

11 << 12; //45056

Subtract from original sum

//47168 - 45056 = 2112

Now you can finish the shift right by 6.

2112 >> 6; //33

You now got both values back..

You can do the packing part much easier with the bitwise command above for adding the controls up together.

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

这篇关于当玩家移动时,GameBoy Advance 对象未显示在正确的位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆