Shirakumo / trial

A fully-fledged Common Lisp game engine
https://shirakumo.github.io/trial
Other
1.03k stars 50 forks source link

Turning Left (Sprite flipping) #90

Closed fosskers closed 2 months ago

fosskers commented 2 months ago

Hi, I'm having some trouble getting sprites to flip / pivot. I've tried three approaches:

Emulating Kandria

Emulating facing-entity and manually updating direction as is done for the player and critter classes in various places.

(define-shader-entity farmer (animated-sprite located-entity)  ;; <- any mixins missing?
  ((sprite-data :initform (asset 'farm 'farmer))
   (direction :initarg :direction :initform 1 :accessor direction
              :type integer :documentation "-1 for left, +1 for right")))

(defmethod apply-transforms progn ((farmer farmer))
  (scale-by (direction farmer) 1 1))

Effect: up/right/down movement works as expected, but when I press left, the farmer sprite becomes invisible. Pressing right again makes him reappear.

Two possibilities come to mind:

  1. Because I put direction directly on farmer and not in a similar abstracted facing-entity class, the order in which apply-transforms runs w.r.t. located-entity might be in conflict.
  2. I have perhaps made a wrong assumption about sprite data, assuming Trial would auto-magically flip all the pixels if scale-by is called in this way. The assumption is based on Kandria, but maybe I missed some detail while reading its code.

Farmer as a scaled-entity

(define-shader-entity farmer (animated-sprite located-entity scaled-entity)
  ((sprite-data :initform (asset 'farm 'farmer))))

Instead of flirting with the powers of apply-transforms directly, we let scaled-entity do the work. In tick we have:

    (cond ((> (vx movement) 0) (setf (scaling farmer) (vec 1 1 1)))
          ((< (vx movement) 0) (setf (scaling farmer) (vec -1 1 1))))))

Unfortunately the farmer still turns invisible when the X-scaling is negative. The inheritance order of located-entity and scaled-entity doesn't affect this particular outcome.

Farmer as a pivoted-entity

Intuitively the word "pivot" seems like what I'd what. Referencing the Scene Graph page of the guide:

(define-shader-entity farmer (animated-sprite located-entity pivoted-entity)
  ((sprite-data :initform (asset 'farm 'farmer))))

The pivot slot holds (vec 0 0 0) as its default, and seeing that this class isn't used in Kandria, I wasn't sure what constitutes a proper "pivot vector", so the following is almost certainly wrong (seeing that apply-transforms on pivoted-entity calls down to translate, and then lower into your math library):

    (cond ((> (vx movement) 0) (setf (pivot farmer) (vec 0 0 0)))
          ((< (vx movement) 0) (setf (pivot farmer) (vec -1 0 0))))))

Although even with this, left-presses don't make the farmer invisible, but they also don't flip the sprite.

If I should indeed just do it the Kandria way with manually tracked direction, can you think of a reason why scaled-by would turn him invisible? Thanks again.

Shinmera commented 2 months ago

Flipping the sprite like this will make the geometry's backside show the camera. By default backface culling is on, and you probably haven't turned it off.

(defmethod setup-rendering :after ((main main))
  (disable-feature :cull-face))
fosskers commented 2 months ago

Worked like a charm, thanks again. Key points for anyone from the future:

  1. My entity definition is:

    (define-shader-entity farmer (animated-sprite scaled-entity located-entity)
    ((sprite-data :initform (asset 'farm 'farmer))))

    Notice that scaled-entity comes before located-entity in the inheritance order. Otherwise he will flip, yes, but also be teleported around.

  2. My tick handler looks like:

    (define-handler (farmer tick :before) ()
    (let ((movement (directional 'move)))
    (incf (vx (location farmer)) (vx movement))
    (incf (vy (location farmer)) (vy movement))
    (cond ((> (vx movement) 0) (setf (scaling farmer) (vec 1 1 1)))
          ((< (vx movement) 0) (setf (scaling farmer) (vec -1 1 1))))))

    Although, this might be allocating a new vector for every tick (of movement). Doing it the Kandria way, shown above, would be more efficient since you're passing an integer directly to scale-by. You could also argue it's clearer for the entity to only manage its "direction". Is it obvious what effect setting scaling directly here in the handler has? Probably not.

  3. Here is how Kandria sets up its own setup-rendering, where we indeed see culling disabled.

Shinmera commented 2 months ago

Instead of allocating a new vector every time you can just use (setf (vx (scaling farmer)) -1)

fosskers commented 2 months ago

Right, duh, just as I'm doing for the location. Thanks.