karai17 / Simple-Tiled-Implementation

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

Cannot use draw function for sprite layer #281

Closed LemonBreezes closed 1 year ago

LemonBreezes commented 1 year ago

Hi. So I have this code

(local (w h) (love.window.getMode))
(local sti (require "lib.sti"))
(local lume (require :lib.lume))
(local utils (require :utils))

(local tile-size 12)
(local map (sti "assets/maps/test-map.lua"))
(local scale 5)
(local camera {:tx 0 :ty 0})
(local accel 0.1)

(local keys {:left [:x -1] :right [:x 1] :up [:y -1] :down [:y 1]})

;; Draw sprites.
(local (player-layer player-layer-index) (utils.get-layer map "player"))
(local player-start (-> (. player-layer "objects")
                        (lume.match (fn [o] (= o.type "player")))))
(var player nil)
(local sprites [])

(set player-layer.visible false)
(let [layer (: map :addCustomLayer "sprites" player-layer-index)]
  ;; This doesn't work. I think it's because of the draw order.
  (set layer.draw
       (fn []
         (each [_ sprite (ipairs sprites)]
           (sprite.draw sprite "fill")))))

(fn make-sprite [x y]
  {:x x :y y
   :draw (fn [self mode]
           (love.graphics.rectangle "fill"
                                    (math.floor (* self.x scale))
                                    (math.floor (* self.y scale))
                                    (math.floor (* tile-size scale))
                                    (math.floor (* tile-size scale))))})

(fn init []
  (set player (make-sprite player-start.x player-start.y))
  (table.insert sprites player))

(fn update [dt set-mode]
  (: map :update dt)
  (each [key action (pairs keys)]
    (let [[dir speed] action]
      (when (love.keyboard.isDown key)
        (tset player dir (+ (. player dir) (* accel speed)))))))

(fn keypressed [key set-mode])

(fn draw [message]
  (: map :draw (- camera.tx) (- camera.ty) scale scale)
  ;; I had to move the draw code down here to make it work.
  (each [_ sprite (ipairs sprites)]
    (sprite.draw sprite "fill")))

{:name "overworld" :init init :update update :keypressed keypressed  :draw draw}

and the issue I'm having is that the layer.draw for the Sprite layer does not draw any sprites visibly on the screen even though it gets called. When I copied the code from layer.draw after (: map :draw ...), the sprites draw correctly. What could be causing this? Is it possible to draw the sprites within the layer itself?

EDIT: The player layer is the top layer so the layer order should not be the issue.

karai17 commented 1 year ago

I am struggling to read that code.. However, STI is specifically designed to allow you to insert "custom" layers inside of your map that have your own update and draw functions attached. It looks like you are adding a new custom layer and assigning the draw function. Are you certain that the draw order is correct?

Edit: Perhaps you can use this tutorial I wrote to help you as well: https://github.com/karai17/Simple-Tiled-Implementation/blob/master/tutorials/01-introduction-to-sti.md.

LemonBreezes commented 1 year ago

I am struggling to read that code.. However, STI is specifically designed to allow you to insert "custom" layers inside of your map that have your own update and draw functions attached. It looks like you are adding a new custom layer and assigning the draw function. Are you certain that the draw order is correct?

Edit: Perhaps you can use this tutorial I wrote to help you as well: master/tutorials/01-introduction-to-sti.md.

Thank you! I ended up using the approach from your tutorial and scrapping what I had and working with that. For some reason,

(set layer.draw
     (fn [self]
       (love.graphics.rectangle "fill"
                       (math.floor (* self.player.x scale))
                       (math.floor (* self.player.y scale))
                       (math.floor (* tile-size scale))
                       (math.floor (* tile-size scale)))))

does not work but making a sprite and using

(set layer.draw
     (fn [self]
       (love.graphics.draw self.player.sprite
                           (math.floor self.player.x)
                           (math.floor self.player.y)
                           0 (if (= self.player.dir :left) -1 1) 1
                           self.player.ox
                           self.player.oy)))

does work.

karai17 commented 1 year ago

My guess here is that you were scaling the origin point of the rectangle (first 2 args) as well as the rectangle's size (second two args), so it was likely drawing, just off screen.

LemonBreezes commented 1 year ago

Ok! Hmm. For now I'll use sprites, but if I run into that kind of issue again, I'll be a bit more creative.

karai17 commented 1 year ago
(set layer.draw
     (fn [self]
       (love.graphics.rectangle "fill"
                       (math.floor (* self.player.x scale)) -- this probably shouldn't be scaled
                       (math.floor (* self.player.y scale)) -- nor this
                       (math.floor (* tile-size scale))
                       (math.floor (* tile-size scale)))))

If your player's position is at, say, 200,200, when you multiply that by scale which in your code is 5, that sets the rectangle's top-left corner to 1000,1000 which would be out of bounds in a default 800x600 LOVE window. So not only are you scaling up its size, but also scaling its position which is likely why the function was being called but you weren't seeing anything. I hope that makes a little more sense.

LemonBreezes commented 1 year ago
(set layer.draw
     (fn [self]
       (love.graphics.rectangle "fill"
                       (math.floor (* self.player.x scale)) -- this probably shouldn't be scaled
                       (math.floor (* self.player.y scale)) -- nor this
                       (math.floor (* tile-size scale))
                       (math.floor (* tile-size scale)))))

If your player's position is at, say, 200,200, when you multiply that by scale which in your code is 5, that sets the rectangle's top-left corner to 1000,1000 which would be out of bounds in a default 800x600 LOVE window. So not only are you scaling up its size, but also scaling its position which is likely why the function was being called but you weren't seeing anything. I hope that makes a little more sense.

Oh wow! This code worked:

(fn [self]
  (love.graphics.rectangle "fill"
                           (math.floor self.player.x)
                           (math.floor self.player.y)
                           (math.floor tile-size)
                           (math.floor tile-size)))

What threw me off was that the code with the scaling worked outside of layer.draw after map:draw but I guess that's because the map was being scaled after the rectangle was being drawn.