Go Down

Topic: Garbage when drawing level map (Read 671 times) previous topic - next topic

andylatham82

Nov 20, 2017, 11:14 am Last Edit: Nov 20, 2017, 11:20 am by andylatham82
Hi, I have a tile in my game defined as a char array in a header file called bitmaps.h:

Code: [Select]
const unsigned char floorTile[] PROGMEM = {
  // Width & Height:
  8, 8,
  // Frame 0:
  0xff, 0x81, 0x8d, 0x8d, 0x81, 0xa1, 0x83, 0xff,};


I have made a LevelMap class:
Code: [Select]
#include "Tile.cpp"
#include "Bitmaps.h"

class LevelMap {
  public:
    Tile tiles[2] = {Tile(NULL),Tile(floorTile)};
   
    const unsigned char tileMap[2][8][16] = {
      { // Level 0:
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
      },
      { // Level 1:
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
      }
    };

    LevelMap(int a) {
     
    }

    Draw() {
      for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 16; j++) {
          if (tileMap[0][i][j] > 0)
            tiles[1].Draw(j * 8 * 256, i * 8 * 256);
        }
      }
    }
};



And I have made a Tile class:
Code: [Select]
#pragma once
#include "Sprite.cpp"
#include "Bitmaps.h"

class Tile {
  public:
    Sprite sprite;
    unsigned char tileTexture[10];

    Tile(const unsigned char texture[]) {
      memcpy(tileTexture, texture, sizeof(tileTexture));
    }

    void Update() {
      sprite.active = false;
      sprite.Update(true);
    }
   
    void Draw(int posX, int posY) {
      sprite.Draw(posX, posY, tileTexture);
    }
};


The idea is that the LevelMap has a tileMap array which details how the level should be drawn on the screen, using simple chars as an index. A separate array of Tiles is created, and cross-referencing the two arrays dictates which type of tile is drawn where.

For some reason, however, I'm not getting the tile artwork through. I get garbage organised in the shape of the level. So I can recognise the level structure on the screen, but the actual artwork isn't being used. Instead there are various letters shown.

I know my Sprite class works fine with the char array artwork because I use the same things for character art, and that all shows correctly. Also if I say:
Code: [Select]
sprite.Draw(posX, posY, floorTile)
I get the level art drawing correctly. So maybe there is something wrong with how I'm passing the char array around.

I hope I'm explaining this ok. And I appreciate any help anyone can offer! Thanks :)

andylatham82

I should explain that the reason I multiply my tile drawing positions by 256 is that all positions in my game are multiplied up to give a greater range of numbers, ignoring the lower 8 bits at draw time.

AWOL

Are you using PROGMEM methods to read the array data?

andylatham82

Are you using PROGMEM methods to read the array data?
I wasn't aware that there are specific PROGMEM methods. However I have been reading character images from PROGMEM and displaying them on the screen without issue.

andylatham82

Here's my Sprite class. I use this for both character drawing and level tile drawing.

Code: [Select]
#include<Arduboy2.h>

#pragma once
class Sprite {
  public:
    Sprites sprites;

    // Sprite animation variables.
    unsigned int framesSinceAnimUpdate = 0; // Number of updates since last animation frame advance.
    unsigned int currentFrame = 0;          // Displayed frame.
    unsigned int animationLength = 6;       // Number of frames.
    unsigned int frameRate = 60;            // Sprite frame rate.
    unsigned int framesBetweenUpdates;      // Number of frames between sprite animation updates.
    unsigned int height = 16;
    unsigned int width = 16;
    bool active = true;
    
    Sprite() {
      // Calculate number of frames between animation updates.
      framesBetweenUpdates = round((1.0 / frameRate) * 100);
    }

    void Update(bool sameState) {
      if (active) {
        framesBetweenUpdates = round((1.0 / frameRate) * 100);
        // Advance the sprite animation frame.
        framesSinceAnimUpdate++;
        if (framesSinceAnimUpdate > framesBetweenUpdates){
          currentFrame = (currentFrame + 1) % animationLength;
          framesSinceAnimUpdate = 0;
        }
        if (!sameState)
          currentFrame = 0;
      }
    }

