Simple(?) inventory and item menu


(Simon) #188

Oh I can see the problem in the sample code you posted. Again, its an example of you blindly cutting and pasting code without actually understanding what it does.


(Pharap) #189

Instead of putting bits here and there, why not just put all the code on github and keep it up to date?


(Simon) #190

Its in the first section of code you posted.

And its not the misspelling of collision.


(Jeremy Wood) #191

Ask and you shall receive…

We’ve been going over the inventory code for days I thought I knew enough about it. The collision code hasn’t changed since I was able to get it working. the only thing I did was add the dependency for the adding of inventory

I don’t see how that would make it where you can only see the inventory while triggering the object that adds it to inventory.

Serial testing shows its not in the inventory when touching as well

do I need to make printinventory Boolean like

bool printInventory(void);


(Simon) #192

I’ll give you a clue … here is your original commit.

bool checkcolision(void) // Transformed it into a function
{
  for (uint16_t i = 0; i < tft.numcolision + 1; i++)
  {
    if (tft.collideRectRect(player.player_x, player.player_y, 16, 16, tft.solid[i].x, tft.solid[i].y, 16, 16))
    {
      if (tft.solid[i].spritecol == bedtop)return true;
      else if (tft.solid[i].spritecol == bedbot)return true;
      else if (tft.solid[i].spritecol == bones11)return true;
      else if (tft.solid[i].spritecol == bones12)return true;
      else if (tft.solid[i].spritecol == bones13)return true;
      else if (tft.solid[i].spritecol == bones21)return true;

.. etc.

(Pharap) #193

(I apologise in advance because this unintentionally turned into a bit of an unsolicited lecture halfway down. I did solve the problem you asked about though.)


I think some of your code could do with reorganising.
There’s a lot of strange things floating around.

(For a start, I think your image data ought to be separate from your code, and you could do with using less forward slashes.)


How to fix the problem you asked about:

You’re clearing out your inventory every time you check a collision with a chest.

This means that in the first frame the item is added and the message displays correctly, but in the second frame your inventory is completely cleared out again.

So the answer is (if you’ll pardon the internet joke), delete this:

      for (uint8_t i = 0; i <  NUMBER_OF_SLOTS; i++) {

        Slot *slot = &slots[i];
        slot->quantity = 0;
        slot->itemId = SLOT_AVAILABLE;

      }

Your code should work after that.

(Also bool added could do with being moved down a bit since it isn’t needed outside of the inventory adding stuff.)

Your issue after that is that because you don’t have a way of knowing that the chest has been opened, the code will just fill your inventory full of spice one frame at a time.


About game engine design and fixing the chest problem in general

(The following is both a general observation based on some of your other code and a specific discussion of the chest problem.)

It seems that every specific case of something happening in your world seems to have an if condition somewhere checking for that specific issue. That’s not how games are usually written for a good reason - it gets very complicated and hard to debug because conditions start conflicting with each other.

This is shown in your chest code.
Checking that the player is colliding with a chest is fine, but you’re checking the room number as well.
Following that logic, you’ll end up with a long chain of else if conditions checking for specific room and chest numbers, which might work to begin with but soon you find yourself dealing with ridiculous amounts of code.

A better approach would be to define what a chest is, e.g.

class Chest
{
  int x, y;
  int itemId;
  bool isOpen;
}:

Then maintain a list/array of chests in each room.

class Room
{
public:
  Chest chests[MaxChests];
  int chestCount;
};

And then when it comes to checking for chests, you can do something like this:
(Note this code is for demonstration only, some of these functions haven’t been defined, they’re here to demonstrate what the code could look like.)

else if (tft.solid[i].spritecol == chest)
{
  Room & currentRoom = world.getCurrentRoom();
  for(int i = 0; i < currentRoom.getChestCount(); ++i)
  {
    Chest & chest = currentRoom.getChest(i);
    if(chest.x == player.x && chest.y == player.y)
    {
      if(!chest.isOpen)
      {
        bool success = player.inventory.add(chest.itemId);
        if(success)
        {
          popupSystem.popup(getPopupMessage(chest.itemId));
          chest.isOpen = true;
          chest.itemId = NoItem;
        }
      }
    }
  }
}

Which still isn’t ideal, but it’s getting towards proper game architecture. Doing it this way means that it works for any chest in any room and you only need to write the code once, you don’t need to add a new block every time you make a new chest.

Instead, to add a new chest you just put the chest information into the room data. That way the code doesn’t change - the room data does. That’s a sign of good design. You want to write the code for your game engine so it works and then to build upon your world you write more world data instead of writing more code.

Take Dark & Under for example (which both me and @filmote worked on). We developed a Level Editor. The level editor works because the code for Dark & Under itself is driven by the level data. The game engine stays fixed and to introduce new things we modify the levels rather than the code.

Granted that if we wanted to add a new feature that involved new logic (like the ability to flee battle) then we’d need to alter the code, but if we just want to add new monsters or change the structure of a level then we only need to change the world data. That’s how game engines should work, and it’s how the majority do work. You don’t keep adding code to the engine forever, you build up the engine until it can do what you need it to and then you start building level data to feed into the engine.


(Jeremy Wood) #194

See I thought I needed the bool added stuff in the collision as dependency to get it to work. And yes that solved it. I was looking for something I might have changed in the original code.

I appreciate any information you can give me to learn to do this on my own. I haven’t looked at the example to see the gist of it yet. I see your point totally. this will give me something to strive for.

Speaking of fleeing in battle…

I have a battle function drawn up that I got from here I’m sure. its supposed to count plater steps randomly from 5 to 30 steps and then it changes to state battle. To do this I had to separate the the trigger part of the code from interface part to accomadate the switch break.

So now I put trigger battle in the case with drawplayer but it cant get it to trigger.

int count = 0;

uint16_t x;
uint16_t y;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////

void triggerBattle(uint16_t player_x, uint16_t player_y) {

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if (count == 0)
  {
    x = random(6, 30); //sets the horizontal axis trigger from ten to thirty steps
    y = random(6, 30); //sets the vertical axis trigger from ten to thirty steps
    count = 1;
  }

  Rect rectA {x, y, 16, 16};
  Rect rectB {player.player_x, player.player_y, 16, 16};

  if (tft.collideRectRect( rectA.x, rectA.y, rectA.width, rectA.height, rectB.x, rectB.y, rectB.width, rectB.height))
  {
    state = STATE_Battle;
  }
};
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////Loop////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void loop() {

  switch (state) {

    case STATE_Title:

      titlepage();
      if (!digitalRead(IRQ_PIN1)) {
        uint32_t buttons = ss1.digitalReadBulk(button_mask);
        if (! (buttons & (1 << BUTTON_SEL))) {
          Serial.println("Button SEL pressed");
          state = STATE_Player;

        }
      }
      break;

    case STATE_Player:
      drawplayer();
      triggerBattle(player.player_x, player.player_y);
      break;

    case STATE_Menu:
      Menu();
      break;

    case STATE_Item_list:
      printItemlist();
      break;

    case STATE_Battle:
      drawBattle();
      break;

  }
  tft.updateScreen();

}

ive got state battle defined else it would error.


(Simon) #195

Ahhh … you told him! I was seeing how long it would take for him to see it.

I totally agree @pharap and I posted that same concern 85 posts ago.

I am really not sure why you would trigger battles like that - look for an overlap between rectangles? Why not simply pick a random number and use it to decide if you want to battle or not. The code you have has nothing to do with steps as each time you execute the function new random numbers and there a new rectangle is chosen.

When you debug this using the serial monitor, what values are you seeing for player.player_x and player.player_y ?


(Jeremy Wood) #196

this is the original part of the code as working on a different version…

void drawbattle(int16_t player_x, int16_t player_y) {


 if(count == 0)
{
x = random(0,320); //0 to 320 pixels across
y = random(0,240); //0 to 240 pixels down
count = 1;
}

Rect rectA {x,y,16,16}; //rectangle player will collide with
Rect rectB {player_x, player_y,16,16};
Rect rectC {x,y,33,7};
Rect rectD {x,y,22,7};
Rect rectE {x,y,47,7};
Rect rectF {x,y,23,7};
Rect rectG {cursor_x,cursor_y,26,26};

if(tft.collideRectRect( rectA.x, rectA.y, rectA.width, rectA.height, rectB.x, rectB.y, rectB.width, rectB.height)) // 
{
do battle screen stuff
}

and included like so…

void loop(void) {
  
   drawplayer();
   drawbattle(player_x, player_y);
   tft.updateScreen();

}

the values I have set are up down left and right


(Simon) #197

Oh I see, count doesn’t mean count it means ‘I have done this before’.

So in the second bit of code (drawBattle), you wait until the player wanders into the portion of the screen that you randomly selected.

In the first example, you have to wander into the top left hand portion of the screen.

What are rectangles C - G?


(Jeremy Wood) #198

rects c through g are the cursor and the option rectangles for fight item magic and flee.

The original code with 0 to 320 and such worked weird to begin with but its Omni-directional. the first time it triggers after a step and it starts after that going more and more steps every time. it never resets back to zero.


(Simon) #199

Use the serial port and log out the rectangle coordinates and see if they should even collide.


(Jeremy Wood) #200

Uh I’m still learning the serial monitor and prints. I wouldn’t know what to put to see if its colliding.

ok I just added a serial print to the trigger that simply prints collision when one is made and tested. No collision is being reported

I’m wondering if he definitions of x and y are conflicting. notice how both functions use uint16 y and x

#ifndef _Battle_H_
#define _Battle_H_

#include "Player.h"
#include "Variables.h"
#include "Monsters.h"

//int battle options[] = {"Attack", "Item", "Weirding", "Flee"};

struct CursorB
{
  int cursorB_x;
  int cursorB_y;
  int cursorB_direction;
};

CursorB cursorb = { 9, 136, 1};

int count = 0;
uint16_t x;
uint16_t y;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////

void triggerBattle(uint16_t player_x, uint16_t player_y) {

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if (count == 0)
  {
    x = random(6, 30); //sets the horizontal axis trigger from ten to thirty steps
    y = random(6, 30); //sets the vertical axis trigger from ten to thirty steps
    count = 1;
  }

  Rect rectA {x, y, 16, 16};
  Rect rectB {player.player_x, player.player_y, 16, 16};

  if (tft.collideRectRect( rectA.x, rectA.y, rectA.width, rectA.height, rectB.x, rectB.y, rectB.width, rectB.height))
  {
    state = STATE_Battle;
    Serial.print("collision");
  }
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////

void drawBattle() {
  //////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////Palette////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  palette[0] = 0;            palette[8] = BEIGE;
  palette[1] = BLACK;        palette[9] = GREEN;
  palette[2] = BLUE;         palette[a] = DARKGREY;
  palette[3] = BROWN;        palette[b] = LIGHTGREY;
  palette[4] = DARKGREEN;    palette[c] = YELLOW;
  palette[5] = GREY;         palette[d] = PURPLE;
  palette[6] = PINK;         palette[e] = WHITE;
  palette[7] = RED;          palette[f] = ORANGE;
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
  Rect rectC {x, y, 33, 7};
  Rect rectD {x, y, 22, 7};
  Rect rectE {x, y, 47, 7};
  Rect rectF {x, y, 23, 7};
  Rect rectG {cursorb.cursorB_x, cursorb.cursorB_y, 26, 26};
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  tft.fillScreen(BLACK);
  tft.fillRect(0, 30, 320, 50, WHITE);  ////// draws a back grounds stripe for the monsters to sit on the length nof the screen
  tft.writeRectNBPP(143, 50, 34, 24, 4, cavespider, palette);
  tft.drawRoundRect(40, 82, 240, 40, 4, WHITE);
  tft.fillRoundRect(41, 83, 237, 37, 4, BLUE);
  tft.setCursor(90, 94);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.println("Cave spider");
  tft.drawRoundRect(10, 130, 118, 104, 4, WHITE);
  tft.fillRoundRect(11, 131, 115, 101, 4, BLUE);
  tft.setCursor(24, 136);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.println("Attack");
  tft.setCursor(24, 160);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.println("Item");
  tft.setCursor(24, 181);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.println("Weirding");
  tft.setCursor(24, 211);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);
  tft.println("Flee");
  ///////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////////
  int y1 = ss1.analogRead(2);
  int x1 = ss1.analogRead(3);
  ///////////////////////////////////////////////////////////////////////////////
  ////////////////////////////Up/////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////////
  if (x1 > 600 && last_x < 600) {
    tft.writeRectNBPP(cursorb.cursorB_x, cursorb.cursorB_y, 16, 16, 4, cursordot2, palette);
    cursorb.cursorB_y -= 26;
  }
  if (cursorb.cursorB_y <= 136) {
    cursorb.cursorB_y = 136;
  }

  //////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////Down///////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  if (x1 < 400 && last_x > 400) {
    tft.writeRectNBPP(cursorb.cursorB_x, cursorb.cursorB_y, 16, 16, 4, cursordot2, palette);
    cursorb.cursorB_y += 26;
  }
  if (cursorb.cursorB_y >= 240) {
    cursorb.cursorB_y = 240;
  }

  last_x = x1;
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if (cursorb.cursorB_direction == 1) {
    tft.writeRectNBPP(cursorb.cursorB_x, cursorb.cursorB_y, 26, 26, 4, cursordot2, palette);
  }
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if (!digitalRead(IRQ_PIN2)) {
    uint32_t buttons = ss2.digitalReadBulk(button_mask2);
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////

    if ((! (buttons & (1 << BUTTON_X)) && tft.collideRectRect( rectC.x, rectC.y, rectC.width, rectC.height, rectG.x, rectG.y, rectG.width, rectG.height)))
    {
      tft.fillScreen(GREEN);
    }
    else  if ((! (buttons & (1 << BUTTON_X)) && tft.collideRectRect( rectD.x, rectD.y, rectD.width, rectD.height, rectG.x, rectG.y, rectG.width, rectG.height)))
    {
      tft.fillScreen(RED);
    }
    else  if ((! (buttons & (1 << BUTTON_X)) && tft.collideRectRect( rectE.x, rectE.y, rectE.width, rectE.height, rectG.x, rectG.y, rectG.width, rectG.height)))
    {
      tft.fillScreen(PURPLE);
    }
    else  if ((! (buttons & (1 << BUTTON_X)) && tft.collideRectRect( rectF.x, rectF.y, rectF.width, rectF.height, rectG.x, rectG.y, rectG.width, rectG.height)))
    {
      state = STATE_Player;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  }
};
#endif

I’m all eyes on a better way to trigger state battle though


(Simon) #201

You already knew they were not colliding so its little surprise the printing of ‘collision’ didn’t reveal anything.

You need to see what the boundaries of the two rectangles are. That way you will be able to visualize whether they are actually colliding or not.

Serial.print(rectA.x);
Serial.print(" ");
Serial.print(rectA.y);
Serial.print(" ");
Serial.print(rectA.width);
Serial.print(" ");
Serial.print(rectA.height);
.. and so on.

(Jeremy Wood) #202

Ok I have player set up like this with the serial prints within the call to collision inside each movement.

  int y = ss1.analogRead(2);
  int x = ss1.analogRead(3);
#define MoveRepeatRate 50 // set this for how long in milis to wait for auto repeat move

  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  if ( (x > 600) && ( (last_x < 600) || ( 1 == player.player_directionRepeat && MoveRepeat >= MoveRepeatRate ) ) ) {
    Serial.println(F("UP")); ///RIGHT
    MoveRepeat = 0;
    player.player_directionRepeat = 1;
    tft.writeRectNBPP(player.player_x, player.player_y,  16, 16, 4, paulrearwa, palette); //// this mixed with the call below it calls bitmaps for walking
    tft.writeRectNBPP(player.player_x, player.player_y, 16, 16, 4, paulrearwb, palette); ///// read above

    player.player_direction = 1; ////when movement ends player bitmap faces this direction paul rear
    player.player_y -= 4;    ///move player y - 4 pixels
    if (checkcolision())
    {
      Serial.print(rectB.y);
      Serial.print(" ");
      Serial.print(rectB.x);
      Serial.print(" ");
       Serial.print(rectB.width); 
       Serial.print(" ");
        Serial.print(rectB.height); 
       Serial.print(" ");
      player.player_y += 4; ///causes player to stop when collision happens
      player.player_directionRepeat = 1;
    }
    last_x = x;   ////added this
    last_y = y;   ////added this
  }
  if (player.player_y <= 16) {  ////keeps player from walking off the screen.
    player.player_y = 16;

  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  if ( (x < 400) && ( (last_x > 400) || ( 2 == player.player_directionRepeat && MoveRepeat >= MoveRepeatRate ) ) ) {
    Serial.println(F("DOWN")); ///LEFT
    MoveRepeat = 0;
    player.player_directionRepeat = 2;
    tft.writeRectNBPP(player.player_x, player.player_y, 16, 16, 4, paulfrontwa, palette);
    tft.writeRectNBPP(player.player_x, player.player_y, 16, 16, 4, paulfrontwb, palette);

    player.player_direction = 2; ////when movement ends player bitmap faces this direction paul rear
    player.player_y += 4;    ///move player y - 4 pixels
    if (checkcolision())
    {

      Serial.print(rectB.y);
      Serial.print(" ");
      Serial.print(rectB.x);
      Serial.print(" ");
       Serial.print(rectB.width); 
       Serial.print(" ");
        Serial.print(rectB.height); 
       Serial.print(" ");
      player.player_y -= 4;
      player.player_directionRepeat = 2;
    }
    last_x = x;    ////added this
    last_y = y;    ////added this
  }
  if (player.player_y >= 224) {
    player.player_y = 224;
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  if ( (y < 400) && ( (last_y > 400) || ( 2 == player.player_directionRepeat && MoveRepeat >= MoveRepeatRate ) ) ) {
    Serial.println(F("LEFT"));   ///DOWN
    MoveRepeat = 0;
    player.player_directionRepeat = 2;
    tft.writeRectNBPP(player.player_x, player.player_y, 16, 16, 4, paulleftw, palette);
    tft.writeRectNBPP(player.player_x, player.player_y, 16, 16, 4, paulleft, palette);

    player.player_direction = 3;
    player.player_x -= 4;
    if (checkcolision())
    {
      Serial.print(rectB.x);
      Serial.print(" ");
      Serial.print(rectB.y);
      Serial.print(" ");
       Serial.print(rectB.width); 
       Serial.print(" ");
        Serial.print(rectB.height); 
       Serial.print(" ");
      player.player_x += 4;
      player.player_directionRepeat = 2;
    }
    last_x = x;      ////added this
    last_y = y;       ////added this
  }
  if (player.player_x >= 304) {
    player.player_x = 304;
  }
  ///////////////////////////////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////////////////////////////////
  if ( (y > 600) && ( (last_y < 600) || ( 1 == player.player_directionRepeat && MoveRepeat >= MoveRepeatRate ) ) ) {
    Serial.println(F("RIGHT")); ///UP
    MoveRepeat = 0;
    player.player_directionRepeat = 1;
    tft.writeRectNBPP(player.player_x, player.player_y, 16, 16, 4, paulrightw, palette);
    tft.writeRectNBPP(player.player_x, player.player_y, 16, 16, 4, paulright, palette);

    player.player_direction = 4;
    player.player_x += 4;
    if (checkcolision())
    {
      Serial.print(rectB.y);
      Serial.print(" ");
      Serial.print(rectB.x);
      Serial.print(" ");
       Serial.print(rectB.width); 
       Serial.print(" ");
        Serial.print(rectB.height); 
       Serial.print(" ");
      player.player_x -= 4;
      player.player_directionRepeat = 1;
    }
    last_x = x;      ////added this
    last_y = y;      ////added this
  }
  if (player.player_x <= 16) {
    player.player_x = 16;
  }

I’m unsure where to add the rectA stuff since we know the collision in trigger battle doesn’t work. Like it is now it prints the onscreen coordinates of the collision when made from the tile collision list

I’m up for learning how to fix this but if there is a better way it might be more beneficial to learn it.


(Simon) #203

But the logging for both Rect A and Rect B …

void triggerBattle(uint16_t player_x, uint16_t player_y) {

  if (count == 0)
  {
    x = random(6, 30); //sets the horizontal axis trigger from ten to thirty steps
    y = random(6, 30); //sets the vertical axis trigger from ten to thirty steps
    count = 1;
  }

  Rect rectA {x, y, 16, 16};
  Rect rectB {player.player_x, player.player_y, 16, 16};

RIGHT ABOUT HERE!

  if (tft.collideRectRect( rectA.x, rectA.y, rectA.width, rectA.height, rectB.x, rectB.y, rectB.width, rectB.height))
  {
    state = STATE_Battle;
  }

(Simon) #204

Then capture the Serial output and show me where the two rectangles overlap.


(Jeremy Wood) #205

Cant figure out how to copy and paste the serial monitor results. I thought it was giving me coordintes but it just gives me 160,170,

malloc failed
Button Sel pressed   ///// starts game

160,170,16,16,left
160,170,16,16,right
170,160,16,16,up
170,160,16,16,down


(Simon) #206

You need to log both rectangle boundaries out at the spot I specified. You may need to log out ‘rect a’ and ‘rect b’ so it’s obvious which is which.


(Jeremy Wood) #207

sorry not seeing where you specified. What do you mean log out? Where do I put the rectA stuff?