Tilemap drawing question

Hi,

Still very new to Playdate development and just trying to wrap my head around all the different systems. Currently trying to figure out how tilemaps work.

I can create and draw the tilemap no problem. But I seem to be running into a weird thing in that depending on where I put the tilemap:draw() function, it may or may not be visible.

Also, if I try to use tilemap:setTileAtPosition(), I can't figure out how to make the drawn tilemap update to reflect the new tile at the position. I assume I need to call tilemap:draw() again but, if I do that, the whole tilemap just disappears.

I assume this is because I simply don't understand how Playdate does drawing/clearing properly.

Code for where I make my tilemap originally (not part of any function):

local pd <const> = playdate
local gfx <const> = pd.graphics

tilesheet = gfx.imagetable.new("images/tilemap")
tiles = {1,1,1,1,2,5,3,2,4,4,4,4,10,10,10,10}
tm = gfx.tilemap.new()
tm:setImageTable(tilesheet)
tm:setTiles(tiles, 4)
tm:draw(0, 0)

Code where I try to update the tilemap with a button press inside playdate.update():

function pd.update() 
	
	if pd.buttonJustPressed(pd.kButtonA) then
		tm:setTileAtPosition(2, 2, 6)
		tm:draw(0, 0)
	end
	
	gfx.sprite.update()
	pd.timer.updateTimers()
end

When I press A, the tile at 2,2 does not turn to a 6, but the whole tilemap disappears.
tilemapbug

Any help appreciated!

Taking exactly your pasted code and supplying my own tilemap-table-8-8.png, it works. I press A and 2,2 changes to 6.

I added code to switch back to 5 when B is pressed. I don't have a little sprite dude :slight_smile: Do you have a gfx.clear call somewhere near where you render the sprite?
tilemap

1 Like

Hey thanks for helping out!

Sorry I didn't include the player code. Was trying not to spam. Interestingly, if I commend out the two lines that add the little guy (just creating an instance of the class and then add()-ing it) then I instead get this slightly better result where the new tile draws over the old one:

tilesheet2

The answer there is probably at least simply to not have transparent pixels in the tilesheet, which is fine.

The Player class has no gfx.clear() calls, so still not sure what's causing it. If you are happy to have a look, here's the full project. I can't upload a zip as a new user, apparently, so here is the full code. Thanks a lot!

main.lua

import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"
import "CoreLibs/math"
import "CoreLibs/crank"
import "CoreLibs/animation"

import "player"

local pd <const> = playdate
local gfx <const> = pd.graphics

tilesheet = gfx.imagetable.new("images/tilemap")
tiles = {1,1,1,1,2,5,3,2,4,4,4,4,10,10,10,10}
tm = gfx.tilemap.new()
tm:setImageTable(tilesheet)
tm:setTiles(tiles, 4)
tm:draw(0, 0)

player = Player(100, 60, gfx.imagetable.new("images/player"))
player:add()


function pd.update() 
	
	if pd.buttonJustPressed(pd.kButtonA) then
		tm:setTileAtPosition(2, 2, 6)
		tm:draw(0, 0)
	end
	
	gfx.sprite.update()
	pd.timer.updateTimers()
end

