-
Notifications
You must be signed in to change notification settings - Fork 124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Render order #251
Comments
Looks like #166 is related. |
you'll need to do some custom sorting of objects rather than just making a
bunch of tile layers. I'd say have an object layer in your map, create a
custom layer, copy the object info into that layer in some way you can
easily manipulate, add the player to the layer as well, and then use custom
update and draw functions that sort and draw the objects
…On Mon., Feb. 1, 2021, 04:11 David Konsumer, ***@***.***> wrote:
Looks like #166
<#166> is
related.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#251 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEQD7BI5KRVZGNPEN36QCLS4ZO3RANCNFSM4W4GRJEA>
.
|
Thanks. I will look into that. I'd prefer to not have to duplicate information in the map, if I can help it, and not draw anything multiple times. I realize you aren't suggesting this, just mentioning it as part of my goals, here. I'm trying to come up with a basic system that is extremely asset-driven, so you can mostly edit your whole game in tiled, with a few properties and all entity behaviors defined in lua files. Kinda like solarus, but for tiled/love2d. Maybe layer-grouping would help? Like add a property to layer-group such as Is this something that would be useful to others, as a plugin, or does it make more sense as just layer-code in my app? |
The implementation is probably more application specific, but yeah, should
be able to just drop objects in a layer in tiled and sort it in love
…On Tue., Feb. 2, 2021, 18:09 David Konsumer, ***@***.***> wrote:
Thanks. I will look into that.
I'd prefer to not have to duplicate information in the map, if I can help
it, and not draw anything multiple times. I realize you aren't suggesting
this, just mentioning it as part of my goals, here. I'm trying to come up
with a basic system that is extremely asset-driven, so you can mostly edit
your whole game in tiled, with a few properties and all entity behaviors
defined in lua files. Kinda like solarus <https://www.solarus-games.org/>,
but for tiled/love2d.
Maybe layer-grouping would help? Like add a property to layer-group such
as z="y", which means "figure out render-order for layers in this group,
based on Y position."
[image: Screenshot from 2021-02-02 14-06-44]
<https://user-images.githubusercontent.com/83857/106669064-f0222480-655f-11eb-9c82-810d3fd82e92.png>
Is this something that would be useful to others, as a plugin, or does it
make more sense as just layer-code in my app?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#251 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEQD7GOQQ4NKPY6FIPSFETS5BZZPANCNFSM4W4GRJEA>
.
|
I started work on a general solution for this at sti-renderorder, but it's just a stub for now. I will close this issue. Thanks for taking a look. |
if you feel like it ends up being a useful plugin, feel free to submit it
in a pr!
…On Tue., Feb. 2, 2021, 19:04 David Konsumer, ***@***.***> wrote:
I started work on a general solution for this at sti-renderorder
<https://github.com/notnullgames/sti-renderorder>, but it's just a stub
for now. I will close this issue. Thanks for taking a look.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#251 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEQD7H2YDC4GPLNWDOMPPLS5CAHFANCNFSM4W4GRJEA>
.
|
I have a basic system that can draw a layer "in front" or "behind": local function in_front(location, layer)
-- always "behind"
return false
end
local function sti_renderorder(map, renderorder_layer, location)
local orig_draw = renderorder_layer.draw
-- find layers that could overlap & disable drawing (keeping a ref to original draw)
local layers = {}
for i=1,#map.layers do
local layer = map.layers[i]
if layer.properties.ysort then
layer.orig_draw = layer.draw
function layer:draw() end
table.insert(layers, layer)
end
end
-- overwrite the renderlayer's draw function to account for front/behind
function renderorder_layer:draw()
orig_draw()
for _,layer in pairs(layers) do
if in_front(location, layer) then
layer:orig_draw()
orig_draw()
else
layer:orig_draw()
end
end
end
end But I am having trouble with how to implement |
I got a bit closer by just exploiting the fact that there is local function sti_renderorder(map, renderorder_layer, location)
local orig_draw = renderorder_layer.draw
-- find layers that could overlap & disable drawing (keeping a ref to original draw)
local layers = {}
for i=1,#map.layers do
local layer = map.layers[i]
if layer.properties.ysort then
layer.orig_draw = layer.draw
function layer:draw() end
table.insert(layers, layer)
end
end
-- overwrite the renderlayer's draw function to account for front/behind
function renderorder_layer:draw()
orig_draw()
local targetx, targety = map:convertPixelToTile (location.x, location.y)
targetx = math.floor(targetx)
targety = math.floor(targety)
for _,layer in pairs(layers) do
if layer.data[targety] and layer.data[targety][targetx] then
layer:orig_draw()
orig_draw()
else
layer:orig_draw()
end
end
end
end The choppiness is in the gif, it seems to run ok. |
note that it is y,x not x,y in layer.data!
…On Thu., Feb. 4, 2021, 02:22 David Konsumer, ***@***.***> wrote:
I got a bit closer by just exploiting the fact that there is
layer.data[x][y]. I would still appreciate any input:
local function sti_renderorder(map, renderorder_layer, location)
local orig_draw = renderorder_layer.draw
-- find layers that could overlap & disable drawing (keeping a ref to original draw)
local layers = {}
for i=1,#map.layers do
local layer = map.layers[i]
if layer.properties.ysort then
layer.orig_draw = layer.draw
function layer:draw() end
table.insert(layers, layer)
end
end
-- overwrite the renderlayer's draw function to account for front/behind
function renderorder_layer:draw()
orig_draw()
local targetx, targety = map:convertPixelToTile (location.x, location.y)
targetx = math.floor(targetx)
targety = math.floor(targety)
for _,layer in pairs(layers) do
if layer.data[targety] and layer.data[targety][targetx] then
layer:orig_draw()
orig_draw()
else
layer:orig_draw()
end
end
endend
[image: Peek 2021-02-03 22-17]
<https://user-images.githubusercontent.com/83857/106853181-26e66080-666e-11eb-9555-bd89cea2b797.gif>
The choppiness is in the gif, it seems to run ok.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#251 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEQD7EGENWDO563KICF24LS5I4IZANCNFSM4W4GRJEA>
.
|
Yeh, I swapped them initially in the description, but in code, I have it |
I am still having a great deal of trouble getting this to work in a decent way. it seems like around any mention of tiled + z-ordering people say "use a custom layer that sorts the tiles correctly" but I can't find any actual code examples of this. You have used this technique, and offered this advice in the forums and here. Can you link me to an example of actually doing this? |
https://github.com/excessive/rpg/blob/master/states/gameplay.lua#L222-L246 This code is a bit old to say the least, but I believe this is where we wete doing this in a test game we were working on. |
Ah, I see, you just treat things that can be over/under the player like you would the player position, placing on the map after, in the right order. I totally get that approach, and it does seem much simpler. I will fall back to that, if I can't figure this out, soon. I like the idea of the map just knowing how to do it correctly, using tile locations (not exact pixel positions, but 32x32 grid or whatever) & spritebatches, for max efficiency. I'm going to keep playing with it. |
I am still having trouble with this. I tried a few different methods. I put all the objects with gids (tile-objects) in a separate table, then set so if I do this, it will draw all the tile objects from an object-layer, without accounting for player: function self.map.layers.player.draw()
for _, batch in pairs(self.map.layers.objects.batches) do
love.graphics.draw(batch, 0, 0)
end
end I also have a collection of the original objects with function self.map.layers.player.draw()
for _,tile in pairs(self.object_tiles) do
-- what do do here?
local tileInstance = self.map.tileInstances[tile.gid]
end
end I imagine I can do 2 loops, 1 under, then draw the player, then 1 for over-player, but I can't figure out how to separate the tiles like this. Since I can get the tile-instance, the sprite-batches, etc, it seems like I am very close, but I still don't understand how to order and draw the tiles separate from their spritebatch. Am I thinking about the problem incorrectly? |
Instead of Tile Objects, I'd use Point Objects to denote locations for things to be, and then use that info to load custom objects into a custom layer and sort those objects by their Y value. You could batch them each frame and draw the whole layer only once and it should still be very fast. You could use a temporary tile layer to place tiles down where you'd want them to be so you can design your maps and then in the object layer place points on the objects. A little trick to help with aligning your objects to the tile drid would be to divide the point's XY location by tile size, floor those values, and them multiply them back to pixels~ Below is some pseudocode that should help you figure out exactly what you need to do, it's been a while so i don't quite remember love's syntax off the top of my head local layer = map.layers["Objects and Stuff"]
local old_objects = layer.objects
local clayer = map:createCustomLayer("Fancy Stuff Here!")
clayer.objects = {}
local objects = clayer.objects
clayer.batch = love.graphics.newSpriteBatch() -- etc
-- Align objects to tile grid even if the points in the map data are loosely placed
for i, object in ipairs(old_objects) do
objects[i] = {}
local o = objects[i]
o.x = math.floor(object.x / map.tilewidth) * map.tilewidth
o.y = math.floor(object.y / map.tileheight) * map.tileheight
o.texture = some_texture -- texture all your objects will be from, for batching
o.sprite = some_quad -- the area of the texture to be drawn for this particular object
end
-- Add player to the custom layer
table.insert(clayer.objects, player)
function clayer:update(dt)
table.sort(self.objects, function(a, b)
return a.y < b.y
end)
-- clear self.batch
-- re-batch from self.objects
end
function clayer:draw()
love.graphics.draw(self.batch)
end |
I think I understand, and maybe have a different way, using regular quads, so I can display things that are just tiles, and also things that are dynamic objects (animated NPC for example) and also do per-pixel drawing. But either way, I could use help getting First I load up a bunch of objects: function noop() end
for k, object in pairs(self.map.objects) do
-- disable the layer's draw & update, as I will be taking these over in player-layer
object.layer.draw = noop
object.layer.update = noop
-- copy id because bump leaves it out
object.properties.id = object.id
if object.type and object.type ~= '' then
if objectTypes[ object.type ] then
-- add an instance of the type class, if it exists
self.objects[object.id] = objectTypes[object.type](object, self)
-- fill in some basics from map to make objects nicer to use
self.objects[object.id].id = object.id
self.objects[object.id].gid = object.gid
self.objects[object.id].type = object.type
self.objects[object.id].x = object.x
self.objects[object.id].y = object.y
self.objects[object.id].width = object.width
self.objects[object.id].height = object.height
-- HOW DO I GET image/quad IN self.objects[object.id].tile HERE?
if object.type == "player" then
self.player = self.objects[object.id]
end
else
-- non-fatal warning that no behavior has been setup
self.objects[object.id] = object
print("No script found for " .. object.type .. "(".. object.id ..")")
end
end
end Then, in player layer I do all the sorting and drawing: function self.map.layers.player.update(layer, dt)
-- run update for every object
for k,object in pairs(self.objects) do
if object.update then
object:update(dt)
end
end
-- this manages physical collisions (`collidable` layers)
local playerX = self.player.x + (self.player.move[1] * (dt + self.player.speed))
local playerY = self.player.y + (self.player.move[2] * (dt + self.player.speed))
local actualX, actualY, collisions, len = self.world:move(self.player, playerX, playerY)
self.player.x = actualX
self.player.y = actualY
-- sort self.objects by Y
table.sort(self.objects, function(a, b)
if a and b then
return a.y > b.y
end
end)
end
function self.map.layers.player.draw()
for o,object in pairs(self.objects) do
-- run the objects map_draw, to draw in context, or draw it's tile
if object.map_draw then
object:map_draw()
else
if object.tile then
-- I DON'T HAVE THIS PART
love.graphics.draw(object.tile.image, object.tile.quad, object.x, object.y)
end
end
end
end Where does |
you'd need to create them manually. The texture may already be cached in
sti since it auto caches textures it interacts with when loading the map.
but for your objects you'll need to define the quad for each one based on
the tiles you want to use from the texture, and then insert those quads
into the sprite batch for that texture
…On Wed., Feb. 10, 2021, 00:07 David Konsumer, ***@***.***> wrote:
I think I understand, and maybe have a different way, using regular quads,
so I can display things that are just tiles, and also things that are
dynamic objects (animated NPC for example) and also do per-pixel drawing.
But either way, I could use help getting some_texture and some_quad in
your example.
First I load up a bunch of objects:
function noop() end
for k, object in pairs(self.map.objects) do
-- disable the layer's draw & update, as I will be taking these over in player-layer
object.layer.draw = noop
object.layer.update = noop
-- copy id because bump leaves it out
object.properties.id = object.id
if object.type and object.type ~= '' then
if objectTypes[ object.type ] then
-- add an instance of the type class, if it exists
self.objects[object.id] = objectTypes[object.type](object, self)
-- fill in some basics from map to make objects nicer to use
self.objects[object.id].id = object.id
self.objects[object.id].gid = object.gid
self.objects[object.id].type = object.type
self.objects[object.id].x = object.x
self.objects[object.id].y = object.y
self.objects[object.id].width = object.width
self.objects[object.id].height = object.height
-- HOW DO I GET image/quad IN self.objects[object.id].tile HERE?
if object.type == "player" then
self.player = self.objects[object.id]
end
else
-- non-fatal warning that no behavior has been setup
self.objects[object.id] = object
print("No script found for " .. object.type .. "(".. object.id ..")")
end
end
end
Then, in player layer I do all the sorting and drawing:
function self.map.layers.player.update(layer, dt)
-- run update for every object
for k,object in pairs(self.objects) do
if object.update then
object:update(dt)
end
end
-- this manages physical collisions (`collidable` layers)
local playerX = self.player.x + (self.player.move[1] * (dt + self.player.speed))
local playerY = self.player.y + (self.player.move[2] * (dt + self.player.speed))
local actualX, actualY, collisions, len = self.world:move(self.player, playerX, playerY)
self.player.x = actualX
self.player.y = actualY
-- sort self.objects by Y
table.sort(self.objects, function(a, b)
if a and b then
return a.y > b.y
end
end)
end
function self.map.layers.player.draw()
for o,object in pairs(self.objects) do
-- run the objects map_draw, to draw in context, or draw it's tile
if object.map_draw then
object:map_draw()
else
if object.tile then
love.graphics.draw(object.tile.map, object.tile.quad, object.x, object.y)
end
end
end
end
Where does some_texture and some_quad in your example come from? I need
to pre-cache it in object.tile in my code, but can't figure out how to
pull that out of the map from an object, and I think this is the last piece
I need. From docs, it seems like map.tileInstances[object.gid] should
have it (using x/y, and batch) but I don't see it.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#251 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEQD7CCRBC7A5BV4HXJGFTS6IBB5ANCNFSM4W4GRJEA>
.
|
So the data doesn't exist in the map, anywhere? If I could at least get to the spritebatch, I could do |
you can probably partially automate it by adding some info to the custom properties of your objects or having some lookup table that matches an object's name to quad coords. local lookup = {
"Tree_01" = { x=5, y=6, w=2, h=3 }
}
for _, o in ipairs(objects) do
if lookup[o.name] then
local asdf = lookup[o.name]
o.quad = love.graphics.newQuad(asdf.x*map.tilewidth-map.tilewidth, asdf.y*map.tileheight-map.tileheight, asdf.w*map.tilewidth, asdf.h*map.tileheight) -- or whatever the syntax is
layer.batch:addSprite(o.quad)
end
end |
my thought here is that your objects are gonna have more than a single tile involved. like a tree might be a 2x3 structure but instead of treating it as 6 tiles you treat it as a single large tile, which is why you need to put in a bit of leg work since tiled itself doesn't really have that sort of feature, as far as i know. |
I am so sure the image + quad must be somewhere in the map object, I just can't seem to find it. STI is using that info to draw object-layers, with no meddling, so it had it, at least at one time, even if it doesn't expose it.
The objects can be a placeholder for something else (that is why I have Aside from that, If I have a 2x3 tree, I want it to render with correct z-order (based on Y) for all of the tiles, and I can put a collision hitbox in the center of the trunk so the player can't stand in the middle of the tree trunk, or end up "under" things it should be "over". then I put the top of the tree on a layer over the player, so it's always over everything. this is similar to trees in zelda alttp, where they are made with several tiles, there are tree tops that can obscure the player, and you can walk in front of and behind some trunk sprites. I'm fairly sure this is done by sorting by layer+Y. I have seen this limitation of tiled come up in the forums as a reason it can't do what I am trying to do, but I've also seen other tiled loaders, in other languages, that don't have a problem with it. My issue is right now is with single-tile objects (although I think it will carry over to all tiles) that the player might be under or over, depending on Y. A single-tile sign is an example. I add a sign tile object to the map, on a object layer, and set it's type to Now, I am attempting to solve this by giving up on spritebatches for objects, and just place them all manually, using the image & quad it links to, if no If it helps to put it into context of a complete project, here is the game I'm working on. I appreciate all your help, but If I can't work out what I'm trying to do with STI, I can just go back to writing my own tiled-loader. I made some progress, and I think I can work it out that way. I'm fairly comfortable with tiled (I contributed to the plugin API, and have worked on importers and exporters for other things) and am getting comfortable with love, so I think it's totally doable, if not a pain to duplicate a lot of effort. I also noticed there is no per-tile linked collisions in STI (like in tiled, choose collision-editor) which is another feature I really want, so I don't have to draw all my collisions over and over, on a seperate layer. I looked around at other tiled-map loaders, in other languages and many of them support these things (for example flixel's tiled loader.) |
https://github.com/karai17/Simple-Tiled-Implementation/blob/master/sti/init.lua#L175-L219 STI generates quad data and batches for tile layers based on the width and height of the map and texture, etc. this data just doesn't exist in the tiled map format. you basically need to re-implement this code to handle your own game-specific needs for custom layers. whether you use STI or your own map loader you'll still need to do this~ |
Yep, as I'm digging into how tile-objects work, I am starting to see what you mean! Here is one object:
In this case, {
name = "objects",
firstgid = 1025,
filename = "objects.tsx",
tilewidth = 32,
tileheight = 32,
spacing = 0,
margin = 0,
columns = 10,
image = "objects.png",
imagewidth = 320,
imageheight = 320,
objectalignment = "unspecified",
tileoffset = {
x = 0,
y = 0
},
grid = {
orientation = "orthogonal",
width = 32,
height = 32
},
properties = {},
terrains = {},
tilecount = 100,
tiles = {}
}
}, So in STI or in my own attempts, it's the same problem, and it's more complicated than regular tiles. I can get the if that code you linked to is parsing tile objects, the same as the regular map tiles, then it should be in map.tiles[object.gid].quad
map.tilesets[object.tileset].image If I am understanding the code right, that is exactly what I was looking for! If I can get this working, I'd much rather use STI. I will look closer at this. In my earlier experiments, these weren't showing in pprint, I think because it doesn't always print complex class objects (I have seen this with images and quads) so it might have been there all along. Or, in re-reading your comments, you are saying I will need to do similar to what tiles are doing, for objects? That is totally workable, still, as I can make a little util that uses that snippet of code to grab the image+quad, and cache it in objects, before render. I think I could make a As for composite-collisions (made in tiled's collision-editor) if you are interested, a terrain tile with one looks like this:
This is a tile of a coastline terrain-brush, and the collision is the shape of water in that tile. The sub-field in the tile I will look into doing this, in STI plugin-space, with HC (which can support more complex polygons than bump) and if I can get it all working, I will make a PR to add the plugin for STI. It might be a good general approach to physics/collisions, and make it easier to write other plugins (they can just loop over Sorry for all the back-and-forth, and thanks again for all your help. |
I might even be able to pull out |
I made a plugin called I overwrite the layer.draw = function()
for _, object in pairs(layer.objects) do
-- this allows overwriting with custom in-place drawing (like animation or whatever)
if object.draw_yz then
object.draw_yz()
else
if object.yz_tile then
local x, y, w, h = object.yz_tile.quad:getViewport()
lg.draw(object.yz_tile.image, object.yz_tile.quad, object.x, object.y-h)
end
end
end
end I am pretty sure the |
here is the whole thing, for context. |
As a sidenote, if I disable the sort on update, it stops flashing, but also stops negotiating z-order. |
because you have two things at the exact same location, the gpu can't
really determine which one to draw first so it ends up choosing randomly.
this is usually called Z-fighting in 3d games. not to be confused with a
certain popular anime/manga series ;)
alternatively, your sort may be shuffling a few things around each frame.
if you're using <= to compare, try just using <
…On Thu., Feb. 11, 2021, 04:02 David Konsumer, ***@***.***> wrote:
As a sidenote, if I disable the sort on update, it stops flashing, but
also stops negotiating z-order.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#251 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEQD7G7VY7QP32UDKMMEY3S6OFKHANCNFSM4W4GRJEA>
.
|
Yeh, I tried I read somewhere that the function map:yz_sort_objects(a, b)
if a and b then
return a.y > b.y
end
end
layer.update = function(dt)
for i,o in pairs(layer.objects) do
if not o then
table.remove(layer.objects, i)
end
end
table.sort(layer.objects, map.yz_sort_objects)
end but that also failed. This seems like not a STI problem, just can't figure out how to make it work. |
try printing out the values in your sorted table. my suspicion is that it
changes a bit every frame
…On Thu., Feb. 11, 2021, 06:49 David Konsumer, ***@***.***> wrote:
Yeh, I tried <= and < with same prob. I also tried putting it in draw
instead. The only thing that fixes it is to disable the sort, so it seems
like it's not z-fighting (as the same number of things occupy the same
space without making the problem.)
I read somewhere that the nil in draw-table has problems, so I did this:
function map:yz_sort_objects(a, b)
if a and b then
return a.y > b.y
endend
layer.update = function(dt)
for i,o in pairs(layer.objects) do
if not o then
table.remove(layer.objects, i)
end
end
table.sort(layer.objects, map.yz_sort_objects)end
but that also failed. This seems like not a STI problem, just can't figure
out how to make it work.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#251 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEQD7EN3WBIJWGMMSZDN43S6OYZ5ANCNFSM4W4GRJEA>
.
|
Isn;t that what I'm shooting for? It moves too fast, with a regular print, so I did this in my top-level for o, object in pairs(self.map.layers.objects.objects) do
love.graphics.print(object.id, 0, (o-1)*12)
end It definitely shouldn't be swapping order that fast. |
In the sort function I noticed there were layer.update = function(dt)
for i,o in pairs(layer.objects) do
if not o then
table.remove(layer.objects, i)
end
end
table.sort(layer.objects, map.yz_sort_objects)
end |
weirdly first, middle, and last seem to stay the same. |
yeah you want them to change every frame only if they should. if they are
changing when nothing requires a change, that's definitely a problem. my
first thought is an issue with pairs since pairs doesn't have any
particular order. but I'm not sure if that's relevant here..
…On Thu., Feb. 11, 2021, 07:30 David Konsumer, ***@***.***> wrote:
weirdly first, middle, and last seem to match.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#251 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEQD7HMMZQCWRD6UUXJBI3S6O5UJANCNFSM4W4GRJEA>
.
|
yeh, I was thinking that about |
In some tile-based engines (like godot, with some config) render order is determined by a combination of layer height + X/Y position. This makes it possible to have a thing the player can be "in front of" and "behind" in a zelda-style map.
I made an example map that has player layer under some stuff. When there is overlap (like with the bird feeder) it would be better if it check Y position to determine which to render first, so it draws the player over it, when they are below it, in Y position.
The text was updated successfully, but these errors were encountered: