#18502 - Darmstadium - Sun Mar 28, 2004 2:05 am
Before I share my troubles with you, I would just like to thank everbody who puts up with my endless questions. You're all really great. I could never thank you enough for your help.
Now, I'm working on this physics dealy for handling how sprites move around on the screen. So I have this struct called object. It hold the mass, height, width, x, y and another struct that stores the force of a sprite. The struct that stores the force has 4 variables to keep track of the movement in each direction. It also stores the "speed". The speed is how many vertical blanks are waited for until the sprite is moved. This is usually always 1 since anything else looks choppy. Ok, I think I have blabbed quite enough, here's the code:
Code: |
#include "gba.h"
#include "dispcnt.h"
#include "keypad.h"
#include "sprite.h"
#include "dma.h"
#include "box.raw.c"
#include "box.pal.c"
struct force
{
u8 speed;
s16 up, down, right, left;
};
struct object
{
u8 x, y, h, w, m;
force v;
};
inline void WaitForVsync()
{
while((volatile u16)REG_VCOUNT != 160){}
}
inline void CopyOAM()
{
REG_DM3SAD = (u32)sprites;
REG_DM3DAD = (u32)OAM;
REG_DM3CNT = 256 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
}
void apply(force * v, object * obj)
{
obj->v.speed += v->speed;
obj->v.up += v->up;
obj->v.down += v->down;
obj->v.left += v->left;
obj->v.right += v->right;
}
void hitback(force * v, object * obj)
{
obj->v.speed -= v->speed;
obj->v.up -= v->up;
obj->v.down -= v->down;
obj->v.left -= v->left;
obj->v.right -= v->right;
}
#define OBJSLEN 1
object * objs[OBJSLEN];
int main()
{
u16 loop;
for(loop = 0; loop < 128; loop++)
{
sprites[loop].attribute0 = 160;
sprites[loop].attribute1 = 240;
}
REG_DISPCNT = MODE_1 | OBJ_ENABLE | OBJ_MAP_1D;
REG_DM3SAD = (u32)box_Palette;
REG_DM3DAD = (u32)OBJPaletteMem;
REG_DM3CNT = 64 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
REG_DM3SAD = (u32)box_Bitmap;
REG_DM3DAD = (u32)&OAMData[0];
REG_DM3CNT = 64 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
object box;
objs[0] = &box;
box.x = 20;
box.y = 140;
box.h = 16;
box.w = 16;
box.m = 3;
box.v.speed = 5;
box.v.up = 0;
box.v.down = 0;
box.v.left = 0;
box.v.right = 0;
sprites[0].attribute0 = COLOR_256 | SQUARE | box.y;
sprites[0].attribute1 = SIZE_16 | box.x;
sprites[0].attribute2 = 0;
u32 vsyncs;
force gravity;
gravity.speed = 0;
gravity.up = 0;
gravity.down = 8;
gravity.left = 0;
gravity.right = 0;
force jump;
jump.speed = 0;
jump.up = 5;
jump.down = 0;
jump.left = 0;
jump.right = 0;
u16 holdcount = 0;
while (1)
{
sprites[0].attribute0 = COLOR_256 | SQUARE | box.y;
sprites[0].attribute1 = SIZE_16 | box.x;
if ((volatile u16)REG_VCOUNT == 160)
vsyncs++;
if ((!(*KEYS & KEY_A)) && (box.y + box.h == 159))
apply(&jump, &box);
if (!(*KEYS & KEY_LEFT))
box.v.left += 1;
if (!(*KEYS & KEY_RIGHT))
box.v.right += 1;
for (loop = 0; loop < OBJSLEN; loop++)
{
if (vsyncs % objs[loop]->v.speed == 0)
{
apply(&gravity, objs[loop]);
if (objs[loop]->y == 159 - objs[loop]->h)
hitback(&gravity, objs[loop]);
objs[loop]->x -= objs[loop]->v.left;
objs[loop]->x += objs[loop]->v.right;
objs[loop]->y -= objs[loop]->v.up;
objs[loop]->y += objs[loop]->v.down;
if (objs[loop]->y > 159 - objs[loop]->h)
objs[loop]->y = 159 - objs[loop]->h;
if (objs[loop]->y < 1)
objs[loop]->y = 1;
if (objs[loop]->x > 239 - objs[loop]->w)
objs[loop]->x = 239 - objs[loop]->w;
if (objs[loop]->x < 1)
objs[loop]->x = 1;
if (objs[loop]->y > 140)
{
objs[loop]->v.left = 0;
objs[loop]->v.right = 0;
}
}
}
WaitForVsync();
CopyOAM();
if (vsyncs > 200)
vsyncs = 0;
}
return 0;
}
|
Now when I press A, my sprite jumps and is pulled back down with no problem. The problem is that it seems to jump different amounts depending on how long you hold A. The game only cares if you press A when the sprite is on the ground, and the force struct jump never changes, so I see no reason for the inconsistency. I would really, really appreciate your help for this problem and comments on my system. Thanks in advance.
#18503 - dagamer34 - Sun Mar 28, 2004 2:16 am
I never get tired of your endless questions, in fact, you've just helped me with how to design my game mechanics by posting your code.
Now, for me to help you. The problem isn't the physics code but the way you check for input. You are basically asking to see if a key is pressed once and act on that input. So, just check and see if a key was pressed this frame, but wasn't pressed last frame.
There is also a topic somewhere around here that explains what I am talking about. I will search around for it and give you a link.
And there has to be a way to take out that modulus operator. There are so many problems with that one line right there that could mess you up completely. Using the modulus operate requires a divide, AND you are using GCC for the division which is even slower. It takes ~50 cycles using the BIOS function but assuming you do this for all 128 sprites, you have wasted a lot of VBlank time that should be focused on something else. Change it.
_________________
Little kids and Playstation 2's don't mix. :(
#18504 - dagamer34 - Sun Mar 28, 2004 2:21 am
Here's the link I promised:
http://forum.gbadev.org/viewtopic.php?t=3002&highlight=keys
_________________
Little kids and Playstation 2's don't mix. :(
#18505 - Miked0801 - Sun Mar 28, 2004 2:54 am
Quote: |
It takes ~50 cycles using the BIOS function...
|
Add an extra 100 cycles or so on top of that. Check the other thread for specifics on the BIOS Div().
Why a limit of 200 on the VSyncs? Make vsyncs a U8 and it will wrap automatically at 256 for you - on top of that since 256 is a base 2 number, you'll be able to get rid of that icky % operator.
Also, you may want to look up some of the thread on fixed point math to allow your system to handle sub-pixel movement/velocity/force.
#18512 - Darmstadium - Sun Mar 28, 2004 5:17 am
dagamer - could I ask you to modify my code and show me what you mean? I do not understand how to do this.
I'm going to try to find another way to see if it's time to move sprites. I;m not sure how I'm going to do it yet though.
Thanks a lot for your help guys
#18517 - sajiimori - Sun Mar 28, 2004 8:17 am
Did you want to apply the jumping force continuously while the A button is being pressed? It sounds like you only want it to be applied once, when A is first pressed. If A was not being pressed on the previous frame, and it's being pressed now, then apply the force.
What do you need in order to tell if it was being pressed on the previous frame? You need to save the contents of the keypad register every frame in order to refer back to on the next frame.
For forces, try using just 2 directions: x and y. I mean, a force can't be both upward and downward, so why have seperate variables? Use a negative y for upward and a positive y for downward, unless you have some other rationale for doing it the way it is now.
BTW, I like your coding style. Seems like you'll be a great programmer if you stick to it.
#18543 - tepples - Sun Mar 28, 2004 7:33 pm
sajiimori wrote: |
Did you want to apply the jumping force continuously while the A button is being pressed? |
Two considerations:- Some game characters have a jetpack.
- Many games cut off the peak of a character's jump parabola when the player releases A.
Quote: |
What do you need in order to tell if it was being pressed on the previous frame? You need to save the contents of the keypad register every frame in order to refer back to on the next frame. |
This is a rather common question, and it isn't search-friendly (keys vs. joypad vs. buttons vs. controller). I'll have to think of how to word it for the FAQ.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#18559 - sajiimori - Sun Mar 28, 2004 10:47 pm
tepp, I wasn't claiming that his code was wrong, but his posts seemed to suggest that he didn't want the force applied continuously.
As a FAQ question, I think a common way people ask is:
"How do I detect single button taps instead of continuous presses?"
It's an awkward way to phrase the question, but if they knew how to phrase it better they'd probably answer their own question:
"How do I know if a button is pressed on the current frame, but wasn't pressed on the previous frame?"
So, the awkward question is better in that sense. It more accurately reflects the thoughts of the asker.
#18570 - Darmstadium - Mon Mar 29, 2004 1:20 am
I'm still trying to figure out how I can get rid of that modulus operator, but I did what sajiimori suggested. I'm still having trouble checking if a button wasn't pressed last frame. I ask you to tell me what I am doing wrong with that. Here's the code:
Code: |
#include "gba.h"
#include "dispcnt.h"
#include "keypad.h"
#include "sprite.h"
#include "dma.h"
#include "box.raw.c"
#include "box.pal.c"
struct force
{
u8 speed;
s16 x, y;
};
struct object
{
u8 x, y, h, w, m;
force v;
};
inline void WaitForVsync()
{
while((volatile u16)REG_VCOUNT != 160){}
}
inline void CopyOAM()
{
REG_DM3SAD = (u32)sprites;
REG_DM3DAD = (u32)OAM;
REG_DM3CNT = 256 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
}
void apply(force * v, object * obj)
{
obj->v.x += v->x;
obj->v.y += v->y;
}
void hitback(force * v, object * obj)
{
obj->v.x -= v->x;
obj->v.y -= v->y;
}
#define OBJSLEN 1
object * objs[OBJSLEN];
int main()
{
u16 loop;
for(loop = 0; loop < 128; loop++)
{
sprites[loop].attribute0 = 160;
sprites[loop].attribute1 = 240;
}
REG_DISPCNT = MODE_1 | OBJ_ENABLE | OBJ_MAP_1D;
REG_DM3SAD = (u32)box_Palette;
REG_DM3DAD = (u32)OBJPaletteMem;
REG_DM3CNT = 64 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
REG_DM3SAD = (u32)box_Bitmap;
REG_DM3DAD = (u32)&OAMData[0];
REG_DM3CNT = 64 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
object box;
objs[0] = &box;
box.x = 20;
box.y = 140;
box.h = 16;
box.w = 16;
box.m = 3;
box.v.speed = 4;
box.v.x = 0;
box.v.y = 0;
sprites[0].attribute0 = COLOR_256 | SQUARE | box.y;
sprites[0].attribute1 = SIZE_16 | box.x;
sprites[0].attribute2 = 0;
u8 vsyncs = 0;
force gravity;
gravity.speed = 0;
gravity.x = 0;
gravity.y = 8;
force jump;
jump.speed = 0;
jump.x = 0;
jump.y = -30;
u8 a_vcount = 0;
u16 keys_lastframe = 0x3FF;
while (1)
{
sprites[0].attribute0 = COLOR_256 | SQUARE | box.y;
sprites[0].attribute1 = SIZE_16 | box.x;
keys_lastframe = *KEYS;
WaitForVsync();
CopyOAM();
vsyncs++;
if ((!(*KEYS & KEY_A)) && (keys_lastframe & KEY_A) && (box.y + box.h == 159))
apply(&jump, &box);
if (!(*KEYS & KEY_LEFT))
box.v.x -= 1;
if (!(*KEYS & KEY_RIGHT))
box.v.x += 1;
for (loop = 0; loop < OBJSLEN; loop++)
{
if (vsyncs % objs[loop]->v.speed == 0)
{
apply(&gravity, objs[loop]);
if (objs[loop]->y + objs[loop]->h == 159)
hitback(&gravity, objs[loop]);
objs[loop]->x += objs[loop]->v.x;
objs[loop]->y += objs[loop]->v.y;
//crude collision detection
if (objs[loop]->y > 159 - objs[loop]->h)
objs[loop]->y = 159 - objs[loop]->h;
if (objs[loop]->y < 1)
objs[loop]->y = 1;
if (objs[loop]->x > 239 - objs[loop]->w)
objs[loop]->x = 239 - objs[loop]->w;
if (objs[loop]->x < 1)
objs[loop]->x = 1;
//ground friction
if (objs[loop]->y + objs[loop]->h == 159)
objs[loop]->v.x = 0;
}
}
}
return 0;
}
|
Thanks a lot for your help
Last edited by Darmstadium on Mon Mar 29, 2004 10:55 pm; edited 1 time in total
#18581 - sajiimori - Mon Mar 29, 2004 4:57 am
I'm not sure I understand the logic of your keystate variables. The current frame's keystate is in the GBA's keystate register, so you don't need a special variable for that (unless you're worried about sub-frame keypress accuracy).
Code: |
#define REG_KEYS (*(volatile u16*)0x4000130)
while(1)
{
if (A is pressed in REG_KEYS and A is not pressed in old_keys)
jump();
old_keys = REG_KEYS;
}
|
Note that old_keys has to be declared before the while loop, or else it's effectively destroyed on each iteration. The whole point is that the information is retained between iterations -- you want a memory of the previous frame.
#18582 - tepples - Mon Mar 29, 2004 4:57 am
Darmstadium: Your code looks way too contorted for me to explain, let alone maintain. I'd do it a different way. If you don't understand my way, look up a bit of set theory.
First set keys_thisframe to the set of pressed buttons. (In your framework, this set is called ~*KEYS.) Then the key to computing the set of newly pressed buttons is the set-difference operator, made of an intersection with a complement, called & ~ in C. For example, A & ~B computes the set of bits that are 1 in A and 0 in B.
In context, such computation would look like the following:
Code: |
/* SNIP */
int main()
{
/* SNIP */
u16 keys_lastframe = 0x3FF;
while (1)
{
/* SNIP */
u16 keys_thisframe = ~*KEYS;
u16 keys_taps = keys_thisframe & ~keys_lastframe;
keys_lastframe = keys_thisframe;
/* SNIP: act on keys_thisframe and keys_taps */
}
return 0;
}
|
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#18613 - Darmstadium - Mon Mar 29, 2004 10:51 pm
Thanks a lot for your help and patience, but I still need a little but more help.
Instead of posting all my code again, I have edited the code of my previous post.
I fixed the game so you can't hold the A button and make the box jump quite as insanely as it did before, but after you make it jump, you have to press A twice to make it jump again. It also jumps at two different heights. It seems to alternate between these two different heights every time you make it jump. I'm beginning to suspect that the varying jump heights before were not entirely the work of my poor controls. Perhaps the gravity influece is not going quite right, or maybe it is the influence from when the ground returns gravity's force. hmm...
#18631 - sajiimori - Tue Mar 30, 2004 7:00 am
Your input code looks fine now, but your code is getting too messy for me to help anymore. Seperate the tasks into functions, and abstract over the repetitive calculations like finding the bottom of an object. It should be obvious what a function does at a glance.
Code: |
int obj_bottom(object* o)
{
return o->y + o->h;
}
|
Also, isn't the hitback function redundant if you're simultaneously doing simple collision detection?
#18647 - dagamer34 - Tue Mar 30, 2004 11:07 pm
You could make that loop a lot faster if you first check to see that you are going to be within your boundaries before calling apply (), that way, you don't have to call hitback whenever it hits your lower boundary since you do that calculation anyway.
Code: |
// Untested, but should be faster
if (objs[loop]->y + objs[loop]->h + gravity.y == 159)
apply(&gravity, objs[loop]);
|
EDIT: Shouldn't you be checking to see if it is actually lower than 159 on the screen? I think it would be better to use >= instead of ==.
_________________
Little kids and Playstation 2's don't mix. :(
#18657 - Darmstadium - Wed Mar 31, 2004 2:00 am
Well, I've tried to make my code a bit cleaner. I also tried to add in my collision detection system. It doesn't seem to work since the box goes right through the ground. I really hate to ask for so much help, but I have no idea why this isn't working! Here is my newest code:
Code: |
#include "gba.h"
#include "dispcnt.h"
#include "keypad.h"
#include "sprite.h"
#include "dma.h"
#include "box.raw.c"
#include "box.pal.c"
struct force
{
s16 x, y;
};
struct object
{
u8 n, x, y, h, w, m, speed;
force v;
void move();
};
inline void WaitForVsync();
inline void CopyOAM();
bool MoveOK(object *,s16,s16);
inline void apply_force(force *,object *);
inline void return_force(force *,object *);
void TakeBoxInput();
void ApplyAllForce();
u8 vsyncs = 0;
u16 keys_lastframe;
#define OBJSLEN 5
object objs[OBJSLEN];
force gravity;
force jump;
void object::move()
{
if (MoveOK(this, v.x, v.y))
{
x += v.x;
y += v.y;
}
}
inline void WaitForVsync()
{
while((volatile u16)REG_VCOUNT != 160){}
}
inline void CopyOAM()
{
REG_DM3SAD = (u32)sprites;
REG_DM3DAD = (u32)OAM;
REG_DM3CNT = 256 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
}
bool MoveOK(object * client, s16 xmove, s16 ymove)
{
u16 loop;
for (loop = 0; loop < OBJSLEN; loop++)
{
if (objs[loop].n != client->n)
{
if (client->x + xmove + client->w <= objs[loop].x)
continue;
if (client->x + xmove >= objs[loop].x + objs[loop].w)
continue;
if (client->y + ymove + client->h <= objs[loop].y)
continue;
if (client->y + ymove >= objs[loop].y + objs[loop].h)
continue;
return false;
}
}
return true;
}
inline void apply_force(force * v, object * obj)
{
obj->v.x += v->x;
obj->v.y += v->y;
}
inline void return_force(force * v, object * obj)
{
obj->v.x -= v->x;
obj->v.y -= v->y;
}
void TakeBoxInput()
{
if ((!(*KEYS & KEY_A)) && (keys_lastframe & KEY_A) && (objs[0].y + objs[0].h == 159))
apply_force(&jump, &objs[0]);
if (!(*KEYS & KEY_LEFT))
objs[0].v.x -= 1;
if (!(*KEYS & KEY_RIGHT))
objs[0].v.x += 1;
}
void ApplyAllForce()
{
u16 loop;
for (loop = 0; loop < OBJSLEN; loop++)
{
if (vsyncs % objs[loop].speed == 0)
{
apply_force(&gravity, &objs[loop]);
if (objs[loop].y + objs[loop].h == 159)
return_force(&gravity, &objs[loop]);
objs[loop].move();
}
}
}
int main()
{
gravity.x = 0;
gravity.y = 8;
jump.x = 0;
jump.y = -30;
u16 loop;
for(loop = 0; loop < 128; loop++)
{
sprites[loop].attribute0 = 160;
sprites[loop].attribute1 = 240;
}
REG_DISPCNT = MODE_1 | OBJ_ENABLE | OBJ_MAP_1D;
REG_DM3SAD = (u32)box_Palette;
REG_DM3DAD = (u32)OBJPaletteMem;
REG_DM3CNT = 64 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
REG_DM3SAD = (u32)box_Bitmap;
REG_DM3DAD = (u32)&OAMData[0];
REG_DM3CNT = 64 | DMA_ENABLE | DMA_TIMEING_IMMEDIATE | DMA_32;
//box
objs[0].x = 20;
objs[0].y = 140;
objs[0].h = 16;
objs[0].w = 16;
objs[0].m = 3;
objs[0].speed = 4;
objs[0].v.x = 0;
objs[0].v.y = 0;
sprites[0].attribute0 = COLOR_256 | SQUARE | objs[0].y;
sprites[0].attribute1 = SIZE_16 | objs[0].x;
sprites[0].attribute2 = 0;
//ground
objs[1].n = 5;
objs[1].x = 0;
objs[1].y = 160;
objs[1].h = 0;
objs[1].w = 240;
//ceiling
objs[2].n = 6;
objs[2].x = 0;
objs[2].y = 0;
objs[2].h = 0;
objs[2].w = 240;
//left barrier
objs[3].n = 7;
objs[3].x = 0;
objs[3].y = 0;
objs[3].h = 160;
objs[3].w = 0;
//right barrier
objs[4].n = 8;
objs[4].x = 240;
objs[4].y = 0;
objs[4].h = 160;
objs[4].w = 0;
while (1)
{
sprites[0].attribute0 = COLOR_256 | SQUARE | objs[0].y;
sprites[0].attribute1 = SIZE_16 | objs[0].x;
keys_lastframe = *KEYS;
WaitForVsync();
CopyOAM();
vsyncs++;
TakeBoxInput();
ApplyAllForce();
}
return 0;
}
|
Thank you for your help!
#18663 - sajiimori - Wed Mar 31, 2004 5:24 am
Pretty weird code. The edges of the screen are being pulled by gravity, and the velocity of objects isn't affected when they collide -- they simply refuse to move, so the velocity added by gravity just keeps piling up. It could make for a good Salvador Dali painting.
#18686 - dagamer34 - Wed Mar 31, 2004 11:43 pm
You code is beginning to be very hard to read. This is why I decided to create a library first and then start dealing with other stuff like collision detection because with all this code around, you are bound to make mistakes.
It would be best to make a library of functions that you would commonly use. For instance:
Code: |
if (!(*KEYS & KEY_LEFT))
|
isn't the cleanest way to check for input. Put stuff like this in a function and you will make your life a lot easier. DMA should also be put into a macro, and more things like that.
And if you don't feel like writing your own library, I have one ready for use, I need someone to test it out.
_________________
Little kids and Playstation 2's don't mix. :(
#18697 - LOst? - Thu Apr 01, 2004 5:38 am
dagamer34 wrote: |
You code is beginning to be very hard to read. This is why I decided to create a library first and then start dealing with other stuff like collision detection because with all this code around, you are bound to make mistakes.
It would be best to make a library of functions that you would commonly use. For instance:
Code: |
if (!(*KEYS & KEY_LEFT))
|
isn't the cleanest way to check for input. Put stuff like this in a function and you will make your life a lot easier. DMA should also be put into a macro, and more things like that.
And if you don't feel like writing your own library, I have one ready for use, I need someone to test it out. |
Is your library good? Does it have better name than the standard GBA_H file you can get all over the place? Does it have a better way to manage sprites and background on each frame?
#18728 - dagamer34 - Fri Apr 02, 2004 2:15 am
I wouldn't really say better, but just easier. Now that I think about it, it's just better to write your own because you learn more that way instead of depending on others.
But, if you want to take a look at mine and see how mine is set up to get a few ideas, just e-mail me and I'll send it to you.
_________________
Little kids and Playstation 2's don't mix. :(