karai17 / Simple-Tiled-Implementation

Tiled library for LÖVE
Other
856 stars 121 forks source link

Render order #251

Closed konsumer closed 3 years ago

konsumer commented 3 years ago

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.

Peek 2021-01-31 22-18

konsumer commented 3 years ago

Looks like #166 is related.

karai17 commented 3 years ago

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, notifications@github.com wrote:

Looks like #166 https://github.com/karai17/Simple-Tiled-Implementation/issues/166 is related.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/karai17/Simple-Tiled-Implementation/issues/251#issuecomment-770660363, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEQD7BI5KRVZGNPEN36QCLS4ZO3RANCNFSM4W4GRJEA .

konsumer commented 3 years ago

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 z="y", which means "figure out render-order for layers in this group, based on Y position."

Screenshot from 2021-02-02 14-06-44

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?

karai17 commented 3 years ago

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, notifications@github.com 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 https://github.com/karai17/Simple-Tiled-Implementation/issues/251#issuecomment-772037520, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEQD7GOQQ4NKPY6FIPSFETS5BZZPANCNFSM4W4GRJEA .

konsumer commented 3 years ago

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.

karai17 commented 3 years ago

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, notifications@github.com 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 https://github.com/karai17/Simple-Tiled-Implementation/issues/251#issuecomment-772075610, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEQD7H2YDC4GPLNWDOMPPLS5CAHFANCNFSM4W4GRJEA .

konsumer commented 3 years ago

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 in_front(). It works ok if I set it to always true or always false and the location is correctly updated, so I think it's an ok start, but it is drawing the player-layer multiple times (either under, or under/over.) I can live with that, if it works, and optimize it later. I have demo code here. What can I do in in_front() to determine if the layer wants to draw in the current location space? I realize I am calling in_front in a loop and the in_front will probly also be a loop, so this will probly suck for performance, but I'm not sure how I could further optimize that. Maybe somehow assign z to each layer in { playerlayer, ...zlayers } for a single-pass sort? Any help would be greatly appreciated.

konsumer commented 3 years ago

I got a bit closer by just exploiting the fact that there is layer.data[y][x]. 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
  end
end

Peek 2021-02-03 22-17

The choppiness is in the gif, it seems to run ok.

karai17 commented 3 years ago

note that it is y,x not x,y in layer.data!

On Thu., Feb. 4, 2021, 02:22 David Konsumer, notifications@github.com 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() origdraw() 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 https://github.com/karai17/Simple-Tiled-Implementation/issues/251#issuecomment-773062708, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEQD7EGENWDO563KICF24LS5I4IZANCNFSM4W4GRJEA .

konsumer commented 3 years ago

Yeh, I swapped them initially in the description, but in code, I have it [y][x].

konsumer commented 3 years ago

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?

karai17 commented 3 years ago

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.

konsumer commented 3 years ago

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.

konsumer commented 3 years ago

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 visible to false on all their (object) layers. After this, I want to update the player-layer to reorder the tiles, but they are all in SpriteBatchs, so I'm not sure how to reorder & draw them, individually.

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 gid, that I collected in an earlier step called self.object_tiles and I can get the tile-instances like this:

    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?

karai17 commented 3 years ago

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
konsumer commented 3 years ago

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
            --  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 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.

karai17 commented 3 years ago

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, notifications@github.com 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 https://github.com/karai17/Simple-Tiled-Implementation/issues/251#issuecomment-776424915, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEQD7CCRBC7A5BV4HXJGFTS6IBB5ANCNFSM4W4GRJEA .

konsumer commented 3 years ago

So the data doesn't exist in the map, anywhere? If I could at least get to the spritebatch, I could do batch:getImage() but I still don't know how to get the quad coordinates.

karai17 commented 3 years ago

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
karai17 commented 3 years ago

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.

konsumer commented 3 years ago

you can probably partially automate it by adding some info to the custom properties of your objects

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.

like a tree might be a 2x3 structure but instead of treating it as 6 tiles you treat it as a single large tile

The objects can be a placeholder for something else (that is why I have object.map_draw() for things like NPCs that are just linked to with graphical objects), but the position (in x,y, and z, which is layer + Y-order) & props tell my engine what animation to load, where to put it, etc. Things like this are easy to deal with in custom per-type drawing code, if needed.

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 sign. I load the code to handle user-trigger & touching, and show a sign. That's all working. I just need the player to be able to walk behind the sign, without having to use a collision box to block them out of being able to overlap (or end up "behind".)

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 object.map_draw is found, but it's still very challenging because I can't get the images or quads that must be in the map-data, somewhere (but seemingly not where the STI docs say they are, unless I am looking at it wrong.)

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.)

karai17 commented 3 years ago

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~

konsumer commented 3 years ago

Yep, as I'm digging into how tile-objects work, I am starting to see what you mean!

Here is one object:

{ 
  gid = 1025,
  height = 32,
  id = 1,
  name = '',
  properties = { 
    direction = 'S' 
  },
  rotation = 0,
  shape = 'rectangle',
  type = 'player',
  visible = true,
  width = 32,
  x = 798.203,
  y = 801.822 
}

