Puzzle Elements


To create puzzles, I need to create the building blocks to use, these include:

  • Functional doors;
  • Emitters;
  • Moving Photons;
  • Receivers;
  • Buttons;
  • Filters;
  • Mirrors;
  • Boxes;
  • and a trigger system.

When I started implementing these, I thought I would be using the tile map, but once I started, I realised that I needed game objects to contain the more specific information, like the colours of objects. Because of this, I tried using the game object property of tiles, which was so close to working. However, since the tiles only instantiate their game objects at runtime, this does not allow for editing values individually like I had hoped. Instead, I opted to placing game objects in the world, and in specific cases like doors, they would add a tile to the world at their position. Each object has a GridObject component, which snaps it to the grid and allows for setting a tile at its position, this is only used for doors and buttons, however. This approach does leave the editor looking a little off, as the objects placed are not actually tiles, and hence can be placed anywhere, and the sprites do not update until runtime.

Triggers

To connect elements, I created a system of Triggerables and Triggerers. Triggerers can be used by any other component on the object, emitting either a rising or falling signal that is forwarded to all connected Triggerables which then forward it to any listeners on the object. This system allows for easy wiring between objects, with two types of signals to control things like doors.

Doors

First, I created doors, as I already had sprites for these in the wall sprite sheet. They just have a property to control if they are open or not. They hide their placeholder sprite once the game starts and place the appropriate tile in the tile map, which allows adjacent walls to connect. This tile is also updated when the door's state is changed by a trigger, which happens on both rising and falling signals.

Figure 1, doors in the scene view, showing just a default sprite to make them easy to see

Figure 2, same doors after the game as started

Emitters

Emitters require some other values to function, namely a direction and a colour of photon to emit. To accommodate these I created two components, one for each property. Since these will be reused across multiple elements, having a consistent way of storing the values is a good idea. The direction is stored with an enum of compass directions, with methods for rotating to make each direction work the same. The colour component is more or less a wrapper for Color as a component. When the emitter receives a rising signal, it emits a photon in the space it is facing with the same properties as itself. The emitter has 8 sprites currently, but in future it should have a display for what colour it is emitting like in the concept art. These 8 sprites are arranged around a central void to more easily distinguish the directions. I used 8 sprites since it is easier to make 8 for each direction in an image editor than rotate the same two sprites by different amounts. This is a common pattern throughout the elements. As stated earlier, only the door and button actually place tiles in the world.

Figure 3, emitter placed on top of a wall, as it is not a tile

Photons

The main mechanic, photons. Similar to emitters, these also have a direction and colour, so I reused these components for the photon. Since these are mobile, I now needed to implement the Step method from the player. This is achieved by using a Steppable component on the photon which stores callbacks to run on step, each of these components adds itself to a list stored in the level grid, which the player uses to update everything. Like the player, photons use the IsWall method to determine where they can move. The photons initially used a similar set of 8 sprites to the emitters, but eventually once I created filters and mirrors, this increased to 32. These 32 sprites are composed of 8 for each of: going straight, turning left, turning right, and reversing. And each sprite has a shader applied to it that fills in the correct colours based on the situation. The red channel of the sprite is dedicated to the colour of the photon, the green channel is just a brightness value, and the blue channel is the previous colour. 

Figure 4, the "turn left" portion of the photon sprite sheet, magenta is where both colours are combined.

Figure 5, a photon emitted from an emitter

Receivers

Receivers do not have a direction, as I decided that this merely overcomplicated them. They do still have a colour component for the required photon, although there is a toggle to remove this requirement and allow any photon to work. When a photon moves, it attempts to enter any receivers on its tile and emits a rising signal if it can before disappearing.


Figure 6, a receiver placed on a wall

Buttons

This is the other element which places tiles in the world, although it doesn't have to. To make the button work, I created a SpaceListener component which communicates with the level grid to receive updates when either a box or the player enters its space.  When something enters the space, it emits a rising signal and when it leaves, it emits a falling signal. This means any connected doors will only be toggled while the button is pressed.


Figure 7, button sprites


Figure 8, standing on a button

Filters and Mirrors

Mirrors are just more advanced filters in a way, they act exactly the same, but also reflect the photon back. I created a few modes for the filters, including subtractive, additive, shift right, shift left, exact and invert. These work about how you'd expect when considering the colours as vectors of RGB. The sprites use a similar shader to the photons, with the corners having a highlight to denote the type of filter determined by the blue channel. These filters update the photon's colour and make it render with two colours for the step that it hits the filter. Mirrors have all of this functionality plus the ability to reflect photons, which also updates the sprite to have a bounce sprite.


Figure 9, filters and mirrors in the editor (as you can see, they don't sit on the grid perfectly)

Figure 10, two photons passing through a filter and reflecting off a mirror, the filter is yellow which only allows the red channel of magenta through, and the mirror is a shifter, which changes the red into green

Boxes

Boxes combine a lot of the other features into one and required a rewrite of the player movement code to merge the two. Boxes can contain filters, mirrors or nothing at all and are able to be pushed around. Since the player needs to be able to push the boxes, I combined their logic into a single component that allows for pushing an arbitrary number of objects with the component in a row unless there is a wall blocking them. This required careful adjustments to the SpaceListener code to not send an extra event when swapping what is covering a space (which would emit an extra photon or whatever).

Figure 11, box pushed onto a button

Figure 12, box reflecting a green photon back

Figure 13: player using boxes, buttons and doors.

Leave a comment

Log in with itch.io to leave a comment.