    void Draw(int posX, int posY, const unsigned char bitmap[]) {
      sprites.drawSelfMasked(posX >> 8, posY >> 8, bitmap, currentFrame);
    }
};

arduarn

Take AWOL's hint and research the access methods for data in flash memory. Start here:
http://www.atmel.com/webdoc/avrlibcreferencemanual/pgmspace.html

andylatham82

Take AWOL's hint and research the access methods for data in flash memory. Start here:
http://www.atmel.com/webdoc/avrlibcreferencemanual/pgmspace.html
Hmmm, well I've now looked into these access methods and tried them out, but haven't had much luck.

Is this definitely the problem? I ask because if I remove the PROGMEM command entirely, I still can't get the correct result.

arduarn

It's definitely a problem, but may well not be the only problem.
Post your updated code - preferably all of it.

andylatham82

Here's all my code. It's split into several files so sorry if it's difficult to figure out. I've also got to post it in a couple of replies as there's a 9000 character limit! There are also some bits I have commented out while I'm trying to fix up the level art stuff.

PART 1:

Main code:
Code: [Select]
#include "Player.cpp"
#include "Constants.h"
#include "LevelMap.cpp"
#include "Tile.cpp"

Arduboy2 arduboy;
Player player1;
LevelMap levelMap = new LevelMap(1);
int gameState = 0;
bool buttonA = false;
bool buttonB = false;
bool buttonLeft = false;
bool buttonRight = false;

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(60);
  arduboy.clear();
  player1.Reset();
}

void loop() {
  if (!arduboy.nextFrame()) // Check time for next frame.
    return;
    
  switch (gameState) {
    case 0: // Title screen.
      DrawTitleScreen();
      if (arduboy.pressed(A_BUTTON) and !buttonA) {
        gameState = 1;
        buttonA = true;
      }
      break;
    case 1: // Gameplay.
      if (arduboy.pressed(RIGHT_BUTTON) and player1.velocityX < player1.velocityXMax) {
        player1.velocityX += player1.velocityStep;
        player1.directionX = 1;
      }
      if (arduboy.pressed(LEFT_BUTTON) and player1.velocityX > -player1.velocityXMax) {
        player1.velocityX += -player1.velocityStep;
        player1.directionX = -1;
      }

      // Jump:
      if (arduboy.pressed(A_BUTTON) and !buttonA) {
        player1.velocityY = -3 * 214;
        buttonA = true;
      }

      UpdateGameObjects();
      DrawGameObjects();
      
      // Reset game:
      if (arduboy.pressed(B_BUTTON) and !buttonB) {
        gameState = 0;
        player1.Reset();
        buttonB = true;
      }
      break;
  }
  
  if (arduboy.notPressed(A_BUTTON))
    buttonA = false;
  if (arduboy.notPressed(B_BUTTON))
    buttonB = false;
}

void UpdateGameObjects() {
  //player1.Update(levelMap);   // Update player.
}

void DrawGameObjects() {
  arduboy.clear();    // Clear the screen.

  levelMap.Draw();
  player1.Draw();     // Draw player.
  //arduboy.setCursor(0, 0);
  //arduboy.print();
  arduboy.display();  // Copy bitmap from frame buffer to display.
}

void DrawTitleScreen() {
  arduboy.clear();    // Clear the screen.
  arduboy.setCursor(48, 28);
  arduboy.print("Sprite");
  arduboy.display();  // Copy bitmap from frame buffer to display.
}


Bitmaps.h:
Code: [Select]
#pragma once
#include<Arduino.h>

