Right, I haven't programmed games in ages. I forgot.
Turns out you can use Tilemaps and ImageTables: they are quite nice indeed.
Tilemaps are great for straight maps (orthogonal). They don't support isometric maps, because you can't offset rows the way you need to for an isometric perspective.
I'll be working (slowly) on finishing a Tiled map loader in Lua. It would be interesting to see when it can load infinite maps and scroll.
Do you use any other editors like Tiled?
Any other suggestions?
I would LOVE a Tiled map loader! It's a blocker for me at the moment.
Before the Playdate SDK was released I started learning Lua using Love2D. There is a great library called Simple Tiled Implementation (STI) . It would be awesome to have something like that for Playdate.
@polyphonicboy What sort of game are you making? I'd love to collaborate on this. I've started doing isometric maps, but I can switch it needed. Let me know, it will be much more productive to work on real example maps.
Ideally, it would support all the features from Tiled, you would point it to a lua export and that would be it.
I'm working on a little platformer, so would be simple orthogonal maps.
I'm very much a novice programmer, but I would love to help as much as I can. I've made a test map in Tiled with 3 layers (sprites, walls, background).
Here's how the game is looking just now. Currently the background is one image, and I've manually positioned a few block sprites. The elevator is controlled with the crank.
Yeah I'm Yoshimi on the discord. I couldn't find you there, but send me a friend request and we can chat.
I'm planning on both axis scrolling. Initially thinking of starting at the bottom of the level and working up to the goal at the top, using elevators etc to reach the end. Using the crank to interact with various contraptions to progress or take out enemies.
In case anyone stumbles on this thread while searching for "Playdate isometric Tiled loader" here is code I'm using in my game to do that.
This is loading an isometric Tiled map saved as a .tmj with embedded tilesets. The tileset is created using images named properly for use as an imagetable on the Playdate.
This is a minimal class which doesn't support multiple layers or tilesets. The device also doesn't have the performance to scroll the tilemap very well without a fair amount of extra work which isn't shown here (my game has a whole chunk load/unload system which keeps about 1 screenful of tiles in place at a time and even that just barely hits 30fps).
What's most useful here, I think, is the math in addSprites() which shows how to do the coordinate calculations.
This isn't a drop-in "just works" thing but it is really small and not too opinionated so it can be a reasonable place to start if someone wants to use an isometric Tiled map. From here one could add things to support various Tiled features as needed.
Note: that the tiled format has some relatively involved mapping from the tile id numbers in the tmj files to tile id numbers in tilesets. However, if you only have one tileset the mapping can be simplified to a -1 operation on the tile id which is what I do here.
import 'CoreLibs/graphics'
import 'CoreLibs/object'
import 'CoreLibs/sprites'
import 'TileType'
local gfx <const> = playdate.graphics
local geo <const> = playdate.geometry
--
-- Tilemap
--
-- Loads Tiled map files (.tmj) and displays them with sprites.
-- Currently only supports iso maps.
--
class('Tilemap').extends(Object)
function Tilemap:init()
end
function Tilemap:load(filename)
self.mapData = json.decodeFile(filename)
assert(self.mapData)
for _, layer in ipairs(self.mapData.layers) do
print('Layer: '..layer.name..' ('..layer.type..')')
end
-- Set this to whatever your tileset name is.
self.tileset = self:getTileset('ground')
print('Tileset: '..self.tileset.name)
local path = 'assets/images/'..self.tileset.name ..'/'..self.tileset.name
print(' '..path)
self.tilesetImages = gfx.imagetable.new(path)
assert(self.tilesetImages, 'Couldn\'t load tileset images')
print(' '..#self.tilesetImages..' images loaded')
self.tileWidth = self.mapData.tilewidth
self.tileHeight = self.mapData.tileheight
self.scaleX = 2
self.scaleY = 1
self:addSprites(self.mapData.layers[1].data, self.mapData.layers[1].width, self.mapData.layers[1].height)
end
function Tilemap:getTileset(name)
for _, tileset in ipairs(self.mapData.tilesets) do
if tileset.name == name then
return tileset
end
end
return nil
end
function Tilemap:getLayer(name)
for _, layer in ipairs(self.mapData.layers) do
if layer.name == name then
return layer
end
end
return nil
end
function Tilemap:addSprites(tiles, width, height)
assert(tiles)
for i = 0, height - 1 do -- Loop through rows
for j = 0, width - 1 do -- Loop through cols in the rows
local tileIndex = (i * width) + j + 1
if tiles[tileIndex] ~= 0 then -- If there is a tile to draw
local x =
(j * (self.tileWidth / 2)) -- The width on rows
- (i * (self.tileWidth / 2)) -- The width on cols
local y =
(i * (math.floor(self.tileHeight / 2))) -- The height on rows
+ (j * (math.floor(self.tileHeight / 2))) -- The width on cols
local tile = tiles[tileIndex] - 1
local image, error = self.tilesetImages:getImage(tile)
assert(image, tile)
local sprite = gfx.sprite.new(image)
assert(sprite)
-- If anything has high Z bump them up to a higher layer so that overlapping works properly.
-- I'm using an enum but you could just keep a table of wall-like tiles.
local z = 0
if tile == TileType.Wall or
tile == TileType.Spikes or
tile == TileType.Bumper then
z = 1
end
-- Your player character and other moving objects should be at the same layer as the walls. You will need to set their Z index every time they move using a call similar to the one below.
-- The `setCenter()` offset here accounts for the size of tile graphics.
-- We want the origin to be right at the top center pixel of a ground tile.
-- You will likely need to adjust this based on the visual "thickness" of your tiles.
sprite:setCenter(0.5, 0.393939)
sprite:moveTo(x, y)
sprite:setZIndex(y + 34 + z * 1000) -- `The setZindex()` call also takes into account the tile graphic height.
sprite:add()
end
end
end
end
The ints here match up with tile ids in the Tilemap file. You don't have to use the enum and, in fact, not using it will be a bit faster but I use it because it's easier to read.
The main thing that's happening here is that tile types which have art that extends above the flat terrain should have in increased Z index to ensure proper overlapping behavior. So, in my example, Walls, Spikes, and Bumpers get pushed up into the next "layer". For your game you should identify which of your tiles ore like this this and give them a Z-boost. If all of your tiles are flat against the ground then you can skip this.