Simple Tiled Implementation - STI v1.2.3.0

Showcase your libraries, tools and other projects that help your fellow love users.
TheLaw
Prole
Posts: 3
Joined: Sun Jul 16, 2017 4:23 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by TheLaw »

I have some trouble with displaying a player sprite. I'm doing steps from tutorial http://lua.space/gamedev/using-tiled-maps-in-love but player sprite doesn't appear. My sprite related in attachments.

Code: Select all

local sti = require "sti"

function love.load()
	windowWidth = love.graphics.getWidth()
	windowHeight = love.graphics.getHeight()

	love.physics.setMeter(32)

	map = sti("map.lua", { "box2d" })

	local layer = map:addCustomLayer("Sprites", 8)

	local player
	for k, object in pairs(map.objects) do
		if object.name == "Player" then
			player = object
			break
		end
	end

	local sprite = love.graphics.newImage("sprite.png")
	layer.player = {
		sprite = sprite,
		x = player.x,
		y = player.y,
		ox = sprite:getWidth() / 2,
		oy = sprite:getHeight() / 1.35		
	}

	layer.draw = function(self)
		love.graphics.draw(
			self.player.sprite,
			math.floor(self.player.x),
			math.floor(self.player.y),
			0,
			1,
			1,
			self.player.ox,
			self.player.oy
		)

		love.graphics.setPointSize(5)
		love.graphics.points(math.floor(self.player.x), math.floor(self.player.y))
	end

	map:removeLayer("Spawn Point")

	world = love.physics.newWorld(0, 0)

	map:box2d_init(world)
end

function love.update(dt)
	map:update(dt)
end

function love.draw()
	love.graphics.setColor(255, 255, 255)
	map:draw()

end
Image
Attachments
sprite.png
sprite.png (132 Bytes) Viewed 8520 times
User avatar
Karai17
Party member
Posts: 930
Joined: Sun Sep 02, 2012 10:46 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by Karai17 »

They layer you have set, 8, might be too high. STI uses ipairs to draw layers in order so the layer number needs to be continuous.

If you map has 2 layers, then set the sprite layer to 3.
STI - An awesome Tiled library
LÖVE3D - A 3D library for LÖVE 0.10+

Dev Blog | GitHub | excessive ❤ moé
TheLaw
Prole
Posts: 3
Joined: Sun Jul 16, 2017 4:23 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by TheLaw »

Works, thanks a lot
TheLaw
Prole
Posts: 3
Joined: Sun Jul 16, 2017 4:23 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by TheLaw »

Moving a player is working, but map scale and moving a camera with the player doesn't work.

Code: Select all

local sti = require "sti"

function love.load()
	windowWidth = love.graphics.getWidth()
	windowHeight = love.graphics.getHeight()

	love.physics.setMeter(32)

	map = sti("map.lua", { "box2d" })

	local layer = map:addCustomLayer("Sprites", 3)

	local player
	for k, object in pairs(map.objects) do
		if object.name == "Player" then
			player = object
			break
		end
	end

	local sprite = love.graphics.newImage("sprite.png")
	layer.player = {
		sprite = sprite,
		x = player.x,
		y = player.y,
		ox = sprite:getWidth() / 2,
		oy = sprite:getHeight() / 1.35		
	}

	layer.update = function(self, dt)
		local speed = 96

		if love.keyboard.isDown("w") or love.keyboard.isDown("up") then
			self.player.y = self.player.y - speed * dt
		end

		if love.keyboard.isDown("s") or love.keyboard.isDown("down") then
			self.player.y = self.player.y + speed * dt
		end

		if love.keyboard.isDown("a") or love.keyboard.isDown("left") then
			self.player.x = self.player.x - speed * dt
		end

		if love.keyboard.isDown("d") or love.keyboard.isDown("right") then
			self.player.x = self.player.x + speed * dt
		end
	end

	layer.draw = function(self)
		love.graphics.draw(
			self.player.sprite,
			math.floor(self.player.x),
			math.floor(self.player.y),
			0,
			1,
			1,
			self.player.ox,
			self.player.oy
		)

		love.graphics.setPointSize(5)
		love.graphics.points(math.floor(self.player.x), math.floor(self.player.y))
	end

	map:removeLayer("Spawn Point")

	world = love.physics.newWorld(0, 0)

	map:box2d_init(world)
end

function love.update(dt)
	map:update(dt)
end

function love.draw()
	local scale = 2
	local screen_width = love.graphics.getWidth() / scale
	local screen_height = love.graphics.getHeight() / scale
	
	-- Translate world so that player is always centred
    local player = map.layers["Sprites"].player
    local tx = math.floor(player.x - love.graphics.getWidth() / 2)
    local ty = math.floor(player.y - love.graphics.getHeight() / 2)
    

	love.graphics.scale(scale)
	love.graphics.translate(-tx, -ty)

	love.graphics.setColor(255, 255, 255)
	map:draw()

end
Image
User avatar
Karai17
Party member
Posts: 930
Joined: Sun Sep 02, 2012 10:46 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by Karai17 »

Map:draw(tx, ty, sx, sy)

I had to incorporate translate and scale inside the map:draw() function to fix some really annoying bugs.
STI - An awesome Tiled library
LÖVE3D - A 3D library for LÖVE 0.10+

Dev Blog | GitHub | excessive ❤ moé
PerdeT
Prole
Posts: 7
Joined: Wed Sep 21, 2016 9:23 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by PerdeT »

I've got two questions.

1) I'm trying to remove a tile instance from my tilemap with Map:swaptile, to no avail. (I tried to replace the tile instance with an empty tile.)

So I first acquired the gid of the tile to be replaced:

Code: Select all

local xcoord, ycoord = map:convertPixelToTile(x, y)
local gid = map.layers[1].data[ycoord+1][xcoord+1].gid
Then i tried to swap it with an empty tile, found in map.tiles[1]:

Code: Select all

map:swapTile(map.tileInstances[gid], map.tiles[1])
But no, it doesn't seem to work like that. Now when I think about it... is that function even meant for that kind of actions - if I want to have destructible (or otherwise dynamic) tiles, should I have them as objects instead? If they were objects, would replacing or deleting tiles be easier? (I'm not quite sure how that would work, either.)

2) Is it possible to make the tilemap move? For instance by updating its offset coordinates.
PerdeT
Prole
Posts: 7
Joined: Wed Sep 21, 2016 9:23 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by PerdeT »

So LD39 is over and I've got finally time to reflect the struggles I had with understanding how STI handles its data structure two days ago. I've now figured its syntax out to some extent, and actually made some functions that someone else might also find useful, so I'll share them here. I'll also go through STI's data structure in the end - just to recap things for myself.

The first function I did returns a tile instance by its (x,y) coordinates. If the tile's layer index is not specified, layer 1 is used.

Code: Select all

function Map:getInstanceByPixel( x, y , layerindex) 
	local tilex, tiley = self:convertPixelToTile(x,y)
	if not layerindex then layerindex = 1 end
	local gid = self.layers[layerindex].data[tiley+1][tilex+1].gid
	for i,ins in ipairs(self.tileInstances[gid]) do
		if ins.x == x and ins.y == y then
			return ins
		end
	end
end
The second and third functions are special cases of Map:swapTile. The second one removes a tile instance from its tilemap and spritebatch:

Code: Select all

function Map:removeInstance( instance )
	if instance.batch then
		instance.batch:set(instance.id, 0,0,0,0,0)
	end
	for i, ins in ipairs(self.tileInstances[instance.gid]) do
		if  ins.batch == instance.batch and ins.id == instance.id then
			table.remove(self.tileInstances[instance.gid], i)
			break
		end
	end
	local tilex, tiley = self:convertPixelToTile(instance.x, instance.y)
	instance.layer.data[tiley+1][tilex+1] = nil
end
The third function is a bit of a hack. It adds a tile instance to a tilemap and a spritebatch - the position the tile is added to must be empty, this won't work otherwise! I didn't figure out any elegant ways to acquire the correct spritebatch so I'll just loop through them and pick the last one (:D). If the correct spritebatch is known, it can be given as an argument.

Code: Select all

