Posted December 15, 2022 by GrahamT
#post-mortem #lcd handheld #tutorial #unity #programming #demake #tiger handheld #technical
During my first playthrough of Far: Lone Sails, I was struck with the idea that the basic gameplay loop of moving around the vehicle, loading fuel, putting the sail up and down, and pressing the motor button had a similar structure to an old Tiger Handheld or Game & Watch style game. The spaces in between weren’t so important, but rather the interactions at each location. Initially I shelved the idea as ‘kind of neat’, but didn’t think much more of it.
Far: Lone Sails main gameplay area
A few years later, playing through the nautical sequel, Far: Changing Tides, the idea came up again. By this time I’d spent a bit more time on game development and it seemed like something achievable, if not obvious. At first I assumed someone would have already made something like this for Far: Lone Sails, but after some Googling, I discovered that I couldn’t really find any actual playable LCD Handheld demakes. Only a few videos or animations of the concept were readily available for games like Skyrim and Elden Ring.
Skyrim LCD Handheld demake video (Credit to 64bits)
With the idea firmly taking root, I decided to try and see if I could make something similar in Unity.
At the core of how these LCD handhelds work is a few main components:
The LCD screen is really the core of what made these games so cheap to mass produce. Rather than controlling a large array of individual pixels, or ‘picture elements’ composing pictures, like the Gameboy or modern Playdate, the LCD Screen was manufactured with only a few discrete pictures that could be either on or off. On the Gameboy, which has a 160px x 144px resolution screen, this meant having to process, control, and illuminate over 23,000 outputs, each with different output brightnesses, in order to create a game. By contrast, a handheld LCD could be limited to around 6-50 outputs, with pre-drawn sprites baked into the screen itself, and only an on/off state for each picture.
Kirby’s Adventures on Gameboy showing off the available pixels.
This arrangement made for a significant cost reduction, but much less generality. Work would have to be put in to design one game, but once it was designed, printing the board and baked LCD screen were relatively inexpensive, because they were purpose built to the designed game. This also meant once you had the board and screen designed and ready for production, you were stuck with it. No loading other games, no modifying it. The handheld would only play that game because the screen only had the sprites to play it, and the board was hardwired for playing that game.
From a design perspective, this creates an interesting challenge: Sprites didn’t move around and could not be redrawn anywhere else on the screen. They were static in the location they were baked into, so the game had to be designed around that limitation. There was also no option to add additional colour, brightness, or textures to sprites: The lcd elements are activated by applying voltage to a polarizer, which turns elements either opaque or transparent, and similar to how only the digit pieces on a microwave LCD are specified. For the handheld games, only the sprite areas were defined by etching them into the LCD screens. Sprites couldn’t move, and two sprites couldn’t occupy the same space.
This made for several design rules I wanted to use to put a demake together:
I spent some time looking at options for how to begin implementing the design in Unity for this demake. I wanted to use Unity because I was relatively familiar with it, but wasn’t sure how to set up the coding. After asking around and doing some research, I hit on the concept of a State Machine, similar to Unity’s animation states, but set up in programming for specific non-animation purposes. The idea was to have a core abstract ‘PlayerState’ function that contained empty functions for each of the actions I wanted a player to be able to do: move, jump, pickup/drop. Then for each location where the player could physically be, there would be a script for that location, PlayerState#, which inherits the PlayerState script, but overrides the empty functions with functions specific to that state.
Basic prototype PlayerStates
In this example, playerstate0 can only move right to playerstate1. Playerstate1 can either move left to playerstate0, or after a certain amount of time, fall to playerState2. Playerstate2 can either move left to playerState3 or jump to playerState1. If the player presses ‘Right’, there should only be a reaction from the character if they’re in state 0 or 3. In states 1 and 2, pressing ‘Right’ should do nothing.
Somewhere, a GameManager script needed to keep track of what state the player was in, and ensure that only commands applicable to that state are activated when the player presses a button. By using the inheritance, this was accomplished by simply creating a playerState variable, currentPlayerState, within the GameManager script, and updating it whenever the player moved by assigning their current state. Because the functions are inherited, commanding currentPlayerState to activate the specific functions, the function would either be blocked if the current state didn’t have an overridden definition, or executed for that specific state if it did.
When I originally programmed this, I was expecting it would only need to scale up to 17-20 PlayerStates to cover the entire vehicle for Far: Lone Sails. In the end I wound up with 52 player states. Keeping the scope more in line with a traditional handheld LCD handheld game would probably mean more like 5-10 states, which is far more manageable.
Because the player doesn’t actually move, and the only ‘movement’ is shown by turning off one sprite and turning on another, I created a GameObject at each of the locations where I wanted the player to be, then created a list of each playObject in GameManager, allowing it to select which one is turned on by which playerState is active.
Falling happens over time, and only on playerState1, where the player is in the air. Therefore it has to be checked each frame. To do this, I coded it as the function PlayerUpdate(), which is automatically called in the Update() function of GameManager. In this case only PlayerState1 contains an override for the PlayerUpdate function. It checks how long it’s been since the state was entered and if it’s longer than the defined playerHangTime variable, it exits the state to playerState2.
As part of the game, I knew I wanted to allow the player to pick up boxes from the ground in order to continuously add fuel to the vehicle and keep the motor running. Similarly to PlayerState, this meant that anywhere the player might be while holding a box, needed to have a HeldBox GameObject in that location to turn on and off. There also needed to be a list of all the possible HeldBoxes contained in GameManager to turn them on and off as the player moved with a box. Because boxes could be picked up and put down, this also meant a second GameObject, GroundBox, was needed at each location where boxes could be left on the ground. To make things easier for keeping track, and for programming, I labelled each box object with the number corresponding to the playerState at that location.
Box numbering and identification to match player states
Because boxes couldn’t be put down in locations where the player jumps/falls, there would be no groundBox and those locations. However because I wanted the index of the list of boxes to correspond to the state # (groundBox[0] was the ground box for playerState0, groundBox[10] for playerState10 etc.), I couldn’t just skip the list entries. Instead I created an empty GameObject called nullBox, which was added in the list for any groundBox or heldBox where there was actually no box. For the Far: Lone Sails demake, only a few places don’t allow you to get there while holding a box, mostly due to constraints with squeezing in more sprites.
For the Far: Lone Sails Demake, picking up and putting down boxes was the main player action that wasn’t movement. For a different handheld style game, this might be a different action like shoot, attack, open door, spin, etc. In a more general case, this could be coded as a ‘PlayerAction()’ function, which could even provide different functions at different locations. Maybe it swings a weapon in location if the player is pushing forward, but blocks if the player is holding back.
At this point I had a functional prototype that performed the core functions I wanted. If you are looking to build your own LCD demake, you can use this core set up to start building your own. Specific layouts, functions, and timing will need tweaking for your own project, but the basics will be there. For convenience I’ve included the above prototype as a GitHub file you can download and start your own project from:
https://github.com/billtg/LCD-Handheld-Demake-Prototype
From here I’ll cover some of the considerations I made as I took this from a working prototype up to the full scope of Far: Lone Sails, and talk about what I considered along the way.
The core gameplay loop for Far: Lone Sails as I saw relevant for my handheld demake is:
Within that loop are smaller loops for managing health, increasing speed through steam power, and dealing with fires and damage caused by mistakes. Within the actual game, you’re often required to get off your vehicle, travel through derelict areas, and upgrade your vehicle as you progress. Initially I thought this could be part of the mechanics of my handheld, but as time dragged on I had to drop it and focus instead on realizing the core gameplay loop as it takes place on your vehicle.
The primary gameplay would need to take place on board the vehicle. In the original game, the camera would adjust based on your location to allow you to focus internally on the vehicle, or externally on the environment as you interacted with it. Because an LCD handheld would only be made up of static images turning on and off, and because I wanted to include gameplay both on and off the vehicle, I aimed for a layout and view striking a balance between on and off vehicle space.
In-Game vehicle layout allowing player inside and outside the vehicle
LCD Handheld games have a phenomenally small resolution. This is partly due to the restrictions of the screen technology at the time, only allowing relatively large sprites to be created in the screens, but also due to a limitation in the hardware, which could only manage a certain number of sprites before the board and wiring had to be increased, increasing cost and complexity. Because I had neither of those limitations, and because I wanted to capture the detail of the vehicle in my background image, I stuck with a 1920x1080p resolution. Looking back, this is a pretty major diversion in terms of what could now fit into the game, and added significantly more scope than would be included in a typical LCD handheld. If I were redoing this project I would have gone with a much smaller resolution and tried to contain it to something that could conceivably have fit onto a handheld LCD screen.
With this in mind I created a photoshop document with the screenshotted vehicle/environment as the background layer. Keeping the same dimensions in this image and in the game would allow me to start building the visual aspects of the game while ensuring they would look the same in Unity once imported.
One of the limitations of LCD Handhelds is the lack of colour. Because liquid crystals use polarization to switch between states of transparency and opaqueness, it requires they only use one colour. Any other detail or colour in the games is instead required to come from the background image.
Batman Handheld showing off negative space over static background
This means details are coming from the negative space in your sprites. In traditional handhelds, this generally took on the form of lineart, but also occasionally took on incredibly detailed work.
One of the most distinctive aspects of LCD handhelds from the Tiger era is the low quality, grating sound output generated by the low-cost chips. While researching the topic I came across the original data book from Sharp Microsystems, who supplied the chips for most of the handheld games during that era with their SM511 and SM512 chips.
Melody Table from the Sharp Microsystems 1990 Data Book
The table specifies the exact frequencies for the individual higher octave scales, with instructions for how the lower octave is generated. It also specifies the note length of either 125 or 62.5ms for each note.
Using this information I created a custom keyboard in Ableton to generate square wave tones at the specific frequencies, with frequencies assigned to their intended notes. This provided the short, chipped sound of the handhelds while allowing me to create music and sound effects using a MIDI keyboard. By attaching an arpeggiator and setting the repeat timing to either 64 or 125ms, I was able to create longer repeated notes and tones.
Unmake me
Overall I felt like I created a faithful adaptation of Far: Lone Sails that was true to my original idea. The main divergence was the massively increased resolution and sprite count, which really created a different feel than the slow, claustrophobic handhelds I grew up with. If I were starting the project again, I would restrict myself to a much smaller play screen and force all the sprites to exist in that small space. At the same time, a lot of the functionality would fall by the wayside.
One aspect I would have liked to improve is player direction. During an initial paytest by my brother, who had never played the original, he let me know that he had tried putting a box in every space on the vehicle, but didn’t know if that was the point. I asked if he had pushed the button to get maximum speed and he asked “Button?”
Push it
Better player direction would have let him know what the intent of the game is without having played Far: Lone Sails. Any veteran player would immediately know to push the button to make the car go, but without that knowledge, it’s a confusing start. I added an audio cue that it was being pushed, but he may not have been the only player to hit that confusion.
At the same time, this sense of not knowing exactly what’s happening is perfectly in line with the old LCD handheld games. While some games gave a great idea what was happening, others left a lot of interpretation up to the player. I like to think I found a good halfway point that shows a lot of what’s going on, while still requiring some imagination from the player.
Overall this project taught me a lot and gave me a focused, creative project for a few months that kept me motivated to work on. I could see making a smaller, more faithful LCD demake in the future, but for now it’s time to put this one down and move onto other projects.