// Player sprite.
const unsigned char playerRunRight[] PROGMEM = {
  // Width & Height:
  16, 16,
  // Frame 0:
  0x00, 0x00, 0x00, 0x00, 0x34, 0xfe, 0x8e, 0x34, 0x6, 0x36, 0x8e, 0x94, 0x60, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x2, 0x1e, 0xe6, 0xa3, 0xda, 0x83, 0xc4, 0xb8, 0x00, 0x00, 0x00, 0x00,
  // Frame 1:
  0x00, 0x00, 0x00, 0x00, 0x68, 0xf8, 0x1e, 0x68, 0xc, 0x6c, 0x1c, 0x24, 0xc0, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x2, 0xbf, 0xcd, 0xc6, 0xb4, 0x86, 0x89, 0xf1, 0x80, 0x00, 0x00, 0x00,
  // Frame 2:
  0x00, 0x00, 0x00, 0x10, 0x3a, 0xfc, 0x8c, 0x36, 0x6, 0x36, 0x8c, 0x9c, 0x60, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xf0, 0xfc, 0x66, 0x23, 0x5a, 0x43, 0xc4, 0xe8, 0x70, 0x00, 0x00, 0x00,
  // Frame 3:
  0x00, 0x00, 0x00, 0xa, 0x1e, 0x7e, 0x46, 0x9b, 0x3, 0x9b, 0x46, 0x4c, 0x30, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x30, 0x7c, 0x7e, 0x33, 0x11, 0x2d, 0x21, 0x62, 0x74, 0x38, 0x18, 0x00, 0x00,
  // Frame 4:
  0x00, 0x00, 0x00, 0x6, 0xf, 0x3f, 0xa3, 0xcd, 0x81, 0xcd, 0x23, 0x27, 0x18, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x1c, 0x3e, 0x1f, 0x19, 0x8, 0x16, 0x10, 0x11, 0x3a, 0x1c, 0xe, 0x00, 0x00,
  // Frame 5:
  0x00, 0x00, 0x00, 0x4, 0xf, 0x3f, 0xa3, 0xcd, 0x81, 0xcd, 0x23, 0x27, 0x18, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x18, 0x3e, 0x3f, 0x19, 0x8, 0x16, 0x10, 0x31, 0x3a, 0x1c, 0xc, 0x00, 0x00,};

const unsigned char playerRunLeft[] PROGMEM = {
  // Width & Height:
  16, 16,
  // Frame 0:
  0x00, 0x00, 0x00, 0x60, 0x94, 0x8e, 0x36, 0x6, 0x34, 0x8e, 0xfe, 0x34, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xb8, 0xc4, 0x83, 0xda, 0xa3, 0xe6, 0x1e, 0x2, 0x00, 0x00, 0x00, 0x00,
  // Frame 1:
  0x00, 0x00, 0x00, 0xc0, 0x24, 0x1c, 0x6c, 0xc, 0x68, 0x1e, 0xf8, 0x68, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x80, 0xf1, 0x89, 0x86, 0xb4, 0xc6, 0xcd, 0xbf, 0x2, 0x00, 0x00, 0x00, 0x00,
  // Frame 2:
  0x00, 0x00, 0x00, 0x60, 0x9c, 0x8c, 0x36, 0x6, 0x36, 0x8c, 0xfc, 0x3a, 0x10, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x70, 0xe8, 0xc4, 0x43, 0x5a, 0x23, 0x66, 0xfc, 0xf0, 0x00, 0x00, 0x00, 0x00,
  // Frame 3:
  0x00, 0x00, 0x00, 0x30, 0x4c, 0x46, 0x9b, 0x3, 0x9b, 0x46, 0x7e, 0x1e, 0xa, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x18, 0x38, 0x74, 0x62, 0x21, 0x2d, 0x11, 0x33, 0x7e, 0x7c, 0x30, 0x00, 0x00, 0x00,
  // Frame 4:
  0x00, 0x00, 0x00, 0x18, 0x27, 0x23, 0xcd, 0x81, 0xcd, 0xa3, 0x3f, 0xf, 0x6, 0x00, 0x00, 0x00,
  0x00, 0x00, 0xe, 0x1c, 0x3a, 0x11, 0x10, 0x16, 0x8, 0x19, 0x1f, 0x3e, 0x1c, 0x00, 0x00, 0x00,
  // Frame 5:
  0x00, 0x00, 0x00, 0x18, 0x27, 0x23, 0xcd, 0x81, 0xcd, 0xa3, 0x3f, 0xf, 0x4, 0x00, 0x00, 0x00,
  0x00, 0x00, 0xc, 0x1c, 0x3a, 0x31, 0x10, 0x16, 0x8, 0x19, 0x3f, 0x3e, 0x18, 0x00, 0x00, 0x00,};

