Descending into the depths

In this chapter we’re going to add stairs and create a new Level when the player uses the Descend action on the stairs.

Adding a stair component

The first thing we’re going to want to do is create a new tag component. This component will be used to indicate that this actor is a staircase.

Navigate to modules/MyGame/components/ and create a new file called stair.lua. This will be a simple tag component.

--- @class Stair : Component
local Stair = prism.Component:extend("Stair")
Stair.name = "Stair"

return Stair

This is just a simple tag component like void earlier in the tutorial.

Creating the stairs actor

Next we’ll create a stairs actor, again really simple.

prism.registerActor("Stairs", function()
   return prism.Actor.fromComponents {
      prism.components.Position(),
      prism.components.Drawable("<"),
      prism.components.Stair(),
      prism.components.Remembered(),
   }
end)

Placing the stairs on the map

Okay we’ve got our stairs created, it’s time to place them on the map. Navigae to levelgen.lua and head to the bottom of the function right above return.

--- @type Rectangle[]
local availableRooms = {}
for _, room in pairs(rooms) do
   if room ~= startRoom then
      table.insert(availableRooms, room)
   end
end

local stairRoom = availableRooms[rng:getUniformInt(1, #availableRooms)]
local corners = stairRoom:toCorners()
local randCorner = corners[rng:getUniformInt(1, #corners)]

builder:addActor(prism.actors.Stairs(), randCorner.x, randCorner.y)

We collect all the rooms the player didn’t spawn in into a table, and then choose a random room. We place the stairs in a random corner of that room for now.

So we’ve got stairs now, but we can’t do anything with them. Let’s move on.

The descend message

Navigate to moudles/MyGame/messages and create a new file descend.lua.

--- @class DescendMessage : Message
--- @overload fun(): DescendMessage
local DescendMessage = prism.Object:extend("DescendMessage")

return DescendMessage

For now we don’t need anything inside of Descend, we’ll get to that later.

The descend action

local DescendTarget = prism.Target()
   :with(prism.components.Stair)
   :range(1)


---@class Descend : Action
---@overload fun(owner: Actor, stairs: Actor): Descend
local Descend = prism.Action:extend("Descend")
Descend.targets = { DescendTarget }

function Descend:perform(level)
   level:removeActor(self.owner)
   level:yield(prism.messages.Descend())
end

return Descend

First we create a target that targets actors with the stair component within range 1. Then we create our Descend action, which is really similar to Die except we send a different message.

Now let’s add some code to MyGameLevelState:keypressed. After we figure out which direction the user just pressed we’ll add the following.

if keybindOffsets[action] then
   local destination = owner:getPosition() + keybindOffsets[action]

   -- add this
   local descendTarget = self.level:query(prism.components.Stairs)
      :at(destination:decompose())
      :first()

   local descend = prism.actions.Descend(owner, descendTarget)
   if self.level:canPerform(descend) then
      decision:setAction(descend)
      return
   end

Creating the next floor

Now that we’ve got everything set up we need to actually handle the descend message. In MyGameLevelState:handleMessage we’ll add the following message handling.

if prism.messages.Descend:is(message) then
   self.manager:enter(MyGameLevelState(self.display))
end

Okay and now if we run the game and go find ourselves a staircase we’ll be able to go down to a new floor!

A couple of problems, though. The new level has a completely new player on it and we’re not tracking depth anywhere.

In the next chapter

We’ll set up a Game object that tracks what depth we’re on and manages level generation. We’ll pass the player to the new level so that we’re playing the same character all the way down.