#117463 - neilio - Sun Feb 04, 2007 11:39 pm
Hi, just thought I'd introduce myself! A couple of weeks ago I got the crazy idea in my head that I wanted to make a vertical scrolling shoot-em-up, no doubt inspired by countless internet Flash games. I first looked at developing on the PC and then released that GBA development would be much more suited to me! So I've been following some tutorials on The Pern Project and quickly dabbled with devkitARM and BoycottAdvance.
I've got a small amount of basic C experience, and I knocked up this based on one of the Pern tutorials >
Quote: |
//program to paint the screen one whole colour
//user can increase or decrease saturation of either red, green or blue
//the A button toggles between the colours
//the UP and DOWN buttons change the saturation of the current colour
#include <gba.h>
// Update the screen with a new colour
void ScrRefresh(int rwrite,int gwrite, int bwrite)
{
unsigned char x,y;
for(x = 0; x < SCREEN_WIDTH; x++)
for(y = 0; y < SCREEN_HEIGHT; y++)
VideoBuffer [x+ y * SCREEN_WIDTH] = RGB16(rwrite,gwrite,bwrite);
}
///////////////// C code entry (main())/////////////////////
int main()
{
int red;
int green;
int blue;
int satchange; //change saturation of current colour up or down
int scrchange; //flag to control whether screen refreshes or not... 0 - no change, 1 - change
int colmode; //value will choose colour being changed... 0 - change red, 1 - change green, 2 - change blue
red=0;
green=0;
blue=0;
satchange=0;
scrchange=0;
colmode=0; //default to red
SetMode(MODE_3 | BG2_ENABLE);
ScrRefresh(red,green,blue);
while(1)
{
if(!(REG_KEYS & KEY_A)) //if A button is pressed then released, colour mode should change 0>1>2>0>1>2>0...etc
{
while(!(REG_KEYS & KEY_A))
{
}
if(colmode<2)
{
colmode++;
}
if(colmode=2)
{
colmode=0;
}
}
if(!(REG_KEYS & KEY_UP)) //increase saturation of current colour on UP press
{
satchange++;
scrchange=1;
}
if(!(REG_KEYS & KEY_DOWN)) //decrease saturation of current colour on DOWN press
{
satchange--;
scrchange=1;
}
if(colmode=0) //change red value if mode is set to 0
{
red=red+satchange;
if(red>31)
{
red=31;
}
if(red<0)
{
red=0;
}
satchange=0;
}
if(colmode=1) //change green value if mode is set to 0
{
green=green+satchange;
if(green>31)
{
green=31;
}
if(green<0)
{
green=0;
}
satchange=0;
}
if(colmode=2) //change blue value if mode is set to 0
{
blue=blue+satchange;
if(blue>31)
{
blue=31;
}
if(blue<0)
{
blue=0;
}
satchange=0;
}
if(scrchange=1) //if button has been pressed and colour changed, update the screen
{
ScrRefresh(red,green,blue);
scrchange=0;
}
}
}//end main
|
I hope you can follow it, I wasn't intending for it to be a work of art. Please forgive me on the structure of it!
Basically, I'm having the problem that once compiled and running, it's not doing what I want it to do! The A button doesn't change which colour component is being changed (in Boycott Advance, it's stuck on green). The UP and DOWN buttons change the saturation of green, so it's 33% working I guess.
Can anybody point out where this is going wrong? Cheers!
_________________
I'd like to think this signature is under development, but it isn't.
#117465 - keldon - Mon Feb 05, 2007 12:16 am
Hopefully you are coding with indents! But anyway you should really try to start 'small', instead of trying to begin with a space shooter why not try making something simple like pong.
I've probably directed people thousands of times; in fact ...
keldon wrote: |
There was a page written by (I think)Geoff Frohwein about what order you should choose when deciding what games to begin making. e.g. 1: pong clone, 2: tetris clone, 3: Mario vs Luigi clone etc., but I have no idea where I found it or why I can't find it again. |
I wish I could find that page, although you may also want to go back to the tonc gba tutorials and learn more about tile modes, trying out demos as you go along and then try the following:
- 1: a simple game where you have a 10x10 grid and you simply move your character within it
- 2: continue with game #1 and add some boxes that your character must navigate to; each time you reach your target you respawn another
- 3: follow on from game #2, but this time you will not only ensure that you do not spawn where your character already is, but your player has a tail. This means that if your player moves to the right that his tail follows behind (like snakes)
- 4: make snakes!
Once you have made snakes you can take your next challenge at tetris; now don't be deceived, tetris can be more complicated than snakes if you want it to feel 'right'.
#117468 - neilio - Mon Feb 05, 2007 12:23 am
Thanks, the quoting messed up the indents on the program... I'd be completly lost if i didn't!
I'll have to check out the Tonc tutorials, always good to get a second opinion on these things - thanks for the advice!
_________________
I'd like to think this signature is under development, but it isn't.
#117472 - sgeos - Mon Feb 05, 2007 1:11 am
I have an HSB library for the GBA. Do you want it?
-Brendan
#117487 - wintermute - Mon Feb 05, 2007 2:30 am
keldon wrote: |
I've probably directed people thousands of times; in fact ...
keldon wrote: | There was a page written by (I think)Geoff Frohwein about what order you should choose when deciding what games to begin making. e.g. 1: pong clone, 2: tetris clone, 3: Mario vs Luigi clone etc., but I have no idea where I found it or why I can't find it again. |
I wish I could find that page
|
I believe the page you're talking about was on gamedev.net. Unfortunately they're down for maintainence right now so I can't find the page either.
_________________
devkitPro - professional toolchains at amateur prices
devkitPro IRC support
Personal Blog
#117495 - tepples - Mon Feb 05, 2007 3:46 am
keldon wrote: |
- 1: a simple game where you have a 10x10 grid and you simply move your character within it |
On a plane, right?
Quote: |
- 2: continue with game #1 and add some boxes that your character must navigate to; each time you reach your target you respawn another
- 3: follow on from game #2, but this time you will not only ensure that you do not spawn where your character already is, but your player has a tail. This means that if your player moves to the right that his tail follows behind (like snakes)
- 4: make snakes! |
So you have the plane, and you have the snakes. But where do you get the Samuel L. Jackson?
Quote: |
Once you have made snakes you can take your next challenge at tetris; now don't be deceived, tetris can be more complicated than snakes if you want it to feel 'right'. |
You're right. By the time you've read everything in the TCWiki glossary, you see that Tetris franchise is more complicated than some clone developers think.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#117521 - neilio - Mon Feb 05, 2007 10:28 am
Looking up on Tonc I found out something about reading in the button register at the beginning of the main loop cycle and comparing previous/current states of buttons... I'm going to give that a try and see if I can fix up my program with that, looks a lot better than just reading the button register straight out.
_________________
I'd like to think this signature is under development, but it isn't.
#117531 - neilio - Mon Feb 05, 2007 12:31 pm
I've made some changes and it's working now:
1) The colour mode is now changed only when the A button is released
2) The way the colour mode is incremented and looped has been changed
3) The If statements for changing the saturation of the selected colour have been replaced with a Switch statement
There's still some work to be done to make the code tidier and shorter, but it's working now and the user can select any one of 32,000+ colours as the screen colour... though quite why you'd want to, I don't know. I was just doing it to get used to programming in C again!
Here's the code (I've used the key handling procedures from Tonc in the header file by the way):
Code: |
//program to paint the screen one whole colour
//user can increase or decrease saturation of either red, green or blue
//the A button toggles between the colours
//the UP and DOWN buttons change the saturation of the current colour
#include <gba.h>
// Globals to hold the key state
u16 key_curr=0, key_prev=0;
// Update the screen with a new colour
void ScrRefresh(int rwrite,int gwrite, int bwrite)
{
unsigned char x,y;
for(x = 0; x < SCREEN_WIDTH; x++)
for(y = 0; y < SCREEN_HEIGHT; y++)
VideoBuffer [x+ y * SCREEN_WIDTH] = RGB16(rwrite,gwrite,bwrite);
}
///////////////// C code entry (main())/////////////////////
int main()
{
int red;
int green;
int blue;
int satchange; //change saturation of current colour up or down
int scrchange; //flag to control whether screen refreshes or not... 0 - no change, 1 - change
int colmode; //value will choose colour being changed... 0 - change red, 1 - change green, 2 - change blue
red=0;
green=0;
blue=0;
satchange=0;
scrchange=0;
colmode=0; //default to red
SetMode(MODE_3 | BG2_ENABLE);
ScrRefresh(red,green,blue);
while(1)
{
key_poll();
if(key_released(KEY_A)) //if A button is pressed then released, colour mode should change 0>1>2>0>1>2>0...etc
{
colmode++;
if(colmode>2)
{
colmode=0;
}
}
if(key_is_down(KEY_UP)) //increase saturation of current colour on UP press
{
satchange++;
scrchange=1;
}
if(key_is_down(KEY_DOWN)) //decrease saturation of current colour on DOWN press
{
satchange--;
scrchange=1;
}
switch(colmode) //changes saturation of currently selected colour
{
case 0:
red=red+satchange;
satchange=0;
break;
case 1:
green=green+satchange;
satchange=0;
break;
case 2:
blue=blue+satchange;
satchange=0;
break;
}
// following if statements stop colour variables being outside RGB boundaries
if(red>31)
{
red=31;
}
if(red<0)
{
red=0;
}
if(green>31)
{
green=31;
}
if(green<0)
{
green=0;
}
if(blue>31)
{
blue=31;
}
if(blue<0)
{
blue=0;
}
if(scrchange=1) //if button has been pressed and colour changed, update the screen
{
ScrRefresh(red,green,blue);
scrchange=0;
}
}
}//end main
|
_________________
I'd like to think this signature is under development, but it isn't.
#117551 - Cearn - Mon Feb 05, 2007 4:30 pm
Also interesting: using an array to store the R,G,B values instead of variables. Then you can do something like this:
Code: |
int shades[3]= {0, 0, 0};
... stuff ...
if( key_is_down(KEY_UP | KEY_DOWN) )
{
if( shades[colmode]>0 && key_is_down(KEY_DOWN) )
shades[colmode]--;
else if( shades[colmode]<31 && key_is_down(KEY_UP) )
shades[colmode]++;
ScrRefresh(RGB16(shades[0], shades[1], shades[2]));
}
|
This would replace everything from the KEY_UP test on down in the main loop. Additionally, it's always a good idea to have some sort of timing for the main loop like waiting for the VBlank. It's not really necessary here because your ScrRefresh() takes three frames, but it's still something to think about for later.
#117671 - neilio - Tue Feb 06, 2007 2:44 pm
Thanks, it's really good to see an example of code that's been 'optimized' (in the C program sense, not compiling). Hopefully I'll try applying that kind of logic in future when writing code.
I'll give the VBlank timing a try so I can get used to using it, and I want to do a little more with this program before I move on to sprites and tiled backgrounds.
_________________
I'd like to think this signature is under development, but it isn't.
#117705 - Miked0801 - Tue Feb 06, 2007 7:48 pm
Nasty bug in your last if:
if(scrchange=1) //if button has been pressed and colour changed, update the screen
Will always return TRUE and assing scrchange to 1. Here's a quick pass to make your code a bit more readable (in my opinion). It uses constants and enums to make the code closer to english. It also minimizes a lot other code calling.
Code: |
#define COLOR_MIN_SATURATION 0
#define COLOR_MAX_SATURATION 31
typedef enum
{
COLOR_FIRST,
COLOR_RED = COLOR_FIRST,
COLOR_GREEN,
COLOR_BLUE,
COLOR_MAX,
} COLOR_TYPE;
int main(void)
{
u32 colors[COLOR_MAX];
COLOR_TYPE colmode; //value will choose colour being changed... 0 - change red, 1 - change green, 2 - change blue
s32 satchange; //change saturation of current colour up or down
colors[COLOR_RED] = 0;
colors[COLOR_GREEN] = 0;
colors[COLOR_BLUE] = 0;
satchange = 0;
colmode = COLOR_RED; //default to red
SetMode(MODE_3 | BG2_ENABLE);
// I would change this function to accept the colors array directly. Cleaner to code with
ScrRefresh(colors);
while(1)
{
key_poll();
if(key_released(KEY_A)) //if A button is pressed then released, colour mode should change 0>1>2>0>1>2>0...etc
{
colmode++;
if(colmode >= COLOR_MAX)
{
colmode = COLOR_FIRST;
}
}
if(key_is_down(KEY_UP)) //increase saturation of current colour on UP press
{
if(color[colMode] < COLOR_MAX_SATURATION)
{
satchange++;
}
}
if(key_is_down(KEY_DOWN)) //decrease saturation of current colour on DOWN press
{
if(color[colMode] > COLOR_MIN_SATURATION)
{
satchange--;
}
}
if(satchange)
{
color[colMode] += satchange;
satchange = 0;
// Assume Refresh takes the array now
ScrRefresh(colors);
}
}
}//end main
|
Untested/uncompiled, but it gets my points across. I dropped the scrChange var as you don't need it, but it could easily be added back in if you had other stuff going on.
#117706 - sgeos - Tue Feb 06, 2007 8:05 pm
Miked0801 wrote: |
Nasty bug in your last if:
if(scrchange=1) //if button has been pressed and colour changed, update the screen |
Everyone has probably been hit by this. To avoid the above bug, you can write test cases like this:
The compiler will complain that you can't set 1 to variable, then you can fix it:
Admittedly, the mental exercise is often enough to avoid such bugs in the first place.
-Brendan
#117731 - neilio - Wed Feb 07, 2007 12:26 am
So I should have been using ==... doh! Should have known better, that's why the screen was refreshing constantly.
I've put in the code for Vblank waiting and I've now got a white line box on the screen, drawn on top of the coloured background. Since the box is flickering when the screen is being refreshed constantly (ie up/down button held down), I'm assuming this is because the routines for writing the pixels take longer to write the pixels than Vblank does?
_________________
I'd like to think this signature is under development, but it isn't.
#117747 - sgeos - Wed Feb 07, 2007 2:34 am
A complete mode 3 screen refresh can not be done every frame.
The framerate should drop, but I'd do something like this:
Code: |
writePixelsToBufferInRAM();
waitForVblank();
dmaPixelsToVram(); |
You might need/want to spread the DMA across 2 frames. I don't think that you can refresh in one frame even using DMA, but I might be wrong.
-Brendan
#117753 - tepples - Wed Feb 07, 2007 3:15 am
sgeos wrote: |
I don't think that you can refresh in one frame even using DMA |
Copying from EWRAM to VRAM happens at 4 cycles per 2-byte pixel. This comes out to 153600 cycles for a full screen copy, or 55 percent of the frame time.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#117757 - sgeos - Wed Feb 07, 2007 4:02 am
How far into V-Draw is that? I guess what I really want to know is, can DMA be for a full screen refresh in optimum conditions? Even so, you are left with 45% of the frame to write to the frame buffer (using brute force, this is impossible) and do everything else (sprites, sound, ai). Would sound DMA get in the way?
I think a 30 FPS interlaced structure might work:
Code: |
writeToPixelRAMBuffer();
waitForVblank();
dmaPixels();
updateGameState();
writeToOamRAMBuffer();
waitForVblank();
dmaOam(); |
Although writeToPixelRAMBuffer() might want to be spread across 2 frames.
EDIT: V-Draw takes longer than 55% of a frame- ie, DMA finishes faster than the video controller so it should work. DMA should remain ahead of the video controller as long as it is started at least 1232 (one scanline) cycles before V-Draw begins. It should lag behind the video controller (ie, update VRAM for the next frame) as long as it starts after 153600 (DMA) + 1232 (one scanline) cycles before V-Blank begins; it can start on or after scanline 35.
-Brendan
#117808 - Miked0801 - Wed Feb 07, 2007 7:32 pm
Sounds about right. This is how I did my first ever GBA scroll routine way back when. Using 15-bit color mode, I refreshed the whole screen every time the position changed by starting the DMA redraw with a vcount interrupt. It worked, but it wasn't practical for a full game :)
#117810 - neilio - Wed Feb 07, 2007 8:19 pm
Since I'm going to be downloading the program to EWRAM by a multiboot cable, I'll need to leave space between the beginning of the EWRAM 'display buffer' and the end of the program. Since the size required for the screen is 75kB (240 * 160 * 2 bytes/pixel) I assume starting the RAM buffer somewhere about 0202:0000 hex would be fine?
_________________
I'd like to think this signature is under development, but it isn't.
#117812 - wintermute - Wed Feb 07, 2007 8:36 pm
neilio wrote: |
Since I'm going to be downloading the program to EWRAM by a multiboot cable, I'll need to leave space between the beginning of the EWRAM 'display buffer' and the end of the program. Since the size required for the screen is 75kB (240 * 160 * 2 bytes/pixel) I assume starting the RAM buffer somewhere about 0202:0000 hex would be fine? |
There are a couple of ways you can handle this, the most convenient is just to use malloc to allocate a buffer. Some of the newlib standard functions can get a bit heavy though so it depends on how much space you need for the rest of your code and variable space ( malloc adds around 32k iirc).
The other way is to use the address of the end of your application defined by the devkitARM linkscripts. Use this code to get that address
Code: |
extern unsigned char __end__[];
|
You may need to align this value for use with DMA but it should be much safer than just randomly choosing an arbitrary constant address.
_________________
devkitPro - professional toolchains at amateur prices
devkitPro IRC support
Personal Blog
#117816 - sgeos - Wed Feb 07, 2007 9:17 pm
Seems like mode 4 (indexed bitmap mode) might make for a good data size/quality trade off. I don't know if you are in a position to deal with indexed images.
-Brendan
#117831 - neilio - Wed Feb 07, 2007 11:44 pm
Got the EWRAM pixel buffer set up now and have almost got the DMA transfer into the video RAM, except that not all of the pixels are transferring... only about 7/8ths are transferred.
Because the DMA count in its register is 16 bits, I can set up to FFFF for the count (FFFF * 32 bits for the DMA register = 2097120 bits moved?). Now, the whole of the pixel data is 614400 bits (240 * 160 * 2 (bytes) * 8 (bits per byte) = 614400?)... so I must have got something wrong somewhere!
I've tried moving the pixel RAM buffer forward in EWRAM but the same still happens. If anyone can correct me on this I'd much appreciate it! Apologies for keeping on asking questions...
This is the DMA routine I'm using:
Code: |
void DmaPixels(void *dst, const void *src, u16 count)
{
REG_DMA0CNT = 0; // shut off any previous transfer
REG_DMA0SAD = src;
REG_DMA0DAD = dst;
REG_DMA0CNT = count | DMA_32NOW;
} |
_________________
I'd like to think this signature is under development, but it isn't.
#117832 - gmiller - Thu Feb 08, 2007 12:25 am
You might try a 16 bit transfer instead of a 32 bit if your data is not aligned on a 4 byte boundary. I had problems with OAM transfers using DMA and I corrected it by using the alignment directives to force my local copy to a 4 byte aligned address.
#117836 - neilio - Thu Feb 08, 2007 12:36 am
Sorry to sound dumb but how would you go about aligning data?
_________________
I'd like to think this signature is under development, but it isn't.
#117843 - gmiller - Thu Feb 08, 2007 1:44 am
I use the following:
Code: |
#define ALIGN(m) __attribute__((aligned (m)))
//create an array of 128 sprites equal to OAM
static Sprite sprites[128] ALIGN (4);
|
#117849 - sgeos - Thu Feb 08, 2007 2:06 am
Why not use devkitpro's dmaCopy?
Code: |
#include <gba_dma.h>
dmaCopy(src, dst, size); |
-Brendan
#118289 - neilio - Sun Feb 11, 2007 10:16 pm
Thanks for the help, the program now works pretty much as I wanted it to. I'll add some more features but in slow time (such as showing the BGR value of the colour as text).
I've also been working with sprites, with the aid of Usenti for converting graphics I now have a little character sprite that can fall from sky and jump up in the air (with basic acceleration included) and lands on/falls off platforms reasonably well. Unfortunately there's no background yet, so I'll work on that next.
I've also made an Xboo cable...cheap and simple, but it's really great to be able to see what you've done on a real Game Boy - makes programming something all the more rewarding!
P.S. On second look, backgrounds are slightly confusing... can anybody point me in the direction of good tutorial/example codes other than Pern/Tonc?
_________________
I'd like to think this signature is under development, but it isn't.