const unsigned char playerIdleRight[] PROGMEM = {
  // Width & Height:
  16, 16,
  // Frame 0:
  0x00, 0x00, 0x00, 0x00, 0x34, 0xfe, 0x8e, 0x34, 0x6, 0x36, 0x8e, 0x94, 0x60, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x2, 0x1e, 0xe6, 0xa3, 0xda, 0x83, 0xc4, 0xb8, 0x00, 0x00, 0x00, 0x00,
  // Frame 1:
  0x00, 0x00, 0x00, 0x00, 0x34, 0xfe, 0x8e, 0x34, 0x6, 0x36, 0x8e, 0x94, 0x60, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x1a, 0x3c, 0xcc, 0xe7, 0x96, 0x87, 0xc4, 0xf8, 0x00, 0x00, 0x00, 0x00,
  // Frame 2:
  0x00, 0x00, 0x00, 0x00, 0x68, 0xf8, 0x1e, 0x68, 0xc, 0x6c, 0x1c, 0x24, 0xc0, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x34, 0x4f, 0xcd, 0xe6, 0x84, 0x86, 0xc9, 0xf1, 0x30, 0x00, 0x00, 0x00,
  // Frame 3:
  0x00, 0x00, 0x00, 0x00, 0x68, 0xfc, 0x1c, 0x68, 0xc, 0x6c, 0x1c, 0x28, 0xc0, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x8, 0x3f, 0xe7, 0xb2, 0xca, 0x82, 0xc5, 0xb9, 0x20, 0x00, 0x00, 0x00,};

const unsigned char playerIdleLeft[] PROGMEM = {
  // Width & Height:
  16, 16,
  // Frame 0:
  0x00, 0x00, 0x00, 0x60, 0x94, 0x8e, 0x36, 0x6, 0x34, 0x8e, 0xfe, 0x34, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xb8, 0xc4, 0x83, 0xda, 0xa3, 0xe6, 0x1e, 0x2, 0x00, 0x00, 0x00, 0x00,
  // Frame 1:
  0x00, 0x00, 0x00, 0x60, 0x94, 0x8e, 0x36, 0x6, 0x34, 0x8e, 0xfe, 0x34, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xf8, 0xc4, 0x87, 0x96, 0xe7, 0xcc, 0x3c, 0x1a, 0x00, 0x00, 0x00, 0x00,
  // Frame 2:
  0x00, 0x00, 0x00, 0xc0, 0x24, 0x1c, 0x6c, 0xc, 0x68, 0x1e, 0xf8, 0x68, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x30, 0xf1, 0xc9, 0x86, 0x84, 0xe6, 0xcd, 0x4f, 0x34, 0x00, 0x00, 0x00, 0x00,
  // Frame 3:
  0x00, 0x00, 0x00, 0xc0, 0x28, 0x1c, 0x6c, 0xc, 0x68, 0x1c, 0xfc, 0x68, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x20, 0xb9, 0xc5, 0x82, 0xca, 0xb2, 0xe7, 0x3f, 0x8, 0x00, 0x00, 0x00, 0x00,};

const unsigned char playerFallRight[] PROGMEM = {
  // Width & Height:
  16, 16,
  // Frame 0:
  0x00, 0x00, 0x00, 0xa, 0x1e, 0x7e, 0x46, 0x9b, 0x3, 0x9b, 0x46, 0x4c, 0x30, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x30, 0x7c, 0x7e, 0x33, 0x11, 0x2d, 0x21, 0x62, 0x74, 0x38, 0x18, 0x00, 0x00,
  // Frame 1:
  0x00, 0x00, 0x00, 0x00, 0x1e, 0x7f, 0x46, 0x9b, 0x3, 0x9b, 0x47, 0x4d, 0x30, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x30, 0x7c, 0x7e, 0x33, 0x11, 0x2d, 0x21, 0x62, 0x74, 0x38, 0x18, 0x00, 0x00,};

const unsigned char playerFallLeft[] PROGMEM = {
  // Width & Height:
  16, 16,
  // Frame 0:
  0x00, 0x00, 0x00, 0x30, 0x4c, 0x46, 0x9b, 0x3, 0x9b, 0x46, 0x7e, 0x1e, 0xa, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x18, 0x38, 0x74, 0x62, 0x21, 0x2d, 0x11, 0x33, 0x7e, 0x7c, 0x30, 0x00, 0x00, 0x00,
  // Frame 1:
  0x00, 0x00, 0x00, 0x30, 0x4d, 0x47, 0x9b, 0x3, 0x9b, 0x46, 0x7f, 0x1e, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x18, 0x38, 0x74, 0x62, 0x21, 0x2d, 0x11, 0x33, 0x7e, 0x7c, 0x30, 0x00, 0x00, 0x00,};
  
