Help with game development


(Simon) #41

Get yourself a GitHub account ! It will make sharing your code so much easier.

You will need to put #pragma once at the top of each .h file to prevent the compiler linking the code numerous times.


(Simon) #42

There appear to be many errors in the code you posted:

 contact(); 
 playerinput();
 drawball(); 
 drawplayer();
 scoreGoal();

^ none of these functions exist.

With regards to the original error:

In file included from sketch/player.h:8:0, from /Users/davidbauer/Documents/Arduino/Soccer_Game/Soccer_Game.ino:15: sketch/opponent.h: In function 'void oppAttack()': opponent.h:50: error: 'ballRect' was not declared in this scope if (arduboy.collide(ballRect, oppRect)) { ^ exit status 1 'ballRect' was not declared in this scope

Your player.h file declares the ballRect structure. This file also includes opponent.h which is trying to refer to the ballRect. The compiler processes the main .ino file and then tries to validate and compile the includes as they are nominated. If an included file also has includes, these are processed first.

Ultimately, the compiler is trying to process the opponent.h code which refers to the ballRect before it is even declared.

The fix for this is to ‘unwind’ the dependencies and make the code ‘flatter’ by removing the nested includes.

Also in your images.h file, all images in memory should declared const unsigned char imageName[] PROGMEM =. Some of them are simple chars and others are not in PROGMEM.


(JohnnydCoder) #43

The code is compiling!

However, the opponent, that I am trying to move to the player when the player has the ball, is flickering.

struct oppVector {
  int x, y;
};

struct Opponent {
  int x;
  int y;
  OppStance stance;
  bool hasBall;
  char image;
};

Opponent opp = {OPP_X_OFFSET, OPP_Y_OFFSET, OppStance::oppStanding, false, opponentImages};

void oppAttack() {
  if (opp.hasBall == false) {
    
  oppVector oppV;

  oppV.x = (WIDTH / 2 - OPP_WIDTH / 2) - opp.x;
  oppV.y = (HEIGHT / 2 - OPP_HEIGHT / 2) - opp.y;

  opp.x = oppV.x;
  opp.y = oppV.y;

  if (arduboy.collide(ballRect, oppRect)) {
    opp.hasBall = true;
  }
  }
}

Am I storing the information of the opponent’s position wrong?:confused:


(Simon) #44

Assume the values:

OPP_WIDTH = 8
OPP_HEIGHT = 16

… and intially …

opp.x = 20;
opp.y = 10;

When oppAttack is first called, the values for oppV are calculated …

oppV.x = (128 / 2 - 8 / 2) - 20;
       = (64 - 4) - 20;
       = 40;
oppV.y = (64 / 2 - 16 / 2) - 10;
       = (32 - 8) - 10;
       = 14;

These values are then copied to opp.x and opp.y respectively.

Next iteration of oppAttach():

oppV.x = (128 / 2 - 8 / 2) - 40;
       = (64 - 4) - 40;
       = 20;
oppV.y = (64 / 2 - 16 / 2) - 14;
       = (32 - 8) - 14;
       = 10;

And then again …

oppV.x = (128 / 2 - 8 / 2) - 20;
       = (64 - 4) - 20;
       = 40;
oppV.y = (64 / 2 - 16 / 2) - 10;
       = (32 - 8) - 10;
       = 14;

Every iteration the player oscillates between the two coordinates (14, 14) and (20, 10). If one of the coordinate sets is off screen then the player will appear to blink. If both sets are onscreen, then the player will flash between two sets.

Have you worked out how to debug using the serial monitor?

At the bottom of your openAttack() function add:

Serial.print("Opp x: ");
Serial.print(opp.x);
Serial.print(", y: ");
Serial.println(opp.y);

And view the output via the serial monitor.


(Pharap) #45

@filmote’s explained what this is actually doing
My question is: what is this supposed to be doing?


(Simon) #46

I couldn’t determine that.


(JohnnydCoder) #47

It is supposed to travel the the player’s x and y position, and take the ball away from the player.:slightly_smiling_face:

Thanks @filmote! I’ll fix that as soon as possible!

The width is 10 pixels and the height is 16 pixels, just for future reference. :wink:


(Pharap) #48

Move the player where though?
Move the player to the right, move the player to the left?
Move the player to the centre of the screen?

And at the moment oppAttack is doing the opposite - it’s running if the player doesn’t have the ball and then giving the ball to the player if the player is colliding with the ball.


Also, there’s a bit of redundancy.
This:

oppVector oppV;

oppV.x = (WIDTH / 2 - OPP_WIDTH / 2) - opp.x;
oppV.y = (HEIGHT / 2 - OPP_HEIGHT / 2) - opp.y;

opp.x = oppV.x;
opp.y = oppV.y;

Could just be:

opp.x = (WIDTH / 2 - OPP_WIDTH / 2) - opp.x;
opp.y = (HEIGHT / 2 - OPP_HEIGHT / 2) - opp.y;

(JohnnydCoder) #49

I fixed it!
I was trying to move the opponent to the player’s x, y position and I realized that this:

opp.x = (WIDTH / 2 - OPP_WIDTH / 2) - opp.x;
opp.y = (HEIGHT / 2 - OPP_HEIGHT / 2) - opp.y;

should be this:

opp.x = (WIDTH / 2 - OPP_WIDTH / 2);
opp.y = (HEIGHT / 2 - OPP_HEIGHT / 2);

(Pharap) #50

Ok, that’s good.


If you need to ask for help again (which you probably will - programming is hard),
can I ask that you:

  • Consider uploading to a code hosting site like GitHub or GitLab
  • Try to explain your problem in detail
    • what you’re trying to do
    • the expected result
    • the result that you’re actually getting

If we don’t have enough code to see the whole picture and/or we don’t understand what it is that you’re trying to do then it’s hard for us to help you.


(Holmes) #51

This is so cool that everyone is helping out and able to not only contribute, but also explain why things should be added a certain way!


(JohnnydCoder) #52

Okay,
I got my opponent moving! :joy:

I have a problem, however:

When I try to move my player around the screen, the opponent’s Y axis stays the same as the player’s Y axis and I can’t move the player around without the AI cornering me. Also, the opponent’s X axis is heading towards the player and keeps going until it is exactly in the player’s position.

I am trying to get the opponent to go to the player (which I have succeeded in doing), and want the opponent to attack the player but not corner the player.

Here’s the updated movement code:

#define PLAYER_WIDTH    10
#define PLAYER_HEIGHT  16
#define PLAYER_X_OFFSET   WIDTH / 2 - PLAYER_WIDTH / 2
#define PLAYER_Y_OFFSET    HEIGHT / 2 - PLAYER_HEIGHT / 2

Rect playerRect = {
  PLAYER_X_OFFSET, PLAYER_Y_OFFSET, PLAYER_WIDTH, PLAYER_HEIGHT
};


#define OPP_WIDTH    10
#define OPP_HEIGHT  16
#define OPP_X_OFFSET   WIDTH / 2 + (OPP_WIDTH) * 2
#define OPP_Y_OFFSET    HEIGHT / 2 - OPP_HEIGHT / 2

Rect oppRect = {
  OPP_X_OFFSET, OPP_Y_OFFSET, OPP_WIDTH, OPP_HEIGHT
};

void oppAttack() {
  if (opp.hasBall == false) {
    if (arduboy.everyXFrames(7)) {
    if(ballx > opp.x) {
      opp.x += 0.5;
      switch(opp.stance) {
    case OppStance::oppStanding:
    case OppStance::oppRunningL1:
    case OppStance::oppRunningB1:
    case OppStance::oppRunningF1:
    opp.stance = OppStance::oppRunningR1;
    break;

    case OppStance::oppRunningR1:
    opp.stance = OppStance::oppRunningR2;
    break;
    
    case OppStance::oppRunningF2:
    case OppStance::oppRunningL2:
    case OppStance::oppRunningR2:
    case OppStance::oppRunningB2:
    opp.stance = OppStance::oppRunningR1;
    break;
  }
    }
    else if(ballx < opp.x) {
      opp.x -= 0.5; 
       switch(opp.stance) {
    case OppStance::oppStanding:
    case OppStance::oppRunningR1:
    case OppStance::oppRunningB1:
    case OppStance::oppRunningF1:
    opp.stance = OppStance::oppRunningL1;
    break;

    case OppStance::oppRunningL1:
    opp.stance = OppStance::oppRunningL2;
    break;
    
    case OppStance::oppRunningF2:
    case OppStance::oppRunningL2:
    case OppStance::oppRunningR2:
    case OppStance::oppRunningB2:
    opp.stance = OppStance::oppRunningL1;
    break;
  }
    }
    else if (bally > opp.y) {
      opp.y += 0.5;
      switch(opp.stance) {
    case OppStance::oppStanding:
    case OppStance::oppRunningR1:
    case OppStance::oppRunningL1:
    case OppStance::oppRunningB1:
    opp.stance = OppStance::oppRunningF1;
    break;

    case OppStance::oppRunningF1:
    opp.stance = OppStance::oppRunningF2;
    break;
    
    case OppStance::oppRunningF2:
    case OppStance::oppRunningL2:
    case OppStance::oppRunningR2:
    case OppStance::oppRunningB2:
    opp.stance = OppStance::oppRunningF1;
    break;
  }
    }
    else if(bally < opp.y) {
      opp.y -= 0.5;
      switch(opp.stance) {
    case OppStance::oppStanding:
    case OppStance::oppRunningR1:
    case OppStance::oppRunningL1:
    case OppStance::oppRunningF1:
    opp.stance = OppStance::oppRunningB1;
    break;

    case OppStance::oppRunningB1:
    opp.stance = OppStance::oppRunningB2;
    break;
    
    case OppStance::oppRunningF2:
    case OppStance::oppRunningL2:
    case OppStance::oppRunningR2:
    case OppStance::oppRunningB2:
    opp.stance = OppStance::oppRunningB1;
    break;
  }
    }
    }

  if (arduboy.collide(oppRect, ballRect)) {
    opp.hasBall = true;
    arduboy.setCursor(64, 32);
    arduboy.print("Collision");
  }
  }
arduboy.setCursor(64, 32);
Serial.print("Opp x: ");
Serial.print(opp.x);
Serial.print(", y: ");
Serial.println(opp.y);
}

Also, when I look at the serial monitor, it says that the X axis is changing, but the Y axis is staying at 24.
Only when the player’s Y axis is higher than the opponent’s Y axis does the opponent’s Y axis change when it is headed for the ball.


(Miloslav Číž) #53

Do you have the code in its entirety somewhere (all the files, like on GitHub, or you could zip all the files and upload them in your post)? I’d like to look at it and this would make it easier.

EDIT:

want the opponent to attack the player but not corner the player.

Right now I have two ideas of how to do this:

Option 1: Add some randomness to the opponent movement. Now it’s moving perfectly towards the player. You have to make it make small mistakes to give the player a chance to escape. Like here

if(ballx > opp.x) {
  opp.x += 0.5;

The AI always decides to correctly go towards the player’s x coordinate. You could do something like

if (rand() % 5 != 0) // <-- 4 in 5 chance
  opp.x += 0.5;

Which will make the AI stay in place on average once in every 5 steps.

(You could also make it move in the wrong direction sometimes. Experimenting is needed here.)

Option 2: Make the opponent change stance once every several frames less often than it actually moves. This will achieve a “delayed” reaction of the AI on the ball movement. Like

if (arduboy.everyXFrames(30))
  {
    // change stance here
  }

if (arduboy.everyXFrames(7))
  {
    // move here
  }

Don’t forget to update the movement (x =, y =) each every 7 frames, i.e. in the below if, otherwise it will just move more slowly (which might however be a solution as well).

comments on improving the code (you can ignore now):

  • Make 0.5 a constant:
    #define OPP_SPEED 0.5
    so that you can easily change it in one place.
  • if (opp.hasBall == false)
    would better be written as
    if (!opp.hasBall)
    (! means not).
  • You have a lot of unnecessary copy/paste code. oppAttack could look something like (not tested):
void oppAttack() {
  if (!opp.hasBall && arduboy.everyXFrames(7)) {
    if(ballx > opp.x) {
      opp.x += OPP_SPEED;

      opp.stance = opp.stance == OppStance::oppRunningR1 ?
        OppStance::oppRunningR2 : 
        OppStance::oppRunningR1;
      /* ^ this is a ternary operator, it's the same as

           if (opp.stance == OppStance::oppRunningR1)
              opp.stance = OppStance::oppRunningR2;
           else
              opp.stance = OppStance::oppRunningR1;
      */
    }
  else if(ballx < opp.x) {
    opp.x -= OPP_SPEED; 

    opp.stance = opp.stance == OppStance::oppRunningL1 ?
      OppStance::oppRunningL2 : 
      OppStance::oppRunningL1;
    }
  else if (bally > opp.y) {
    opp.y += OPP_SPEED;

    opp.stance = opp.stance == OppStance::oppRunningF1 ?
      OppStance::oppRunningF2 : 
      OppStance::oppRunningF1;
    }
  else if(bally < opp.y) {
    opp.y -= OPP_SPEED;

    opp.stance = opp.stance == OppStance::oppRunningB1 ?
      OppStance::oppRunningB2 : 
      OppStance::oppRunningB1;
    }
  }

It’s still redundant, but better.

  • Instead of having two states for each stance (oppRunningL1 and oppRunningL2) you should have a direction state (oppRunningL) and a frame number. Because many times you will just want to check the direction and you’ll be able to do it with one condition instead of checking for two states of each direction. Other times you may just want to change the animation frame without caring about the direction. It would actually help reduce the above code to only a few lines.

(JohnnydCoder) #54

Thanks @drummyfish! :grinning:

No, sadly I don’t have a Github account, though that would make things a lot easier.

I will try your two ideas as soon as possible! :wink:

I tested your recommended opponent movement code (with the ternary operator) and it works!

I have a question: how do I do what you said here?

Thanks again!


(Miloslav Číž) #55

I’d change the OppStance enum to:

enum OppStance {
  oppStanding,
  oppRunningR,
  oppRunningL,
  oppRunningF,
  oppRunningB
};

Then add the animation frame property to opponent:

struct Opponent {
  int x;
  int y;
  OppStance stance;
  int animationFrame; // <-- new
  bool hasBall;
  char image;
};

And then the oppAttack could look something like this (again. not tested, just for illustration):

void oppAttack() {
  if (!opp.hasBall && arduboy.everyXFrames(7)) {

    if(ballx > opp.x) {
      opp.x += OPP_SPEED;
      opp.stance = OppStance::oppRunningR;
    }
    else if(ballx < opp.x) {
      opp.x -= OPP_SPEED;
      opp.stance = OppStance::oppRunningL;
    }
    else if (bally > opp.y) {
      opp.y += OPP_SPEED;
      opp.stance = OppStance::oppRunningF;
    }
    else if(bally < opp.y) {
      opp.y -= OPP_SPEED;
      opp.stance = OppStance::oppRunningB;
    }

    opp.animationFrame = (opp.animationFrame + 1) % 2;
    /* ^ this will keep changing opp.animationFrame from 0 to 1 and back

         % is a mod (division remainder) operator, so it gives you the remainder
         of opp.animationFrame divided by 2, which will be 0, 1, 0, 1, ...

         it does the same thing as

         if (opp.animationFrame == 0)
           opp.animationFrame = 1;
         else
           opp.animationFrame = 0;
     */
  }

(It could be reduced even more, but this is already very okay.)

So now instead of having e.g. OppStance::oppRunningL1 and OppStance::oppRunningL2 you have only OppStance::oppRunningL plus animationFrame that is always either 0 or 1. Now if you want to e.g. check if the opponent is facing left, you can do simply if (opp.stance == OppStance::oppRunningL).

Also as I don’t see the whole code, you may need to change a lot of things in other places, such as where you are drawing the opponent (I can help if you show the code). Warning: if you’re going to try to do this (or maybe even if you aren’t)

back up your current code right now!

Because you may very easily break it and may need to return back to the older version. This happens all the time, so watch out :slight_smile: (This is actually what the systems like Git/GitHub are for. They track all changes you make and allow you to get back to earlier versions etc.)


(JohnnydCoder) #56

I did what you said and added the animationFrame integer. I am also trying your second idea:

However, the opponent is moving backwards and is not changing frames.

Here is my code:

void oppAttack() {
  if (arduboy.everyXFrames(30)) {

     if(ballx > opp.x) {
     opp.stance = OppStance::oppRunningR;
    }
    
  else if(ballx < opp.x) {
    opp.stance = OppStance::oppRunningL;
  }
  
  else if (bally > opp.y) {
  opp.stance = OppStance::oppRunningF;
    }
  
  else if(bally < opp.y) {
    opp.stance = OppStance::oppRunningB;
  }

  if (opp.stance == OppStance::oppRunningL) {
      opp.animationFrame = (opp.animationFrame + 1) % 2;
  }

   else if (opp.stance == OppStance::oppRunningR) {
      opp.animationFrame = (opp.animationFrame + 1) % 2;
  }

  else if (opp.stance == OppStance::oppRunningF) {
      opp.animationFrame = (opp.animationFrame + 1) % 2;
  }

  else if (opp.stance == OppStance::oppRunningB) {
      opp.animationFrame = (opp.animationFrame + 1) % 2;
  }
  }
  
  if (!opp.hasBall && arduboy.everyXFrames(7)) {
    if(ballx > opp.x) {
      opp.x += OPP_SPEED;

    }
  else if(ballx < opp.x) {
    opp.x -= OPP_SPEED; 

  }
  
  else if (bally > opp.y) {
    opp.y += OPP_SPEED;
    }
  
  else if(bally < opp.y) {
    opp.y -= OPP_SPEED;
    }
}
}

I don’t know exactly what I’m doing wrong.:slightly_frowning_face:


(Miloslav Číž) #57

You should make the movement based on stance, that is (the end part of your code):

    if(ballx > opp.x) {
      opp.x += OPP_SPEED;
    }

Should be

    if(opp.stancd == OppStance::oppRunningR) {
      opp.x += OPP_SPEED;
    }

etc. There are more things I’d like to write, but am on mobile. Once I get to PC I’ll leave you more info.

EDIT:

Let’s go :smiley: Here is what it could look like:

void oppAttack() {
  /* Every 30 frames change the stance - this affects how
     quickly the opponent "reacts" to the player movement.
     Experiment with different values than 30. Lower values
     will make the game more difficult (the oponent will
     have faster reaction time). */

  if (arduboy.everyXFrames(30)) {
    if(ballx > opp.x) {
      opp.stance = OppStance::oppRunningR;
    }
    else if(ballx < opp.x) {
      opp.stance = OppStance::oppRunningL;
    }
    else if (bally > opp.y) {
      opp.stance = OppStance::oppRunningF;
    }
    else if(bally < opp.y) {
      opp.stance = OppStance::oppRunningB;
    }
  }

  /* Every 5 frames we change the animation frame - this
     will only affect the animation, so choose a value that
     just looks nice. */
  if (arduboy.everyXFrames(5))
    opp.animationFrame = (opp.animationFrame + 1) % 2;
  
  /* Every 7 frames we actually move the opponent in direction
     in which he is facing. */
  if (!opp.hasBall && arduboy.everyXFrames(7)) {
    switch (opp.stance)
    {
      case OppStance::oppRunningR:
        opp.x += OPP_SPEED;
        break;

      case OppStance::oppRunningL:
        opp.x -= OPP_SPEED;
        break;

      case OppStance::oppRunningF:
        opp.y += OPP_SPEED;
        break;

      case OppStance::oppRunningB:
        opp.y -= OPP_SPEED;
        break;
    }
  }
} 

Again, not tested. I can’t tell you how this will play because I can’t test it, so you’ll have to try it out and maybe fine tune some of the values etc.


(JohnnydCoder) #58

I think there is something wrong with the way I am storing my opponent images because the animation frame is not changing the picture. Here is the way I am storing them:

const unsigned char opponentImages[] PROGMEM =
{
  // Width, Height
  10, 16,
     
  // Standing
  0x78, 0xfc, 0xfe, 0xb7, 0x7f, 0x7f, 0xb7, 0xfe, 0xfc, 0x78, 0x00, 0x3c, 0x85, 0xff, 0x1f, 0x1f,
  0xff, 0x85, 0x3c, 0x00, 
  // RunningR1   
  0x78, 0xfc, 0xfe, 0xff, 0xff, 0xbf, 0x77, 0x7e, 0xfc, 0x78, 0x00, 0x18, 0x85, 0xff, 0x1f, 0x1f,
  0xff, 0x85, 0x08, 0x04, 
  // RunningR2   
  0x78, 0xfc, 0xfe, 0xff, 0xff, 0xbf, 0x77, 0x7e, 0xfc, 0x78, 0x00, 0x00, 0x99, 0x7f, 0xff, 0xff,
  0xff, 0x99, 0x00, 0x00, 
  // RunningL1
  0x78, 0xfc, 0x7e, 0x6f, 0xbf, 0xff, 0xff, 0xfe, 0xfc, 0x78, 0x04, 0x08, 0x85, 0xff, 0x1f, 0x1f,
  0x7f, 0x85, 0x18, 0x00, 
  //RunningL2
  0x78, 0xfc, 0x7e, 0x6f, 0xbf, 0xff, 0xff, 0xfe, 0xfc, 0x78, 0x00, 0x00, 0x8d, 0xff, 0xff, 0xff,
  0x7f, 0x8d, 0x00, 0x00, 
  //RunningF1
  0x78, 0xfc, 0xfe, 0xb7, 0x7f, 0x7f, 0xb7, 0xfe, 0xfc, 0x78, 0x00, 0x18, 0x05, 0xff, 0x1f, 0x5f,
  0x3f, 0x05, 0x06, 0x00, 
  //RunningF2
  0x78, 0xfc, 0xfe, 0xb7, 0x7f, 0x7f, 0xb7, 0xfe, 0xfc, 0x78, 0x00, 0x06, 0x05, 0x3f, 0x5f, 0x1f,
  0xff, 0x05, 0x18, 0x00, 
  //RunningB1
  0x78, 0xfc, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfc, 0x78, 0x00, 0x18, 0x05, 0xff, 0x1f, 0x5f,
  0x3f, 0x05, 0x06, 0x00, 
  //RunningB2
  0x78, 0xfc, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfc, 0x78, 0x00, 0x06, 0x05, 0x3f, 0x5f, 0x1f,
  0xff, 0x05, 0x18, 0x00, 
};

(Miloslav Číž) #59

This looks good, I think it’s the code where you’re drawing the opponent. Can you post that?


(JohnnydCoder) #60

Here it is:

struct Opponent {
  int x;
  int y;
  OppStance stance;
  int animationFrame;
  bool hasBall;
  char image;
};

Opponent opp = {OPP_X_OFFSET, OPP_Y_OFFSET, OppStance::oppStanding, 0, false, opponentImages};


void drawopponent() {
opp.image = opponentImages[opp.stance];
arduboy.fillRect(opp.x, opp.y, OPP_WIDTH, OPP_HEIGHT, BLACK);  
sprites.drawExternalMask(opp.x, opp.y, opponentImages, opponentImages, opp.stance, opp.stance);
}