Storing images or image data on SD (gaming)

Status
Not open for further replies.

Duhjoker

Well-known member
Storing images or image data on SD / teensy gaming console

Ok let's try this again. Most of you know I've been working on an a teensy gaming project that allows any one to build their own hand held gaming console and library for creating games. Every thing is working well enough but I'm using way to much storage to create world maps. At first I thought maybe compression would work. But then it hit me after looking at some old Gamebuino posts. Why not use the T3.6's SD card. I've done some looking but can't really find what I'm looking for.

So I'm asking you guys....... Is there a way that I can store my tilemap data that exists in c-code array on the SD so it's not taking up all my storage space. Maybe a way to have the draw commands look at the SD for the info it needs?

Any ideas?
 
Last edited:
Stupid me forgot to include sample code for my problem..........

For those of you not familiar with my library it can be found here

github.com/Duhjoker/GameR-Iot_T3

So here is the exact problem. I have a function called tilemap that allows you to create a sprite list or list of bitmaps that gives each bitmap an integer that can then be used to create a tile map by putting the integers in a c code array which can be called to make a world map.

here is that function....
Code:
boolean GrafxT3::getBitmapPixel(const uint8_t* bitmap, uint16_t x, uint16_t y){
	return pgm_read_byte(bitmap + 2 + y * ((pgm_read_byte(bitmap) + 7) / 8) + (x >> 3)) & (B10000000 >> (x % 8));
}

void GrafxT3::drawTilemap(int x, int y, const uint16_t *tilemap, const uint8_t **spritesheet, const uint16_t * palette){
	drawTilemap(x, y, tilemap, spritesheet, 0, 0, GrafxT3_TFTHEIGHT, GrafxT3_TFTWIDTH, palette);
}

void GrafxT3::drawTilemap(int x, int y, const uint16_t *tilemap, const uint8_t **spritesheet, uint16_t dx, uint16_t dy, uint16_t dw, uint16_t dh, const uint16_t * palette){
 //  uint8_t tilemap_width = pgm_read_byte(tilemap);
//   uint8_t tilemap_height = pgm_read_byte(tilemap + 1);
//   uint8_t tile_width = pgm_read_byte(tilemap + 2);
//   uint8_t tile_height = pgm_read_byte(tilemap + 3);
//   tilemap += 4; // now the first tiyleis at tilemap
 uint16_t tilemap_width = pgm_read_byte(tilemap)* 256 + pgm_read_byte(tilemap + 1);
 uint16_t tilemap_height = pgm_read_byte(tilemap + 2)* 256 + pgm_read_byte(tilemap + 3);
 uint16_t tile_width = pgm_read_byte(tilemap + 4);
 uint16_t tile_height = pgm_read_byte(tilemap + 5);
 tilemap += 6; // now the first tile is at tilemap

	uint16_t ddw = dw + dx;
	uint16_t ddh = dh + dy;
	uint16_t maxDdx = (dw - x + tile_width - 1) / tile_width;
	uint16_t maxDdy = (dh - y + tile_height - 1) / tile_height;
	if (tilemap_width < maxDdx){
		maxDdx = tilemap_width;
	}
	if (tilemap_height < maxDdy){
		maxDdy = tilemap_height;
	}
	int16_t startDdx = (-x) / tile_width;
	int16_t startDdy = (-y) / tile_height;
	if (startDdx < 0){
		startDdx = 0;
	}
	if (startDdy < 0){
		startDdy = 0;
	}
	if (flagcollision)numcolision = 0;                                 //Line 735 - clear numcolision - ADD by Summoner123

	for (uint16_t ddy = startDdy; ddy < maxDdy; ddy++){
		for (uint16_t ddx = startDdx; ddx < maxDdx; ddx++){
			int16_t drawX = ddx*tile_width + x + dx;
			int16_t drawY = ddy*tile_height + y + dy;
			uint16_t tile = pgm_read_byte(tilemap + ddy*tilemap_width + ddx);
			if (drawX >= dx && drawY >= dy && drawX <= (ddw - tile_width) && drawY <= (ddh - tile_height)){
				writeRectNBPP(drawX, drawY,tile_width, tile_height, 4, spritesheet[tile], palette );

				if (flagcollision){
					solid[numcolision].x = drawX;                     //Save X coordinate      - ADD by Summoner123
					solid[numcolision].y = drawY;                     //Save Y coordinate      - ADD by Summoner123
					solid[numcolision].spritecol = spritesheet[tile]; //Save Sprite of tile    - ADD by Summoner123
					numcolision++;                                    //Increment numcolision  - ADD by Summoner123
				}
			}
			else{ // we need to draw a partial bitmap
				writeRect4BPPtm(drawX, drawY, tile_width, tile_height, spritesheet[tile], dx, dy, dw, dh, palette);
			}
		}
	}
}

