Page 71 of 92

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Sun Jul 16, 2017 4:31 pm
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

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Mon Jul 17, 2017 12:10 am
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.

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Sun Jul 23, 2017 5:45 pm
by TheLaw
Works, thanks a lot

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Tue Jul 25, 2017 5:07 pm
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

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Tue Jul 25, 2017 5:17 pm
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.

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Sun Jul 30, 2017 6:43 pm
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.

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Tue Aug 01, 2017 6:51 pm
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.

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Tue Aug 01, 2017 7:29 pm
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!

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Wed Aug 02, 2017 12:05 pm
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!

Re: Simple Tiled Implementation - STI v0.18.2.1

Posted: Thu Aug 03, 2017 12:45 pm
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.