player.lua (I assume I've done the animation code in a really silly way as well lol but it does actually work at least haha)

import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"
import "CoreLibs/math"
import "CoreLibs/animation"

local pd <const> = playdate
local gfx <const> = pd.graphics

class('Player').extends(gfx.sprite)

function Player:init(x, y, spritesheet)
	self:moveTo(x, y)
	
	self.walk_spd = 2
	self.anim_spd = 100
	
	self.setImageTable = spritesheet
	self.currentAnimation = gfx.animation.loop.new()
	
	self.walkDownAnimation = gfx.animation.loop.new()
	self.walkDownAnimation:setImageTable(self.setImageTable)
	self.walkDownAnimation.startFrame = 3
	self.walkDownAnimation.endFrame = 4
	self.walkDownAnimation.delay = self.anim_spd
	
	self.walkUpAnimation = gfx.animation.loop.new()
	self.walkUpAnimation:setImageTable(self.setImageTable)
	self.walkUpAnimation.startFrame = 5
	self.walkUpAnimation.endFrame = 6
	self.walkUpAnimation.delay = self.anim_spd
	
	self.walkLeftAnimation = gfx.animation.loop.new()
	self.walkLeftAnimation:setImageTable(self.setImageTable)
	self.walkLeftAnimation.startFrame = 9
	self.walkLeftAnimation.endFrame = 10
	self.walkLeftAnimation.delay = self.anim_spd
	
	self.walkRightAnimation = gfx.animation.loop.new()
	self.walkRightAnimation:setImageTable(self.setImageTable)
	self.walkRightAnimation.startFrame = 7
	self.walkRightAnimation.endFrame = 8
	self.walkRightAnimation.delay = self.anim_spd
	
	self.idleAnimation = gfx.animation.loop.new()
	self.idleAnimation:setImageTable(self.setImageTable)
	self.idleAnimation.startFrame = 1
	self.idleAnimation.endFrame = 1
	self.idleAnimation.delay = self.anim_spd
	
	self:setImage(self.walkRightAnimation:image())
	
	self.currentAnimation = self.walkRightAnimation
	
end

function Player:update()
	Player.super.update(self)
	
	px = 0
	py = 0
	
	--get inputs
	if pd.buttonIsPressed(pd.kButtonLeft) then
		px = -self.walk_spd
	elseif pd.buttonIsPressed(pd.kButtonRight) then
		px = self.walk_spd
	end
	if pd.buttonIsPressed(pd.kButtonUp) then
		py = -self.walk_spd
	elseif pd.buttonIsPressed(pd.kButtonDown) then
		py = self.walk_spd
	end
	
	--move
	self:moveBy(px, py)
	
	--determine animation
	if not (px == 0) then
		if px < 0 then
			self.currentAnimation = self.walkLeftAnimation
		else
			self.currentAnimation = self.walkRightAnimation
		end
	elseif not (py == 0) then
		if py < 0 then
			self.currentAnimation = self.walkUpAnimation
		else
			self.currentAnimation = self.walkDownAnimation
		end
	else
		self.currentAnimation = self.idleAnimation
	end
	
	
	self:setImage(self.currentAnimation:image())
	
end

You should create a sprite for your tilemap (there is a setTilemap method on Sprite class). Otherwise it will be erased by the method that update and draw sprites.

1 Like

Ah great, thank you! I didn't understand that bit of the process. So rather than simply drawing the tilemap, you can generate a sprite that uses the tilemap and draw that instead. Got it.

I did that and it works perfectly now.

tilemap3

Thanks both for your help!

I've placed my full revised main.lua code below just in case it's useful for anyone else:

import "CoreLibs/object"
import "CoreLibs/graphics"
import "CoreLibs/sprites"
import "CoreLibs/timer"
import "CoreLibs/math"
import "CoreLibs/crank"
import "CoreLibs/animation"

import "player"

local pd <const> = playdate
local gfx <const> = pd.graphics

tilesheet = gfx.imagetable.new("images/tilemap")
tiles = {1,1,1,1,2,5,3,2,4,4,4,4,10,10,10,10}
tm = gfx.tilemap.new()
tm:setImageTable(tilesheet)
tm:setTiles(tiles, 4)
--tm:draw(0, 0)

player = Player(100, 60, gfx.imagetable.new("images/player"))
player:add()

background = gfx.sprite.new(tm)
background:setCenter(0, 0)
background:add()

function pd.update() 
	
	if pd.buttonJustPressed(pd.kButtonA) then
		tm:setTileAtPosition(2, 2, 6)
		--tm:draw(0, 0)
	end
	
	gfx.sprite.update()
	pd.timer.updateTimers()
end
3 Likes

Just to explain what was probably happening initially: when you mix "regular" drawing with sprites, you will generally want to do your regular drawing after your call to gfx.sprite.update(). This is because gfx.sprite.update() will first clear any areas that have been marked dirty with your background color.

So I suspect in your case your tilemap was drawing fine, but then was being drawn over with the default white background color when gfx.sprite.update() was called.

Moving all of your drawing to sprites is a good solution :+1: and means you don't have to worry about that stuff.

2 Likes

Ah great, that makes sense! Thanks for explaining!