Skip to content

Changed Display from one sprite per cell to sprite stack per cell.#276

Open
marcojulian wants to merge 1 commit into
PrismRL:masterfrom
marcojulian:marcojulian/transparency-not-working
Open

Changed Display from one sprite per cell to sprite stack per cell.#276
marcojulian wants to merge 1 commit into
PrismRL:masterfrom
marcojulian:marcojulian/transparency-not-working

Conversation

@marcojulian

@marcojulian marcojulian commented Jun 1, 2026

Copy link
Copy Markdown

PR summary: Display now supports layered sprites per cell, preserving terrain beneath transparent actor/item sprites while keeping existing depth ordering.

Old behavior:

  • cell.char held single tile.
  • Floor drawn first, actor drawn later.
  • Actor replaced floor tile.
  • Transparent actor pixels showed display clear color, not floor.

New behavior:

  • Each cell has sprites = { ... }.
  • put() appends sprite with depth/layer.
  • draw() sorts cell sprites by depth and draws all.
  • Transparent pixels now reveal lower sprite, usually floor.
  • Background color got separate bgDepth, so sprite depth and bg depth no longer fight.
  • clear() empties sprite stack each frame.

Before:
before

After:
after

@MatthewBlanchard

Copy link
Copy Markdown
Member

Hey! I really appreciate the PR and this is a really common request, so all great there. I'm thinking more about where this fits into the broader library.

I think it's kind of nice that the default display is dead simple, but I'd be totally willing to include this in spectrum as well as say StackedDisplay or somesuch. I'll do a deeper dive and give more feedback tomorrow or the next day hopefully.

Thanks so much for your contribution!

@MatthewBlanchard

Copy link
Copy Markdown
Member

Also any idea how this plays with Animations?

@marcojulian

Copy link
Copy Markdown
Author

Of course! Please keep in mind that this is a late Sunday night solution so I know there could be room for improvement before merging.

I didn't test it with animations since I haven't implemented any in my game yet. But I could add a couple and let you know how it goes!

@MatthewBlanchard

Copy link
Copy Markdown
Member

Discussed it in the discord with @fingoltin he's open to making this the default.

@marcojulian

marcojulian commented Jun 1, 2026

Copy link
Copy Markdown
Author

Seems to be working for animations as it is 🙌

Animation attached to actor:

local firepitlit1 = { index = 478 }
local firepitlit2 = { index = 479 }
local firepitlit3 = { index = 480 }
local firepitlit4 = { index = 481 }
local firepitlit5 = { index = 482 }
local firepitlit6 = { index = 483 }

spectrum.registerAnimation("FirePitLit", function()
   return spectrum.Animation({ firepitlit1, firepitlit2, firepitlit3, firepitlit4, firepitlit5, firepitlit6 }, 0.2)
end)
prism.registerActor("FirePit", function()
   return prism.Actor.fromComponents {
      prism.components.Name("Fire Pit"),
      prism.components.Position(),
      prism.components.Drawable { index = 478 },
      prism.components.IdleAnimation("FirePitLit"),
      prism.components.Collider(),
      prism.components.Remembered(),
   }
end)
fire lit

Animation around on action:

local hidden = { index = 1, layer = math.huge }
local visible = { index = 506, layer = math.huge }

local function putAround(sprite)
   return function(display, x, y)
      display:putDrawable(x + 1, y, sprite)
      display:putDrawable(x - 1, y, sprite)
      display:putDrawable(x, y + 1, sprite)
      display:putDrawable(x, y - 1, sprite)
   end
end

spectrum.registerAnimation("PlayerWait", function()
   return spectrum.Animation(
      { putAround(hidden), putAround(visible), putAround(hidden), putAround(visible) },
      0.08,
      "pauseAtEnd"
   )
end)
--- @class Wait : Action
--- @overload fun(owner: Actor): Wait
local Wait = prism.Action:extend("Wait")

function Wait:canPerform()
   return true
end

function Wait:perform(level)
   if not self.owner:has(prism.components.PlayerController) then return end

   level:yield(prism.messages.AnimationMessage {
      animation = spectrum.animations.PlayerWait(),
      actor = self.owner,
      blocking = true,
   })
end

return Wait
animation

Same cell animation on action:

local hidden = { index = 1 }
local visible = { index = 506 }

spectrum.registerAnimation("PlayerWait", function()
   return spectrum.Animation({ visible, hidden, visible, hidden, visible }, 0.2, "pauseAtEnd")
end)

With same Wait action code as in the prev example:
ezgif-42c70ad4dc7b4d44
and with override = true:
ezgif-4b11e2b8ef578df7

@fingoltin

Copy link
Copy Markdown
Member

After testing it out we do need a way to enable the current behaviour. Maybe we try subclassing?

Comment thread spectrum/display.lua Outdated
@marcojulian marcojulian force-pushed the marcojulian/transparency-not-working branch from e1bdf33 to 8d0c620 Compare June 4, 2026 21:53
@marcojulian

Copy link
Copy Markdown
Author

✔️ I repeated the same manual tests that I shared above after replacing Display with StackedDisplay in my own main.lua

@MatthewBlanchard

Copy link
Copy Markdown
Member

This looks quite good. I have a few remaining questions:

Where/if this breaks with the Display API? Will this mostly work drop in? Does it work with geometer?

@marcojulian

Copy link
Copy Markdown
Author

This looks quite good. I have a few remaining questions:

Where/if this breaks with the Display API? Will this mostly work drop in? Does it work with geometer?

@MatthewBlanchard not sure I fully got the question, but:

  • None of the changes applied in StackedDisplay should affect end users if they don't use it
  • In my use case I replaced Display with StackedDisplay in my main.lua and worked out of the box
  • I saw no errors, I even used the in-game geometer tool to put the animated firepit that I shared above

Also I'm not jealous of my PR, if you feel the need to jump-in and tweak something feel free to do it 🙌 Otherwise please let me know if you have any other questions/concerns or want me to test anything else!

@MatthewBlanchard

MatthewBlanchard commented Jun 6, 2026

Copy link
Copy Markdown
Member

Thank you! I think I've got it from here and this is going to be merged with a few minor changes!

Thanks a ton. Should hit main this weekend.

Different displays that are interchangeable, or for the most part, was actually one of our design goals we never really got time to do so thank you for helping us!

@MatthewBlanchard MatthewBlanchard added the enhancement New feature or request label Jun 6, 2026
@fingoltin

Copy link
Copy Markdown
Member

I didn't think StackedDisplay would be so big? Isn't there more overlap?

@MatthewBlanchard

Copy link
Copy Markdown
Member

I didn't think StackedDisplay would be so big? Isn't there more overlap?

I looked at this. It's because of issues with the Display API. I suggest we accept this now and make changes to Display to make it easier to subclass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants