这可能需要一段时间才能解释-在阅读本文时,请先品尝小吃。
我正在为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个控件。
每个控件都可以被按下(按住)或不被按下。
这意味着每个控件应仅使用数字1
或0
。
由于1
或0
仅占用1位信息。在一个字节中,您最多可以存储8个不同的位,适合所有控件。
现在您可能正在考虑如何通过添加或其他方式将它们组合在一起?是的,您可以这样做,但是这会使您的理解变得非常复杂,并且会给您带来这个问题。
假设您有半杯空的水,并且向其中添加了更多的水,并且想要将新添加的水与旧水分离。.您就是不能这样做,因为水都变成了一种水,无法取消此操作(除非我们标记每个水分子,而且我们还不是外星人。..哈哈)。
但是对于按位运算,它使用数学运算来找出整个位流(列表)中的哪个位确切是1
或0
。
因此,您要做的第一件事就是将每一点都交给控件。
每个位都是二进制的2的倍数,因此您只需将值加倍即可。Up - Down - Left - Right - A - B - Select - Start
1 - 2 - 4 - 8 - 16 - 32 - 64 - 128
同样,按位运算不仅用于确定哪个位是1
或0
,还可以使用它们将某些内容组合在一起。控件可以很好地完成此操作,因为您可以一次按住多个按钮。
这是我用来找出被按下或未被按下的代码。
我不使用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
假设您同时按下
AND
和Up
所以您按下了A
和1
您制作一个包含所有控件的字节,可以说
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加到那里。
所以现在有了
2816
和6
。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/