Please or Register to create posts and topics.

S01E07 - Environmental Concerns

Welcome to episode seven of our Let's Remake Manic Miner series. In a shorter episode for today, we are going to get back into our main source file and take a look at some environmental concerns. That is, we are going to turn our attention to the environment that we are going to place Miner Willy.

Before We Begin

In our last episode, we created a simple program that uses the sprite bank we have and saved off a representation of the Central Cavern level as a Degas format screen. We will need to load that image into our game in order to make Miner Willy feel at home. One of the things that we need to remember is that we are creating a game engine, so I think we should start adding some elements to respect that. We'll need to make some adjustments to our game loop. In fact, we need to build a new loop around the game loop we have already created so that we can control the loading of level information and the setup of each level before the main game loop begins. We'll also setup some placeholder subroutines to handle things like the main menu.

The above shows what our main game loop looked like previously. What we are going to do is change that drastically now so that we have much more logic going on. Remove the code and replace it with the following:

gosub @LoadAssets
gosub @MainMenu
end

Now, let's add a new subroutine to deal with the main menu We're not going to do anything here at all other than create this as a space holder that then calls our main game loop.

@MainMenu
    QUIT = false
    repeat
        gosub @NewGame
    until QUIT
    return

Command Description
QUIT = false Here we are defining a new boolean variable called QUIT. We are setting it's initial state to be false, and will use this variable to determine whether to quit the main menu. Quitting the main menu will return out of the loop and end the program.
REPEAT
gosub @NewGame
UNTIL QUIT
As we're not actually coding a menu at the moment, we'll just simply call a new subroutine that we will create shortly to start a new game. Ultimately we will do stuff in this menu loop and only call @NewGame when the option to start a new game has been selected by the player.

Notice how we don't do UNTIL (QUIT = true)? That's because (if you remember earlier episodes) everything evaluates to true or false. By specifying QUIT = true, we would effectively be saying UNTIL (true = true) or UNTIL (false = true). We can take the check out and use the QUIT variable instead, which gives us UNTIL true or UNTIL false. When Quit is false, then the statement does not equate to true, and the loop is repeated.

RETURN As we know, it returns us to the line of code that called the GOSUB instruction.

We now need to create the @NewGame subroutine to deal with the main gameplay. This is where we will see more changes as we prepare our game engine for various gameplay elements such as finishing a level, Miner Willy being killed by falling too far, or hitting an obstacle or baddy.

@NewGame
    LVL = 1 : LIVES = 3 : GAMEOVER = false

    // Level loop
    repeat
        gosub @InitMinerWilly
        LVLDONE = false : MWDEAD = false

        // Gameplay loop
        repeat
            gosub @ReadJoystick
            gosub @DisplayMinerWilly
            gosub @WaitVBL
        until LVLDONE or MWDEAD

        if MWDEAD then dec LIVES : GAMEOVER = (LIVES = 0)
        if LVLDONE then inc LVL
    until GAMEOVER
    return

Instruction Description
LVL = 1 We introduce a new variable, LVL to hold the current level that we are on. Because this is a newly initialised game, we set the level to 1. We could employ a cheat at some point too that allows us to start at a different level number. That would come in handy when testing further level structures out.
LIVES = 3 We introduce a new variable, LIVES to hold the number of lives that we have left. We start with three lives, and each time Miner Willy dies, we decrease the number of lives we have.
GAMEOVER = false Our final new variable holds whether it's game over. This works hand in hand with the LIVES variable, and when that reaches zero, we set GAMEOVER to true.
REPEAT Now that we have initialised our new variables, we enter what I call the level loop. It is within this loop that we prepare the environment, load the level graphics, and reset Miner Willy to his starting positions.
gosub @InitMinerWilly This is our line of code that we previously had earlier in the code. We've moved it within this loop to make sure Miner Willy is initialised each time a level starts. We can do this here as one of the features of Manic Miner is that when Miner Willy dies, the whole level is reset back to the beginning.
LVLDONE = false Another new variable, this time to determine if the level is done. Initially, the level is not done because we need to complete it.
MWDEAD = false This variable holds whether Miner Willy is dead. It will be set when we collide with something that we shouldn't or fall too far.
REPEAT
GOSUB @ReadJoystick
GOSUB @DisplayMinerWilly
GOSUB @WaitVBL
UNTIL LVLDONE OR MWDEAD
This is the same game loop that we had before, just moved in location. We've also changed the UNTIL false to be UNTIL LVLDONE OR MWDEAD, which allows us to set either of these variables to break out of the game loop.
IF MWDEAD THEN DEC LIVES : GAMEOVER = (LIVES = 0) Once we are outside the game loop, we then check the MWDEAD variable to see if the reason we dropped out of the loop was because Miner Willy died. If so, we use the DEC instruction to decrease the value of the LIVES variable, and then we set the GAMEOVER variable by evaluating if LIVES = 0.