// Floor
const unsigned char floorTile[] = {
  // Width & Height:
  8, 8,
  // Frame 0:
  0xff, 0x81, 0x8d, 0x8d, 0x81, 0xa1, 0x83, 0xff,};

const unsigned char nullTile[] = {
  // Width & Height:
  8, 8,
  // Frame 0:
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};



andylatham82

PART 2:

Constants.h:
Code: [Select]
#pragma once

const char tileArraySize = 31;


LevelMap.cpp:
Code: [Select]
#include "Tile.cpp"
#include "Bitmaps.h"

class LevelMap {
  public:
    Tile tiles[2] = {Tile(nullTile),Tile(floorTile)};
    
    const unsigned char tileMap[2][8][16] = {
      { // Level 0:
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
      },
      { // Level 1:
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
      }
    };

    LevelMap(int a) {
      
    }

    Draw() {
      for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 16; j++) {
          if (tileMap[0][i][j] > 0)
            tiles[1].Draw(j * 8 * 256, i * 8 * 256);
        }
      }
    }
};


Player.cpp:
Code: [Select]
#include "Sprite.cpp"
//#include "Solid.cpp"
#include "Constants.h"
#include "Tile.cpp"

class Player {
  public:
    Sprite sprite;
    int screenPosX = 64;  // -32768 to 32767 (screen 128 pixels wide).
    int screenPosY = 0;  // -128 to 127 (screen only 64 pixels high).
    int posX = screenPosX * 256;  // 16-bit value.
    int posY = screenPosY * 256;
    int state = 0;      // 0 = idleRight
                        // 1 = idleLeft
                        // 2 = runRight
                        // 3 = runLeft
                        // 4 = fallRight
                        // 5 = fallLeft
    
    int velocityX = 0;
    int velocityY = 0;
    char velocityStep = 16;
    char gravity = 32;
    int velocityXMax = 256;
    int velocityYMax = 256;
    char directionX = 1;

  void Update() {
    char stateOld = state;
    if (velocityY == 0) {
      sprite.frameRate = 60;
      sprite.animationLength = 6;
      if (velocityX > 0)
        state = 2;
      else if (velocityX < 0)
        state = 3;
      else {
        sprite.frameRate = 15;
        sprite.animationLength = 4;
        if (directionX > 0)
          state = 0;
        if (directionX < 0)
          state = 1;
      }
    }
    else {
      sprite.frameRate = 45;
      sprite.animationLength = 2;
      if (directionX > 0)
        state = 4;
      if (directionX < 0)
        state = 5;
    }
    sprite.Update(stateOld == state);
    
    if (velocityY < (4 * 256))
      velocityY += gravity;
    if (velocityX >= 12)
      velocityX -= 12;
    else if (velocityX <= -12)
      velocityX += 12;
    else
      velocityX = 0;
    int tempX = posX + velocityX;
    int tempY = posY + velocityY;
    /*
    for (int i = 0; i < tileArraySize; i++) {
      // Bottom collision.
      if ((tempY >> 8) + sprite.height >= (tiles[i].posY >> 8)
          and (tempY >> 8) < (tiles[i].posY >> 8) + 8
          and (tempX >> 8) + 11 > tiles[i].posX >> 8
          and (tempX >> 8) + 5 < (tiles[i].posX >> 8) + 8) {
        posY += (tiles[i].posY - (sprite.height * 256)) - posY;
        velocityY = 0;
      }
      else {
      // Right collision.
      if ((tempX >> 8) + 11 >= (tiles[i].posX >> 8)
          and (tempX >> 8) + 5 < (tiles[i].posX >> 8) + 8
          and (tempY >> 8) + sprite.height > tiles[i].posY >> 8
          and (tempY >> 8) < (tiles[i].posY >> 8) + 8
          and directionX > 0) {
        posX += tiles[i].posX - (posX + (11 * 256));
        velocityX = 0;
      }
      // Left collision.
      if ((tempX >> 8) + 5 <= (tiles[i].posX >> 8) + 8
          and (tempX >> 8) + 5 > tiles[i].posX
          and (tempY >> 8) + sprite.height > tiles[i].posY >> 8
          and (tempY >> 8) < (tiles[i].posY >> 8) + 8
          and directionX < 0) {
        posX -= (posX + (5 * 256)) - (tiles[i].posX + (8 * 256));
        velocityX = 0;
      }
      }
    }
    */
    posX += velocityX;
    posY += velocityY;

    if (posY > (66 * 256))
      Reset();
  }
  void Draw() {
    switch (state) {
      case 0: // Idle Right
        sprite.Draw(posX, posY, playerIdleRight);
        break;
      case 1: // Idle Left
        sprite.Draw(posX, posY, playerIdleLeft);
        break;
      case 2: // Run Right
        sprite.Draw(posX, posY, playerRunRight);
        break;
      case 3: // Run Left
        sprite.Draw(posX, posY, playerRunLeft);
        break;
      case 4: // Fall Right
        sprite.Draw(posX, posY, playerFallRight);
        break;
      case 5: // Fall Left
        sprite.Draw(posX, posY, playerFallLeft);
        break;
    }
  }