In this case, gid is 1025 and looks like this in the tileset:

    {
      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 image from the tileset, and the quad has to be gathered from doing some tricky (and possibly totally wrong) math with other fields in tileset (using columns, tilewidth, tileheight, spacing, margin, etc) If I can assume that gid's are linear (they start at firstgid, and increment 1 to the right/down, etc) then I think I can do it, but it's not really a problem with STI, as it has the same map-info. I had to dig into parsing it myself, to understand that code you linked to, but I totally get it, now.

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 layer.draw function that ignores the spritebatch, and then does z-ordering of all on a single layer with Y, which should solve my original issue. In tiled, I will just put all the things that need to be z-ordered together on a layer, and let them figure out what is over/under. I could probly make a pretty simple & reusable function you can just insert like layer.draw=drawzfight when you want a layer to act like this (maybe via a layer-prop?) Should I PR for this, in the shape of a plugin?

As for composite-collisions (made in tiled's collision-editor) if you are interested, a terrain tile with one looks like this:

{
          id = 311,
          terrain = { 19, -1, 19, -1 },
          objectGroup = {
            type = "objectgroup",
            draworder = "index",
            id = 2,
            name = "",
            visible = true,
            opacity = 1,
            offsetx = 0,
            offsety = 0,
            properties = {},
            objects = {
              {
                id = 1,
                name = "",
                type = "",
                shape = "polygon",
                x = 13.5166,
                y = 0.500615,
                width = 0,
                height = 0,
                rotation = 0,
                visible = true,
                polygon = {
                  { x = 0, y = 0 },
                  { x = 5.00615, y = 5.79283 },
                  { x = 0, y = 11.0135 },
                  { x = 4.29098, y = 20.0246 },
                  { x = 3.9334, y = 24.8162 },
                  { x = -1.21578, y = 31.2527 },
                  { x = 18.2367, y = 31.3242 },
                  { x = 18.2367, y = -0.429098 }
                },
                properties = {}
              }
            }
          }
        },

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 objects has the shape of the collision polygon. In my own parser, I was thinking I'd keep a separate map.collisions that holds the shapes of these (along with the gid of the parent tile they are embedded in) + shapes on collidable layers, so it'd be faster to loop over them all at once, and check for collisions in other libraries.

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 map.collisions, instead of worrying about layers.)

Sorry for all the back-and-forth, and thanks again for all your help.

konsumer commented 3 years ago

I might even be able to pull out map:getImageForGid(gid) that jumps through all the hoops, and returns an image (by pulling out the quad and image from tileset) so you have a drawable all ready to go for your tile-objects.

konsumer commented 3 years ago

I made a plugin called yz that I think does mostly the right thing, but it's flashing when 2 objects overlap. I sort the table by Y, on update.

I overwrite the layer.draw, like this:

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 object.yz_tile is setup right, because it's displaying all the right tiles. Any idea why it's flashing? I also noticed I needed to do object.y-h where h is tile-height, otherwise it was off by 1.

konsumer commented 3 years ago

here is the whole thing, for context.

konsumer commented 3 years ago

As a sidenote, if I disable the sort on update, it stops flashing, but also stops negotiating z-order.

karai17 commented 3 years ago

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, notifications@github.com 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 https://github.com/karai17/Simple-Tiled-Implementation/issues/251#issuecomment-777262587, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEQD7G7VY7QP32UDKMMEY3S6OFKHANCNFSM4W4GRJEA .

konsumer commented 3 years ago

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
    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.

karai17 commented 3 years ago

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, notifications@github.com 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 https://github.com/karai17/Simple-Tiled-Implementation/issues/251#issuecomment-777357820, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEQD7EN3WBIJWGMMSZDN43S6OYZ5ANCNFSM4W4GRJEA .

konsumer commented 3 years ago

my suspicion is that it changes a bit every frame

Isn;t that what I'm shooting for?

It moves too fast, with a regular print, so I did this in my top-level draw():

for o, object in pairs(self.map.layers.objects.objects) do
  love.graphics.print(object.id, 0, (o-1)*12)
end

2021-02-11 03 02 20

It definitely shouldn't be swapping order that fast.

konsumer commented 3 years ago

In the sort function I noticed there were nils so I tried this too, with no effect:

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
konsumer commented 3 years ago

weirdly first, middle, and last seem to stay the same.

karai17 commented 3 years ago

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, notifications@github.com 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 https://github.com/karai17/Simple-Tiled-Implementation/issues/251#issuecomment-777383094, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEQD7HMMZQCWRD6UUXJBI3S6O5UJANCNFSM4W4GRJEA .

konsumer commented 3 years ago

yeh, I was thinking that about pairs(), like issue reminds me of the old days of javascript with associative-array objects. Like the order used to be random (or at least "not guaranteed") unless you used an array to keep the order. I could build a separate numeric array of just the gids, then lookup in the sorting function. I will experiment with that and pre-setting Z, then adjusting it based on Y. It seem unlikely to me that they are all on the same Y value, which would cause the whole "I don't care which order because they are the same Y" problem, in sorting, so it must be some other problem with sort.