function Map:addInstance( tile, x, y, batch, layerindex)
	if not layerindex then layerindex = 1 end
	if not batch then
		for k,v in pairs(self.layers[layerindex].batches) do
			batch = v --lol it's the last one 
		end
	end
	id = batch:add(tile.quad, x, y, tile.r, tile.sx, tile.sy)
	local instance = {
		layer = self.layers[layerindex],
		batch = batch,
		id    = id,
		gid   = tile.gid,
		x     = x,
		y     = y,
		r     = tile.r,
		oy    = tile.r ~= 0 and tile.height or 0
	}
	local tilex, tiley = self:convertPixelToTile(x,y)
	self.layers[layerindex].data[tiley+1][tilex+1] = tile
	if not self.tileInstances[tile.gid] then self.tileInstances[tile.gid] = {} end
	table.insert(self.tileInstances[tile.gid], instance)
	return instance
end
If you use bump:
Then it's important to also update bump's world object when adding or removing tiles. With removal it's easy: if you're looping through collisions (cols) then just do this:

Code: Select all

world:remove(cols[i].other)
instance = map:getInstanceByPixel(cols[i].other.x, cols[i].other.y)
map:removeInstance(instance)
But when adding an instance there's more to it:

Code: Select all

instance = scene.map:addInstance(tile, x,y)
local t = {
    x          = instance.x + scene.map.offsetx,
    y          = instance.y + scene.map.offsety,
    width      = scene.map.tilewidth,
    height     = scene.map.tileheight,
    layer      = instance.layer,
    properties = tile.properties
}
scene.bumpworld:add(t, t.x, t.y, t.width, t.height)
table.insert(scene.map.bump_collidables, t)
So that's it. I hope these are helpful - I wouldn't mind seeing something like this in STI itself! Of course, I don't know if these are unnecessarily complex or redundant in some ways (or even fail in some scenarios I didn't yet witness), so all feedback&critique is welcome. At least they were useful for my LD39 entry!

Also, it was important to realise how tile instances or tiles differ and how they are located. Tile instances are found in

Code: Select all

map.tileInstances[gid][i]
where i is just some iterable number. Tile instances have sprite batches assigned to them: sprite batch is used to draw things, so deleting a tile instance from the aforementioned two locations doesn't change how things are drawn on screen. Sprite batches are located in

Code: Select all

self.layers[layerindex].batches
Tiles themselves (essentially portions of the tileset) are located in

Code: Select all

map.tiles[id+1]
where id is the tile's id in its tileset and can be read from Tiled. We have to add one to it because tile id=0 is reserved for an empty tile. Tiles can also be accessed by

Code: Select all

map.layers[layerindex].data[tiley+1][tilex+1]
Coordinates tilex and tiley can also be read from Tiled - we have to add one to them, too, because Lua tables start from 1 instead of 0.

EDIT 17.8.2017. Tables in map.layers.data are tiles, not tile instances. Fixed my message accordingly.
Last edited by PerdeT on Wed Aug 16, 2017 10:25 pm, edited 2 times in total.
User avatar
Karai17
Party member
Posts: 930
Joined: Sun Sep 02, 2012 10:46 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by Karai17 »

Thanks for taking the time to write this, I'll see if I can incorporate more helper functions into the API. Sorry for not being able to respond to you sooner, I was also participating in LD!
STI - An awesome Tiled library
LÖVE3D - A 3D library for LÖVE 0.10+

Dev Blog | GitHub | excessive ❤ moé
PerdeT
Prole
Posts: 7
Joined: Wed Sep 21, 2016 9:23 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by PerdeT »

Heh, no problem! There surely was some panicking at first but as I carefully examined the functions I started to understand the API bit by bit. I couldn't have done my LD entry without something like STI, so thank you for that!
neenayno
Prole
Posts: 2
Joined: Tue Aug 01, 2017 3:11 pm

Re: Simple Tiled Implementation - STI v0.18.2.1

Post by neenayno »

Hi everyone,

I'm a new hobbyist game developer, and I'm trying to recreate a very basic Plants vs Zombies clone.

I've gotten myself up to speed with Tiled basics, specifically tile and object layers.

Next, I've followed the STI tutorial.

My specific question is about access to a "grid construct" through STI. For the Plants vs Zombies clone, I'd like to be able to do a bunch of grid-based things, such as placing a unit inside a grid. By looking at the map in Lua format, I don't see useful grid information in here. Does STI give me anything to work with the grids? I don't see anything in the API documents.

Any help is greatly appreciated.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest