VgaGames4 - tutorial

Tutorial 4 

screenshot.gif

In the last tutorial-part we added the ball and checked for collisions by iterating over all objects: checking ball with each border and with player-racket. Now we want to use the accelerated checking for collisions via a quadtree, which means that only adjacent objects are checked. To do this, the borders are now created as objects just as player-racket and ball. For our little game this method is certainly oversized, but we want to show the functionality.


At first an overview about modifications in the file pingpong.c.

pingpong.c: Overview about modifications

/* global declarations */
[CODEBOX]

/* show help-text */
[...]

/* check for collisions */
[CODEBOX]

/* main function */
int main(int argc, char **argv) {
  /* variable declaration */
  [CODEBOX]

  /* initializing */
  [...]      /* opening */
  [...]      /* set keys */
  [CODEBOX]  /* create collision tag */
  [CODEBOX]  /* initialize borders */
  [CODEBOX]  /* initialize player-racket */
  [CODEBOX]  /* initialize ball */
  [...]      /* show help, start background music */

  /* game loop */
  [...]      /* receive input-events and check the local key-strokes */
  [CODEBOX]  /* check key-strokes for player-racket */
  [CODEBOX]  /* move ball */
  [CODEBOX]  /* draw out */
  [...]      /* flush and wait */

  /* end game */
  [...]      /* destroy and exit */
}

At the beginning of the file pingpong.c we add declarations for static unique instance-IDs (needed by the collision-functions) and the collision-tag.

[To Position]
pingpong.c: global declarations

/* keys */
struct {
  int k_quit_lalt;  /* part of quit: Left-ALT */
  int k_quit_q;     /* part of quit: Q */
  int k_pause;      /* pause */
  int k_up;         /* move up */
  int k_down;       /* move down */
} kref;

/* instance-IDs for each object-instance */
enum { INSTANCE_PLAYER = 1, INSTANCE_BALL, INSTANCE_BORDER_TOP, INSTANCE_BORDER_BOTTOM, INSTANCE_BORDER_RIGHT };

/* collision-tag */
unsigned int coll_tag;

An overview about the used variables in function main(). We add the border structure.

[To Position]
pingpong.c: the variables in function main()

const int border_size = 10;  /* size of borders in pixels */

int winw, winh;  /* window's width and height */
int audc_hit, audc_gameover, audc_bgmusic;  /* audio descriptors */

struct {  /* player's structure */
  struct VG_Image *imgp;  /* image */
  struct VG_Rect rect;    /* rectangle position */
} player;

struct {  /* ball's structure */
  struct VG_Image *imgp;     /* image */
  struct VG_RectCent rectc;  /* rectangle position */
  int angle;                 /* moving direction angle */
  int factor;                /* moving velocity in 1/1000 pixels */
} ball;

struct {  /* border's structure */
  struct VG_Image *imgp;  /* image */
  struct VG_Rect rect;    /* rectangle position */
} border[3];

/* helper variables */
struct VG_RectCent *rectcp;
int rectc_nr, ipos;
struct VG_Position posi;

To use accelerated collisions we need a collision-tag, which is created here.

[To Position]
pingpong.c: function main()

/* create collision-tag */
coll_tag = vg4->collision->create(NULL, 0, 0);

We initialize the three borders, using the structure-array border. The border-images are created and their rectangles are set according to their position. Then we insert them into the collision-tag.

[To Position]
pingpong.c: function main()

/* initialize borders */

/* top border */
border[0].rect.x = border[0].rect.y = 0; border[0].rect.w = winw; border[0].rect.h = border_size;
border[0].imgp = vg4->image->create(border[0].rect.w, border[0].rect.h);
vg4->image->fill(border[0].imgp, VG_COLOR_RGB(0xb1, 0, 0));
vg4->collision->insert(coll_tag, INSTANCE_BORDER_TOP, &border[0].rect, 100);  /* insert into collision-tag */

/* bottom border */
border[1].rect.x = 0; border[1].rect.y = winh - border_size; border[1].rect.w = winw; border[1].rect.h = border_size;
border[1].imgp = vg4->image->create(border[1].rect.w, border[1].rect.h);
vg4->image->fill(border[1].imgp, VG_COLOR_RGB(0xb1, 0, 0));
vg4->collision->insert(coll_tag, INSTANCE_BORDER_BOTTOM, &border[1].rect, 100);  /* insert into collision-tag */

/* right border */
border[2].rect.x = winw - border_size; border[2].rect.y = 0; border[2].rect.w = border_size; border[2].rect.h = winh;
border[2].imgp = vg4->image->create(border[2].rect.w, border[2].rect.h);
vg4->image->fill(border[2].imgp, VG_COLOR_RGB(0xb1, 0, 0));
vg4->collision->insert(coll_tag, INSTANCE_BORDER_RIGHT, &border[2].rect, 100);  /* insert into collision-tag */

We initialize the player-racket. Then we insert it into the collision-tag.

[To Position]
pingpong.c: function main()

/* initialize player-racket, put it at the left */

/* load image */
player.imgp = vg4->image->load("files/player.bmp");
if (player.imgp == NULL) { VG_dest(); exit(1); }

/* set rectangle */
vg4->image->getsize(player.imgp, NULL, &player.rect.w, &player.rect.h);
player.rect.x = 0;
player.rect.y = (winh - player.rect.h) / 2;

/* insert player-racket with 90% of size into collision-tag */
vg4->collision->insert(coll_tag, INSTANCE_PLAYER, &player.rect, 90);

We initialize the ball. Then we insert it into the collision-tag.

[To Position]
pingpong.c: function main()

/* initialize ball, put it at the center of the window */

/* load image */
ball.imgp = vg4->image->load("files/ball.bmp");
if (ball.imgp == NULL) { VG_dest(); exit(1); }

/* set rectangle */
vg4->image->getsize(ball.imgp, NULL, &ball.rectc.rect.w, &ball.rectc.rect.h);
ball.rectc.rect.x = (winw - ball.rectc.rect.w) / 2;
ball.rectc.rect.y = (winh - ball.rectc.rect.h) / 2;
ball.rectc.centx = ball.rectc.centy = 0;

/* set moving angle and factor */
ball.angle = 45 + 90 * vg4->random->get("ball-angle", 0, 3);  /* randomized moving direction */
ball.factor = 3500;

/* insert ball with 90% of size into collision-tag */
vg4->collision->insert(coll_tag, INSTANCE_BALL, &ball.rectc.rect, 90);

Now we are in the game-loop. When moving the player-racket we also check for collisions. The check for touching a border is removed.

[To Position]
pingpong.c: in the game-loop of the function main()

  /* +++ player: check for key-strokes +++ */

  /* moving up */
  if (vg4->input->key_pressed(kref.k_up)) {
    int y = player.rect.y;
    player.rect.y -= 2;
    /* check for collision with ball */
    if (check_for_collisions(INSTANCE_PLAYER, &player.rect, &ball.angle)) {
      /* set back to previous position */
      player.rect.y = y;
      vg4->collision->setpos(coll_tag, INSTANCE_PLAYER, &player.rect, NULL);
      vg4->audio->play(audc_hit, VG_FALSE, VG_FALSE);
    }
  }

  /* moving down */
  if (vg4->input->key_pressed(kref.k_down)) {
    int y = player.rect.y;
    player.rect.y += 2;
    /* check for collision with ball */
    if (check_for_collisions(INSTANCE_PLAYER, &player.rect, &ball.angle)) {
      /* set back to previous position */
      player.rect.y = y;
      vg4->collision->setpos(coll_tag, INSTANCE_PLAYER, &player.rect, NULL);
      vg4->audio->play(audc_hit, VG_FALSE, VG_FALSE);
    }
  }

We also have to move the ball. We remove the manually checking for collisions with borders and call check_for_collisions() for checking collisions with all objects.

[To Position]
pingpong.c: in the game-loop of the function main()

  /* +++ move ball +++ */

  /* increase ball-factor for 1/1000 pixel */
  ball.factor++;

  /* get moving steps */
  rectc_nr = vg4->misc->moving_one_step(&ball.rectc, ball.angle, ball.factor / 100, &rectcp);

  /* iterate moving steps and check for collisions */
  for (ipos = 0; ipos < rectc_nr; ipos++) {
    /* player missed the ball? */
    if (rectcp[ipos].rect.x + ball.rectc.rect.w <= 0) {
      /* play audio and wait for its end */
      vg4->audio->stop(audc_bgmusic, VG_FALSE);
      vg4->audio->play(audc_gameover, VG_FALSE, VG_FALSE);
      while (vg4->audio->is_playing(audc_gameover, NULL)) {
        vg4->window->flush();
        vg4->misc->wait_time(40);
      }
      goto endgame;
    }

    /* check for collisions */
    if (check_for_collisions(INSTANCE_BALL, &rectcp[ipos].rect, &ball.angle)) {
      /* set back to previous position */
      vg4->collision->setpos(coll_tag, INSTANCE_BALL, &ball.rectc.rect, NULL);
      vg4->audio->play(audc_hit, VG_FALSE, VG_FALSE);
      break;
    }

    /* actualize rectangle position */
    ball.rectc = rectcp[ipos];
  }

We have all moving done, now we draw out all to the window. As borders are now objects we replace the vg4->window->draw_rect() with vg4->window->copy().

