Tutorial 4
For simple games like the ping-pong game the former manner of programming games may be ok.
Complexer games with a lot of objects should be more clearly arranged by separating these objects with their attributes from the main code.
Using C++ could be an option, but VgaGames also supports separating objects for the C-language using VgaGames3-objects.
VgaGames3-objects are contained in the structure struct vg3_ofunc which is created by the function ofunc_new() and destroyed by the function VG3_ofunc_free().
Files for VgaGames3-objects
A VgaGames3-object is of type struct vg3_ofunc_object and should be defined in a separate C-file.The corresponding H-file defines a private structure for this object containing variables it needs.
Each VgaGames3-object must have an individual object-ID, an unique arbitrary string.
In the C-file several functions must be defined, at least a construction function (f_new()), a destroying function (f_free()), and normally a running function (f_run()) and a drawing function (f_draw()).
These functions must be declared according to struct vg3_ofunc_objfunc and will be retrieved by other objects or the main program with VG3_ofunc_get_objfunc().
An example for a VgaGames3-object in this tutorial are the files obj-player.h and obj-player.c.
The proceedings are always the same for each object-file:
- in the construction function (f_new())
- allocate and fill the private structure
- allocate and fill the VgaGames3-object structure (struct vg3_ofunc_object) and set the private structure into it
- insert this object-instance into the list of object-instances with VG3_ofunc_objlist_insert()
- insert this object-instance with its position and size into the global collision-quadtree with VG3_coll_q_insert()
- return the object-instance
- in the destroying function (f_free())
- remove the object-instance from the global collision-quadtree with VG3_coll_q_remove()
- remove the object-instance from the list of object-instances with VG3_ofunc_objlist_remove()
- clean up the private structure and free it
- free the VgaGames3-object structure
- in the optional running function (f_run())
- remove the object-instance from the global collision-quadtree with VG3_coll_q_remove()
- move the object-instance and check for collisions, e.g. using VG3_coll_q_find() or using the more complex but comfortable function VG3_move_object_check_collision()
- insert the object-instance with its new position and size into the global collision-quadtree again with VG3_coll_q_insert()
- in the optional drawing function (f_draw())
- draw the object-instance onto the window with VG3_image_copy()
- the optional function (f_data())
- is used to modify or return data of the object-instance
contained in the structure struct vg3_ofunc_objfunc.
The functions f_new() and f_data() and the functions in the individual structure void *vpriv have to be called manually.
The function f_free() is being called with VG3_ofunc_objlist_call_free() from the main program.
The function f_run() is being called with VG3_ofunc_objlist_call_run() from the main program.
The function f_draw() is being called with VG3_ofunc_objlist_call_draw() from the main program.
Now we know how to instantiate, run, draw and destroy an instance of an object using VgaGames3-objects.
We also know how to retrieve functions from an object by another object or the main program.
But how do we process the interaction of the objects?
Do we have to include all possible collisions with other objects into f_run() when moving and checking for collision?
We need another sort of files, which contain the interaction of two objects, mainly the collision.
This we will do with object-to-object-functions for VgaGames3-objects.
Files for object-to-object-functions for two VgaGames3-objects
Two VgaGames3-objects interact in a separate C-file which contains object-to-object-functions for the two VgaGames3-objects.This C-file includes the H-files of both VgaGames3-objects to have access to their private structures.
Object-to-object-functions for the two VgaGames3-objects are defined in the structure struct vg3_ofunc_objobjfunc.
In the C-file two functions may be defined, the collision function (f_collision()) and a quit function (f_quit()), which informs the parent which child object is being destroyed.
These functions must be declared according to struct vg3_ofunc_objobjfunc and will be retrieved by other objects or the main program with VG3_ofunc_get_objobjfunc().
An example for a object-to-object functions in this tutorial is the file objobj-player-ball.c.
The proceedings are always the same for each object-to-object function file:
- in the function (f_collision())
- determine which of both passed VgaGames3-objects is which object
- according to moving object (first passed object) and hit object (second passed object) and hit-side do actions
- if the hit object is being destroyed call its f_free() function
- return one of VGAG3_COLL_RETURNS
- in the optional function (f_quit())
- determine which of both passed VgaGames3-objects is which object
- do actions
It should return how to go on:
- VGAG3_COLL_RETURN_NOOP: no collision, go on
- VGAG3_COLL_RETURN_HIT: moving object has been hit
- VGAG3_COLL_RETURN_DEAD: moving object has been hit and freed
- VGAG3_COLL_RETURN_HALFSTOP: moving object has been stopped in x- or y-direction
- VGAG3_COLL_RETURN_FULLSTOP: moving object has been stopped totally
- VGAG3_COLL_RETURN_CONTINUE: collision, but go on with new calculation of direction
The function f_quit() must be called manually by the child object, which calls f_quit() of the parent object.
The tutorial ping-pong game - a complete rewrite
In step 1 we create the main-program.
In step 2 we create the object files.
In step 3 we create the object-to-object function files, i.e. the files for interaction of the objects.
- Step 1
At first for clarity we create the main-program.
- Step 1.1
The individual game structure which will be available for all files.
We need:
- a pointer to the opened window
- the size of the opened window
- a pointer to the VgaGames3-object, created by ofunc_new()
- a pointer to the collision-quadtree
- an integer, which we set to 1 if the player missed the ball, to end the game
main.h:#ifndef MAIN_H_ #define MAIN_H_ #include <vgagames3.h> /* individual game structure */ struct game { struct vg3_window *wstruct; /* window structure */ int winw, winh; /* size of window */ struct vg3_ofunc *ofstruct; /* common VgaGames3-object structure */ struct vg3_quadtree *qdtr; /* collision-quadtree */ int endgame; /* flag to end the game */ }; /* list of used object-IDs */ #define OID_PLAYER "player" #define OID_BORDER "border" #define OID_BALL "ball" #endif /* MAIN_H_ */
- Step 1.2
The main-program.
We
- fill the individual game structure
- create the objects we need: we search for the function f_new() and call it
- create the game-loop (get key-strokes, call run-functions and draw-functions of objects, output and sleep)
- end game freeing objects and the individual game structure
main.c: - Step 1.2.1
We fill the individual game structure#include <vgagames3.h> #include "main.h" extern struct vg3_ofunc * ofunc_new(void); int main(int argc, char **argv) { struct game game; const struct vg3_ofunc_objfunc *ofc; struct vg3_rect rect; (void)argc; /* +++ set game structure +++ */ memset(&game, 0, sizeof(game)); /* open window */ game.wstruct = VG3_window_new(argv[0], VGAG3_VGAVERSION_LOW, VGAG3_WINSCALE_BESTSCALE); if (game.wstruct == NULL) { fprintf(stderr, "%s\n", VG3_error()); exit(1); } /* get window size */ VG3_window_getsize(game.wstruct, &game.winw, &game.winh); /* create the common VgaGames3-object structure */ game.ofstruct = ofunc_new(); /* create collision-quadtree with size of window */ rect.x = rect.y = 0; rect.w = game.winw; rect.h = game.winh; game.qdtr = VG3_coll_q_new(&rect, 0, 0); if (game.qdtr == NULL) { fprintf(stderr, "%s\n", VG3_error()); goto endgame; } game.endgame = 0;
- Step 1.2.2
We create the objects we need:
- with VG3_ofunc_get_objfunc() we get the structure with object-functions for the VgaGames3-object
- we call the function f_new() from this structure to create the object-instance/* +++ create VgaGames3-objects +++ */ /* create player */ ofc = VG3_ofunc_get_objfunc(game.ofstruct, OID_PLAYER); if (ofc == NULL) { fprintf(stderr, "Player-object not found\n"); goto endgame; } if (ofc->f_new(&game, 0) == NULL) { fprintf(stderr, "%s\n", VG3_error()); goto endgame; } /* create borders */ ofc = VG3_ofunc_get_objfunc(game.ofstruct, OID_BORDER); if (ofc == NULL) { fprintf(stderr, "Border-object not found\n"); goto endgame; } /* upper horizontal border */ if (ofc->f_new(&game, 0, 1) == NULL) { fprintf(stderr, "%s\n", VG3_error()); goto endgame; } /* lower horizontal border */ if (ofc->f_new(&game, 0, 2) == NULL) { fprintf(stderr, "%s\n", VG3_error()); goto endgame; } /* vertical border */ if (ofc->f_new(&game, 0, 3) == NULL) { fprintf(stderr, "%s\n", VG3_error()); goto endgame; } /* create ball */ ofc = VG3_ofunc_get_objfunc(game.ofstruct, OID_BALL); if (ofc == NULL) { fprintf(stderr, "Ball-object not found\n"); goto endgame; } if (ofc->f_new(&game, 0) == NULL) { fprintf(stderr, "%s\n", VG3_error()); goto endgame; } /* +++ print infos +++ */ printf("\n"); printf("Keys:\n"); printf("- cursor up, cursor down: move\n"); printf("- ALT+Q: exit\n");
- Step 1.2.3
We create the game-loop:
- we get key-strokes
- we call the run-functions (f_run()) of the created objects
- we call the draw-functions (f_draw()) of the created objects
- we draw out window-contents and sleep for up to 30 milliseconds/* +++ game-loop +++ */ VG3_discard_input(game.wstruct); for (;;) { if (VG3_inputevent_update(game.wstruct) > 0) { break; } /* ALT+Q: exit */ if (VG3_key_ispressed(game.wstruct, VGAG3_KEY_Q, VGAG3_IS_NEW_PRESSED) && VG3_key_ispressed(game.wstruct, VGAG3_KEY_LALT, VGAG3_IS_PRESSED)) { break; } /* clear window with halfdark blue background */ VG3_draw_clear(game.wstruct, NULL, VG3_color_brightness(VGAG3_COLOR_BLUE, 50)); /* execute run-function of loaded object-instances */ VG3_ofunc_objlist_call_run(game.ofstruct, &game); /* execute draw-function of loaded object-instances */ VG3_ofunc_objlist_call_draw(game.ofstruct, &game); /* uncomment to mark all collision-quadtree objects */ /* VG3_coll_q_mark(game.qdtr, game.wstruct, NULL, VGAG3_COLOR_ORANGE, NULL); */ /* draw out window-contents */ VG3_window_update(game.wstruct, 0, 0); /* sleep until 30 msec are gone for this game-loop */ VG3_wait_time(30); /* if end game is requested, quit */ if (game.endgame) { break; } } VG3_discard_input(game.wstruct);
- Step 1.2.4
We end the game:
- we play the gameover music
- we unload and free the remaining object-instances
- we free the individual game structure/* +++ end game +++ */ /* load and play gameover.wav, wait for finishing and unload */ { int audiofd = VG3_audio_load(game.wstruct, "gameover.wav", 100, VGAG3_AUDIO_VOLUME_SOUND); if (audiofd > 0) { VG3_audio_play(game.wstruct, audiofd, 0, 0); while (VG3_audio_isplaying(game.wstruct, audiofd)) { VG3_wait_time(100); } } VG3_audio_unload(game.wstruct, audiofd); } /* unload and free still existing object-instances */ VG3_ofunc_objlist_call_free(game.ofstruct, &game, NULL); /* free quadtree */ VG3_coll_q_free(game.qdtr); /* free main-struct for object-functions */ VG3_ofunc_free(game.ofstruct); endgame: /* close window and exit */ VG3_window_free(game.wstruct); exit(0); }
- Step 2
Now we create the VgaGames3-objects.
- Step 2.1
The ball object
- Step 2.1.1
The private structure for the ball object.
We need:
- a pointer to the loaded image of the ball
- a rectangle containing position and size of the ball-image
- two integers for moving the ball in x- and y-direction in 1/100 pixels
- two integers containing the remainders for x- and y-moving
- a variable for the loaded audio file, when the ball hits another object
The four integer variables for moving are designed to use them with the function VG3_move_object_check_collision()
obj-ball.h:#ifndef BALL_H_ #define BALL_H_ /* private structure for ball object */ struct o_ball { struct vg3_image *img; /* loaded image */ struct vg3_rect rect; /* position and size */ int xdelta, ydelta; /* for moving: 1/100 pixels steps for x- and y-direction */ int xremainder, yremainder; /* for moving: remainders for x- and y-moving */ int audio_hit; /* audio descriptor for hit.wav */ }; #endif /* BALL_H_ */
- Step 2.1.2
The ball object
obj-ball.c: - Step 2.1.2.1
At first we define the functions we want to export:
The function getofc_ball() fills the structure struct vg3_ofunc_objfunc with the functions of the ball object.
Then name getofc_ball() is required by the helper program vg3-objfunc-create-cfile which creates the function ofunc_new().#include <stdarg.h> #include <vgagames3.h> #include "main.h" #include "obj-ball.h" static struct vg3_ofunc_object * f_new(void *, unsigned int, ...); static void f_free(void *, struct vg3_ofunc_object *); static void f_run(void *, struct vg3_ofunc_object *); static void f_draw(void *, struct vg3_ofunc_object *); /* fill out the passed structure for object-functions of this VgaGames3-object */ void getofc_ball(struct vg3_ofunc_objfunc *ofc) { if (ofc == NULL) { return; } snprintf(ofc->oid, sizeof(ofc->oid), "%s", OID_BALL); ofc->f_new = f_new; ofc->f_free = f_free; ofc->f_run = f_run; ofc->f_draw = f_draw; /* we don't need f_data */ }
- Step 2.1.2.2
The function f_new() to create a new object-instance of the ball:
- we fill out the private structure of the ball-instance
- - we don't pass information via the variadic parameters
- - we load the image of the ball-object
- - we get the size of the image and set the position of the ball-instance
- - we set a random moving direction of the ball: one of four diagonal directions
- - we load the sound file which is being played when the ball collides with an object
- we fill out the VgaGames3-object structure (object-instance) which is being returned
- - the drawing level is 1 = lowest level, because we use only one level
- - we set the private structure of the ball-instance into the object-instance
- we insert the created object-instance into the list of object-instances
- we insert the created object-instance into the global collision-quadtree
/* f_new: create a new ball object-instance */ static struct vg3_ofunc_object * f_new(void *vgame, unsigned int parent_objid, ...) { struct game *game = (struct game *)vgame; /* individual game structure */ struct vg3_ofunc_object *objp; /* pointer to object-instance */ struct o_ball *gobj; /* pointer to private structure of ball object */ va_list ap; struct vg3_coll coll; /* allocate structures */ objp = calloc(1, sizeof(*objp)); /* create VgaGames3-object structure for this instance */ gobj = calloc(1, sizeof(*gobj)); /* create private structure for this instance */ /* +++ set private structure +++ */ /* with the variadic parameters we don't pass anything */ va_start(ap, parent_objid); va_end(ap); /* load image read-only */ gobj->img = VG3_image_load(game->wstruct, "ball.bmp", 1); if (gobj->img == NULL) { return NULL; } /* get width and height of image and set position */ VG3_image_getsize(game->wstruct, gobj->img, NULL, &gobj->rect.w, &gobj->rect.h); gobj->rect.x = (game->winw - gobj->rect.w) / 2; /* x-position centered */ gobj->rect.y = VG3_nw_get_random(game->winh / 4, game->winh * 3 / 4); /* random y-position */ /* set initial random moving-direction */ if (VG3_nw_get_random(1, 2) == 2) { gobj->xdelta = -100; } else { gobj->xdelta = 100; } if (VG3_nw_get_random(1, 2) == 2) { gobj->ydelta = -100; } else { gobj->ydelta = 100; } gobj->xremainder = gobj->yremainder = 0; /* load hit.wav */ gobj->audio_hit = VG3_audio_load(game->wstruct, "hit.wav", 100, VGAG3_AUDIO_VOLUME_SOUND); if (gobj->audio_hit == 0) { return NULL; } /* +++ set VgaGames3-object structure +++ */ snprintf(objp->oid, sizeof(objp->oid), "%s", OID_BALL); objp->subid = 0; objp->drawlevel = 1; objp->instanceid = 0; /* will be set in VG3_ofunc_objlist_insert() */ objp->ostruct = gobj; /* set private structure into VgaGames3-object structure */ /* +++ insert object-instance into list of object-instances and collision-quadtree +++ */ /* insert instance into list of object-instances */ VG3_ofunc_objlist_insert(game->ofstruct, objp); /* insert into collision-quadtree */ coll.rect = gobj->rect; /* collision rectangle equals to image rectangle */ snprintf(coll.oid, sizeof(coll.oid), "%s", OID_BALL); coll.optr = objp; VG3_coll_q_insert(game->qdtr, &coll); /* return object-instance */ return objp; }
- Step 2.1.2.3
The function f_free() to destroy an object-instance of the ball:
- we remove the object-instance from the global collision-quadtree
- we remove the object-instance from the list of object-instances
- we free the private structure of the ball-instance
- we free the VgaGames3-object structure (object-instance)
/* f_free: destroy a ball object-instance */ static void f_free(void *vgame, struct vg3_ofunc_object *objp) { struct game *game = (struct game *)vgame; /* individual game structure */ struct o_ball *gobj; /* pointer to private structure of ball object */ /* get private structure */ gobj = (struct o_ball *)objp->ostruct; /* remove object-instance from collision-quadtree */ VG3_coll_q_remove(game->qdtr, objp); /* remove object-instance from list of object-instances */ VG3_ofunc_objlist_remove(game->ofstruct, objp); /* free object-instance */ VG3_image_unload(game->wstruct, gobj->img); VG3_audio_unload(game->wstruct, gobj->audio_hit); free(gobj); free(objp); }
- Step 2.1.2.4
The function f_run() to move an object-instance of the ball and check for collisions:
- we remove the object-instance from the global collision-quadtree
- we move the object-instance according to xdelta and ydelta of the private structure
- - using the function VG3_move_object_check_collision() we move and check for collisions
- - we check its return value and do actions according to it
- if the player missed the ball (ball is out of the window) we set endgame to end the game
- we insert the created object-instance into the global collision-quadtree again
/* f_run: move a ball object-instance */ static void f_run(void *vgame, struct vg3_ofunc_object *objp) { const int moving_factor = 30; /* factor for moving: 3 pixels */ struct game *game = (struct game *)vgame; /* individual game structure */ struct o_ball *gobj; /* pointer to private structure of ball object */ int erg; struct vg3_coll coll; /* get private structure */ gobj = (struct o_ball *)objp->ostruct; /* remove ball object-instance from collision-quadtree */ VG3_coll_q_remove(game->qdtr, objp); /* +++ move ball according to moving direction and check for collisions +++ */ erg = VG3_move_object_check_collision(game, game->ofstruct, game->qdtr, objp, &gobj->rect, moving_factor, &gobj->xdelta, &gobj->ydelta, &gobj->xremainder, &gobj->yremainder); if (erg < 0) { /* error: destroy ball object-instance */ f_free(game, objp); return; } if (erg == VGAG3_COLL_RETURN_DEAD) { return; } if (erg == VGAG3_COLL_RETURN_CATCHED) { /* unmovable: destroy player object-instance */ f_free(game, objp); return; } /* we don't need to check the other return values */ /* +++ check for leaving the window => end game +++ */ if (gobj->rect.x + gobj->rect.w - 1 <= 0) { /* player missed ball: end game */ game->endgame = 1; return; } /* +++ insert into collision-quadtree again +++ */ coll.rect = gobj->rect; /* collision rectangle equals to image rectangle */ snprintf(coll.oid, sizeof(coll.oid), "%s", OID_BALL); coll.optr = objp; VG3_coll_q_insert(game->qdtr, &coll); }
- Step 2.1.2.5
The function f_draw() to draw an object-instance of the ball:
- we draw the object-instance
/* f_draw: draw a ball object-instance */ static void f_draw(void *vgame, struct vg3_ofunc_object *objp) { struct game *game = (struct game *)vgame; /* individual game structure */ struct o_ball *gobj; /* pointer to private structure of ball object */ /* get private structure */ gobj = (struct o_ball *)objp->ostruct; /* draw ball object-instance */ VG3_image_copy(game->wstruct, NULL, gobj->img, gobj->rect.x + gobj->rect.w / 2, gobj->rect.y + gobj->rect.h / 2, NULL, 0); }
- Step 2.2
The border object
- Step 2.2.1
The private structure for the border object
We need:
- a pointer to the loaded image of the border
- a rectangle containing position and size of the border-image
obj-border.h:#ifndef BORDER_H_ #define BORDER_H_ /* private structure for border object */ struct o_border { struct vg3_image *img; /* loaded image */ struct vg3_rect rect; /* position and size */ }; #endif /* BORDER_H_ */
- Step 2.2.2
The border object.
We declare the functions f_new(), f_free() and f_draw(), but need no f_run(), because borders don't move.
For the function f_new() we pass via the variadic parameters an integer, which just defines the position of the border:
- value 1: upper horizontal border
- value 2: lower horizontal border
- value 3: right vertical border
The rest is similar to the ball-object obj-ball.c
obj-border.c:/* border object */ #include <stdarg.h> #include <vgagames3.h> #include "main.h" #include "obj-border.h" static struct vg3_ofunc_object * f_new(void *, unsigned int, ...); static void f_free(void *, struct vg3_ofunc_object *); static void f_draw(void *, struct vg3_ofunc_object *); /* fill out the passed structure for object-functions of this VgaGames3-object */ void getofc_border(struct vg3_ofunc_objfunc *ofc) { if (ofc == NULL) { return; } snprintf(ofc->oid, sizeof(ofc->oid), "%s", OID_BORDER); ofc->f_new = f_new; ofc->f_free = f_free; ofc->f_draw = f_draw; /* we don't need f_run and f_data */ } /* object functions */ /* f_new: create a new border object-instance */ static struct vg3_ofunc_object * f_new(void *vgame, unsigned int parent_objid, ...) { struct game *game = (struct game *)vgame; /* individual game structure */ struct vg3_ofunc_object *objp; /* pointer to object-instance */ struct o_border *gobj; /* pointer to private structure of border object */ va_list ap; int bordertype; struct vg3_coll coll; /* allocate structures */ objp = calloc(1, sizeof(*objp)); /* create VgaGames3-object structure for this instance */ gobj = calloc(1, sizeof(*gobj)); /* create private structure for this instance */ /* +++ set private structure +++ */ /* with the variadic parameters we pass which border: * value 1: upper horizontal border * value 2: lower horizontal border * value 3: right vertical border */ va_start(ap, parent_objid); bordertype = va_arg(ap, int); va_end(ap); /* load image read-only */ if (bordertype == 1 || bordertype == 2) { /* horizontal border */ gobj->img = VG3_image_load(game->wstruct, "hborder.bmp", 1); } else { /* vertical border */ gobj->img = VG3_image_load(game->wstruct, "vborder.bmp", 1); } if (gobj->img == NULL) { return NULL; } /* get width and height of image and set position */ VG3_image_getsize(game->wstruct, gobj->img, NULL, &gobj->rect.w, &gobj->rect.h); if (bordertype == 1) { /* horizontal upper border */ gobj->rect.x = gobj->rect.y = 0; } else if (bordertype == 2) { /* horizontal lower border */ gobj->rect.x = 0; gobj->rect.y = game->winh - gobj->rect.h; } else { /* vertical border */ gobj->rect.x = game->winw - gobj->rect.w; gobj->rect.y = 0; } /* +++ set VgaGames3-object structure +++ */ snprintf(objp->oid, sizeof(objp->oid), "%s", OID_BORDER); objp->subid = 0; objp->drawlevel = 1; objp->instanceid = 0; /* will be set in VG3_ofunc_objlist_insert() */ objp->ostruct = gobj; /* set private structure into VgaGames3-object structure */ /* +++ insert object-instance into list of object-instances and collision-quadtree +++ */ /* insert instance into list of object-instances */ VG3_ofunc_objlist_insert(game->ofstruct, objp); /* insert into collision-quadtree */ coll.rect = gobj->rect; /* collision rectangle equals to image rectangle */ snprintf(coll.oid, sizeof(coll.oid), "%s", OID_BORDER); coll.optr = objp; VG3_coll_q_insert(game->qdtr, &coll); /* return object-instance */ return objp; } /* f_free: destroy a border object-instance */ static void f_free(void *vgame, struct vg3_ofunc_object *objp) { struct game *game = (struct game *)vgame; /* individual game structure */ struct o_border *gobj; /* pointer to private structure of border object */ /* get private structure */ gobj = (struct o_border *)objp->ostruct; /* remove object-instance from collision-quadtree */ VG3_coll_q_remove(game->qdtr, objp); /* remove object-instance from list of object-instances */ VG3_ofunc_objlist_remove(game->ofstruct, objp); /* free object-instance */ VG3_image_unload(game->wstruct, gobj->img); free(gobj); free(objp); } /* f_draw: draw a border object-instance */ static void f_draw(void *vgame, struct vg3_ofunc_object *objp) { struct game *game = (struct game *)vgame; /* individual game structure */ struct o_border *gobj; /* pointer to private structure of border object */ /* get private structure */ gobj = (struct o_border *)objp->ostruct; /* draw border object-instance */ VG3_image_copy(game->wstruct, NULL, gobj->img, gobj->rect.x + gobj->rect.w / 2, gobj->rect.y + gobj->rect.h / 2, NULL, 0); }
- Step 2.3
The player object - the racket on the left
- Step 2.3.1
The private structure for the player object.
We need:
- a pointer to the loaded image of the racket
- a rectangle containing position and size of the racket-image
- two integers for moving the racket in x- and y-direction in 1/100 pixels (but we'll just move in y-direction)
- two integers containing the remainders for x- and y-moving
The four integer variables for moving are designed to use them with the function VG3_move_object_check_collision()
obj-player.h:#ifndef PLAYER_H_ #define PLAYER_H_ /* private structure for player object */ struct o_player { struct vg3_image *img; /* loaded image */ struct vg3_rect rect; /* position and size */ int xdelta, ydelta; /* for moving: 1/100 pixels steps for x- and y-direction */ int xremainder, yremainder; /* for moving: remainders for x- and y-moving */ }; #endif /* PLAYER_H_ */
- Step 2.3.2
The player object.
We declare the functions f_new(), f_free(), f_run() and f_draw().
In the function f_run() we move the racket according to key-strokes cursor-up and cursor-down.
The rest is similar to the other objects.
obj-player.c:/* player object */ #include <stdarg.h> #include <vgagames3.h> #include "main.h" #include "obj-player.h" static struct vg3_ofunc_object * f_new(void *, unsigned int, ...); static void f_free(void *, struct vg3_ofunc_object *); static void f_run(void *, struct vg3_ofunc_object *); static void f_draw(void *, struct vg3_ofunc_object *); /* fill out the passed structure for object-functions of this VgaGames3-object */ void getofc_player(struct vg3_ofunc_objfunc *ofc) { if (ofc == NULL) { return; } snprintf(ofc->oid, sizeof(ofc->oid), "%s", OID_PLAYER); ofc->f_new = f_new; ofc->f_free = f_free; ofc->f_run = f_run; ofc->f_draw = f_draw; /* we don't need f_data */ } /* object functions */ /* f_new: create a new player object-instance */ static struct vg3_ofunc_object * f_new(void *vgame, unsigned int parent_objid, ...) { struct game *game = (struct game *)vgame; /* individual game structure */ struct vg3_ofunc_object *objp; /* pointer to object-instance */ struct o_player *gobj; /* pointer to private structure of player object */ va_list ap; struct vg3_coll coll; /* allocate structures */ objp = calloc(1, sizeof(*objp)); /* create VgaGames3-object structure for this instance */ gobj = calloc(1, sizeof(*gobj)); /* create private structure for this instance */ /* +++ set private structure +++ */ /* with the variadic parameters we don't pass anything */ va_start(ap, parent_objid); va_end(ap); /* load image read-only */ gobj->img = VG3_image_load(game->wstruct, "player.bmp", 1); if (gobj->img == NULL) { return NULL; } /* get width and height of image and set position centered at the left */ VG3_image_getsize(game->wstruct, gobj->img, NULL, &gobj->rect.w, &gobj->rect.h); gobj->rect.x = 0; gobj->rect.y = (game->winh - gobj->rect.h) / 2; /* initalize moving direction */ gobj->xdelta = gobj->ydelta = 0; gobj->xremainder = gobj->yremainder = 0; /* +++ set VgaGames3-object structure +++ */ snprintf(objp->oid, sizeof(objp->oid), "%s", OID_PLAYER); objp->subid = 0; objp->drawlevel = 1; objp->instanceid = 0; /* will be set in VG3_ofunc_objlist_insert() */ objp->ostruct = gobj; /* set private structure into VgaGames3-object structure */ /* +++ insert object-instance into list of object-instances and collision-quadtree +++ */ /* insert instance into list of object-instances */ VG3_ofunc_objlist_insert(game->ofstruct, objp); /* insert into collision-quadtree */ coll.rect = gobj->rect; /* collision rectangle equals to image rectangle */ snprintf(coll.oid, sizeof(coll.oid), "%s", OID_PLAYER); coll.optr = objp; VG3_coll_q_insert(game->qdtr, &coll); /* return object-instance */ return objp; } /* f_free: destroy a player object-instance */ static void f_free(void *vgame, struct vg3_ofunc_object *objp) { struct game *game = (struct game *)vgame; /* individual game structure */ struct o_player *gobj; /* pointer to private structure of player object */ /* get private structure */ gobj = (struct o_player *)objp->ostruct; /* remove object-instance from collision-quadtree */ VG3_coll_q_remove(game->qdtr, objp); /* remove object-instance from list of object-instances */ VG3_ofunc_objlist_remove(game->ofstruct, objp); /* free object-instance */ VG3_image_unload(game->wstruct, gobj->img); free(gobj); free(objp); } /* f_run: move a player object-instance */ static void f_run(void *vgame, struct vg3_ofunc_object *objp) { const int moving_factor = 30; /* factor for moving: 3 pixels */ struct game *game = (struct game *)vgame; /* individual game structure */ struct o_player *gobj; /* pointer to private structure of player object */ int erg; struct vg3_coll coll; /* get private structure */ gobj = (struct o_player *)objp->ostruct; /* remove player object-instance from collision-quadtree */ VG3_coll_q_remove(game->qdtr, objp); /* +++ check for key-strokes and set moving direction +++ */ gobj->ydelta = 0; if (VG3_key_ispressed(game->wstruct, VGAG3_KEY_UCURS, VGAG3_IS_PRESSED)) { gobj->ydelta = -100; /* set moving upwards: full y-direction, no x-direction */ } if (VG3_key_ispressed(game->wstruct, VGAG3_KEY_DCURS, VGAG3_IS_PRESSED)) { gobj->ydelta = 100; /* set moving downwards: full y-direction, no x-direction */ } /* +++ move player according to moving direction and check for collisions +++ */ erg = VG3_move_object_check_collision(game, game->ofstruct, game->qdtr, objp, &gobj->rect, moving_factor, &gobj->xdelta, &gobj->ydelta, &gobj->xremainder, &gobj->yremainder); if (erg < 0) { /* error: destroy player object-instance */ f_free(game, objp); return; } if (erg == VGAG3_COLL_RETURN_CATCHED) { /* unmovable: end game */ game->endgame = 1; return; } /* we don't need to check the other return values */ /* +++ insert into collision-quadtree again +++ */ coll.rect = gobj->rect; /* collision rectangle equals to image rectangle */ snprintf(coll.oid, sizeof(coll.oid), "%s", OID_PLAYER); coll.optr = objp; VG3_coll_q_insert(game->qdtr, &coll); } /* f_draw: draw a player object-instance */ static void f_draw(void *vgame, struct vg3_ofunc_object *objp) { struct game *game = (struct game *)vgame; /* individual game structure */ struct o_player *gobj; /* pointer to private structure of player object */ /* get private structure */ gobj = (struct o_player *)objp->ostruct; /* draw player object-instance */ VG3_image_copy(game->wstruct, NULL, gobj->img, gobj->rect.x + gobj->rect.w / 2, gobj->rect.y + gobj->rect.h / 2, NULL, 0); }
- Step 3
The objects are created, now we still need the interaction between the objects.
There are three interactions:
- between ball and border: push back the ball
- between ball and player: push back the ball
- between player and border: stop moving player
So we need three files for object-to-object-functions for two VgaGames3-objects. - Step 3.1
Interaction between ball and border.
When the ball touches a border, it will be pushed back.
If the ball touches the upper or lower horizontal border, the y-direction of the ball will be inverted.
If the ball touches the vertical border at the right, the x-direction of the ball will be inverted.
This will be done in the function f_collision().
We don't need a function f_quit().
We include besides "main.h" the H-files of both VgaGames3-objects.
At first we define the functions we want to export:
The function getoofc_ball_border() fills the structure struct vg3_ofunc_objobjfunc with the functions of interaction between ball and border objects.
Then name getoofc_ball_border() is required by the helper program vg3-objfunc-create-cfile which creates the function ofunc_new().
objobj-ball-border.c:/* collision between ball and border */ #include <vgagames3.h> #include "main.h" #include "obj-ball.h" #include "obj-border.h" static int f_collision(void *, struct vg3_ofunc_object *, struct vg3_ofunc_object *, struct vg3_coll_ret *); /* fill out the passed structure for object-to-object-functions of these two VgaGames3-objects */ void getoofc_ball_border(struct vg3_ofunc_objobjfunc *oofc) { snprintf(oofc->oid1, sizeof(oofc->oid1), "%s", OID_BALL); snprintf(oofc->oid2, sizeof(oofc->oid2), "%s", OID_BORDER); oofc->f_collision = f_collision; } /* f_collision: collision-function */ static int f_collision(void *vgame, struct vg3_ofunc_object *objp1, struct vg3_ofunc_object *objp2, struct vg3_coll_ret *collret) { struct game *game = (struct game *)vgame; /* individual game structure */ struct o_ball *gobj_ball; /* pointer to private structure of ball object */ struct o_border *gobj_border; /* pointer to private structure of border object */ /* objp1 is moving and collides with objp2, * but it is not sure which of both is the ball and the border, * so we have to check the object-ID to get the private structures of both. * (Here we could assume the ball being objp1, because the border isn't moving). */ if (strcmp(objp1->oid, OID_BALL) == 0) { /* objp1 = ball and objp2 = border */ gobj_ball = (struct o_ball *)objp1->ostruct; gobj_border = (struct o_border *)objp2->ostruct; } else { /* objp1 = border and objp2 = ball */ gobj_border = (struct o_border *)objp1->ostruct; gobj_ball = (struct o_ball *)objp2->ostruct; } (void)gobj_border; /* the ball hits the border (as the border isn't moving): * according to the hit-side invert the x- or y-direction of the ball. */ if (collret->side & (VGAG3_COLLSIDE_LEFT | VGAG3_COLLSIDE_RIGHT)) { gobj_ball->xdelta = -gobj_ball->xdelta; } if (collret->side & (VGAG3_COLLSIDE_TOP | VGAG3_COLLSIDE_BOTTOM)) { gobj_ball->ydelta = -gobj_ball->ydelta; } /* play hit.wav */ VG3_audio_play(game->wstruct, gobj_ball->audio_hit, 0, 0); /* continue moving (with changed direction) */ return VGAG3_COLL_RETURN_CONTINUE; }
- Step 3.2
Interaction between ball and player.
When the ball touches the racket, it will be pushed back just as by the border.
If the ball touches the right side of the racket, the x-direction of the ball will be inverted.
If the ball touches the upper or lower side of the racket, the y-direction of the ball will be inverted.
This will be done in the function f_collision().
We don't need a function f_quit().
We include besides "main.h" the H-files of both VgaGames3-objects.
At first we define the functions we want to export:
The function getoofc_player_ball() fills the structure struct vg3_ofunc_objobjfunc with the functions of interaction between ball and player objects.
Then name getoofc_player_ball() is required by the helper program vg3-objfunc-create-cfile which creates the function ofunc_new().
objobj-player-ball.c:/* collision between player and ball */ #include <vgagames3.h> #include "main.h" #include "obj-player.h" #include "obj-ball.h" static int f_collision(void *, struct vg3_ofunc_object *, struct vg3_ofunc_object *, struct vg3_coll_ret *); /* fill out the passed structure for object-to-object-functions of these two VgaGames3-objects */ void getoofc_player_ball(struct vg3_ofunc_objobjfunc *oofc) { snprintf(oofc->oid1, sizeof(oofc->oid1), "%s", OID_PLAYER); snprintf(oofc->oid2, sizeof(oofc->oid2), "%s", OID_BALL); oofc->f_collision = f_collision; } /* f_collision: collision-function */ static int f_collision(void *vgame, struct vg3_ofunc_object *objp1, struct vg3_ofunc_object *objp2, struct vg3_coll_ret *collret) { struct game *game = (struct game *)vgame; /* individual game structure */ struct o_player *gobj_player; /* pointer to private structure of player object */ struct o_ball *gobj_ball; /* pointer to private structure of ball object */ /* objp1 is moving and collides with objp2, * but it is not sure which of both is the player and the ball, * so we have to check the object-ID to get the private structures of both. * This is required because both objects can move. */ if (strcmp(objp1->oid, OID_PLAYER) == 0) { /* objp1 = player and objp2 = ball */ gobj_player = (struct o_player *)objp1->ostruct; gobj_ball = (struct o_ball *)objp2->ostruct; } else { /* objp1 = ball and objp2 = player */ gobj_ball = (struct o_ball *)objp1->ostruct; gobj_player = (struct o_player *)objp2->ostruct; } (void)gobj_player; /* the player hits the ball or the ball hits the player: * according to the hit-side invert the x- or y-direction of the ball. * We distinguish between player hitting ball and ball hitting player * to demonstrate the difference, despite the code has no difference here. */ if (strcmp(objp1->oid, OID_PLAYER) == 0) { /* objp1 = player and objp2 = ball */ /* the collret->side is the side of the destination, i.e. the ball */ if (collret->side & (VGAG3_COLLSIDE_LEFT | VGAG3_COLLSIDE_RIGHT)) { gobj_ball->xdelta = -gobj_ball->xdelta; } if (collret->side & (VGAG3_COLLSIDE_TOP | VGAG3_COLLSIDE_BOTTOM)) { gobj_ball->ydelta = -gobj_ball->ydelta; } } else { /* objp1 = ball and objp2 = player */ /* the collret->side is the side of the destination, i.e. the player */ if (collret->side & (VGAG3_COLLSIDE_LEFT | VGAG3_COLLSIDE_RIGHT)) { gobj_ball->xdelta = -gobj_ball->xdelta; } if (collret->side & (VGAG3_COLLSIDE_TOP | VGAG3_COLLSIDE_BOTTOM)) { gobj_ball->ydelta = -gobj_ball->ydelta; } } /* play hit.wav */ VG3_audio_play(game->wstruct, gobj_ball->audio_hit, 0, 0); /* continue moving (with changed direction) */ return VGAG3_COLL_RETURN_CONTINUE; }
- Step 3.3
Interaction between player and border.
When the racket touches the upper or lower horizontal border, the racket is being stopped.
This will be done in the function f_collision().
We don't need a function f_quit().
We include besides "main.h" the H-files of both VgaGames3-objects.
At first we define the functions we want to export:
The function getoofc_player_border() fills the structure struct vg3_ofunc_objobjfunc with the functions of interaction between player and border objects.
Then name getoofc_player_border() is required by the helper program vg3-objfunc-create-cfile which creates the function ofunc_new().
objobj-player-border.c:/* collision between player and border */ #include <vgagames3.h> #include "main.h" #include "obj-player.h" #include "obj-border.h" static int f_collision(void *, struct vg3_ofunc_object *, struct vg3_ofunc_object *, struct vg3_coll_ret *); /* fill out the passed structure for object-to-object-functions of these two VgaGames3-objects */ void getoofc_player_border(struct vg3_ofunc_objobjfunc *oofc) { snprintf(oofc->oid1, sizeof(oofc->oid1), "%s", OID_PLAYER); snprintf(oofc->oid2, sizeof(oofc->oid2), "%s", OID_BORDER); oofc->f_collision = f_collision; } /* f_collision: collision-function */ static int f_collision(void *vgame, struct vg3_ofunc_object *objp1, struct vg3_ofunc_object *objp2, struct vg3_coll_ret *collret) { if (vgame == NULL || objp1 == NULL || objp2 == NULL || collret == NULL) { return VGAG3_COLL_RETURN_NOOP; } /* the moving object (objp1) must be the player, because the border isn't moving, * so stop the player */ return VGAG3_COLL_RETURN_FULLSTOP; }
- Putting together
See the files in example-games/tutorial4/ - Running
Running the game
<<Prev | Top | Next>> |