heres an example of the spritelist

Code:
const byte *spritesheet[] = { blank_tile,
bedtop, bedbot, bones11, bones12, bones13, bones21, bones22, bones23, bookstop, booksbot,                                                                 ////////10
cabtop, cabbot, cactus, chair, chimneytop, chimneybot, coconut, couchtop, couchbot, debris,                                                               ////////20
dish11, dish12, dish13, dish21, dish22, dish23, dish31, dish32, dish33, dishv2,                                                                           ////////30
floort, machine1t, machine1b, machine2t, machine2b, machine3t, machine3b, machine4t, machine4b, machine511, etc};

and here is the c code array made from it

Code:
const uint16_t weatherstation1[] = {0,25,0,25,
16,16,
74, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 75,
73, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 72,
73, 83, 83, 83, 83, 83, 83, 83, 83, 83, 15, 15, 15, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 72, 
73, 83, 83, 92, 93, 83, 83, 83, 83, 83, 15, 15, 15, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 72, 
73, 34, 34, 94, 95, 32, 32, 32, 83, 83, 15, 15, 15, 83, 38, 38, 83, 32, 32, 32, 83, 83, 83, 83, 72, 
73, 35, 35, 96, 97, 33, 33, 33, 30, 31, 16, 16, 16, 65, 39, 39, 65, 33, 33, 33, 30, 21, 22, 23, 72,
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 24, 25, 26, 72, 
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 27, 28, 29, 72, 
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 72, 
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 72, 
73, 31, 31, 31, 14, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 65, 72,
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 65, 65, 65, 31, 31, 31, 31, 31, 31, 31, 65, 65, 72,
73, 31, 31, 31, 31, 89, 90, 31, 31, 31, 31, 31, 78, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 77,
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 80, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 75,
73, 31, 31, 31, 31, 31, 31, 14, 31, 31, 31, 31, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 72,
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 83, 72,
73, 18, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 83, 34, 34, 9, 36, 36, 9, 32, 32, 83, 83, 83, 72,
73, 19, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 30, 35, 35, 10, 37, 37, 10, 33, 33, 40, 41, 42, 72,
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 43, 44, 45, 72, 
73, 31, 31, 31, 31, 91, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 46, 47, 48, 72,
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 72,
73, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 72,
73, 65, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 65, 72,
73, 65, 65, 31, 31, 31, 31, 31, 31, 31, 105, 105, 105, 105, 105, 31, 31, 31, 31, 31, 31, 31, 65, 65, 72,
76, 70, 70, 70, 70, 70, 70, 70, 70, 70, 105, 105, 105, 105, 105, 70, 70, 70, 70, 70, 70, 70, 70, 70, 77};


so I'm looking for a way to store this data on the sd
 
Isn't there a short tutorial for teensy displaying images from the SD? I remember a pjrc page but can't seem to find it using Google
 
thank you i think thats what im looking for though i could have swore there was a teensy example.

Glad to have you on board. I have a case for the 2.8tft but it also uses tactile switches. If your intetested heres the link....

https://www.thingiverse.com/thing:2404745

Got any ideas on conserving space using tilemap?
 
What is the issue with the tilemap? Can't it be stored in progmem instead of ram? Sorry I haven't read through your code yet.

I was assuming compiled games could be stored as .hex files on the sdcard (along with any bitmap files needed) and loaded on demand similar to gamebuino. Bitmaps could include a spritesheet and the tilemap could just be indexes into the spritesheets. Same style as https://www.lexaloffle.com/pico-8.php

Here's the loader:
https://github.com/Rodot/Gamebuino/tree/master/examples/4.Utilities/Loader

And the load_game macro:
https://github.com/Rodot/Gamebuino/blob/master/Gamebuino.h#L33
Not sure what the teensy equivalent would be for that.

Once I get my hardware working I'll start coding. I plan on porting this game I wrote a while ago: https://github.com/AnthonyDiGirolamo/heliopause.pico-8
 
The tilemap functions were written for the Gamebuino to accommodate the 32kb progmem chip. It's supposed to be really cheap on storage and it is for mono color tilemaps. The color part seems to be the problem but I don't understand why. The array data should be a whole lot cheaper.

