Robotron is complete, and it can be played here,
I will do the "write up" tomorrow, probably.
Game#15 will be my entry for the Underground Jam competition at Scirra, so I won't publish anything regarding that until the competition is over.
I already have an interesting idea for this game, bit different from the obvious platforms in caves type thing, so we will see....... maybe I'll win the Spade Handle ?
Friday, 7 August 2015
Thursday, 6 August 2015
Game#14: Robotron (nearly there)
Robotron is now pretty much playable - there is no "Game Over" display, and there's no title or high score, but it can be played.
Haven't had much time on it today, but tomorrow hopefully it will be made available to play online like all the others, and I will do the 'write up' of the game.
Haven't had much time on it today, but tomorrow hopefully it will be made available to play online like all the others, and I will do the 'write up' of the game.
Wednesday, 5 August 2015
Game 14 : Robotron 2048, onwards and upwards
Feeling better today, and got some actual progress .... here we have a screen with the whole lot of everything on.
Needs a little tweaking. The "Brains" are perhaps a little too smart in going and grabbing humanoids and zombifying them.
It's now at the stage where the level is being built from globals indicating the number of objects in each level.
Fortunately I have found someone who has reverse engineered the original game, so I can get the right number of bad guys on each level.
Needs a little tweaking. The "Brains" are perhaps a little too smart in going and grabbing humanoids and zombifying them.
It's now at the stage where the level is being built from globals indicating the number of objects in each level.
Fortunately I have found someone who has reverse engineered the original game, so I can get the right number of bad guys on each level.
Monday, 3 August 2015
Game# 14: Day something or other
As I crawl out of my sick bed, Robotron moves further towards being playable. I hate being ill, and I'm always suspicious of code written when I'm ill.
All the bad guys are now present except the human eating brains.
Hopefully I'll do that tomorrow.
Here you can see the Tanks and Enforcers with their bouncing and crawling bullets. Seems to work quite well.
All the bad guys are now present except the human eating brains.
Hopefully I'll do that tomorrow.
Here you can see the Tanks and Enforcers with their bouncing and crawling bullets. Seems to work quite well.
Sunday, 2 August 2015
Game # 14: Robotron (continued)
Still feeling awful :(
Have managed to do a bit of work on Robotron. I have the two enemies that spawn others working, and I've also refactored the chaser code out of the Grunts into another family (because I'll need to have other objects chase I think).
Amendments to the Wanderer family made it suitable for use for the Quarks and Spheroids (the two enemy types that spawn other objects) they move in a similar random pattern, just with the option of diagonal movement.
Also have decided to go in for Scirra's "Underground" competition. I did go in for the "Deception" one, but I only had about a day to write the game for various reasons, so I was quite pleased to be in the top 20 :)
Have managed to do a bit of work on Robotron. I have the two enemies that spawn others working, and I've also refactored the chaser code out of the Grunts into another family (because I'll need to have other objects chase I think).
Amendments to the Wanderer family made it suitable for use for the Quarks and Spheroids (the two enemy types that spawn other objects) they move in a similar random pattern, just with the option of diagonal movement.
Also have decided to go in for Scirra's "Underground" competition. I did go in for the "Deception" one, but I only had about a day to write the game for various reasons, so I was quite pleased to be in the top 20 :)
Friday, 31 July 2015
Game#14: Robotron (Day 2)
Shocking cold yesterday and especially today, spend most of today in Bed :(
Have made some minor progress with Robotron though ; the Grunts, Hulks, Enforces and Humans are completed.
Todo : Spheroids (spawn Enforcers), Quarks (spawn Tanks) and Brains. I'll have to spend some time figuring out how they all behave to get a realistic version of the game.
Maybe tomorrow :)
Have made some minor progress with Robotron though ; the Grunts, Hulks, Enforces and Humans are completed.
Todo : Spheroids (spawn Enforcers), Quarks (spawn Tanks) and Brains. I'll have to spend some time figuring out how they all behave to get a realistic version of the game.
Maybe tomorrow :)
Wednesday, 29 July 2015
Game # 14: Robotron
Robotron is a classic. It's the original "totally demented shoot em up".
As you can see, I only currently have the player shooting, grunts (not doing much) and the humans (which you collect for points). If you haven't played Robotron, it looks like this but with about ten times as much going on.
This looks like it's going to family centric, already I have six different families.
One change to the original. The original has dual controllers, one controls where you fire, one controls where you move. I have nicked Jeff "Llamasoft" Minter's idea for his remake "Llamatron" whereby the player automatically fires, and the fire key locks the shooting direction, so you can fire (say) to the left while moving right.
It will be an interesting test of the speed of Construct 2, if I can find a workable control system for a touch screen. When Robotron is going flat out it makes Defender without the planet look like Space Invaders on slow mode.
As you can see, I only currently have the player shooting, grunts (not doing much) and the humans (which you collect for points). If you haven't played Robotron, it looks like this but with about ten times as much going on.
This looks like it's going to family centric, already I have six different families.
One change to the original. The original has dual controllers, one controls where you fire, one controls where you move. I have nicked Jeff "Llamasoft" Minter's idea for his remake "Llamatron" whereby the player automatically fires, and the fire key locks the shooting direction, so you can fire (say) to the left while moving right.
It will be an interesting test of the speed of Construct 2, if I can find a workable control system for a touch screen. When Robotron is going flat out it makes Defender without the planet look like Space Invaders on slow mode.
Tuesday, 28 July 2015
Game# 13: How it works
Hi. I'm back from a sunny week in Germany. Remarkably I've lost weight on Holiday, because the place we stayed is on a steep hill in a river valley (Heimbach in the West of Germany), so lots of climbing up and down.
Anyway, how Jawbreaker works.
Globals and Constants
Lots of globals and constants here. Have some quasi constants as well "Pseudo Constants" - the idea here is they are assigned one value at the start, which then is unchanging - obviously this isn't enforceable in C2 but it's a good idea code wise. Event 3 initialises these values which represent various screen positions.
There are some utility functions here, updating the score and lives, and resetting things when a new game/retry occurs. When you start a new level or game, the layout is reloaded.
Finally this game has an FSM, which was grafted on afterwards, lazily :)
Setup
This game doesn't usee the painting at all, pretty much, so this sets up the screen - event 1 draws the border, repositions the vitamin (it is always there, just not always visible) and the level text. Event 2 creates the moving walls (actually solid walls with a black overlay), and event 3,4 and 5 create the monsters and the sweets.
Play State,
This is the main game stuff. Event 1 initialises everything on layout start. Events 2-4 control the music and the 8 direction controls depending on whether the game is actually being played.
The game code proper starts at event 5. Event 6 clamps the player into the game space, and 7 and 8 handle the potential horizontal movement - this is only permitted either in the left or right columns, or where you are passing through a gateway (gap in the wall).
Event 9 through 13 handle eating sweets - 10/11 have different effects for eating the different coloured sweets, and 13 handles the end of level.
Event 14 makes the vitamin pill appear on its timer, and 15 and 16 make it go into chase mode on collision with appropriate sound and effects, also setting the timer for "end of chase warning" (fired at 17-18) and "end of chase" (fired at 19-23). This also puts the monsters to reappear at the other end of the game board from where you are, allowing the player to get away (i.e. monsters don't reappear on top of you)
Finally 24-26 handle collisions with monsters - this can either be during the chase mode (26) in which case you score, or not, when you lose a life.
Moveables
This handles the monsters. Events 2-4 set up the monstes, with a position and a reverse timer - they are driven by wrapped bullets. On event 5, the monsters reverse direction periodically.
Event 6 shouldn't be here (arguably), it's purpose is to make the gateways invisible or visible depending on the horizontal position, otherwise they will appear over the top of the left and right border. I probably should have done his with z-order/layers, but it's a blitz.
Other states
This handles various states. "New level" (1-7) animates the brush coming in and brushing and out using a bullet and a sine. This is messy but it works by firing timers and checking the position.
Event 8 onwards handles lost life, and this makes the player disappear from the top down. Finally Game over, in events 15-18 fades the Game Over text in and goes back to the title page when the fade is completed.
Anyway, how Jawbreaker works.
Globals and Constants
Lots of globals and constants here. Have some quasi constants as well "Pseudo Constants" - the idea here is they are assigned one value at the start, which then is unchanging - obviously this isn't enforceable in C2 but it's a good idea code wise. Event 3 initialises these values which represent various screen positions.
There are some utility functions here, updating the score and lives, and resetting things when a new game/retry occurs. When you start a new level or game, the layout is reloaded.
Finally this game has an FSM, which was grafted on afterwards, lazily :)
Setup
This game doesn't usee the painting at all, pretty much, so this sets up the screen - event 1 draws the border, repositions the vitamin (it is always there, just not always visible) and the level text. Event 2 creates the moving walls (actually solid walls with a black overlay), and event 3,4 and 5 create the monsters and the sweets.
Play State,
This is the main game stuff. Event 1 initialises everything on layout start. Events 2-4 control the music and the 8 direction controls depending on whether the game is actually being played.
The game code proper starts at event 5. Event 6 clamps the player into the game space, and 7 and 8 handle the potential horizontal movement - this is only permitted either in the left or right columns, or where you are passing through a gateway (gap in the wall).
Event 9 through 13 handle eating sweets - 10/11 have different effects for eating the different coloured sweets, and 13 handles the end of level.
Event 14 makes the vitamin pill appear on its timer, and 15 and 16 make it go into chase mode on collision with appropriate sound and effects, also setting the timer for "end of chase warning" (fired at 17-18) and "end of chase" (fired at 19-23). This also puts the monsters to reappear at the other end of the game board from where you are, allowing the player to get away (i.e. monsters don't reappear on top of you)
Finally 24-26 handle collisions with monsters - this can either be during the chase mode (26) in which case you score, or not, when you lose a life.
Moveables
This handles the monsters. Events 2-4 set up the monstes, with a position and a reverse timer - they are driven by wrapped bullets. On event 5, the monsters reverse direction periodically.
Event 6 shouldn't be here (arguably), it's purpose is to make the gateways invisible or visible depending on the horizontal position, otherwise they will appear over the top of the left and right border. I probably should have done his with z-order/layers, but it's a blitz.
Other states
This handles various states. "New level" (1-7) animates the brush coming in and brushing and out using a bullet and a sine. This is messy but it works by firing timers and checking the position.
Event 8 onwards handles lost life, and this makes the player disappear from the top down. Finally Game over, in events 15-18 fades the Game Over text in and goes back to the title page when the fade is completed.
Saturday, 18 July 2015
Off on Holiday (again)
Eifel National Park near Heimbach, Germany |
When I get back I'll do that and then finish off my list which is (probably) Robotron, Defender, Lode Runner and (maybe) Tempest. Also thinking of porting Richard Garriott's Akalabeth (aka "Ultima 0") to C2 (I have already ported this to "C")
Then maybe some proper work :) This is just practice for a project, which is itself a rewrite of something I did on an Acorn Archimedes a few years ago.
Fortunately for the sanity of the world, it isn't my 3D signing robot for Special Needs that both sang, and signed "When Goldilocks went to the House of the Bears".
Game # 13 : Complete
Jawbreaker can be played here. The source is in the usual github. I will write it up later on.
Friday, 17 July 2015
Game#13 : Jawbreaker (A blitz)
As a blitz, because I'm off for a week tomorrow, yesterday I wrote most of a version of the Atari 2600 game "Jawbreaker" (original below) in a couple of hours. It's nearly finished, except the objects aren't actually moving (yet). Should be available later today, but the write up might be delayed.
When I get back I will move onto Robotron.
Jawbreaker is a Pacman variant. Amazingly they were sued for "copying Pacman" - this was a time when there were umpteen dodgy Pacman clones. Pacman isn't really suited to the Atari 2600 for reasons relating to its hardware, so its a sort of horizontal version, though it has many of the same elements - the sprites only move horizontally as do the gaps in the maze.
This is because it is very difficult to get multiple sprites on the same line, and even if you do it is Space Invaders like (e.g. very repetitive) - Pacman's maze is doable, but the five sprites on the screen at once (4 ghosts, Pacman) flicker because they can be drawn in any vertical position. This homebrew is much better in terms of its resemblance to the original, but it still flickers, for the same reasons.
When I get back I will move onto Robotron.
Jawbreaker is a Pacman variant. Amazingly they were sued for "copying Pacman" - this was a time when there were umpteen dodgy Pacman clones. Pacman isn't really suited to the Atari 2600 for reasons relating to its hardware, so its a sort of horizontal version, though it has many of the same elements - the sprites only move horizontally as do the gaps in the maze.
This is because it is very difficult to get multiple sprites on the same line, and even if you do it is Space Invaders like (e.g. very repetitive) - Pacman's maze is doable, but the five sprites on the screen at once (4 ghosts, Pacman) flicker because they can be drawn in any vertical position. This homebrew is much better in terms of its resemblance to the original, but it still flickers, for the same reasons.
14500 Board, how it works.
Interface
This is probably the interesting bit. The interface works through a dictionary which contains settings of the 'controls' (switches and LEDs), and automatically updates the dictionary or display.
Rather than copying umpteen objects, it uses an IOMarker which specifies detail, this expands at event 2. For each "bit" it requires, it creates an interface object which has different animations for different objects, and tells it whether it is input or output and which bit of which data item it is. 6 and 7 allow it to be labelled.
Events 8 through 10 copy data either to or from the interface object to the dictionary. Event 11 makes toggle switches work. Event 12 handles pushbuttons initialises pushbuttons and their labels - they do not have an entry in the dictionary, but call a function "On<actionName>" (this is done in 15).
Event 17 onwards is a short cut. Data is stored in a 256 x 4 bit memory on this machine, and this allows code to be typed in by pressing 0-9A-F which writes it to the current memory and bumps the program counter - otherwise you'd have to set the switches, press load data, and press single step - this does it for you - you can type 0 A 9 F C 3 etc. to load a program in.
InterfaceRefresh
This is related to interface but it copies data from the emulation globals into the interface dictionary - then they are updated to the display by the code in interface. These are the current address in the program counter (8 + 1 + 1) LEDs, the data at that location (4), the Result Register (14500 Accumulator) (1), the 16 output latches (16 LEDs) in 3 to 6). Event 7 maintains the state of the step/run switch in a global for convenience.
Event 8 is added to make things easier - it displays the mnemonic equivalent of the code in data memory, rather than having to read binary.
Commands
These handle the push buttons, they are all "OnXXX" functions. LoadPC copies the address switches into the program counter, Reset resets the program counter, the 14500 MCU and the run latch, Single step executes a single half-instruction (see emulation), Load Data loads the data switches into memory, and OnRunMode starts the program running by setting the run latch. Finally event 8 causes the emulation to free run at one half-instruction per tick if the run latch is set.
Emulation
This is a classic emulation of the MCU, with one tweak. Each single step is a half instruction - for even address instructions (opcode) it loads the opcode into an internal latch (Event 3), and on odd ones, it loads the operand (IO Address) into an external latch (Event 4) - so it requires two single steps to execute each instruction.
The emulations follow the 14500 Specification, except NOP0 (0000) resets the run latch and NOPF (1111) presets the program counter, loading the address switches into the program counter.
A couple of helper functions read input (Starting at 23) - this supports the "wiring" of input pin 15 to the RR (25) and input pins 8-14 to their corresponding output latches (26), these can be then used as data storage. This is my own addition, as it is not fixed on the original board but done by wiring.
This is probably the interesting bit. The interface works through a dictionary which contains settings of the 'controls' (switches and LEDs), and automatically updates the dictionary or display.
Rather than copying umpteen objects, it uses an IOMarker which specifies detail, this expands at event 2. For each "bit" it requires, it creates an interface object which has different animations for different objects, and tells it whether it is input or output and which bit of which data item it is. 6 and 7 allow it to be labelled.
Events 8 through 10 copy data either to or from the interface object to the dictionary. Event 11 makes toggle switches work. Event 12 handles pushbuttons initialises pushbuttons and their labels - they do not have an entry in the dictionary, but call a function "On<actionName>" (this is done in 15).
Event 17 onwards is a short cut. Data is stored in a 256 x 4 bit memory on this machine, and this allows code to be typed in by pressing 0-9A-F which writes it to the current memory and bumps the program counter - otherwise you'd have to set the switches, press load data, and press single step - this does it for you - you can type 0 A 9 F C 3 etc. to load a program in.
InterfaceRefresh
This is related to interface but it copies data from the emulation globals into the interface dictionary - then they are updated to the display by the code in interface. These are the current address in the program counter (8 + 1 + 1) LEDs, the data at that location (4), the Result Register (14500 Accumulator) (1), the 16 output latches (16 LEDs) in 3 to 6). Event 7 maintains the state of the step/run switch in a global for convenience.
Event 8 is added to make things easier - it displays the mnemonic equivalent of the code in data memory, rather than having to read binary.
Commands
These handle the push buttons, they are all "OnXXX" functions. LoadPC copies the address switches into the program counter, Reset resets the program counter, the 14500 MCU and the run latch, Single step executes a single half-instruction (see emulation), Load Data loads the data switches into memory, and OnRunMode starts the program running by setting the run latch. Finally event 8 causes the emulation to free run at one half-instruction per tick if the run latch is set.
Emulation
This is a classic emulation of the MCU, with one tweak. Each single step is a half instruction - for even address instructions (opcode) it loads the opcode into an internal latch (Event 3), and on odd ones, it loads the operand (IO Address) into an external latch (Event 4) - so it requires two single steps to execute each instruction.
The emulations follow the 14500 Specification, except NOP0 (0000) resets the run latch and NOPF (1111) presets the program counter, loading the address switches into the program counter.
A couple of helper functions read input (Starting at 23) - this supports the "wiring" of input pin 15 to the RR (25) and input pins 8-14 to their corresponding output latches (26), these can be then used as data storage. This is my own addition, as it is not fixed on the original board but done by wiring.
App#1 : MC14500 Board Released
It can be "played" here. It's a reasonably faithful mimic of the original, though the Input and Output have been changed - on the original they ware just headers, but I've put switches in and LEDs to display the input and output. Also Pin 15 of Input is permanently connected to the Result Register pin (the 14500 Accumulator) and Pins 8-14 are routed from the output latch so they can be used as 7 bits of data storage.
An emulator in C2 is perfectly feasible, but for anything significant probably needs to be written in pure Javascript. The tilemap would be very good for old fashioned memory mapped screens, but wouldn't be much use for a pixel graphic one.
Note that the source is available in a different github, see the links bar.
An emulator in C2 is perfectly feasible, but for anything significant probably needs to be written in pure Javascript. The tilemap would be very good for old fashioned memory mapped screens, but wouldn't be much use for a pixel graphic one.
Note that the source is available in a different github, see the links bar.
Thursday, 16 July 2015
App # 1 : MC14500B Evaluation Board
Well, I changed my mind about this. Partly because all the documentation regarding the WDR-1 is in German, and this emulation (on the right) is the Motorola Evaluation kit, which is very similar (the primary difference is memory is arranged as 4 bits rather than 8 bits) and it's in English. I don't speak a lot of German (may improve as I'm spending a week in Germany from Monday, but I'm probably not going to use the German for "Program Counter" much).
Most of this is done, the only bit missing is the actual emulation of the processor.
Seems a bit pointless, but it gave me some opportunities to experiment with ideas for interfaces. It's also an insane thing to program as it has no loops (other than reset to the start) in most implementations, which means everything is state machines. You can have branches and subroutines but none of the hardware is present in the 14500, all it does is pulse a chip pin "Jump" or whatever.
Almost none of the switches and LEDs are placed manually, they are generated by having another object create it, below is what the design screen actually looks like. Each purple IO says what to draw, how many bits, and whether it is input or output. This data is then copied to and from a dictionary, which acts as an intermediary between the interface and the program. Push buttons call a function derived from an instance variable. It works quite well.
Most of this is done, the only bit missing is the actual emulation of the processor.
Seems a bit pointless, but it gave me some opportunities to experiment with ideas for interfaces. It's also an insane thing to program as it has no loops (other than reset to the start) in most implementations, which means everything is state machines. You can have branches and subroutines but none of the hardware is present in the 14500, all it does is pulse a chip pin "Jump" or whatever.
Almost none of the switches and LEDs are placed manually, they are generated by having another object create it, below is what the design screen actually looks like. Each purple IO says what to draw, how many bits, and whether it is input or output. This data is then copied to and from a dictionary, which acts as an intermediary between the interface and the program. Push buttons call a function derived from an instance variable. It works quite well.
Part of Construct 2's Editor Window |
Dungeon : how it works
Dungeon is a relatively simple game ; the complexity involved is in changing from something extremely procedural to event driven. The main problem with this is the message area - there is one line that has a sequence of messages "You have been attacked" "The monster has 50 hit points" and so on. This is done by a queue event sheet which displays these in sequence. It is possible to get way ahead of this :) which is slightly odd, but the alternative is to lock the player out until the queue is emptied. In a real game with a similar issue this would probably be done by a scrolling list box.
GlobalsConstants
Mainly defines globals and constants - the space at the top, the size of the map, tile names. (isRunning in 2 is not actually used). This game is not actually set up in the editor - the text items and the tilemaps are positioned programmatically, and Event 4 does this for the text at the top of the screen. Event 6 processes and hides the decision buttons that are displayed when you are offered a bribe to run away, and 8-11 are simple utility functions to refresh the top screen. The RefreshHitPoints function is, or was, going to be optimised slightly but in the end wasn't, because HP is updated so much.
MessageQueue
A utility in its own right. It keeps a queue of messages seperated by colons in instance variable MessageQueue.text. Event 2 positions it on the second line. Event 4 removes a message on its timeout (each being displayed for 2s) and Event 5 replaces it with the head of the queue if it is not empty. Function 6 (AddMessageToQueue) seperates the implementation from the execution, so it is easy to (say) change the seperator if I need to. I could implement the "scrolling information box" idea here just by replacing this on its own.
Dungeon
All this code is run at start up and its main purpose is to create the tilemaps and objects. There are two tilemaps, one the actual map itself, and the other one with two tiles, solid and transparent, which do the "visible area" bit, where you can only see where you've been. Events 2 and 3 do basic set up.
Event 4-6 creates the rooms, by creating a purple room marker object and moving it if it overlaps with any other room marker object. In Event 6 it is positioned off screen, destroying it does not work because I think destroy is a "request to destroy" rather than "destroy it now". If you stop at this point the screen would be full of purple squares.
Events 7-9 process them. For each room marker on screen, it draws tiles on the tilemap, creates a random gold and monster for each room and initialises the monster. Event 9 keeps moving the monster until it and the gold do not overlap. Event 10 initialises the target the monster is moving towards to where it currently is (i.e. initially it does not move)
Events 11-13 open the paths up and position the player on the path, somewhere. (Probably 12 and 13 should be the other way round !). The path code is simple - attempt a path east and south, and stop when you hit another room, but if you reach the end wall, there is no path. This is done by the recursive ScanPath function, which returns 0 if the wall is not to be drawn and 1 or 2 if it is (2 is for the door). It advances down the prospective path and draws the actual tile after the recursive call, when it "knows" whether the path is to be drawn or not. This code is actually much simpler than the flat original which scanned to the end and drew it retrospectively in Construct 2.
Incidentally, as an idea of how fast a PET was, the code drawing here took about a minute on the original.
Events 22-25 allow you to reset the "look" of the game to the PET original (though the font is still the same)
Player
This handles the player moving. Event 2 opens up the visibility by changing the tile on the top tilemap. Event 3 stops movement if we are waiting for the player to decide whether to accept a bribe or not, otherwise from 5 onwards we handle moving.
Event 5 reads the current tile, 6 handles 'floating' (you can go through walls by holding Shift down), and remembers the last good position in 8. If the current position is bad, it is reset to the last good in 9, this means it cannot leave the paths or rooms without "floating".
Moving reduces your hit points, which is tested for in 10-13. The original game had no concept of maximum hit points, you could heal to 1000000 HP if you were patient enough, so this is added. Different hitpoints are lost depending on whether you are floating or not.
Event 14-16 display the "Gold is near" message if the nearest object is near enough, the first time it is fired, and its collection is processed in 17 and 18. Event 19 autoheals if you aren't moving (originally you had to press "5" for this).
Events 20-24 make the game touch compatible (except there is no float option) and 24-25 check for win (all gold collected) and loss (HP < 1) states. Finally 26 and 27 hide the yes/no buttons when the queue is not empty, a bit of a fudge to improve the synchronisation.
Monsters
Events 2-3 reset all the monster HPs at the start, and 4-7 reset the monster HPs every 1/4 second for every monster except the one you are fighting. This does mean all the others change their HP all the time, but this doesn't matter particularly. Perhaps it could have been done better with an 'initialHP' value - in the original monsters recover when you aren't fighting them.
Event 8-10 are the same as gold, it prints a warning message when monsters are nears, and 11-13 process monster movement, moving them towards xTarget,yTarget if they aren't there already.
Fighting code starts at 15, when not in "offer" mode and colliding with monster the combatants are "wobbled" (brief application of sine to them). Event 16 calculates the damage and updates the scores, and if the player is alive, the option of a bribe (17/18) may be offered, in which case it is put into "offer mode" - the buttons are made visible to allow the player to decide (bribeAttempted is because you get a bribe offer for each monster once only). If not, the rest of the fight code is done in the "ExitFightCode" function because it is duplicated when you decline the bribe (should've been an FSM really).
Events 20-24 process that choice, clicking on a decision button, with the monster doing a bunk for half your gold in 21-22 and carrying on fighting in 23.
Event 23 is the fighting completion code. 27 checks for killing the monster, 28 for levelling up, and if you haven't killed it 29-31 fire, figuring out a new position as an angle from the player position - it attempts to move behind the player and if it can't somewhere random, the new 'move target' is upset in 31).
Event 33 resets a monsters HP to the maximum, here for easy changing, and 35 does the wobble effect, activating the sine for 0.6s using a timer.
GlobalsConstants
Mainly defines globals and constants - the space at the top, the size of the map, tile names. (isRunning in 2 is not actually used). This game is not actually set up in the editor - the text items and the tilemaps are positioned programmatically, and Event 4 does this for the text at the top of the screen. Event 6 processes and hides the decision buttons that are displayed when you are offered a bribe to run away, and 8-11 are simple utility functions to refresh the top screen. The RefreshHitPoints function is, or was, going to be optimised slightly but in the end wasn't, because HP is updated so much.
MessageQueue
A utility in its own right. It keeps a queue of messages seperated by colons in instance variable MessageQueue.text. Event 2 positions it on the second line. Event 4 removes a message on its timeout (each being displayed for 2s) and Event 5 replaces it with the head of the queue if it is not empty. Function 6 (AddMessageToQueue) seperates the implementation from the execution, so it is easy to (say) change the seperator if I need to. I could implement the "scrolling information box" idea here just by replacing this on its own.
Dungeon
All this code is run at start up and its main purpose is to create the tilemaps and objects. There are two tilemaps, one the actual map itself, and the other one with two tiles, solid and transparent, which do the "visible area" bit, where you can only see where you've been. Events 2 and 3 do basic set up.
Event 4-6 creates the rooms, by creating a purple room marker object and moving it if it overlaps with any other room marker object. In Event 6 it is positioned off screen, destroying it does not work because I think destroy is a "request to destroy" rather than "destroy it now". If you stop at this point the screen would be full of purple squares.
Events 7-9 process them. For each room marker on screen, it draws tiles on the tilemap, creates a random gold and monster for each room and initialises the monster. Event 9 keeps moving the monster until it and the gold do not overlap. Event 10 initialises the target the monster is moving towards to where it currently is (i.e. initially it does not move)
Events 11-13 open the paths up and position the player on the path, somewhere. (Probably 12 and 13 should be the other way round !). The path code is simple - attempt a path east and south, and stop when you hit another room, but if you reach the end wall, there is no path. This is done by the recursive ScanPath function, which returns 0 if the wall is not to be drawn and 1 or 2 if it is (2 is for the door). It advances down the prospective path and draws the actual tile after the recursive call, when it "knows" whether the path is to be drawn or not. This code is actually much simpler than the flat original which scanned to the end and drew it retrospectively in Construct 2.
Incidentally, as an idea of how fast a PET was, the code drawing here took about a minute on the original.
Events 22-25 allow you to reset the "look" of the game to the PET original (though the font is still the same)
Player
This handles the player moving. Event 2 opens up the visibility by changing the tile on the top tilemap. Event 3 stops movement if we are waiting for the player to decide whether to accept a bribe or not, otherwise from 5 onwards we handle moving.
Event 5 reads the current tile, 6 handles 'floating' (you can go through walls by holding Shift down), and remembers the last good position in 8. If the current position is bad, it is reset to the last good in 9, this means it cannot leave the paths or rooms without "floating".
Moving reduces your hit points, which is tested for in 10-13. The original game had no concept of maximum hit points, you could heal to 1000000 HP if you were patient enough, so this is added. Different hitpoints are lost depending on whether you are floating or not.
Event 14-16 display the "Gold is near" message if the nearest object is near enough, the first time it is fired, and its collection is processed in 17 and 18. Event 19 autoheals if you aren't moving (originally you had to press "5" for this).
Events 20-24 make the game touch compatible (except there is no float option) and 24-25 check for win (all gold collected) and loss (HP < 1) states. Finally 26 and 27 hide the yes/no buttons when the queue is not empty, a bit of a fudge to improve the synchronisation.
Monsters
Events 2-3 reset all the monster HPs at the start, and 4-7 reset the monster HPs every 1/4 second for every monster except the one you are fighting. This does mean all the others change their HP all the time, but this doesn't matter particularly. Perhaps it could have been done better with an 'initialHP' value - in the original monsters recover when you aren't fighting them.
Event 8-10 are the same as gold, it prints a warning message when monsters are nears, and 11-13 process monster movement, moving them towards xTarget,yTarget if they aren't there already.
Fighting code starts at 15, when not in "offer" mode and colliding with monster the combatants are "wobbled" (brief application of sine to them). Event 16 calculates the damage and updates the scores, and if the player is alive, the option of a bribe (17/18) may be offered, in which case it is put into "offer mode" - the buttons are made visible to allow the player to decide (bribeAttempted is because you get a bribe offer for each monster once only). If not, the rest of the fight code is done in the "ExitFightCode" function because it is duplicated when you decline the bribe (should've been an FSM really).
Events 20-24 process that choice, clicking on a decision button, with the monster doing a bunk for half your gold in 21-22 and carrying on fighting in 23.
Event 23 is the fighting completion code. 27 checks for killing the monster, 28 for levelling up, and if you haven't killed it 29-31 fire, figuring out a new position as an angle from the player position - it attempts to move behind the player and if it can't somewhere random, the new 'move target' is upset in 31).
Event 33 resets a monsters HP to the maximum, here for easy changing, and 35 does the wobble effect, activating the sine for 0.6s using a timer.
Tuesday, 14 July 2015
Game#12 : Dungeon completed
Game 12, the PET Dungeon remake, is complete and can be played here.
I have also added a link to its github (it isn't in the one with everything else) this also contains the original source code reverse engineered.
You can see it here. Truth be told, I'm not totally happy with this one. It's event driven, and in the original BASIC code there's quite a lot of "Print this" (wait) "Print that" (wait), a sequence of messages.
I've done this with a queue of messages, but they easily get behind the "action", I'm not really happy with the way this synchronises. I would probably use a state machine to lock all this stuff out if I was doing it for real. I could probably have made more use of WAIT as well, I think,
I will put the write up tomorrow - probably. Next thing will be something even more different, not actually a game at all. Just a quickie before I go on holiday, I don't want to leave Robotron half done.
It's one of these :
I have also added a link to its github (it isn't in the one with everything else) this also contains the original source code reverse engineered.
You can see it here. Truth be told, I'm not totally happy with this one. It's event driven, and in the original BASIC code there's quite a lot of "Print this" (wait) "Print that" (wait), a sequence of messages.
I've done this with a queue of messages, but they easily get behind the "action", I'm not really happy with the way this synchronises. I would probably use a state machine to lock all this stuff out if I was doing it for real. I could probably have made more use of WAIT as well, I think,
I will put the write up tomorrow - probably. Next thing will be something even more different, not actually a game at all. Just a quickie before I go on holiday, I don't want to leave Robotron half done.
It's one of these :
Most people, even those into retrocomputing, won't know what this is. It's a German machine, known as the "WDR-1", and it is the only "home micro" commercially available based on the MC14500B Microprocessor.
This is an interesting chip. It's only a one bit microprocessor, and it has no real branching and jumping instructions.
Monday, 13 July 2015
Dungeon progress
Making reasonable progress with the Dungeon game. You can wander about a dungeon and collect gold, the monsters don't do anything yet and you can't attack them.
It's basically two tilemaps - the bottom one the maze, the upper one the 'reveal' - it's one of those CRPGs where the game is exposed as you go round (think of it as a primitive version of Sword of Fargoal ?) with sprites on top. These sprites I got from some free RPG tiles I found somewhere.
And it's got more recursive code in it ....
It's basically two tilemaps - the bottom one the maze, the upper one the 'reveal' - it's one of those CRPGs where the game is exposed as you go round (think of it as a primitive version of Sword of Fargoal ?) with sprites on top. These sprites I got from some free RPG tiles I found somewhere.
And it's got more recursive code in it ....
Saturday, 11 July 2015
Retrochallenge 2015 Summer : Pet Dungeon
For various reasons, not excluding broadband problems and holidays, my Retrochallenge 2015 for Summer (to reverse engineer the code of the original Simon toy from 1974) will have to be delayed. It's about 40% done (reversing TMS1100 code is interesting to put it mildly) but I won't complete it in time. Not only is it the missus birthday this week, but it's my mum's as well, and we're going on holiday after that....
In its place, as a one week one shot, and because it fits my list - sort of, I'm going to convert this game to Construct 2.
It's one of the earliest semi commercial "RPG" type games, called just "Dungeon". It was sold as part of a tape based magazine called "Cursor" and precedes things like "Temple of Apshai". (I am actually old enough to have owned a Pet 2001 with the chicklet keys. Anyone who thinks game coding on C2 is hard should try that , or the machine I started on which is the same as the Science of Cambridge MK14 ....)
You can read more about it here, an excellent blog written by atotal loony nice chap who wants to play every CRPG ever made. I'm not sure if this is possible ....
I will try and improve the graphics a little bit.
It will be quite an interesting exercise because like almost all code written in the 1970s, it is not event driven, but procedural.
This will have its own github as it is sort of seperate from the rest of the games.
Once I've done this, then I will move onto Robotron 2084 (Wahey !) and Defender (even better !), but I'm visiting Germany for a week with the extended family in about a week, so that will be into August.
In its place, as a one week one shot, and because it fits my list - sort of, I'm going to convert this game to Construct 2.
It's one of the earliest semi commercial "RPG" type games, called just "Dungeon". It was sold as part of a tape based magazine called "Cursor" and precedes things like "Temple of Apshai". (I am actually old enough to have owned a Pet 2001 with the chicklet keys. Anyone who thinks game coding on C2 is hard should try that , or the machine I started on which is the same as the Science of Cambridge MK14 ....)
You can read more about it here, an excellent blog written by a
I will try and improve the graphics a little bit.
It will be quite an interesting exercise because like almost all code written in the 1970s, it is not event driven, but procedural.
This will have its own github as it is sort of seperate from the rest of the games.
Once I've done this, then I will move onto Robotron 2084 (Wahey !) and Defender (even better !), but I'm visiting Germany for a week with the extended family in about a week, so that will be into August.
Centipede ... how it works
There are two interesting things about this game. The first is it uses a finite state machine, the second is the method by which the centipede moves.
Globals
Declares assorted globals. They are initialised in Event 4 (this is a one restart layout = one set of lives thing). There are some utility functions to update the lives, the score,, add a value to a score. Event 11 is used to randomise the colour on the little score thing that appears when you shoot some things (hence "On created").
Event 12-15 colours an object in the Colourable family by UID, replacing colour 5 (white in this scenario) with orange and adding a "white override" - this is because the poisoned mushrooms are white. The actual colour is derived from the level number. The colour works by adding a SetColor effect in WebGL (hence it requires WebGL to provide the colour). You could do this by having different frames/animations in the different colours but that's not so much fun as playing with WebGL.
Mushrooms
Most of the code here is concerned with initialisation - it creates equally spaced mushrooms at the top, this is to break up the centipede parts early. Duplicate mushrooms are created at event 5 (I think I forgot to put the While in there .... oops). Events 5 and 6 handle the shooting and destroying of mushrooms (which require 3 hits each), and 7 scores when you finally beat the mushroom.
Finally event 8 causes any new mushroom to be coloured appropriately as per above.
Player
Event 2 initialises the positions, Event locks it in a "player area" at the bottom of the screen, and Event 4 fires a missile if the Ctrl key is pressed. Easy enough so far .... well it isn't now :)
Centipede
The heart of it. Each centipede is a singly linked list effectively, with the "nextUIDInChain" instance variable creating a list. The head of the list, which is also the head of the centipede is marked as such (isLeader instance variable).
It moves as follows. Each segment moves under its own code to a new "target" coordinate, but this target coordinate is passed down the list. So the head tells segment 1 to go where it is, segment 1 tells segment 2 to go where it is and so on.
Event 2 detects being shot. This splits the linked list, which becomes two seperate ones - event 3 stores the UID of the head of the new list. Event 4 and 5 do the scoring, and create a new mushroom. Evemt 7, destroying the segment, finally breaks the list (the previous entry now points to a non existent UID). The new head is then selected and made up to be a leader (8 and 9).
Events 10-17 create a centipede of a given size. They are created with all the segments on top of each other (12 picks a spot). 13 and 14 create the body, keeping the uid of the previous part of the chain in "uid", and 15 is the same except it just does the head.
Event 18 onwards moves the centipede(s). It does it with the leaders first (in 18) so that the orders are set up in the correct order.
Event 19 detects if the leader has reach its target. If it has, it calls the "PassNewTargetDown" function which causes each segment in the list to pass its current position on to the next segment as its new target.
Event 20 causes the reversal on hitting a mushroom, or being poisoned, or a collision(21) or being on the edge of the screen (22,23). If none of these applies the new target is one position either space horizontally in the current direction (24). If there was a reversal it is to move down and once that has completed continuing moving horizontally (25).
Event 26 and 27 keep the centipede in the player area at the bottom, making it endlessly cycle in that area.
Event 28 fires for all, and moves the object towards its currently specified target. Event 29 shouldn't be there and doesn't do anything anyway.
Event 29 is a recursive function which passes the target down the linked list - all it does is copy the target from the parameters and then call it with the next segments stuff. I often wondered if recursion worked in C2. It does.
Event 32-37 creates Centipedes or centipede heads dependent on the level, at one of two speeds.
Spider Scorpion and Fleas
This is actually three seperate files really, one for each of the other centipede characters. They all start in a similar fashion with timers for random intervals (e.g. 2 and 3) and the creation starts at 4. The sine behaviour causes the spider's zig zag effect vertically and a simple chaser moves it horizontally, this is done in 5. Every so often the 'target' of the chaser is recalculated by 6.
Events 7 and 8 deal with shooting it - the score depends on how far apart they are, and it spawns a transient score item and a particle effect.
Finally 9 and 10 turn the spider tune off and on as a spider is created or destroyed (there is only one at once).
The Flea initial code is similar (12,13) except they only appear on level 2. The first thing it does is count all the mushrooms in the player area, and if there aren't enough, fires the flea. This drops down the screen using a bullet behaviour, and creates mushrooms on the way down. It stops at the bottom (18) and can be shot (19) with particles and transient score again.
The Scorpion again is very similar. Event 23 creates it (Level 3+) and it moves horizontally, event 24 allow s it to go the other way. Any mushrooms it collides with are poisoned (25) which causes the centipede to crash to the bottom of the screen. They can be destroyed in event 26, but this also unpoisons all mushrooms on the same line (event 27), which isn't strictly accurate as it should only unpoison the ones its just poisoned, but its near enough.
State
This handles the four states of the game. In "new level" it creates a new level (basically centipedes) in 5, recolours all the objects in 6.
The main play state starts at 8. Hitting a segment or animal switches it to "life lost", and destroying all the segments switches it to a new (next) level in 10 and 11. 12 starts the thud thud thud noise.
Life lost starts at 15, where the basic adjustments are made, and the segments are all destroyed. Each mushroom flashes briefly which is set up by a timer in 17 and implemented in 18. When all these are completed (19) it switches either to new level (the same level again) or game over depending on whether there are any lives left, or not.
Finally, 23 and 24 implement the game over state which fades "Game over" onto the screen and restarts the layout when space is pressed.
Title Sheet
Events 3-5 create the wobbling centipede with the letters in (I did not use pin for this because it did not centre properly). Events 6-9 create a vague approximation of the Atari logo with horizontal lines, and 10 and 11 create the mushrooms
Each tick event 13 moves the letters with the circles, and 14 uses time to animate the raster bar. Finally 15 starts the game.
Centipede available to play
..... as the post title, the game is available to play here.
Github is up to date with this version, and I will put the write up in later today or possibly tomorrow. I have a fairly busy day today.
Those of you who know the 2600 version will appreciate my version of the Title page .... those of you who know how the 2600 works will appreciate what an impressive title page this is for a title for the 2600.
Github is up to date with this version, and I will put the write up in later today or possibly tomorrow. I have a fairly busy day today.
Those of you who know the 2600 version will appreciate my version of the Title page .... those of you who know how the 2600 works will appreciate what an impressive title page this is for a title for the 2600.
Friday, 10 July 2015
And ... I'm back with Centipede (Atari 2600 version)
And I now have broadband again. Router appears to have blown up.
In the interim I have been working on Centipede VCS as you can see in the picture. In fact, it is pretty much finished except for the detail that it doesn't launch the right number of centipedes at the start of the level - other than that it is pretty much all working. No sound effects either, so I will finish this maybe tomorrow and then it can be played.
In the interim I have been working on Centipede VCS as you can see in the picture. In fact, it is pretty much finished except for the detail that it doesn't launch the right number of centipedes at the start of the level - other than that it is pretty much all working. No sound effects either, so I will finish this maybe tomorrow and then it can be played.
Tuesday, 7 July 2015
Sunday, 5 July 2015
Game # 11 : Centipede
So, the next game is Centipede. For a change (and because I dropped Pitfall off the list), I'm going to copy the Atari 2600/VCS version of Centipede.
This has the added advantage that my inability to draw, well, anything, doesn't show up too much. Even I can draw a square and fill it in :)
Just.
The first 80x86 Assembler program I wrote was an emulator for the Atari 2600, which is roughly akin to learning to swim by jumping into the middle of the Channel ....... I wrote it in annoyance because the then typical hardware (80386-25 ish) couldn't cope with Stella.
This has the added advantage that my inability to draw, well, anything, doesn't show up too much. Even I can draw a square and fill it in :)
Just.
The first 80x86 Assembler program I wrote was an emulator for the Atari 2600, which is roughly akin to learning to swim by jumping into the middle of the Channel ....... I wrote it in annoyance because the then typical hardware (80386-25 ish) couldn't cope with Stella.
Game#10 Match 3 Completed
Match 3 is completed and can be played here.
The game core hasn't changed much but I've added a swipe left/right type selection screen. There are only four different layouts which are used with increasing difficulty levels, but it would be easy enough to add more in.
As always the source is in Github. One thing about this code is there are very few globals. Only things that actually need to be globals ; other variables are stored in singleton objects like "SelectCtrl".
Selection
Event 1 avoids copyright and requests the key (one value, the highest accessible level), which is processed in 7 and 8. Events 3-6 create the shops you can see in the picture to the right, tracking the UIDs of first and last level.
The shops are updated on a per tick basis starting at 9. 10-14 move the shop towards a given "targetX" - this allows us to move the shops by just setting this value. Events 15-17 copy the size and positions and the cross visibility of each Selector (the shop). Effectively it responds immediately to changing variables. Event 18,19 and 20 handle taps on the shops, setting the level and running the game. Event 21 detects a touch end with swipe. Event 22/23 and 24/25 are very similar - the check is for direction, the first (or last) shop not being on the screen, and it not already being in motion. If this passses it sets the targetX value to move it.
Main Game
The game is driven by a state machine.
Setup
The primary purpose of setup is given a definition string specifying a level to create the initial display. It also contains the dropping code. Event 4 gets the string and decodes it into specific values. Event 5 takes the last part (defining the screen layout) and works out the level dimensions, and from that it works out how big each cell is and where they are. Events 6 and 7 create the grid where there is an "X" (e.g. event 9) and it also adds the grey behind tile. Where there isn't an "X" it creates a launcher, an object which spawns candies. Event 10 creates a row of blockers at the bottom of the grid. (If you disable comment 13 you will see these things in position, also see 2 posts back). Event 11 and 12 handles Launchers followed by Launchers or Blockers, replacing them with Blockers.
Event 14 onwards handles the dropping when the state is DROPPING. Firstly all the launchers are chosen (16) and if they do not already contain an item one is spawned (17). Event 18 access the items in backwards order up the screen, and if they are moving works out a new position (Event 19), If there is a collision with a stopper (blocker/launcher) or another item then the collision flag is set. In event 23, if there was a collision, the item is stopped and it is locked to a cell, otherwise Y is updated with the new position, moving it down the screen.
Events 26-29 cause the pulsing effect, and 30-33 is a function counting the number of items still moving that are in the game.
Matches
Matches is almost all code rather than events. It is a function that for each square counts the number of duplicates in a horizontal and vertical direction (right and down). Most of the work is done in 4, which calls two functions to scan in the directions and maintains the "best result" in each direction.
Event 6 counts the duplicate objects in a given direction. It keeps moving right (or down) whilst there is an object there in the game and the typeID (basically, the displayed graphic) is the same.
Control
Most of the work is here. We first go into NEWBLOCK state which is the start of a possible set of drops, this just resets the bonus multiplier and goes into DROPPING
DROPPING state starts at Event 5. The dropping is actually done in Setup, so 7,8,9 are just waiting for it to finish. Events 10 and 11 decide what to do next - if there is a match-3 (or ore) we go to REMOVE, otherwise we go to WAITSWAP. The first pass flag indicates the first pass, where you don't score, this is removing lines from the randomly selected items. If you watch the game it sort of "plays itself" for a few seconds creating a item grid with no Match-3s
REMOVING starts at 12. It basically picks H or V (horizontal or vertical) depending which is the best scoring, or randomly if they are the same, and then calls "destroyChunk" to delete them, having picked one match line that fits that (17,20) randomly (18,21). Effectively if there are (say) 3 vertical Matches it will pick on only. Each of these triggers an explosion, when these explosions have all finished (22) it goes back into DROPPING state to see if there are any more to process.
Events 24-29 are its helper function - it deletes n objects from x,y in direction dx,dy using a for loop. The exact behaviour depends on "first pass" - if not setting up it fires the explosions sequentially and adds to the score.
WAITSWAP is basically handling the selection and swapping over of adjacent cells. Standard Drag and Drop was deemed inadequate, so it is handled its own way. Initially, we go to EXIT (end of level) if there are no moves left.
Event 34 handles the "touch down". The calculations in the condition centre it on the middle of the Item (the collision area of the item is smaller than the bounding rectangle) and copies relevant information into a TouchManager object. Event 36 handles the drag part.
When we release in 37 (again with the centreing) we keep the drop target details (39) and if they are adjacent and not diagonal (40) we swap them over, otherwise we return the original item to its start place. Events 41,42 and 43 all deal with these tests failing, and just return the original item to its home point. These movements are handled by an ItemMover object whose responsibility it is to move an item to a position and then destroy itself - a bit like fade does.
When all the moves have finished and we swapped (44,45) we check to see if this has resulted in a Match 3, if so we start all the items moving again and go to NewBlock. If not (47) we return them to their original position.
Event 48 and 49 is a function which creates and sets up and ItemMover, and 50-52 use lerp to move it over time, 52 destroying it when it reaches the end position
Event 55 onwards is the exit state and is mostly graphic - all items explode, all tiles fade away, Layer 3 is made visible and a touch takes you back to the select screen, hopefully with "levelComplete" set - this is set when the score exceeds the target score in Event 29 when we are removing stuff.
Finally 62 is the big red button which abandons the level. You can still complete it.
Levels
I don't do much level design here, there are 4 of them (see event 3,4,5,6,7) which are concatenated with the number of discrete items that exist (6), a required score and maximum moves (all calculated in event 2). Effectively the layouts are used over and over again with harder target scores and more, but not quite enough moves.
The game core hasn't changed much but I've added a swipe left/right type selection screen. There are only four different layouts which are used with increasing difficulty levels, but it would be easy enough to add more in.
As always the source is in Github. One thing about this code is there are very few globals. Only things that actually need to be globals ; other variables are stored in singleton objects like "SelectCtrl".
Selection
Event 1 avoids copyright and requests the key (one value, the highest accessible level), which is processed in 7 and 8. Events 3-6 create the shops you can see in the picture to the right, tracking the UIDs of first and last level.
The shops are updated on a per tick basis starting at 9. 10-14 move the shop towards a given "targetX" - this allows us to move the shops by just setting this value. Events 15-17 copy the size and positions and the cross visibility of each Selector (the shop). Effectively it responds immediately to changing variables. Event 18,19 and 20 handle taps on the shops, setting the level and running the game. Event 21 detects a touch end with swipe. Event 22/23 and 24/25 are very similar - the check is for direction, the first (or last) shop not being on the screen, and it not already being in motion. If this passses it sets the targetX value to move it.
Main Game
The game is driven by a state machine.
Setup
The primary purpose of setup is given a definition string specifying a level to create the initial display. It also contains the dropping code. Event 4 gets the string and decodes it into specific values. Event 5 takes the last part (defining the screen layout) and works out the level dimensions, and from that it works out how big each cell is and where they are. Events 6 and 7 create the grid where there is an "X" (e.g. event 9) and it also adds the grey behind tile. Where there isn't an "X" it creates a launcher, an object which spawns candies. Event 10 creates a row of blockers at the bottom of the grid. (If you disable comment 13 you will see these things in position, also see 2 posts back). Event 11 and 12 handles Launchers followed by Launchers or Blockers, replacing them with Blockers.
Event 14 onwards handles the dropping when the state is DROPPING. Firstly all the launchers are chosen (16) and if they do not already contain an item one is spawned (17). Event 18 access the items in backwards order up the screen, and if they are moving works out a new position (Event 19), If there is a collision with a stopper (blocker/launcher) or another item then the collision flag is set. In event 23, if there was a collision, the item is stopped and it is locked to a cell, otherwise Y is updated with the new position, moving it down the screen.
Events 26-29 cause the pulsing effect, and 30-33 is a function counting the number of items still moving that are in the game.
Matches
Matches is almost all code rather than events. It is a function that for each square counts the number of duplicates in a horizontal and vertical direction (right and down). Most of the work is done in 4, which calls two functions to scan in the directions and maintains the "best result" in each direction.
Event 6 counts the duplicate objects in a given direction. It keeps moving right (or down) whilst there is an object there in the game and the typeID (basically, the displayed graphic) is the same.
Control
Most of the work is here. We first go into NEWBLOCK state which is the start of a possible set of drops, this just resets the bonus multiplier and goes into DROPPING
DROPPING state starts at Event 5. The dropping is actually done in Setup, so 7,8,9 are just waiting for it to finish. Events 10 and 11 decide what to do next - if there is a match-3 (or ore) we go to REMOVE, otherwise we go to WAITSWAP. The first pass flag indicates the first pass, where you don't score, this is removing lines from the randomly selected items. If you watch the game it sort of "plays itself" for a few seconds creating a item grid with no Match-3s
REMOVING starts at 12. It basically picks H or V (horizontal or vertical) depending which is the best scoring, or randomly if they are the same, and then calls "destroyChunk" to delete them, having picked one match line that fits that (17,20) randomly (18,21). Effectively if there are (say) 3 vertical Matches it will pick on only. Each of these triggers an explosion, when these explosions have all finished (22) it goes back into DROPPING state to see if there are any more to process.
Events 24-29 are its helper function - it deletes n objects from x,y in direction dx,dy using a for loop. The exact behaviour depends on "first pass" - if not setting up it fires the explosions sequentially and adds to the score.
WAITSWAP is basically handling the selection and swapping over of adjacent cells. Standard Drag and Drop was deemed inadequate, so it is handled its own way. Initially, we go to EXIT (end of level) if there are no moves left.
Event 34 handles the "touch down". The calculations in the condition centre it on the middle of the Item (the collision area of the item is smaller than the bounding rectangle) and copies relevant information into a TouchManager object. Event 36 handles the drag part.
When we release in 37 (again with the centreing) we keep the drop target details (39) and if they are adjacent and not diagonal (40) we swap them over, otherwise we return the original item to its start place. Events 41,42 and 43 all deal with these tests failing, and just return the original item to its home point. These movements are handled by an ItemMover object whose responsibility it is to move an item to a position and then destroy itself - a bit like fade does.
When all the moves have finished and we swapped (44,45) we check to see if this has resulted in a Match 3, if so we start all the items moving again and go to NewBlock. If not (47) we return them to their original position.
Event 48 and 49 is a function which creates and sets up and ItemMover, and 50-52 use lerp to move it over time, 52 destroying it when it reaches the end position
Event 55 onwards is the exit state and is mostly graphic - all items explode, all tiles fade away, Layer 3 is made visible and a touch takes you back to the select screen, hopefully with "levelComplete" set - this is set when the score exceeds the target score in Event 29 when we are removing stuff.
Finally 62 is the big red button which abandons the level. You can still complete it.
Levels
I don't do much level design here, there are 4 of them (see event 3,4,5,6,7) which are concatenated with the number of discrete items that exist (6), a required score and maximum moves (all calculated in event 2). Effectively the layouts are used over and over again with harder target scores and more, but not quite enough moves.
Saturday, 4 July 2015
Match 3 , core complete
A little progress on Match 3 today. Not as much as I'd like, I spent a lot of time tracking down occasional "stops" where an item just didn't drop.
Turned out in the end that when it dropped it very occasionally collided with the one dropping immediately below, causing it to stop.
Basically the core now works - you can play a level through to the end, all that is left is to complete the front end and the "unlocking" of levels.
Turned out in the end that when it dropped it very occasionally collided with the one dropping immediately below, causing it to stop.
Basically the core now works - you can play a level through to the end, all that is left is to complete the front end and the "unlocking" of levels.
Friday, 3 July 2015
Match 3 continued ....
Actually doesn't look that different to yesterday but it's advanced quite a lot. The dropping code now has 'removal code' added (this has a state machine) and most of the dragging code added. To complete the game core all I really need is to do is to check the drop position is legal, swap them over, check that that creates a match, and undo it if it doesn't.
Scoring etc. can then be added, but that's not too difficult.
Ended up not using the drag and drop behaviour for this. The problem is that the collision box for the items is narrower than you might think, if not they get stuck coming down .... and it uses the same collision box for DnD. So there is an own-version of the DnD.
Also created an object whose purpose is to move one object to another position and then self destruct - a bit like Fade with moving. I might (not sure) be able to generalise this so it can move anything, at the moment it's just Item-class only, but this might require some tinkering. Not sure :)
Scoring etc. can then be added, but that's not too difficult.
Ended up not using the drag and drop behaviour for this. The problem is that the collision box for the items is narrower than you might think, if not they get stuck coming down .... and it uses the same collision box for DnD. So there is an own-version of the DnD.
Also created an object whose purpose is to move one object to another position and then self destruct - a bit like Fade with moving. I might (not sure) be able to generalise this so it can move anything, at the moment it's just Item-class only, but this might require some tinkering. Not sure :)
Thursday, 2 July 2015
Game#10 : Match 3
This is a learning exercise, not an actual efficiency exercise. The picture of a Match 3 game just screams "2 Dimensional Array", but I have decided in a moment of advanced masochism to do it without arrays.
So I had some fun with this. The bright idea worked well - you can see the outline of the game, but the faded purple blocks and arrows are seeders and blockers for the pieces - this is implementing the 'falling down' bit, with the arrows pushing the out and the squares stopping them. These have had their invisibility setting code disabled so you can see them, obviously this won't show in the end bit. I also use a faint pink square on the screen which is the 'board space' and everything resizes to fit in that space (you can see a faint pink behind the arrows in the middle)
I originally did this with Physics, but I wasn't happy with that, then I did it with bullets, and I couldn't get it quite right. The movement is now hard coded (basic physics stuff) and it finally works, though I had some fun with spurious collisions and it took twice as long as I thought.
I'm going to think Frogger with this - that had some code which scanned looking for lines of log bits, and that can be tweaked to check for lines of food.
The little square box in the top right does say 555 (this is a technical joke), it's job is maintenance of semi globals, which belong to the Layout but aren't real globals - things like the position of the grid and so on. I just think that is neater than bazillions of globals.
So I had some fun with this. The bright idea worked well - you can see the outline of the game, but the faded purple blocks and arrows are seeders and blockers for the pieces - this is implementing the 'falling down' bit, with the arrows pushing the out and the squares stopping them. These have had their invisibility setting code disabled so you can see them, obviously this won't show in the end bit. I also use a faint pink square on the screen which is the 'board space' and everything resizes to fit in that space (you can see a faint pink behind the arrows in the middle)
I originally did this with Physics, but I wasn't happy with that, then I did it with bullets, and I couldn't get it quite right. The movement is now hard coded (basic physics stuff) and it finally works, though I had some fun with spurious collisions and it took twice as long as I thought.
I'm going to think Frogger with this - that had some code which scanned looking for lines of log bits, and that can be tweaked to check for lines of food.
The little square box in the top right does say 555 (this is a technical joke), it's job is maintenance of semi globals, which belong to the Layout but aren't real globals - things like the position of the grid and so on. I just think that is neater than bazillions of globals.
Wednesday, 1 July 2015
Berzerk - how it works
Game and Title Events are fairly straightforward. The first is just a collection of includes, the second displays the title screen (see right) and functions in the same way as Asteroids.
Globals is mostly (err..) globals but does have a reset check - setting resetGame non zero starts a new game. This game restarts the layout when a life is lost or when the room changes.
MazeCreate is in many ways not dissimilar to the Pacman game, in that it tidies up the position, width and heights of the walls (events 7-11). The majority of the rest of the code implements the Berzerk pseudo random number generator explained here. This uses the room coordinates (x and y) to generate a random sequence which is predictable. The nested for conditions (3-6) set this up, and 4,5 and 6 adjust the position of the 'room middle bars' as extracted from the random seed (the "Set Wall to" in event 3)
Player handles the player code. Event 2 cancels firing - the animation and movement is disabled when the fire key is pressed. Events 3 to 11 manipulate an 8Direction behaviour in two ways, once to the set animation, and another to set the current direction, if any, in instance variables lastVectorX/Y so that the player knows which way to fire.
Event 12 handles actual firing, and 13-18 set the correct firing animation (it sort of "points a gun")
Event 19 handles the collision with 'non fatal walls' - these are not visible (there is one on each exit) and collision with this moves to another room. playerX/Y is the new direction (set in 20,21) and changeX/changeY set the direction of movement (event 20 and 21). Evemt 22 9 not wraps it, stops the game, movement, destroys everything except the frame and plays the audio (23 and 24) depending on whether there are any robots or not.
Event 25 handles scrolling - when you move, the current room 'scrolls off' and 25 to 26 does this, restarting the layout (next room) at the end.
Event 27 handles the bonus count (when you destroy all robots in a room) and this is done in the collision code. The rest are functions which update lives and score
Robots handles robots. The first thing we do is create them, one (possibly) in each of the 5 x 3 'quadrants' of the room, providing they aren't too close (1-5). There is a short delay before it starts - when it does (6,7,8) it starts moving, decides whether it is chasing horizontally or vertically and starts a timer which regularly flips the chase direction, and one for trying to fire.
Events 9 to 18 handle setups for the different robot types, which is dependent on the score, see strategywiki.org for what appears when.
Events 19 to 22 handle movement. It moves in the current direction (evaluateMove function works this out) and if it is overlapping a wall (the else in 21 is fired) it toggles the direction it is chasing in. Event 22 causes it to toggle randomly anyway.
Events 23 to 26 handle firing. Each robot keeps a count of how many it can fire total, and how many it has left. When it fires one, it decrements "bulletsLeft", but each bullet knows who fired it. When the bullet is destroyed the "bulletsLeft" value is incremented (in the colllision code). Event 24 works out the angle to the player, but the bullets only fire in diagonal or straight directions - the or in 25 checks this as "near enough" and the direction is ironed out to the nearest 45. Finally 26 doubles the speed if it is a fast shooting robot.
Event 27 is a function which works out which way the robot should move into the dx and dy instance variables, and sets the animation appropriately. It gets a Robot UID as a parameter.
Events 31-33 handle the infamous evil Otto. A timer to create him is set up in 31, He is created in 32 and the sine behaviour adjusted. This handles the vertical position automatically, but event 33 adjusts him horizontally with a simple chase, doubling the speed if there are no robots left.
Collisions handles (surprise...) The first 3 just stop all bullets against walls and other bullets. Events 4 and 5 allow RobotBullets to kill the player, and a robot (if it isn't the spawning robot). Event 6 allows player to kill robots, 7 and 8 stop collision with the wall or Otto, 9 allows Robots to blow up on collision with the players orrobots, and Event 10 fires if the robot hits a wall (which shouldn't happen !)
The consequences are dealt with in 11 on. Event 11 fires if the player dies, and spawns an explosion which for some reason fires in Event 17 (disorganised ....). Event 12 and 13 handle Robot Bullet destruction and the incrementing of "allowed bullets for this robot". Event 14-16 handle scoring for robot death, and the bonus (note Count = 1 because it still exists). Events 17-19 handle life lost at the end of the explosion(either go back to the title screen or restart the same layout)
Finally 20 starts the game off.
Globals is mostly (err..) globals but does have a reset check - setting resetGame non zero starts a new game. This game restarts the layout when a life is lost or when the room changes.
MazeCreate is in many ways not dissimilar to the Pacman game, in that it tidies up the position, width and heights of the walls (events 7-11). The majority of the rest of the code implements the Berzerk pseudo random number generator explained here. This uses the room coordinates (x and y) to generate a random sequence which is predictable. The nested for conditions (3-6) set this up, and 4,5 and 6 adjust the position of the 'room middle bars' as extracted from the random seed (the "Set Wall to" in event 3)
Player handles the player code. Event 2 cancels firing - the animation and movement is disabled when the fire key is pressed. Events 3 to 11 manipulate an 8Direction behaviour in two ways, once to the set animation, and another to set the current direction, if any, in instance variables lastVectorX/Y so that the player knows which way to fire.
Event 12 handles actual firing, and 13-18 set the correct firing animation (it sort of "points a gun")
Event 19 handles the collision with 'non fatal walls' - these are not visible (there is one on each exit) and collision with this moves to another room. playerX/Y is the new direction (set in 20,21) and changeX/changeY set the direction of movement (event 20 and 21). Evemt 22 9 not wraps it, stops the game, movement, destroys everything except the frame and plays the audio (23 and 24) depending on whether there are any robots or not.
Event 25 handles scrolling - when you move, the current room 'scrolls off' and 25 to 26 does this, restarting the layout (next room) at the end.
Event 27 handles the bonus count (when you destroy all robots in a room) and this is done in the collision code. The rest are functions which update lives and score
Robots handles robots. The first thing we do is create them, one (possibly) in each of the 5 x 3 'quadrants' of the room, providing they aren't too close (1-5). There is a short delay before it starts - when it does (6,7,8) it starts moving, decides whether it is chasing horizontally or vertically and starts a timer which regularly flips the chase direction, and one for trying to fire.
Events 9 to 18 handle setups for the different robot types, which is dependent on the score, see strategywiki.org for what appears when.
Events 19 to 22 handle movement. It moves in the current direction (evaluateMove function works this out) and if it is overlapping a wall (the else in 21 is fired) it toggles the direction it is chasing in. Event 22 causes it to toggle randomly anyway.
Events 23 to 26 handle firing. Each robot keeps a count of how many it can fire total, and how many it has left. When it fires one, it decrements "bulletsLeft", but each bullet knows who fired it. When the bullet is destroyed the "bulletsLeft" value is incremented (in the colllision code). Event 24 works out the angle to the player, but the bullets only fire in diagonal or straight directions - the or in 25 checks this as "near enough" and the direction is ironed out to the nearest 45. Finally 26 doubles the speed if it is a fast shooting robot.
Event 27 is a function which works out which way the robot should move into the dx and dy instance variables, and sets the animation appropriately. It gets a Robot UID as a parameter.
Events 31-33 handle the infamous evil Otto. A timer to create him is set up in 31, He is created in 32 and the sine behaviour adjusted. This handles the vertical position automatically, but event 33 adjusts him horizontally with a simple chase, doubling the speed if there are no robots left.
Collisions handles (surprise...) The first 3 just stop all bullets against walls and other bullets. Events 4 and 5 allow RobotBullets to kill the player, and a robot (if it isn't the spawning robot). Event 6 allows player to kill robots, 7 and 8 stop collision with the wall or Otto, 9 allows Robots to blow up on collision with the players orrobots, and Event 10 fires if the robot hits a wall (which shouldn't happen !)
The consequences are dealt with in 11 on. Event 11 fires if the player dies, and spawns an explosion which for some reason fires in Event 17 (disorganised ....). Event 12 and 13 handle Robot Bullet destruction and the incrementing of "allowed bullets for this robot". Event 14-16 handle scoring for robot death, and the bonus (note Count = 1 because it still exists). Events 17-19 handle life lost at the end of the explosion(either go back to the title screen or restart the same layout)
Finally 20 starts the game off.
Berzerk (Released version)
Evil Otto is about to pounce. |
I will add the 'write up' later on today (hopefully).
I have decided, as an aside, to do a Match-3 game next, as its a bit different, and added a couple of other games and sorted it a bit.
So the list is now.
- Squash (done)
- Pong (done)
- Breakout (done)
- Galaxians (done, no title screen)
- Asteroids (done)
- Jet Pac (done)
- Frogger (done)
- PacMan (done).
- Berzerk (done)
- Match 3 Game
- Centipede - yet another with a very odd movement behaviour.
- Robotron - another classic, a demented single screen shoot em up.
- Some sort of RPG/Arcade simple game ; Atic Atac or that sort of thing.
- Defender - Robotron's "other half", with a scrolling screen.
- Lode Runner (once implemented this on a TI-83 calculator)
Tuesday, 30 June 2015
Berzerk (continued)
Berzerk has made some progress. The main outstanding bit is the robot move and fire, and the "Evil Otto" code (this is a kind of bouncing head that chases you if you don't move quick enough), and the title screen.
I really botched the design of this in places. There is a family "gameObjects" and I really should have had the robots and Otto in one family, it would make things simpler. Still it is supposed to be a learning experience.
One of the advantages of doing something like this is you have to stick to a particular model, you can't just change it to make things simpler. One of the disadvantages of C2 is that it makes certain games very very easy, and the temptation is always to head in that sort of direction.
Fortunately its a fairly simple game. I'm not sure actually why I put it on the list, next up is Robotron which is much more complicated.
Hope to finish it tomorrow and then start thinking about designing Robotron.
I've also considered that it might be an idea to do some different times of game - a Match 3 game, maybe, or some sort of basic dungeon room type RPG (a bit like the old Speccy game Atic Atac). Or maybe Pet Dungeon, I reverse engineered this but lost the code to meddling too much with Github.
I really botched the design of this in places. There is a family "gameObjects" and I really should have had the robots and Otto in one family, it would make things simpler. Still it is supposed to be a learning experience.
One of the advantages of doing something like this is you have to stick to a particular model, you can't just change it to make things simpler. One of the disadvantages of C2 is that it makes certain games very very easy, and the temptation is always to head in that sort of direction.
Fortunately its a fairly simple game. I'm not sure actually why I put it on the list, next up is Robotron which is much more complicated.
Hope to finish it tomorrow and then start thinking about designing Robotron.
I've also considered that it might be an idea to do some different times of game - a Match 3 game, maybe, or some sort of basic dungeon room type RPG (a bit like the old Speccy game Atic Atac). Or maybe Pet Dungeon, I reverse engineered this but lost the code to meddling too much with Github.
Sunday, 28 June 2015
I'm back - with Berzerk
So, I'm back from sunning myself and I've had a bit of time to work on Berzerk, which is Game#9 on my list.
What's interesting about Berzerk as a game is that it has no level design, it's seeded using some form of generator. This means you can have very big mazes in a very small amount of code.
Other games I know of that work like this are Pitfall and Dungeons of Daggorath.
It doesn't do much as yet, you can just wander around and shoot at nothing very much. But the basic room generation code is implemented and apparently working.
It's not a hugely complicated game even by the standards of the 1980s, so with a bit of luck it will be finished tomorrow. I probably won't do more than a simple start screen for it.
What's interesting about Berzerk as a game is that it has no level design, it's seeded using some form of generator. This means you can have very big mazes in a very small amount of code.
Other games I know of that work like this are Pitfall and Dungeons of Daggorath.
It doesn't do much as yet, you can just wander around and shoot at nothing very much. But the basic room generation code is implemented and apparently working.
It's not a hugely complicated game even by the standards of the 1980s, so with a bit of luck it will be finished tomorrow. I probably won't do more than a simple start screen for it.
Friday, 19 June 2015
Pacman .... how it works
This picture is actually from Construct 2, I thought it might help with the explanatory bits.
Frame
This is concerned with basic construction. As you can see only half the maze is there, and it's a bit scruffy.
Events 1,2 and 3 just mirror the horizontal and vertical bars on the other side, as Pacman's maze is symmetrical.
Events 4-7 tidy it up - make things a fixed width and an exact height - in terms of "maze units" - this is MAZE_BOX_SIZE , the width of the maze grid, and position it in the right place. Doing it this way makes it easy to change the width of the maze pieces.
Events 8-13 create the power pills (event 9) and the dots. We scan each square and put a pill there if there isn't a power pill there, and it doesn't overlap a not-dot space (the red crosses). They are all then moved to the bottom of their layer.
Events 14-19 reset the ghosts, this is repetitive except for the animation (held in an instance variable) and the "brains" - which is the chance of it chasing the player rather than just wandering aimlessly.
Finally, events 20-22 reposition the Pacman character and clear the two dots either side of it.
Moving Objects
The Pacman maze can be thought of as a bit like a monorail, with the rails passing through the middle of each square. A specialist moving routine is designed to keep them on the rails - so it can't move up when it is half way between squares for example. This applies to all objects in the Moveables family.
What happens is as follows. It first works out whether the moveable can move horizontally or vertically by seeing how close it is to the 'rail' (Events 5,6) - there is an adjustment so it can be close without being on top of it. It then calls the function named in the instance variable controlFunction to decide what to do (7) - which is different for Pacman (keyboard control) and Ghosts (AI ... ish).
Events 8 and 9 filter those move options according to whether it is possible or not, and then 10 and 11 update the actual direction if there is a change - so moveables will keep moving in one direction until you tell them a different one.
If it is moving, then 13 and 14 lock it to the rail in either direction, and a collision check is done in 15 - and if it can't move it cancels the moving direction (dx,dy) and puts it back in the middle of the rail. Event 16 is the same but is the special case of going down through the door to the Ghosts's den (hence the dy > 0 condition) which you cannot do, but moveables can go up through the door.
Finally, 17 actually physically moves it, and 18 and 19 deal with the tunnel at either end. Lastly another function is called to animate it appropriately.
Event 23 is the Pacman controller which sets the required dx and dy depending on the keyboard position. Event 29 controls a ghost - at any 'junction' (where a horizontal and vertical rail meet) there's a one in four chance of picking a new direction (forced by setting dx = dy = 0). 33 and 34 track whether the last one was a junction, so it can detect moving into a junction. If a directional change is desired (35), 36-39 set a horizontal or vertical direction, chasing (or running away from) the player dependent on the ghost's intellligence. Finally, if it is on a red cross it is forced to move up (i.e. it's in the middle)
Events 41-47 handle animation - the animations are different colours, and there are 4 pairs of frames in each colour. This maps that depending on elapsed time. Effectively I have written my own animation and moving code.
Event 46 is left in from testing, if you press X all the ghosts go home ... shouldn't be there.
Event 49 onwards makes a specific ghost go home - its isControlled instance variable is set to false, and a target is picked and stored in dx,dy. A pathfinder behaviour is used to find away back to the start, displaying eyes. When it reaches home it resets and goes back to being controlled by the AI rather than pathfinding.
Controller
The controller is an object which glues everything together. Doing it this way was a bit experimental.
Event 1 creates it if it doesn't exist (it's created on the Title screen), Event 2 resets in case of a new game, Event 3 calls the code to restart the level, and event 4 actually starts it going once the intro tune has been played.
Event 5 scores a dot, and Event 6 a powerpill. This also plays the new sound, and starts a timer which fires when the chasing ends. Finally 7 sets the attract direction to 'run away' (-1) and the animation code in the previous event file handles the blue and white look.
When the power pill times out, or all the ghosts have been eaten, all ghosts are reset to chasing mode.
Event 10 onwards handles collision with ghosts. It only happens when the ghost is controlled by AI (e.g. it's not returning to the middle). If the ghost is being chased, that is scored, and if not, we are in death mode, so everything is stopped and the death animation and sound is played ; when that ends we lose a life (14) and if we have lives left reset everything, but not the powerpills and dots. If it's the end - end (16) we go to the title screen.
Event 17 hands the completion stuff - no dots or power pills. We flash the maze (18,19,20) and when complete we either go the intermission (I only did one) or the next level.
Event 22 onwards handles the creation of fruit, and 23 makes it go away. If it's been eaten (24) it will be invisible, so if not,we make it disappear and go back to the first fruit. If it is eaten we go to the next fruit in the list, each one giving more points (25)
Event 26 is a function that starts a new level. On level 1 (only) it plays the little tune, skipping it if not (28)
Event 29-32 are simple functions that updte the score and lives. The only interesting one is 30, which does the extra life at 10000 points.
Of the others, IntermissionObjects just plays the little cartoon after Level 2. Title event is broadly the same as the rest. It uses blanker objects - black tiled backgrounds - with a time out to make things appear in the classical fashion.
So I'm off for a week now, when I return I'll continue with the next game, Berzerk.
Frame
This is concerned with basic construction. As you can see only half the maze is there, and it's a bit scruffy.
Events 1,2 and 3 just mirror the horizontal and vertical bars on the other side, as Pacman's maze is symmetrical.
Events 4-7 tidy it up - make things a fixed width and an exact height - in terms of "maze units" - this is MAZE_BOX_SIZE , the width of the maze grid, and position it in the right place. Doing it this way makes it easy to change the width of the maze pieces.
Events 8-13 create the power pills (event 9) and the dots. We scan each square and put a pill there if there isn't a power pill there, and it doesn't overlap a not-dot space (the red crosses). They are all then moved to the bottom of their layer.
Events 14-19 reset the ghosts, this is repetitive except for the animation (held in an instance variable) and the "brains" - which is the chance of it chasing the player rather than just wandering aimlessly.
Finally, events 20-22 reposition the Pacman character and clear the two dots either side of it.
Moving Objects
The Pacman maze can be thought of as a bit like a monorail, with the rails passing through the middle of each square. A specialist moving routine is designed to keep them on the rails - so it can't move up when it is half way between squares for example. This applies to all objects in the Moveables family.
What happens is as follows. It first works out whether the moveable can move horizontally or vertically by seeing how close it is to the 'rail' (Events 5,6) - there is an adjustment so it can be close without being on top of it. It then calls the function named in the instance variable controlFunction to decide what to do (7) - which is different for Pacman (keyboard control) and Ghosts (AI ... ish).
Events 8 and 9 filter those move options according to whether it is possible or not, and then 10 and 11 update the actual direction if there is a change - so moveables will keep moving in one direction until you tell them a different one.
If it is moving, then 13 and 14 lock it to the rail in either direction, and a collision check is done in 15 - and if it can't move it cancels the moving direction (dx,dy) and puts it back in the middle of the rail. Event 16 is the same but is the special case of going down through the door to the Ghosts's den (hence the dy > 0 condition) which you cannot do, but moveables can go up through the door.
Finally, 17 actually physically moves it, and 18 and 19 deal with the tunnel at either end. Lastly another function is called to animate it appropriately.
Event 23 is the Pacman controller which sets the required dx and dy depending on the keyboard position. Event 29 controls a ghost - at any 'junction' (where a horizontal and vertical rail meet) there's a one in four chance of picking a new direction (forced by setting dx = dy = 0). 33 and 34 track whether the last one was a junction, so it can detect moving into a junction. If a directional change is desired (35), 36-39 set a horizontal or vertical direction, chasing (or running away from) the player dependent on the ghost's intellligence. Finally, if it is on a red cross it is forced to move up (i.e. it's in the middle)
Events 41-47 handle animation - the animations are different colours, and there are 4 pairs of frames in each colour. This maps that depending on elapsed time. Effectively I have written my own animation and moving code.
Event 46 is left in from testing, if you press X all the ghosts go home ... shouldn't be there.
Event 49 onwards makes a specific ghost go home - its isControlled instance variable is set to false, and a target is picked and stored in dx,dy. A pathfinder behaviour is used to find away back to the start, displaying eyes. When it reaches home it resets and goes back to being controlled by the AI rather than pathfinding.
Controller
The controller is an object which glues everything together. Doing it this way was a bit experimental.
Event 1 creates it if it doesn't exist (it's created on the Title screen), Event 2 resets in case of a new game, Event 3 calls the code to restart the level, and event 4 actually starts it going once the intro tune has been played.
Event 5 scores a dot, and Event 6 a powerpill. This also plays the new sound, and starts a timer which fires when the chasing ends. Finally 7 sets the attract direction to 'run away' (-1) and the animation code in the previous event file handles the blue and white look.
When the power pill times out, or all the ghosts have been eaten, all ghosts are reset to chasing mode.
Event 10 onwards handles collision with ghosts. It only happens when the ghost is controlled by AI (e.g. it's not returning to the middle). If the ghost is being chased, that is scored, and if not, we are in death mode, so everything is stopped and the death animation and sound is played ; when that ends we lose a life (14) and if we have lives left reset everything, but not the powerpills and dots. If it's the end - end (16) we go to the title screen.
Event 17 hands the completion stuff - no dots or power pills. We flash the maze (18,19,20) and when complete we either go the intermission (I only did one) or the next level.
Event 22 onwards handles the creation of fruit, and 23 makes it go away. If it's been eaten (24) it will be invisible, so if not,we make it disappear and go back to the first fruit. If it is eaten we go to the next fruit in the list, each one giving more points (25)
Event 26 is a function that starts a new level. On level 1 (only) it plays the little tune, skipping it if not (28)
Event 29-32 are simple functions that updte the score and lives. The only interesting one is 30, which does the extra life at 10000 points.
Of the others, IntermissionObjects just plays the little cartoon after Level 2. Title event is broadly the same as the rest. It uses blanker objects - black tiled backgrounds - with a time out to make things appear in the classical fashion.
So I'm off for a week now, when I return I'll continue with the next game, Berzerk.
Wednesday, 17 June 2015
Pacman now complete
Pacman is now completed - pretty much anyway and can be played here
Quite pleased with this one, it's nice. I've even coded the first intermission :)
I will write up the "how it works" part tomorrow.
Quite pleased with this one, it's nice. I've even coded the first intermission :)
I will write up the "how it works" part tomorrow.
Monday, 15 June 2015
Pacman (continued)
Progress on Pacman, I have implemented the moving and AI code. This is, tbh, more programming than Construct 2 ; the moving is handled by an event that acts on a family, and indirect functions (calls where the function is a variable not a constant) are used to get movement stuff and update animation. It's pretty much the same as subclassing a family. Sort of.
Also used Pathfinders for the first time. When Ghosts are eaten (you can't do this yet, everything just moves around) they turn into Eyes and return to the home base. This is done using Pathfinding behaviour.
A lot of the code is written now. The Ghosts especially behave like objects - they have a state (whether they are being chased or not) and operate fairly independently by changing their instance variables. I will probably have a controller object in this game that handles all the sequencing and so on, rather than have it implicit as part of Construct 2's event system.
Also used Pathfinders for the first time. When Ghosts are eaten (you can't do this yet, everything just moves around) they turn into Eyes and return to the home base. This is done using Pathfinding behaviour.
A lot of the code is written now. The Ghosts especially behave like objects - they have a state (whether they are being chased or not) and operate fairly independently by changing their instance variables. I will probably have a controller object in this game that handles all the sequencing and so on, rather than have it implicit as part of Construct 2's event system.
Sunday, 14 June 2015
Game#8 : Pacman
Some progress with Game#8, Pacman. I have most of the graphics, and the layout. I think the walls might benefit from being fatter.
A couple of tricks I have done with this are : that the walls are only approximately positioned and sized, the final positions and size are sharpened up in code. This means making the maze walls fatter or thinner is very easy.
I have taken advantage of the symmetry of the playfield - only half of it is drawn on the C2 editor, the other half is created on the fly when the layout is loaded.
I have been away the last couple of days, and I'm going away for a week next Saturday, but I hope to get a completed Pacman before then.
Then there will be a short break before the next game, Berzerk.
A couple of tricks I have done with this are : that the walls are only approximately positioned and sized, the final positions and size are sharpened up in code. This means making the maze walls fatter or thinner is very easy.
I have taken advantage of the symmetry of the playfield - only half of it is drawn on the C2 editor, the other half is created on the fly when the layout is loaded.
I have been away the last couple of days, and I'm going away for a week next Saturday, but I hope to get a completed Pacman before then.
Then there will be a short break before the next game, Berzerk.
Thursday, 11 June 2015
Game # 7 : Frogger
Frogger is now completed and can be played here. Source code is on the github (see URL on right).
All these things are pretty much beta standard, incidentally. They pretty much work, but there may well be bugs in them I haven't found, things I haven't implemented (this doesn't have the Snake for example) and so on.
The title screen is fairly straightforward. It does use LocalStorage to maintain the high score table, though. I note some people are having problems with the asynchronous nature of LS.
Event 1 checks for the "FROGGER_KEY" - a key that is a Unique ID for the hi score table array and stores those scores in JSON.
Event 3 handles it missing - it initialises the array in Event 4, then puts any current score in the sixth element of the array and calls refreshScoreboard which sorts the array and writes it back out to LocalStorage.
If it exists, Event 5 requests the value - it is a two step process, and when that is retrieved it copies the JSON string into the array and refreshes the scoreboard again.
I wonder if the mistake people make is thinking if you check for a key you can immediately retrieve it - you can't, you have to check it's there, then retrieve it, both with callbacks.
The GameInitialise sets everything up. Event 2 resets to a new game if the level global is -1 - this is because levels are started just by restarting the layout, and Event 3 initialises the time, score and lives part.
Event 6 just sets the speed and direction of motion.
Events 7 through 11 were fiddly. I wanted groups of things - the turtles and the logs (which are built up from a left bit, middle bit and a right bit) to be useable as such so (say) all the turtles in a group dived at the same time. I gave them all a group ID. Doing this automatically is a little fiddly, it picks up all "wrappers" (anything that moves on the screen not including the player) that don't have anything on their left in event 8 (e.g. the left most part of every group). Event 9 marks them as a "first Object" - kind of a leading role, and saves their position. Event 10 reselects all wrappers, and the While loop in 11 keeps going right and assigning objects a group ID while it the position has a wrapper there, sort of scanning to the right.
Event 12 through 19 remove some objects from the screen, so the river is harder at higher levels (the vehicles just go faster). To do this it maintains a count of how many "first Objects" there are in each row. This is required because if you remove every object in a row, you can't get to the home area. (There's probably a better way of doing this !). Events 13 and 14 count this.
Event 15 defines the number to remove. Event 16 picks a wrapper in the top half (e.g. not the cars) that hasn't already been deleted (group ID is +ve). It then picks a random wrapper instance where there are at least two of them in the row, saves the groupID in "groupToKill" and adjusts the count in the array (Event 17). Finally 18 and 19 delete all objects with that Group ID, by setting it to -1 and destroying it (I'm not sure if it is destroyed immediately or it is queued for later ?)
Events 20-25 adjust the start positions. The logs etc. are drawn on so this shifts a randomly chosen line (event 22) 10 times (event 21), wrapping them round (event 24,25). Effectively this means the starting position of everything varies.
Events 26 and 27 set up the turtle animations. You want them to all sink at different times, but turtles in the same group should all sink together. So all turtles are initially non sinking, then all "lead Turtles" have a timer with a random time. When this fires (28,29,30) all turtles in that group have their animation set to sinking which means they will dip under the river from time to time.
Events 31-33 set up the already home frogs, which is checked in a global array HomeFrogOccupied - when a 'slot' is filled, then it stays filled until you have reached the next level. The object of Frogger is to fill all the home slots. If it is not occupied, the home frog is still there, it is just invisible. It is required for collision detection.
Events 35-36 solved a bug which I tried to reproduce but couldn't. I originally used 'wrap' to make the various screen objects wrap, but it didn't quite work with the logs, which were built up from 64x64 parts. Occasionally a pixel wide line appeared between them. I suspect some sort of rounding, but the manual wrapping at 35 and 36 fixes this.
Event 37 updates the timer bar 4 times a second and the remaining events just update the timer, score and lives
Actually that bit was the most difficult to get right, the Player section which follows was much easier. Some of those sections involved quite a lot of experimenting. I knew what I wanted to do, but wasn't quite sure about how C2 actually did it best. So a useful experience.
In the Player section, events 2 and 3 set the level up, play some music and tun the main tune on when the into twiddle has finished.
Sections 5 through 23 handle the movement. This is hard coded, as it is odd. The frog jumps about in 64 pixel squares from oldX,newX to oldY,newY. If it isn't jumping at present, events 5-8 set these values up dependent on the state of the keyboard controls. In event 10, if we are about to jump it sets a whole load of stuff - there is a time value which controls the jump, we aren't riding anything in a jump (ridingUID identifies the object we are riding on), it's now jumping, and we add 10 to the score.
In event 12, if we are riding an object, we update the player position from that offset (if the log moves and you are on the log, you move). An offset is kept from the origin of the log (or turtle).
Events 13 to 16 are the actual jump. If the jump time has expired, the position is updated for accuracy and we are back in non jumping mode. Event 15 handles landing on a turtle or a log by setting ridingUID and the offsets. Alternatively, we are in mid jump, and we lerp the position between the start and end of the jump and use a sine value to make it look as if its jumping by tweaking the scale.
Events 17 to 23 deal with the consequences of colliding with a Home Frog - the visible or invisible marker we created earlier. If its visible, then we've jumped into an occupied slot - which is terminal. Event 18 also checks that we've jumped reasonably accurately, not just that there is some collision.
Event 19 is fired on success - we mark the slot occupied in the HomeFrogOccupied array (note, the home frog knows which slot it is in). and add scoring for thatt and unused time. We also set isLevelComplete to true, which we clear in 20,21 if we find an empty slot in the HomeFrogOccupied array (e.g. we haven't done all five home slots). Finally we restart the layout.
Event 24 to 29 deal with death. 24 is outside layout, or off the bottom of the screen. Event 25 is time out. Event 26 handles landing in the river. Events 27 and 28 handle being on a turtle that has sunk, and 29 handles being hit by a car.
I cheated in event 30,31. If we are dead, we use the sound effect being played (tagged "dead") as timer control, which is probably bad practice. Sorry :) While it is paying 31 spins and shrinks the player.
Event 32 detects the end of that sound effect, and decrements the life count. Depending on whether you have any lives left, the game either restarts the layout, or goes back to the title screen, with a score for this game in the score global.
So onto Game#8 which is, I think, Pacman.
All these things are pretty much beta standard, incidentally. They pretty much work, but there may well be bugs in them I haven't found, things I haven't implemented (this doesn't have the Snake for example) and so on.
The title screen is fairly straightforward. It does use LocalStorage to maintain the high score table, though. I note some people are having problems with the asynchronous nature of LS.
Event 1 checks for the "FROGGER_KEY" - a key that is a Unique ID for the hi score table array and stores those scores in JSON.
Event 3 handles it missing - it initialises the array in Event 4, then puts any current score in the sixth element of the array and calls refreshScoreboard which sorts the array and writes it back out to LocalStorage.
If it exists, Event 5 requests the value - it is a two step process, and when that is retrieved it copies the JSON string into the array and refreshes the scoreboard again.
I wonder if the mistake people make is thinking if you check for a key you can immediately retrieve it - you can't, you have to check it's there, then retrieve it, both with callbacks.
The GameInitialise sets everything up. Event 2 resets to a new game if the level global is -1 - this is because levels are started just by restarting the layout, and Event 3 initialises the time, score and lives part.
Event 6 just sets the speed and direction of motion.
Events 7 through 11 were fiddly. I wanted groups of things - the turtles and the logs (which are built up from a left bit, middle bit and a right bit) to be useable as such so (say) all the turtles in a group dived at the same time. I gave them all a group ID. Doing this automatically is a little fiddly, it picks up all "wrappers" (anything that moves on the screen not including the player) that don't have anything on their left in event 8 (e.g. the left most part of every group). Event 9 marks them as a "first Object" - kind of a leading role, and saves their position. Event 10 reselects all wrappers, and the While loop in 11 keeps going right and assigning objects a group ID while it the position has a wrapper there, sort of scanning to the right.
Event 12 through 19 remove some objects from the screen, so the river is harder at higher levels (the vehicles just go faster). To do this it maintains a count of how many "first Objects" there are in each row. This is required because if you remove every object in a row, you can't get to the home area. (There's probably a better way of doing this !). Events 13 and 14 count this.
Event 15 defines the number to remove. Event 16 picks a wrapper in the top half (e.g. not the cars) that hasn't already been deleted (group ID is +ve). It then picks a random wrapper instance where there are at least two of them in the row, saves the groupID in "groupToKill" and adjusts the count in the array (Event 17). Finally 18 and 19 delete all objects with that Group ID, by setting it to -1 and destroying it (I'm not sure if it is destroyed immediately or it is queued for later ?)
Events 20-25 adjust the start positions. The logs etc. are drawn on so this shifts a randomly chosen line (event 22) 10 times (event 21), wrapping them round (event 24,25). Effectively this means the starting position of everything varies.
Events 26 and 27 set up the turtle animations. You want them to all sink at different times, but turtles in the same group should all sink together. So all turtles are initially non sinking, then all "lead Turtles" have a timer with a random time. When this fires (28,29,30) all turtles in that group have their animation set to sinking which means they will dip under the river from time to time.
Events 31-33 set up the already home frogs, which is checked in a global array HomeFrogOccupied - when a 'slot' is filled, then it stays filled until you have reached the next level. The object of Frogger is to fill all the home slots. If it is not occupied, the home frog is still there, it is just invisible. It is required for collision detection.
Events 35-36 solved a bug which I tried to reproduce but couldn't. I originally used 'wrap' to make the various screen objects wrap, but it didn't quite work with the logs, which were built up from 64x64 parts. Occasionally a pixel wide line appeared between them. I suspect some sort of rounding, but the manual wrapping at 35 and 36 fixes this.
Event 37 updates the timer bar 4 times a second and the remaining events just update the timer, score and lives
Actually that bit was the most difficult to get right, the Player section which follows was much easier. Some of those sections involved quite a lot of experimenting. I knew what I wanted to do, but wasn't quite sure about how C2 actually did it best. So a useful experience.
In the Player section, events 2 and 3 set the level up, play some music and tun the main tune on when the into twiddle has finished.
Sections 5 through 23 handle the movement. This is hard coded, as it is odd. The frog jumps about in 64 pixel squares from oldX,newX to oldY,newY. If it isn't jumping at present, events 5-8 set these values up dependent on the state of the keyboard controls. In event 10, if we are about to jump it sets a whole load of stuff - there is a time value which controls the jump, we aren't riding anything in a jump (ridingUID identifies the object we are riding on), it's now jumping, and we add 10 to the score.
In event 12, if we are riding an object, we update the player position from that offset (if the log moves and you are on the log, you move). An offset is kept from the origin of the log (or turtle).
Events 13 to 16 are the actual jump. If the jump time has expired, the position is updated for accuracy and we are back in non jumping mode. Event 15 handles landing on a turtle or a log by setting ridingUID and the offsets. Alternatively, we are in mid jump, and we lerp the position between the start and end of the jump and use a sine value to make it look as if its jumping by tweaking the scale.
Events 17 to 23 deal with the consequences of colliding with a Home Frog - the visible or invisible marker we created earlier. If its visible, then we've jumped into an occupied slot - which is terminal. Event 18 also checks that we've jumped reasonably accurately, not just that there is some collision.
Event 19 is fired on success - we mark the slot occupied in the HomeFrogOccupied array (note, the home frog knows which slot it is in). and add scoring for thatt and unused time. We also set isLevelComplete to true, which we clear in 20,21 if we find an empty slot in the HomeFrogOccupied array (e.g. we haven't done all five home slots). Finally we restart the layout.
Event 24 to 29 deal with death. 24 is outside layout, or off the bottom of the screen. Event 25 is time out. Event 26 handles landing in the river. Events 27 and 28 handle being on a turtle that has sunk, and 29 handles being hit by a car.
I cheated in event 30,31. If we are dead, we use the sound effect being played (tagged "dead") as timer control, which is probably bad practice. Sorry :) While it is paying 31 spins and shrinks the player.
Event 32 detects the end of that sound effect, and decrements the life count. Depending on whether you have any lives left, the game either restarts the layout, or goes back to the title screen, with a score for this game in the score global.
So onto Game#8 which is, I think, Pacman.
Tuesday, 9 June 2015
Frogger (continued)
Busy couple of days with one thing and another, so not much time spent on "Frogger". I have managed to get it as far as the game core being complete, save for if you die, you just stop - there's no lives down and restart code.
This won't take long, then there's some sort of high score table, and then it's ready to go out.
This won't take long, then there's some sort of high score table, and then it's ready to go out.
Sunday, 7 June 2015
Game#7 : Frogger
Game#7 is under way. This is Frogger, an old game where you have to dodge the traffic and jump across the river on the logs and (diving turtles).
Some oddities with this. Much of what you can see was almost trivially easy. It's basically just wrapping objects. Though the wrap doesn't quite work, as the logs are made up out of chunks, and using Wrap they became just a little seperated leaving black lines.
The interesting stuff comes with the adjustments for levels. I wanted to give all the objects a "groupID" - so the logs are built up of 2-4 bits of log, all of which have the same ID, and the turtles in the same groups all have the same ID. Allocating these automatically was quite tricky.
Then I wanted to remove some of the river objects on higher levels, again programatically so this involved picking a log/turtle group, checking it wasn't the only one (otherwise you couldn't get across ....) and deleting everything.
Then I wanted the turtles not to all dive at the same time. The dive animation was done with animations, so I had to delay the starting of the animation using a timer so they are all mixed up.
All of these things could have been done just by allocating instance variables "start it now" "drop this on level 4" but it was a better learning experience to do it the harder way.
Some oddities with this. Much of what you can see was almost trivially easy. It's basically just wrapping objects. Though the wrap doesn't quite work, as the logs are made up out of chunks, and using Wrap they became just a little seperated leaving black lines.
The interesting stuff comes with the adjustments for levels. I wanted to give all the objects a "groupID" - so the logs are built up of 2-4 bits of log, all of which have the same ID, and the turtles in the same groups all have the same ID. Allocating these automatically was quite tricky.
Then I wanted to remove some of the river objects on higher levels, again programatically so this involved picking a log/turtle group, checking it wasn't the only one (otherwise you couldn't get across ....) and deleting everything.
Then I wanted the turtles not to all dive at the same time. The dive animation was done with animations, so I had to delay the starting of the animation using a timer so they are all mixed up.
All of these things could have been done just by allocating instance variables "start it now" "drop this on level 4" but it was a better learning experience to do it the harder way.
Friday, 5 June 2015
Game # 6 : Jetpac Complete
Jetpac is now complete and is in the repository and can be run here.
As you can see it does look like the original, unsurprisingly :) It also has a title screen and a fake Spectrum loading screen (and no, you cannot select "Kempston Joystick")
I've split the events up into sections in this one, so I'll comment on them one at a time.
System
This controls general flow and set up. Event 1 resets score and lives if the level has been set to -1, and calls "SetupLevel". This function (Event 6 and 7) sets the rocket number (there are four), the enemy type (there are eight) and the game state which is either -3 (about to land) or -2 (construction). You can see -2 above, it's where the rocket needs assembling. -3 animates the rocket landing.
The game state is "How far you are through the process" -2 and -1 are rocket building, 0 to 5 adding fuel. When it reaches 6 the rocket takes off. The state is changed by dropping the correct objects (fuel bars and rocket bits) on the rocket.
Function 8 simply updates the scores.
Function 9 controls the set up of the screen. It selects the animation in the rocket object (there are four, one for each rocket) and stops it, because the rocket is not animated, it uses animation frames to display fuel states (the gradual colouring of the rocket). It creates two "components" - the two rocket bits on the platforms, positions them, selects the appropriate animation, and sets their carryID. This is compared to the game status, e.g. when game status is -1 this means you get the top of the rocket, and the top of this rocket has a carryID of -1 - this is how it knows you've picked up the right bit. If yo've already done the middle bit event 11 moves it onto the rocket and turns its physics off.
Event 12 deals with the fuel levels. Event 13 triggers a rocket landing (flight direction = 1 = down), which is handled by events 3 to 5, which moves the rocket until it is off the top or bottom, depending on which way it is going, only if this flight direction is set. Event 14 corresponds to game state 6 (the rocket is full of fuel) and makes it take off the other way (e.g. flight direction = -1)
Controllers
Controllers just modularises (is this a word ?) the controls. Doing it this way means it is easy to convert for different types of control. Different instance variables in the player maintain values depending on left/right/thrust and fire.
Enemies
Controls the enemies. Event 1 is a testing hack. Events 2 and 3 keep the numbers up to 4 by creating a new one every second if there aren't four. Event 4 handles the set up of the enemy colour, position and type, and Event 5 tweaks that if it is going right to left.
Objects are update every 0.25 seconds by calling "controlEnemy" with the enemy's UID. This allows us to control their behaviour. Event 7 does this, allowing the rate of change of that behaviour to be modified by "adjustRate". Events 9-11 handle platform collisions, enemies either die or bounce depending on an instance variable. Event 11 handles being shot by a laser, and event 12 destroys consequent explosions.
Event 14 is where the behaviour starts. It picks the correct instance and then varies the behaviour depending on the switch statement. I borrowed this description of how enemies move and this is roughly the same as that.
Events 19 through 23 are more complex - an enemy can either be stuck at the edge in which case it moves up and down (20-23) but it can be released to charge across 1 time in 10 (event 22 and 23).
Carriables
Carriables are things the player carries or collects. The current carried object is identified by an instance variable carriedUID in the player, which is cleared in Event 1. Event 2 stops dropping objects from accelerating.
Event 3 is the main object pick up - if the player hits a carriable object, and its the right one (game State = carryID) and its not carrying anything (carriedUID = 0) then pick it up. We make it stick in Event 7, and make it fall in Event 8 if it is approximately over the rocket. It also sets a "yTerminal" value for the carried object, this fires event 11 when it is dropped to a particular height and sets the game to the next status.
Events 12 and 13 launch fuel bars from the top of the screen when required (after the rocket is built if it needs building, e.g. gameState > 0, if it hasn't finished, and only one at a time. Event 14 similarly launches collectables which are objects that are just extra points, and are randomly coloured, these are picked up and scored by Event 15.
Player Movement
Handles player moving and firing, and is all conditional on the Player visibility. The player is driven using physics, so Events 2-4 apply forces for left, right and thrust. Events 5,6 and 7 set the animation depending on what the player is doing (note event 6 also reduces the velocity when walking).
Event 8 detects firing and creates a laser of quasi random colours, event 9 flips it if the player is facing left, and 10 and 11 change the colours.
Event 12 deals with player death (there is a cheat mode here), the number of collisions is reduced to 1 by "Pick Enemy Instance 0", and it reduces lives and spawns an explosion where the player was.
Event 14 handles the consequences of this. When that explosion finishes, it either restarts the layout (if there are lives left) or returns to the title page.
As you can see it does look like the original, unsurprisingly :) It also has a title screen and a fake Spectrum loading screen (and no, you cannot select "Kempston Joystick")
I've split the events up into sections in this one, so I'll comment on them one at a time.
System
This controls general flow and set up. Event 1 resets score and lives if the level has been set to -1, and calls "SetupLevel". This function (Event 6 and 7) sets the rocket number (there are four), the enemy type (there are eight) and the game state which is either -3 (about to land) or -2 (construction). You can see -2 above, it's where the rocket needs assembling. -3 animates the rocket landing.
The game state is "How far you are through the process" -2 and -1 are rocket building, 0 to 5 adding fuel. When it reaches 6 the rocket takes off. The state is changed by dropping the correct objects (fuel bars and rocket bits) on the rocket.
Function 8 simply updates the scores.
Function 9 controls the set up of the screen. It selects the animation in the rocket object (there are four, one for each rocket) and stops it, because the rocket is not animated, it uses animation frames to display fuel states (the gradual colouring of the rocket). It creates two "components" - the two rocket bits on the platforms, positions them, selects the appropriate animation, and sets their carryID. This is compared to the game status, e.g. when game status is -1 this means you get the top of the rocket, and the top of this rocket has a carryID of -1 - this is how it knows you've picked up the right bit. If yo've already done the middle bit event 11 moves it onto the rocket and turns its physics off.
Event 12 deals with the fuel levels. Event 13 triggers a rocket landing (flight direction = 1 = down), which is handled by events 3 to 5, which moves the rocket until it is off the top or bottom, depending on which way it is going, only if this flight direction is set. Event 14 corresponds to game state 6 (the rocket is full of fuel) and makes it take off the other way (e.g. flight direction = -1)
Controllers
Controllers just modularises (is this a word ?) the controls. Doing it this way means it is easy to convert for different types of control. Different instance variables in the player maintain values depending on left/right/thrust and fire.
Enemies
Controls the enemies. Event 1 is a testing hack. Events 2 and 3 keep the numbers up to 4 by creating a new one every second if there aren't four. Event 4 handles the set up of the enemy colour, position and type, and Event 5 tweaks that if it is going right to left.
Objects are update every 0.25 seconds by calling "controlEnemy" with the enemy's UID. This allows us to control their behaviour. Event 7 does this, allowing the rate of change of that behaviour to be modified by "adjustRate". Events 9-11 handle platform collisions, enemies either die or bounce depending on an instance variable. Event 11 handles being shot by a laser, and event 12 destroys consequent explosions.
Event 14 is where the behaviour starts. It picks the correct instance and then varies the behaviour depending on the switch statement. I borrowed this description of how enemies move and this is roughly the same as that.
Events 19 through 23 are more complex - an enemy can either be stuck at the edge in which case it moves up and down (20-23) but it can be released to charge across 1 time in 10 (event 22 and 23).
Carriables
Carriables are things the player carries or collects. The current carried object is identified by an instance variable carriedUID in the player, which is cleared in Event 1. Event 2 stops dropping objects from accelerating.
Event 3 is the main object pick up - if the player hits a carriable object, and its the right one (game State = carryID) and its not carrying anything (carriedUID = 0) then pick it up. We make it stick in Event 7, and make it fall in Event 8 if it is approximately over the rocket. It also sets a "yTerminal" value for the carried object, this fires event 11 when it is dropped to a particular height and sets the game to the next status.
Events 12 and 13 launch fuel bars from the top of the screen when required (after the rocket is built if it needs building, e.g. gameState > 0, if it hasn't finished, and only one at a time. Event 14 similarly launches collectables which are objects that are just extra points, and are randomly coloured, these are picked up and scored by Event 15.
Player Movement
Handles player moving and firing, and is all conditional on the Player visibility. The player is driven using physics, so Events 2-4 apply forces for left, right and thrust. Events 5,6 and 7 set the animation depending on what the player is doing (note event 6 also reduces the velocity when walking).
Event 8 detects firing and creates a laser of quasi random colours, event 9 flips it if the player is facing left, and 10 and 11 change the colours.
Event 12 deals with player death (there is a cheat mode here), the number of collisions is reduced to 1 by "Pick Enemy Instance 0", and it reduces lives and spawns an explosion where the player was.
Event 14 handles the consequences of this. When that explosion finishes, it either restarts the layout (if there are lives left) or returns to the title page.
Subscribe to:
Posts (Atom)