[To Position]
pingpong.c: in the game-loop of the function main()

  /* +++ draw out +++ */

  /* clear window */
  vg4->window->clear();

  /* draw background and borders */

  vg4->window->fill(vg4->misc->colorbrightness(VG_COLOR_BLUE, 50));

  /* top border */
  vg4->window->copy(border[0].imgp, vg4->misc->rect2position(&posi, &border[0].rect), NULL);

  /* bottom border */
  vg4->window->copy(border[1].imgp, vg4->misc->rect2position(&posi, &border[1].rect), NULL);

  /* right border */
  vg4->window->copy(border[2].imgp, vg4->misc->rect2position(&posi, &border[2].rect), NULL);

  /* draw player */
  vg4->window->copy(player.imgp, vg4->misc->rect2position(&posi, &player.rect), NULL);

  /* draw ball */
  vg4->window->copy(ball.imgp, vg4->misc->rect2position(&posi, &ball.rectc.rect), NULL);

At last we replace the function check_for_collision() with another function check_for_collisions(), which checks the moving object (ball or player-racket) for collisions with any object, which is contained in the collision-tag. The angle of the ball will be changed when the ball collides.

[To Position]
pingpong.c: function check_for_collisions()

/* check for collisions
 * @param instanceid   instance-ID of moving object
 * @param moving_rect  actual rectangle of moving object
 * @param ball_angle   angle of the ball, will be updated
 * @return  VG_TRUE = collision or VG_FALSE = no collision
 */
static VG_BOOL
check_for_collisions(unsigned int instanceid, const struct VG_Rect *moving_rect, int *ball_angle)
{
  struct VG_Coll *collp;
  int ipos, canz;
  VG_BOOL retw;

  if (instanceid == 0 || moving_rect == NULL || ball_angle == NULL) { return VG_FALSE; }

  retw = VG_FALSE;

  /* get collisions */
  canz = vg4->collision->setpos(coll_tag, instanceid, moving_rect, &collp);

  /* iterate collisions */
  for (ipos = 0; ipos < canz; ipos++) {
    if (collp[ipos].type == VG_COLL_TYPE_ENTRY) {  /* entering into collision */

      /* act according the the moving object */

      if (instanceid == INSTANCE_PLAYER) {  /* player-racket moves */
        if (collp[ipos].instanceid == INSTANCE_BALL) {  /* player collides with ball */
          /* check collision sides of ball */
          if (collp[ipos].side & VG_COLL_SIDE_LEFT) {
            *ball_angle = 360 - (*ball_angle);
            *ball_angle %= 360; *ball_angle += 360; *ball_angle %= 360;
            retw = VG_TRUE;
          }
          if (collp[ipos].side & (VG_COLL_SIDE_TOP | VG_COLL_SIDE_BOTTOM)) {
            *ball_angle = 180 - (*ball_angle);
            *ball_angle %= 360; *ball_angle += 360; *ball_angle %= 360;
            retw = VG_TRUE;
          }
        } else if (collp[ipos].instanceid == INSTANCE_BORDER_TOP
                   || collp[ipos].instanceid == INSTANCE_BORDER_BOTTOM) {  /* player collides with border */
          retw = VG_TRUE;
        }

      } else if (instanceid == INSTANCE_BALL) {  /* ball moves */
        if (collp[ipos].instanceid == INSTANCE_PLAYER) {  /* ball collides with player */
          /* check collision sides of player */
          if (collp[ipos].side & VG_COLL_SIDE_RIGHT) {
            *ball_angle = 360 - (*ball_angle);
            *ball_angle %= 360; *ball_angle += 360; *ball_angle %= 360;
            retw = VG_TRUE;
          }
          if (collp[ipos].side & (VG_COLL_SIDE_TOP | VG_COLL_SIDE_BOTTOM)) {
            *ball_angle = 180 - (*ball_angle);
            *ball_angle %= 360; *ball_angle += 360; *ball_angle %= 360;
            retw = VG_TRUE;
          }
        } else if (collp[ipos].instanceid == INSTANCE_BORDER_TOP
                   || collp[ipos].instanceid == INSTANCE_BORDER_BOTTOM) {  /* ball collides with top- or bottom-border */
          *ball_angle = 180 - (*ball_angle);
          *ball_angle %= 360; *ball_angle += 360; *ball_angle %= 360;
          retw = VG_TRUE;
        } else if (collp[ipos].instanceid == INSTANCE_BORDER_RIGHT) {  /* ball collides with right border */
          *ball_angle = 360 - (*ball_angle);
          *ball_angle %= 360; *ball_angle += 360; *ball_angle %= 360;
          retw = VG_TRUE;
        }
      }
    }
  }

  /* free collision struct */
  if (collp != NULL) { free(collp); }

  return retw;
}



<<Previous Download Next>>