GP2X Pssst!


great.

question 2: when you run the built gpe file is that the only file you need or do you need the graphics folder as well? when i run the file on the gp2x it does not display the background image though it does quit when you press a button.

EDIT - after Mr JW noted below that image should be in 256 colours I realised I had not done this. now works fine.
cheers (i used paint btw seems to work okay)
 
[ASIDE] The BIN2C originally linked to works fine, at a guess I would say peelie's issue was probably path related [/ASIDE]
 
Thanks Mr.Jabberwocky, nice tutorial.
Very interesting for nOObs like me...
I'll try to do it too and perhaps mess a bit with your code to learn more.
 
QUOTE

You will have to load it into paint or whatever and save it out as background.BMP.

Open a dos prompt in this folder and type :

bin2c background.bmp background.c

Move the file produced (background.c) to your project folder.



That's a very interesting way of encoding an image... Very cool.
 
peelie said:
great.

question 2: when you run the built gpe file is that the only file you need or do you need the graphics folder as well? when i run the file on the gp2x it does not display the background image though it does quit when you press a button.
The image is included in the one file so you do not need anything else. Something has gone awry. Is it me or is it you ? Has anyone got it to work ?

If you have anything other than Paint to use to convert the image use that. Save it as a 256 colour image. The image conversion is proving to be a problem, but photobucket converts any bmp to jpeg. Something I failed to forsee. This may cause problems with the pallette later, selecting the trasparent colour. I need to find a way to make sure we are all using the same data.
 
Last edited by a moderator:
Mr.Jabberwocky said:
peelie said:
great.

question 2: when you run the built gpe file is that the only file you need or do you need the graphics folder as well? when i run the file on the gp2x it does not display the background image though it does quit when you press a button.
The image is included in the one file so you do not need anything else. Something has gone awry. Is it me or is it you ? Has anyone got it to work ?

If you have anything other than Paint to use to convert the image use that. Save it as a 256 colour image. The image conversion is proving to be a problem, but photobucket converts any bmp to jpeg. Something I failed to forsee. This may cause problems with the pallette later, selecting the trasparent colour. I need to find a way to make sure we are all using the same data.



doh 256 colours! edited post above

thx!
 
Last edited by a moderator:
peelie said:
doh 256 colours! edited post above

thx!
My fault. I should have specified. Does it now work ?

I have changed the image to a gif. This seems to convert to a BMP fine, even in paint, pallette too.
 
Last edited by a moderator:
Here are the images for the game objects.

graphics.gif


I have changed the bonus graphics - since a watering-can with no handle, a waffle on a stick and a bag of poo just does not cut it in these modern times.

Download and save it in your graphics folder. Load it into paint or whatever and save it out as graphics.BMP.(256 colour)

Open a dos prompt in this folder and type :

bin2h graphics.bmp graphics.c

Move the file produced (graphics.c) to your project folder. Add it into your project (project->Add Files->(select the file)->Open).

Now to test if we can display an image from it. This involves changes to game.cpp - replace the contents with the following :

CODE
#include <SDL\SDL.h>
#include <SDL\SDL_rwops.h>
#include "game.h"
#include "buffers.h"
#include "ioBasics.h"

int game_loop(void) {
int status = 0;
// this is the ZX Spectrum style display area
SDL_Rect dstrect = { 32, 24,256,192};
// this the position of the Robbie graphic in the bitmap
SDL_Rect robbie_g = { 0,146, 20, 21};
// This is the screen position for Robbie
SDL_Rect robbie_d = {150, 90, 20, 21};
// initialise graphics
status = init_graphics();
// check everything OK
if(status != 0 ) {
// no graphics, no game - so exit
return status;
}
// copy the background to the screen
SDL_BlitSurface(backdrop, NULL, screen, &dstrect );
// put Robbie on the screen
SDL_BlitSurface(images, &robbie_g, screen, &robbie_d );
// make the screen visible
SDL_Flip(screen);
WaitForKey();
return status;
}

int init_graphics(void) {

char *buffer = (char *)background;

// Load the background image into a surface using RWops
SDL_RWops *rw = SDL_RWFromMem(buffer, background_size);
SDL_Surface *temp = SDL_LoadBMP_RW(rw, 1);

// Were we able to load the bitmap?
if (temp == NULL) {
// return 1 to signal failure
return 1;
}
//Convert the image to optimal display format
backdrop = SDL_DisplayFormat(temp);

// Now set up the game object's images
buffer = (char *)graphics;
// Load the graphic's image into a surface using RWops
rw = SDL_RWFromMem(buffer, graphics_size);
temp = SDL_LoadBMP_RW(rw, 1);

// Were we able to load the bitmap?
if (temp == NULL) {
// return 1 to signal failure
return 1;
}
//Convert the image to optimal display format
images = SDL_DisplayFormat(temp);
//Free the temporary surface
SDL_FreeSurface(temp);
// set transparency to background colour of graphics bmp (pink)
SDL_SetColorKey( images, SDL_SRCCOLORKEY, SDL_MapRGB(images->format, 255, 102, 255) );

// return zero to signal success
return 0;
}

