gpt4 book ai didi

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

转载 作者:太空宇宙 更新时间:2023-11-04 13:50:16 27 4
gpt4 key购买 nike

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

我正在为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/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com