VgaGames4

VgaGames4 - tutorial

# Tutorial 4

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>>