The initialisation of the graphics is now in its own function and handling the new bitmap has been added. Notice the selection of a transparent colour - otherwise all the objects would have a fetching pink rectangle behind them. Just to check we can access the new image, a temporary addition is the displaying of the robot graphic.

We need a new file called game.h into which we place the following :

CODE
extern unsigned int background_size; // the size of the background image generated by bin2h.exe (background.c)
extern unsigned int graphics_size; // the size of the graphics image generated by bin2h.exe (graphics.c)

int game_loop(void); // game control loop (game.cpp)
int init_graphics(void); // initialise graphics function (game.cpp)



'extern' tells the compiler that whatever follows it exists somewhere. In this case the sizes of the graphics that bin2h.exe generated for us. We can now use these labels instead of plain numbers. That way we will know when we look at it next week what the hell the value represents.

Also there are the prototypes of the two functions we have in game.cpp so far. The compile would fail when it reached the call to init_graphics() in game_loop() even though it is in the same file. So we have to provide the compiler with a prototype so it knows the call is structured correctly.

We also need to add a place to record where are new graphics are: Add the following to buffers.cpp ...

CODE
SDL_Surface *images = NULL; // tells us where the object graphics are


... and in buffers.h replace the contents with :

CODE
extern SDL_Joystick *joystick; // reference to the joystick device

extern SDL_Surface *screen; // tells us where the screen is
extern SDL_Surface *backdrop; // tells us where the background image is
extern SDL_Surface *images; // tells us where the object graphics are

extern const unsigned char graphics[]; // this is the graphic data for the game objects (graphics.c)
extern const unsigned char background[]; // this is the graphic data for the background image (background.c)


Hopefully it will still build and Robbie will be displayed near the centre of the screen. Press any key to exit.
 
sixfingers.jpg

Is this not beautiful compared to the ugly mess at the end of your arm ?

Have you ever noticed that a plate of five sausages is just not as appetising as a plate of four or six ?

This is because 500 million years ago one of two things happened :
  1. Either conditions on the earth became suitable for carbon based life forms to thrive and the bacteria that had existed for billions of years found new and exciting ways to increase their chances of survival.
  2. Or conditions on the earth became suitable for fossils to be formed and survive into the present time.
This has been called the Cambrian explosion, when evidence of a huge variety of early life-forms suddenly appear in the fossil record.

Even these primitive creatures were astute enough to recognise that five is really, really ugly. This resulted in things that evolved five appendages appearing less appetising to other predators. Subsequently we now find that many modern life-forms have five-digits or five petals etc. and the world is a much uglier place than it could have been.

Either that or God made us that way 6000 years ago, four thousand years after we had domesticated animals. That guy does work in mysterious ways.

The whole point of this is that having ten digits has resulted in the development of the base 10 (denary) counting system and it is as well to bear in mind that it is an artificial construct and there are any other number of base systems.

The GP2X is a lot of little on/off switches that can only represent two states instead of the 10 states, or numbers, that we are familiar with. Whereas we can use ten symbols before having to introduce another symbol to represent a higher value the GP2X has to add a new symbol after only 2 states. This is, of course, what we call the binary system.


CODE
denary binary

0000 0000
0001 0001
0002 0010 <- add extra symbol
0003 0011
0004 0100 <- add extra symbol
0005 0101
0006 0110
0007 0111
0008 1000 <- add extra symbol
0009 1001
0010 <- add extra symbol 1010



So bear in mind that when you are struggling with the compiler's refusal to accept a parameter because it is the 'wrong type' it is for your benefit not the GP2X's. The GP2X has no idea about numbers, C language or anything for that matter. It is a machine. It just reads those switches and follows orders. When it crashes it is your fault. The ultimate Neuremburg defense.

That picture of a hand has made me quite peckish - I'm off to open a tin of hot dogs.
 
I fondly imagined everyone had forgotten about this. I had returned to my normal pursuit of extracting fluff from my navel, when peelie had to go and spoil it for me. If peelie is interested now, maybe someone else will also be, six months down the line, and I will get a warm fuzzy feeling if it helps them.