  void Reset() {
    posX = 16384;
    posY = 0;
    velocityX = 0;
    velocityY = 0;
    directionX = 1;
  }
};


// Fixed Point 8.8
// ---------------
// 8 bits in a byte.
// In 8 bits (1 byte) you can store 0 - 255 (enough for a coordinate
// on the small screen.
// In 16 bits (2 bytes) you can store 0 - 65536. Use 1st byte for integer
// and 2nd byte for fraction.
// Increment the bottom 8 bits until it wraps around 256, and the upper
// 8 bits will increment by 1.
// Acts like a decimal: 0.0 to 255.255
// Use whole 16 bits to calculate velocities but only use the upper 8
// bits to render the object to the screen, discarding the lower 8 with
// the >> operator.
// 256 is 1 pixel per frame. 128 is 0.5 pixels per frame.



Sprite.cpp:
Code: [Select]
#include<Arduboy2.h>

#pragma once
class Sprite {
  public:
    Sprites sprites;

    // Sprite animation variables.
    unsigned int framesSinceAnimUpdate = 0; // Number of updates since last animation frame advance.
    unsigned int currentFrame = 0;          // Displayed frame.
    unsigned int animationLength = 6;       // Number of frames.
    unsigned int frameRate = 60;            // Sprite frame rate.
    unsigned int framesBetweenUpdates;      // Number of frames between sprite animation updates.
    unsigned int height = 16;
    unsigned int width = 16;
    bool active = true;
    
    Sprite() {
      // Calculate number of frames between animation updates.
      framesBetweenUpdates = round((1.0 / frameRate) * 100);
    }

    void Update(bool sameState) {
      if (active) {
        framesBetweenUpdates = round((1.0 / frameRate) * 100);
        // Advance the sprite animation frame.
        framesSinceAnimUpdate++;
        if (framesSinceAnimUpdate > framesBetweenUpdates){
          currentFrame = (currentFrame + 1) % animationLength;
          framesSinceAnimUpdate = 0;
        }
        if (!sameState)
          currentFrame = 0;
      }
    }

    void Draw(int posX, int posY, const unsigned char bitmap[]) {
      sprites.drawSelfMasked(posX >> 8, posY >> 8, bitmap, currentFrame);
    }
};


Tile.cpp:
Code: [Select]
#pragma once
#include "Sprite.cpp"
#include "Bitmaps.h"

class Tile {
  public:
    Sprite sprite;
    unsigned char tileTexture[10];

    Tile(const unsigned char texture[]) {
      memcpy(tileTexture, texture, sizeof(texture));
    }

    void Update() {
      sprite.active = false;
      sprite.Update(true);
    }
    
    void Draw(int posX, int posY) {
      sprite.Draw(posX, posY, tileTexture);
    }
};

andylatham82

I just realised I can attach my code in a zip file. D'oh!

arduarn

#11
Nov 20, 2017, 03:55 pm Last Edit: Nov 20, 2017, 04:22 pm by arduarn
Code: [Select]
   Tile(const unsigned char texture[]) {
      memcpy(tileTexture, texture, sizeof(texture));
    }

You seem to have changed this from your original post. I think the original post was correct in using the member variable tileTexture for the sizeof. When an array is passed as a parameter to a function, the size information is lost and a sizeof will just return the size of a pointer.

"Is this definitely the problem? I ask because if I remove the PROGMEM command entirely, I still can't get the correct result."

It's definitely a problem, but may well not be the only problem.
Oops, definitely was too strong a word...the Arduboy2 library that you are using seems to be expecting the bitmaps to be in flash and uses the appropriate helper functions.

Code: [Select]
    unsigned char tileTexture[10];

    Tile(const unsigned char texture[]) {
      memcpy(tileTexture, texture, sizeof(texture));
    }

The tile bitmaps are constant and are stored in flash (they have to be by the looks of things), so I think you are wrong to try to copy them into RAM (you would have to use a pgm_* function to do so anyway).
Make the bitmaps PROGMEM again. Remove the memcpy() and just store a pointer to the const bitmap data in the Tile.
See if that makes any difference.

andylatham82

#12
Nov 20, 2017, 04:57 pm Last Edit: Nov 20, 2017, 05:02 pm by andylatham82
Ok so I've implemented pointers now, avoiding copying the array.

LevelMap.cpp:
Code: [Select]
#include "Tile.cpp"
#include "Bitmaps.h"

class LevelMap {
  public:
    const unsigned char *pTileTexture = floorTile;
    
    Tile tiles[2] = {Tile(pTileTexture),Tile(pTileTexture)};
    
    const unsigned char tileMap[2][8][16] = {
      { // Level 0:
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
      },
      { // Level 1:
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
      }
    };

    LevelMap(int a) {
      
    }

    Draw() {
      for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 16; j++) {
          if (tileMap[0][i][j] > 0)
            tiles[1].Draw(j * 8 * 256, i * 8 * 256);
        }
      }
    }
};


Tile.cpp:
Code: [Select]
#pragma once
#include "Sprite.cpp"
#include "Bitmaps.h"

class Tile {
  public:
    Sprite sprite;
    unsigned char *pTileTexture;

    Tile(const unsigned char pTexture) {
      pTileTexture = pTexture;
    }

    void Update() {
      sprite.active = false;
      sprite.Update(true);
    }
    
    void Draw(int posX, int posY) {
      sprite.Draw(posX, posY, *pTileTexture);
    }
};


The result is something very close to how the level should look, but is mangling the graphics. I'm not 100% sure if I'm using the pointers right, I haven't used them much before. I've attached an image of the screen.

arduarn

The result is something very close to how the level should look, but is mangling the graphics. I'm not 100% sure if I'm using the pointers right, I haven't used them much before. I've attached an image of the screen.
The pointer dereference is IMO incorrect... I'm a little confused as to why it would compile...
The original LevelMap.cpp was OK, no need to worry about pointers there; floorTile and nullTile will automatically collapse to a pointer when required.

Try this:
Code: [Select]
    const unsigned char *pTileTexture;

    Tile(const unsigned char *pTexture) :  pTileTexture(pTexture) {
    }
   
    void Draw(int posX, int posY) {
      sprite.Draw(posX, posY, pTileTexture);
    }

andylatham82

Try this:
Code: [Select]
   const unsigned char *pTileTexture;

    Tile(const unsigned char *pTexture) :  pTileTexture(pTexture) {
    }
    
    void Draw(int posX, int posY) {
      sprite.Draw(posX, posY, pTileTexture);
    }

This worked! Thank you! I didn't understand the modification to the Tile constructor at first, as I thought the colon was used to pass parameters to an inherited class, but I now understand that this is the only way to modify the value of a const variable. Is that right? Is it necessary for the unsigned char to be const though? I tested without it being const and putting pTileTexture = pTexture in the body of the constructor and it still works. Is it just a case of being best practice to keep types consistent?

The other thing I'm slightly unsure of is the dereferencing. Why do I not need the dereferencing operator (*) when calling the sprite.Draw function? Is it because the pTileTexture pointer is pointing at an array?

Thanks so much for helping me with this.

Go Up