As it stands currently, a map the size of 408x408 costs 36% of the flash. Add all the villages and houses and it adds up to around 68% of flash. I would really like the game to be played by t3.5 and t3.2 would be even better.

Right now I'm storing every thing in the teensy 3.6's huge flash storage. I've tried to store in progmem but it had no effect. It still used the flash. Or it still counted it as being stored in flash from what I could tell from the arduino IDE

To be honest I'm having a hard time learning c++ but I thinks that due to manuals written by human computers. Lol. Need a for dummies guide. So I wouldn't be sure where to start on the boot loaders to get them to work with teensy. Sure would be fun to have though so the teensy could hold multiple amounts of games.
 
Last edited:
Yeah I'm mainly doing this to get used to c++ on embedded hardware.

Ok I'm just typing this as I think about it. Let's say you have a sprite sheet of 8x8 sprites stored as a 320x240 bmp on the SD card. That gives you 40x30 sprites or 1200 sprites total. Sprites can be defined as a single uint16_t index that will be < 1200. So the map would only need to be a single array of uint16_t vars. If it's 408x408 then the array should be 166464 long. Something like:
const uint16_t PROGMEM map[166464]; I'd assume that would be 332,928 bytes total no matter if a cell in the map is set or not. So the math checks out for the Teensy 3.6 1mb of flash.

You can probably get a way with much less for a game. 240x136 for example, similar to TIC-80: https://github.com/nesbox/TIC-80/wiki#specification That would give you 32640 map sprites (1920x1088 pixels total) which would take up 65,280 or so bytes in flash.
 
My plan was to try to port most of the TIC-80 features as possible and use code from the gamebuino repo where applicable and FrankB's ILI9341 optimizations.
 
The tilemap functions were written for the Gamebuino to accommodate the 32kb progmem chip. It's supposed to be really cheap on storage and it is for mono color tilemaps. The color part seems to be the problem but I don't understand why. The array data should be a whole lot cheaper.

As it stands currently, a map the size of 408x408 costs 36% of the flash. Add all the villages and houses and it adds up to around 68% of flash. I would really like the game to be played by t3.5 and t3.2 would be even better.

Right now I'm storing every thing in the teensy 3.6's huge flash storage. I've tried to store in progmem but it had no effect. It still used the flash. Or it still counted it as being stored in flash from what I could tell from the arduino IDE

To be honest I'm having a hard time learning c++ but I thinks that due to manuals written by human computers. Lol. Need a for dummies guide. So I wouldn't be sure where to start on the boot loaders to get them to work with teensy. Sure would be fun to have though so the teensy could hold multiple amounts of games.

Array's are literal bytes of the needed info - they are just the bits needed to add color versus mono - so 408x408 field and a byte per element for color is 166464 bytes - double that for 16 bits and 100% efficient - but not compressed leaves it directly usable but huge.

PROGMEM == FLASH! Noted this some months back - AVR differentiates RAM and FLASH as separate memory address areas, using PROGMEM says force to FLASH and then for the transfer at runtime to RAM for use. On ARM/Teensy - one 32 bit address space covers all, constants are pushed to FLASH area and can be read and used directly.

c/c++ is vast and cool - just have to start with simple and build. There are noob books and tutorials - plus all the samples . . . jumping into the middle of code like this is likely a bit much to wrap one's head around.

Bootloaders are a big jump - indeed Frank did that and it was very stable - I ran many iterations of having it work. But as implemented only pulls in 50% of FLASH space. And always the risk of messing it up and perhaps bricking the processor. I just ran to test and see it - but never looked into the magic under the covers before Frank parked it. There are other implementations out there perhaps - but not supported.
 
Oh I'm learning. I read any thing some one links me to. The last thing was operators or bit shifting. Haven't really used << or >> but I use && a lot.

The tilemap functions are efficient though just not as effecient as I want to be. Say I make a village with 10 houses or homes. If I make the inside of the room only 20x25, it only raises the percentage by about 3/4 of a point. The main city took up a lil more than I wanted. All the buildings had two to three levels plus a huge palace consisting of 4 floors and rooms.

I don't understand why Frank's boot loader is so in-effecient. The boot loader for the Gamebuino works with multiple games installed on the SD.

Maybe y'all can point me in the right direction here. I'm at the point in my RPG that I would like to start doing random battles. I have a lot of work done but I'm stuck at how to make the battle operations use and save to structures. For example the player will have a structure like so