Apologies to everyone else but you may occasionally have an extra topic to ignore under view new posts.

The main point of this was to help people get started and show that any idiot can make a game. If this gets you started, you really should do further reading and learn how to do it properly. I don't follow any programming model. If you are a cool kid you will want to use C++ and use clever sounding words - you won't get any of that here, just plain old C.

So far we have a background and we have managed to copy an image of Robbie (the player controlled object) onto it from a source rectangle to a destination rectangle. If we repeatedly draw the background and change the position of the destination rectangle Robbie will appear to move. If we change the position depending on the state of the joypad the player can control Robbie. YIPEE!

Essentially we need to repeatedly do the following :
  • Check the joy pad
  • update the position of Robbie's destination rectangle (depending on the joy pad state)
  • draw the background (thus erasing any previous screen contents)
  • plonk Robbie on top by zapping his graphic image into the destination rectangle
  • make the updated image the currently displayed image
Since these are discrete steps that must be done again and again, we can direct the program flow to loop back to the first task (checking the joy pad) after all the steps have been performed. There are various ways to implement a loop - loop forever, loop a fixed number of times, loop until a condition is met. My favorite is loop until the side effects of a 'clever' bit of code cause the computer to crash.

In most games there are various conditions that end the player control loop. If Robbie dies we need to restart the level( or end the game if the player has used up all his lives) and we need to progress to the next level if the player successfully completes the current level.

But before we can do any of that we actually need to be able to get the current state of the joy pad. This is tedious, but once you have such a function you never have to think about it again. But for now we need to see what functionality SDL provides and what we actually need from such a function.

This will be covered in the the next installment.
 
Last edited by a moderator:
Hang in there! I am reading this with quite some interest! And the end this could become some nasty fine tutorial!
 
Since we have yaustar's mighty fine multi platform dev setup we want to only use the GP2X for the occassional check. It would be nice, therefore, to have a function that reads the input elegantly on both platforms. If you want that then you will have to write it yourself. I have hacked together the following instead :
CODE
int ReadInput(int iDevice, int iKeyReq) {
SDL_Event event;
static int iKey = NO_INPUT;
static int iJoy = NO_INPUT;

while( SDL_PollEvent( &event ) ) {
switch( event.type ) {
case SDL_JOYBUTTONDOWN: // GP2X buttons
iJoy = event.jbutton.button;
break;
case SDL_JOYBUTTONUP: // GP2X buttons
if(iJoy == event.jbutton.button) {
iJoy = NO_INPUT;
}
break;
case SDL_KEYDOWN: // PC buttons
switch(event.key.keysym.sym) {
case SDLK_UP:
case SDLK_LEFT:
case SDLK_DOWN:
case SDLK_RIGHT:
iJoy = event.key.keysym.sym;
break;
default:
iKey = event.key.keysym.sym;
break;
}
break;
case SDL_KEYUP:
switch(event.key.keysym.sym) {
case SDLK_UP:
case SDLK_LEFT:
case SDLK_DOWN:
case SDLK_RIGHT:
if(iJoy == event.key.keysym.sym) {
iJoy = NO_INPUT;
}
break;
default:
if(iKey == event.key.keysym.sym)
iKey = NO_INPUT;
break;
}
break;
default:
break;
}
}
switch(iDevice) {
case JOYSTICK:
switch(iKeyReq) {
case ANY_KEY:
return iJoy;
break;
default:
if(iKeyReq == iKey) {
return iJoy;
} else {
return NO_INPUT;
}
break;
}
break;
case KEYBOARD:
switch(iKeyReq) {
case ANY_KEY:
return iKey;
break;
default:
if(iKeyReq == iKey) {
return iKey;
} else {
return NO_INPUT;
}
break;
}
break;
}
return iJoy;
}


SDL won't tell you if a button is currently pressed or not, instead it lets you know when the state of an input changes. This means we have to remember the state ourselves. We want to be able to move Robbie and also operate the spray-can he is carrying at the same time. That means we need to record the state of the pad and the state of a button press, so we need two stores - iJoy and iKey.

Notice they are declared as 'static'. Usually any variable declared in a function ceases to exist once the function ends. In this case that would be useless as we want to remember the states and they would be lost. The 'static' keyword makes the variables remain. They are only accessible in this function and we can initialise them like normal variables. They will not be reinitialised every time the function is called.

The idea behind this function is that we can query the joypad or the buttons independently. We can also ask if a particular state is set or just have it return the current state.

Study the switch statement and read up on it here or any of the other millions of on-line C tutorials. We will be using it quite a bit.