Remembering that all expressions evaluate to true or false, GAMEOVER = (LIVES = 0) is the same as saying IF LIVES = 0 THEN GAMEOVER = true. We have therefore achieved the same result without having to perform additional costly calculations.

IF LVLDONE THEN INC LVL If we have quit the game loop because the level has been completed, we increase LVL so that next time the game loop triggers, we will be on the next level.
UNTIL GAMEOVER Keep executing the repeat loop until the GAMEOVER flag has been set to true.

You can give this code a run, but it will just look the same as it did before. Miner Willy will still be able to walk and jump, but at least we have everything safely wrapped in our menu, level, and gameplay loops now, with the appropriate breaks defined and used.

We've got our nice graphic for our level, so let's add that to the equation and implement the code that will load the image and display it in our level loop before the main game loop starts. To do this, we are going to employ the use of a memory bank. Screen memory banks STOS come in two different flavour: a screen or a datascreen.

Screens are temporary memory blocks that are predefined as the correct size for holding an ST screen, no matter what the resolution. When the program has finished running, the memory used for the screen is released. This means you have to always load your assets into them during the operation of your program.

Datascreens are the same as screen banks; however, they are permanent, and when the program finishes executing, the memory bank remains in memory and is available to be used again. You can load assets into a datascreen from the STOS command line rather than as code within your program.

We are going to use a screen because we want to use as little memory as possible and will load our level images as we require them. Add the following line of code at the beginning of the program.

key off : curs off : click off : flash off : mode 0 : hide on : auto back off : anim off : synchro off
palette $000,$777,$005,$007,$500,$700,$504,$707,$050,$070,$055,$077,$550,$770,$555,$777
erase 10 : reserve as screen 10

Instruction Description
ERASE 10 Here we are erasing any data that we may already have within memory bank 10 of STOS. As the program has only just executed at this point, so in theory this call is redundant, but I'm always in the habit of erasing memory banks, so it makes no difference really.
RESERVE AS SCREEN 10 This is the instruction that signals to STOS that we want to create a temporary memory to hold ST screen data. We've specified that we want this bank to be allocated to the memory bank 10 slot.

Once the above has been executed, we now have enough memory space allocated to load our level in. We do this in our level loop. Add the following code:

repeat
        load "LEVEL01.PI1",10
        screen copy 10 to physic : screen copy 10 to back

        gosub @InitMinerWilly
        LVLDONE = false : MWDEAD = false

This is a very simple call to just load the LEVEL01.PI1 file directly into memory bank 10. Because we are using known bank numbers, and known file formats, STOS will handle this for us.

Instruction Description
SCREEN COPY 10 TO physic The SCREEN COPY instruction in STOS has many different forms. Here we are using quite possibly the simplest variation that copies an entire screen of data from one memory address to the other (bank 10 to the physical screen).
SCREEN COPY 10 TO back The same as the above, but this time we are copying the image to the background screen. We do this because STOS automatically saves the background, and if we don't when Willy walks over the landscape, it will be blanked out. Try it and see what happens.

Run that off and see what the results are. We should have an environment that doesn't get over-written when Willy moves around it.

Finally, let's make the level loading a little more dynamic. Rather than having the hard coded "LEVEL01.PI1" image loading, let's calculate the filename based on the LVL variable. This could be done in a couple of ways, but we'll just do a simple way that shows some of the string manipulation instructions in STOS.

Update the LOAD "LEVEL01.PI1" line of code to the following:

LVL$ = str$(LVL) : LVL$=right$(LVL$,len(LVL$)-1) : if len(LVL$) = 1 then LVL$ = "0" + LVL$
load "LEVEL" + LVL$ + ".PI1",10

Instruction Description
LVL$ = STR$(LVL) The STR$ instruction converts a number into a text string. After this call, LVL$ holds the current level as a text string. One thing to note though, is STOS always adds a leading space to any number that it converts to a text string. Therefore with our LVL being set to one, the resulting string is " 1".
LVL$=RIGHT$(LVL$,LEN(LVL$)-1) Here we are compensating for the fact that there is a leading space on the converted text string. We are saying give me the RIGHT most characters of the LVL$, and the number of characters I want is LEN(LVL$)-1.

In otherwords, the length of the resulting string with the leading space, minus 1. The " 1" is therefore converted to "1".

IF LEN(LVL$) = 1 THEN LVL$ = "0" + LVL$ Here we are just checking if the resulting string is only 1 character in length. If so, we are adding a 0 to the front of it to pad anything less than 10 to be 01, 02, 03 etc.
LOAD "LEVEL" + LVL$ + ".PI1",10 Here we are finally constructing the filename to load, and load the image in to screen bank 10.

Ok, that's all we've got time for today folks - I said it was a shorter one for today! As always, the source files can be found in the GitHub repository.

In our next episode, we will start to look at how to add some basic collision detection so Willy can be stopped from walking off the screen and falling into oblivion. Until then… Happy STOSing.

Simon Richardson has reacted to this post.
Simon Richardson