Player struct = {

120, //player health
5, //defense
100, //gold
}

I know that's incorrect but it displays what I'm asking for. Any way so when the random battle starts and I choose fight I need to be able to save the current structure adjustments every time a hit is given or taken. Team ARG has an RPG called arduventure for the arduboy but it's battle code is way over my head.

I don't know what I'm asking for or need to know in order to do so. If any one has any links that might help I would really appreciate it. Like really really


hers what I drew up so far

Code:
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
 if(count == 0)
{
x = random(0,320);
y = random(0,240);
count = 1;
}

Rect rectA {x,y,16,16};
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))
{
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
  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");
  tft.writeRectNBPP(0,130,26,26,4,palette);
///////////////////////////////////////////////////////////////////////////////
////////////////////////////Up/////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
 if (ButtonUp.fallingEdge()){
     tft.writeRectNBPP(cursor_x,cursor_y,16,16,4,cursordot,palette);
     cursor_y -= 26;
     if(checkcolision())
     {
      cursor_y += 26;} 
     }
     if(cursor_y <= 136){
        cursor_y = 136;}
          
//////////////////////////////////////////////////////////////////////////////
///////////////////////////////Down///////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
 if (ButtonDown.fallingEdge()){
   tft.writeRectNBPP(cursor_x, cursor_y,16,16,4,cursordot,palette);
   cursor_y += 26;
    if(checkcolision())
    {
    cursor_y -= 26;}
    }
    if(player_y >= 240){
       player_y = 240;}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
if (player_direction == 1){
  tft.writeRectNBPP(cursor_x, cursor_y,26,26,4,cursordot,palette);
}
       
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

      if ((ButtonA.fallingEdge() && tft.collideRectRect( rectC.x, rectC.y, rectC.width, rectC.height, rectG.x, rectG.y, rectG.width, rectG.height))
{
 do some thing 
}
else if ((ButtonA.fallingEdge() && tft.collideRectRect( rectD.x, rectD.y, rectD.width, rectD.height, rectG.x, rectG.y, rectG.width, rectG.height))
{
 do some thing 
}
else if ((ButtonA.fallingEdge() && tft.collideRectRect( rectE.x, rectE.y, rectE.width, rectE.height, rectG.x, rectG.y, rectG.width, rectG.height))
{
 do some thing 
} 
else if ((ButtonA.fallingEdge() && tft.collideRectRect( rectF.x, rectF.y, rectF.width, rectF.height, rectG.x, rectG.y, rectG.width, rectG.height))
{
 do some thing 
}
  
          }
 };

believe it or not I actually wrote that function myself
 
...
I don't understand why Frank's boot loader ...

Indeed you don't - neither do I - and I paged across the code at least once. Bootloader operations on ARM are processor specific. The linked work is great for loading - multiple images on SD was great as I tested it. It is the processor usage that limits that implementation to 50% of FLASH burning. If it were only a little bit complex it would have been solved given Frank's skill. But it was risky to bricking a Teensy and wasn't in big demand and was something only PJRC could offer as a supported solution - which there was a hint of that dropped down in priority on the work list.

As far as game save and restore - any volatile run time variable that affects the game state [ location, achievements, bad guys alive or dead, hidden items, touched squares, ... ??? ] would have to be saved and restored to pick up where left off. A structure for that would have a fixed size - perhaps it would write to SD and restore from there same as bitmaps are read/written - as a single structure or a group of things to save and restore..
 
Made some progress on the hardware this weekend.
It still needs the audio board and a 3d printed case.

Screenshot_20171112-222413_edited.jpgScreenshot_20171112-222347_edited.jpgScreenshot_20171112-222431_edited.jpg
 
Here's the list so far:

I'm planning on adding the Audio Board with volume potentiometer.

Still finalizing the wiring for everything. I'll update with a table of the connections used.

Screenshot_2017-11-11_10-21-32-01.jpg

Edit: You could replace the Teensy and power management stuff with a single one of these Adafruit HUZZAH32 – ESP32 Feather Board. It plugs in right into the back of the TFT.
 
Last edited:
Can i ask what you might be using the stepup/stepdown? We learned at the beginning of the project that using 5v to the screen causes it to heat up enough that the screen will melt and warp 3D printed plastic. Pla at least. Ive been using straight lipo to teensy and tft led pin and drawing 3.3v for the tfts main power from the teensys 3.3v pin.

3.7v is pretty bright for the led pin. And saves a lil power.
 
Status
Not open for further replies.
Back
Top