Now we will be able to use the pad to move Robbie around.
 
I have a question: what is the practical difference between checking joystick input via events versus checking each individual button to see whether it's pressed or not? From the source codes I've seen around, each method is used in different programs without any apparent reason of choice.
 
I can't speak for anyone else but I don't have a joypad for my PC so my reason for doing it this way is to ensure it works on both platforms while doing as little work/thinking/research as possible. I may have totally misunderstood the whole input bit and would invite anyone to present their solutions . That is part of the object of this thread - to share knowledge and crash ED's server with the abundance of homebrew that shall issue forth from the lessons learned here.
 
I have put this new function in the ioBasics.cpp source file. In ioBasics.h we need to describe it for the compiler using a function prototype. Just copy 'int ReadInput(int, int);' and place it with the existing two.

We have also made up some names to distinguish the pad from the buttons etc.. These need to be defined in the same header. Copy the following into ioBasics.h :

enum input {
JOYSTICK,
KEYBOARD,
ANY_KEY = -2,
NO_INPUT = -1
};

enum is a wonderful directive that automatically generates values for labels you want to use. I think it defaults at 0 and increments for each label you add. You could assign a value to JOYSTICK and it would start incrementing from there. As you can see I have assigned values to ANY_KEY and NO_INPUT, because I don't want them to collide with values returned by the pad/buttons.

Let's make Robbie move.

In game.cpp change game_loop to look like this :

CODE
int game_loop(void) {
int status = 0;
int iJoy;
// this is the ZX Spectrum style display area
SDL_Rect dstrect = { 32, 24,256,192};
// this the position of the Robbie graphic in the bitmap
SDL_Rect robbie_g = { 0,146, 20, 21};
// This is the screen position for Robbie
SDL_Rect robbie_d = {150, 90, 20, 21};
// initialise graphics
status = init_graphics();
// check everything OK
if(status != 0 ) {
// no graphics, no game - so exit
return status;
}
do {
iJoy = ReadInput(JOYSTICK, ANY_KEY);
switch(iJoy) {
case GP2X_BUTTON_UP:
robbie_d.y--;
break;
case GP2X_BUTTON_LEFT:
robbie_d.x--;
break;
case GP2X_BUTTON_DOWN:
robbie_d.y++;
break;
case GP2X_BUTTON_RIGHT:
robbie_d.x++;
break;
}
// copy the background to the screen
SDL_BlitSurface(backdrop, NULL, screen, &dstrect );
// put Robbie on the screen
SDL_BlitSurface(images, &robbie_g, screen, &robbie_d );
// make the screen visible
SDL_Flip(screen);
} while(ReadInput(KEYBOARD, ANY_KEY) != GP2X_BUTTON_SELECT);

return status;
}


We now have a 'do...while' loop that is waiting for the SELECT button (defined as Z on the PC) to be pressed. While it is waiting it checks the pad and updates Robbie's destination rectangle accordingly.

Now was not that simple ?
 
hi

getting compile errror- :: === Pssst, PC Release ===
C:\Pssst\game.cpp:: In function `int game_loop()':
C:\Pssst\game.cpp:26: error: `GP2X_BUTTON_UP' undeclared (first use this function)
C:\Pssst\game.cpp:26: error: (Each undeclared identifier is reported only once for each function it appears in.)
C:\Pssst\game.cpp:29: error: `GP2X_BUTTON_LEFT' undeclared (first use this function)
C:\Pssst\game.cpp:32: error: `GP2X_BUTTON_DOWN' undeclared (first use this function)
C:\Pssst\game.cpp:35: error: `GP2X_BUTTON_RIGHT' undeclared (first use this function)
C:\Pssst\game.cpp:45: error: `GP2X_BUTTON_SELECT' undeclared (first use this function)
:: === Build finished: 6 errors, 0 warnings ===
 
peelie said:
getting compile errror- :: === Pssst, PC Release ===
The lie :
Just testing to see if anyone is paying attention :p
The truth :
This would be because I most probably failed to instruct you to include the file "gp2x.h" into game.cpp.
At the top, with the other include statements add #include "gp2x.h".
Thanks for pointing that out. Next time you will be able to track down the problem yourself and tell me where I have screwed up.

Edit : When you get an error like that click the error you are interested in and code::blocks will take you to the line in the file. You can then right clixk the offending item ie:GP2X_BUTTON_UP, and select find declaration of 'GP2X_BUTTON_UP' to take you to the file it is defined in. You then know that this is the file some fool forgot to include.

It might be best not quote my huge posts where possible. Bandwidth and all that jazz.
 
Last edited by a moderator:
Back
Top