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.
Tuesday, 30 June 2015
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.
Game #6, everything but the bad guys
Well, it's now possible to play JetPac but it's rather easy, because there aren't any bad guys yet. But you can construct the rockets and refuel them and so on.
I made a mistake though. This is using Physics for the Astronaut flight and the dropping of things, and I probably shouldn't have, should have done it in Custom Movement or something.
You can see a consequence in the picture. The astronaut is riding down on the fuel cell, because, well, that's what the Physics engine wants. It does make logical sense. Likewise you can do odd things like carry fuel cells about on your head and so on. Still, that's the whole point of this exercise, learning lessons.
I made a mistake though. This is using Physics for the Astronaut flight and the dropping of things, and I probably shouldn't have, should have done it in Custom Movement or something.
You can see a consequence in the picture. The astronaut is riding down on the fuel cell, because, well, that's what the Physics engine wants. It does make logical sense. Likewise you can do odd things like carry fuel cells about on your head and so on. Still, that's the whole point of this exercise, learning lessons.
Thursday, 4 June 2015
Game#6 : Jetpac Moving only
Not done a lot on Jetpac today, though I was lucky enough to find someone had already ripped the Sprites out which made it much easier. I can't draw at all. I mean, really bad. The Galaxians and Asteroids gfx come from images of the originals. So does this. I have found that 3D modelling with something like Blender helps.
At the moment it can just move about and fire, but there's nothing to hit or collect as yet.
I actually visited ACG (the home of Ultimate) once, when they were in Ashby de la Zouch (in the Midlands) and I was at Keele University. I was trying to get hold of a copy of Alien 8 for the BBC, very difficult, so I went to the source and asked them for a copy.
Amazingly it worked. Perhaps they thought I was mad, so they'd better humour me, I don't know. I also used the same trick with Acornsoft in Wellingborough to get a copy of "Revs" by Geoff Crammond, similarly "unavailable".
At the moment it can just move about and fire, but there's nothing to hit or collect as yet.
I actually visited ACG (the home of Ultimate) once, when they were in Ashby de la Zouch (in the Midlands) and I was at Keele University. I was trying to get hold of a copy of Alien 8 for the BBC, very difficult, so I went to the source and asked them for a copy.
Amazingly it worked. Perhaps they thought I was mad, so they'd better humour me, I don't know. I also used the same trick with Acornsoft in Wellingborough to get a copy of "Revs" by Geoff Crammond, similarly "unavailable".
Game #5 : Asteroids
Asteroids is now up, in both code and runnable form here.
Note that it is now on github in .caproj format, not as a .capx. I had a problem with a C2 bug I cannot reproduce occasionally losing image files.
As you can see this has two layouts, there is a high score table implemented with localstorage and a main screen.
The localstorage implementation is fairly straightforward ; it checks for the presence of the key (in the constant ASTEROID_KEY) and if present, loads it into the array of high scores, if not it loads it with defaults (incidentally the name is always AAA). It then adds the score returned (initially this is -1), sorts the array and reverses it (highest first) and displays it, writing it back to localstorage at the same time.
In the main events, a score of -1 is indicative of a new game, and events 1 + 2 do that reset. Events 3 to 6 create asteroids and set values like speedScalar (how fast it's going), update the score and so on (it reloads the layout to start a new level).
Asteroids are wrapped bullets, and there is only one object which is scaled to make the three types. This is done using currentSize and requiredSize - if you look at 5 it sets current to -1 and required to 1. Going down for a minute to event 10, if it finds these values to be different, it resizes, sets speed, rotate speed and animation of the asteroid. So the asteroid can be automatically resized just by setting "requiredSize" (currentSize is initially set to -1 in case of a previous value).
Events 7-9 are over complicated because I didn't realise you could PickNearest - this works out all the distances the asteroids are from the player if the player is "hiding" - in Asteroids there is a delay when the player appears so it doesn't appear on top of an asteroid, and enables the player if they are all far enough away.
Events 11 through 15 handle the Asteroid moving - rotation, thrust and in 15 the natural friction slowing it down. Event 16 handles spawning of bullets, allowing a maximum of four, and enforcing a delay time between the bullets (the lastFireTime variable). This is all done with the help of CustomMovement.
Events 17 to 23 are slightly tricky. Initially it detects a collision between a bullet and an asteroid, uses hitAsteroid to make it unique (could have used Pick instance here). However it is slightly tricky because when you hit the non-smallest asteroids they split into two. What I do is to shrink the asteroid hit, and send it off in a new direction, and create another one (in 21) and in 24, when it finds this newly created asteroid (currentSize is -1) it fills in the details saved in the variables about how big it is and what direction it is going in.
Event 22 just destroys the smallest (size = 3) asteroid , and 23 forces a restart of the layout with the next level if all the asteroids are dead.
Event 25 onwards drives the UFO - there are two times for this timer, how long it takes the first time, and how long it takes subsequent times. If it has timed out, and there isn't one already, it creats a UFO, with a 50/50 chance of a large or small one (small ones are quicker and fire accurately). The UFO firing is done in 28, and the bullet is initially aimed at the player, 29 checks the instance variable in the UFO to see if it is a big one, and if it is randomises that angle.
Events 30 through 33 check for collisions with the player (hitting a UFO, UFO Bullet or Asteroid) and then if this has happened it restarts, and handles the restart, displaying the level number (35)or Game Over 36, when that Game Over fades out in 37 the game goes back to the high score table.
Finally 38 and 39 update the score and lives display in a function.
This isn't bad but it is a bit messy. I've winged it to date, from now on (Game #6 is Jetpac, a Sinclair Spectrum game) a little more consideration will go into the design, I shall use event groups and so on.
Note that it is now on github in .caproj format, not as a .capx. I had a problem with a C2 bug I cannot reproduce occasionally losing image files.
As you can see this has two layouts, there is a high score table implemented with localstorage and a main screen.
The localstorage implementation is fairly straightforward ; it checks for the presence of the key (in the constant ASTEROID_KEY) and if present, loads it into the array of high scores, if not it loads it with defaults (incidentally the name is always AAA). It then adds the score returned (initially this is -1), sorts the array and reverses it (highest first) and displays it, writing it back to localstorage at the same time.
In the main events, a score of -1 is indicative of a new game, and events 1 + 2 do that reset. Events 3 to 6 create asteroids and set values like speedScalar (how fast it's going), update the score and so on (it reloads the layout to start a new level).
Asteroids are wrapped bullets, and there is only one object which is scaled to make the three types. This is done using currentSize and requiredSize - if you look at 5 it sets current to -1 and required to 1. Going down for a minute to event 10, if it finds these values to be different, it resizes, sets speed, rotate speed and animation of the asteroid. So the asteroid can be automatically resized just by setting "requiredSize" (currentSize is initially set to -1 in case of a previous value).
Events 7-9 are over complicated because I didn't realise you could PickNearest - this works out all the distances the asteroids are from the player if the player is "hiding" - in Asteroids there is a delay when the player appears so it doesn't appear on top of an asteroid, and enables the player if they are all far enough away.
Events 11 through 15 handle the Asteroid moving - rotation, thrust and in 15 the natural friction slowing it down. Event 16 handles spawning of bullets, allowing a maximum of four, and enforcing a delay time between the bullets (the lastFireTime variable). This is all done with the help of CustomMovement.
Events 17 to 23 are slightly tricky. Initially it detects a collision between a bullet and an asteroid, uses hitAsteroid to make it unique (could have used Pick instance here). However it is slightly tricky because when you hit the non-smallest asteroids they split into two. What I do is to shrink the asteroid hit, and send it off in a new direction, and create another one (in 21) and in 24, when it finds this newly created asteroid (currentSize is -1) it fills in the details saved in the variables about how big it is and what direction it is going in.
Event 22 just destroys the smallest (size = 3) asteroid , and 23 forces a restart of the layout with the next level if all the asteroids are dead.
Event 25 onwards drives the UFO - there are two times for this timer, how long it takes the first time, and how long it takes subsequent times. If it has timed out, and there isn't one already, it creats a UFO, with a 50/50 chance of a large or small one (small ones are quicker and fire accurately). The UFO firing is done in 28, and the bullet is initially aimed at the player, 29 checks the instance variable in the UFO to see if it is a big one, and if it is randomises that angle.
Events 30 through 33 check for collisions with the player (hitting a UFO, UFO Bullet or Asteroid) and then if this has happened it restarts, and handles the restart, displaying the level number (35)or Game Over 36, when that Game Over fades out in 37 the game goes back to the high score table.
Finally 38 and 39 update the score and lives display in a function.
This isn't bad but it is a bit messy. I've winged it to date, from now on (Game #6 is Jetpac, a Sinclair Spectrum game) a little more consideration will go into the design, I shall use event groups and so on.
Wednesday, 3 June 2015
Preview of Game#5 : Asteroids
A preview of Game # 5 which is Asteroids. The game itself is pretty much done and dusted, though I may add something resembling a proper high score table. At the moment it's like Galaxians, it just has one layout which plays the whole game.
No code or game here, tomorrow, maybe ;-)
No code or game here, tomorrow, maybe ;-)
Game#4 : Galaxians
I'm back. I've fixed the bug (see later) and I've also uploaded the new .capx to the github account.
As an additional (err....) treat, I've uploaded a built version of the game via ftp, so you can now play it by clicking on this link, keyboards only :)
As this is a bit longer, I'll talk through it bit by bit.
The first bit is relatively simple, it creates the slowly scrolling star backgrounds, which are stars with a setColor effect, so they ripple through colours. They are set to wrap so they keep going round and round.
This is just a single layout, for simplicity, so Event 4 sets everything up on startup and disables firing.
Event 5 enables firing when the "Get Ready" has faded out.
Events 6 through 8 make the galaxian "fleet" move left to right, flipping whenever they hit the edge (nearly) hence the X < 16 or X > LayoutWidth-16
The next section deals with the swooping down bit, where aliens detach from the main fleet. This is done using a timer.(9 onwards). I didn't realise here that timers could be random, so you could have a repeating timer with a random timeout rather than what I do here, fire it once and keep firing it when it fires :)
LaunchDirection here sets whether it attacks left (-1) or right (1) as the ships attack diagonally. 11 picks a ship that attacks.
There is a trick ; as you can see from the picture, aliens can also attack in groups of up to four. To make a whole group of four attack at once, the LaunchCollider is used - this is created and looks like an upside down yellow "T". Collision with this, in 14, sets the "shouldLaunchAttack" flag for each of the ships the LaunchCollider covers.
Event 15 checks to see if any ships have their "shouldLaunchAttack" flag set, and if so they are put into "isAttacking" mode (e.g. they will come down the screen). The bullet is disabled (the bullet is used to do the left/right movement) and the attack direction is stored. Finally it makes a screechy attacking noise.
The attacking is continued in 16 and 17, where it moves according to the attack direction and the speed scalar (how fast the game is running) and it reverses direction if it goes off either edge.
Event 18 is a bit of a cheat. In real Galaxians, if an attacker drops off the bottom, it comes back on the top, dropping down into position. Here, I've faded it into the correct position and set the instance variables/bullet enabling accordingly.
Ah, now how does it know where to go. Well this is what the marker is for. It is a red X at the top left of the "alien pack". The aliens position relative to this is stored in startX and startY (in Event 8) and even if the whole alien pack moves, this relative position to the red "X" (you can see it in the CapX) tells it where to go back to.
This (and the LaunchCollider) are the two tricks that make Galaxians operate. The use of the 'shouldLaunchAttack' instance variable is quite useful too, as it allows more than one action to cause an attack - it can be done individually on the random choice, or through collision with the LaunchCollider.
Beyond that it is fairly straightforwards. Players and Aliens can fire bullets (19-32) and if the player is hit, then he loses a life and the game is over if there are no lives left.
The bug was in Events 26 and 27. Originally the Condition 26 (hitting ships or enemy bullet) fired Event 28 (reduce lives). If the player got hit by more than one thing at the same time, more than one life was lost - it was possible to lose all three lives in the one 'hit'. Using the "playerHit" global works round this - it only occurs once. I was going to use Pick 0th Instance but don't think this works with the "or"
It still has faults ; the speed adjustment stuff doesn't work very well and doesn't reset properly :) Really, I need to move all this into a function, it gets rather fast rather fast.
As an additional (err....) treat, I've uploaded a built version of the game via ftp, so you can now play it by clicking on this link, keyboards only :)
As this is a bit longer, I'll talk through it bit by bit.
The first bit is relatively simple, it creates the slowly scrolling star backgrounds, which are stars with a setColor effect, so they ripple through colours. They are set to wrap so they keep going round and round.
This is just a single layout, for simplicity, so Event 4 sets everything up on startup and disables firing.
Event 5 enables firing when the "Get Ready" has faded out.
Events 6 through 8 make the galaxian "fleet" move left to right, flipping whenever they hit the edge (nearly) hence the X < 16 or X > LayoutWidth-16
The next section deals with the swooping down bit, where aliens detach from the main fleet. This is done using a timer.(9 onwards). I didn't realise here that timers could be random, so you could have a repeating timer with a random timeout rather than what I do here, fire it once and keep firing it when it fires :)
LaunchDirection here sets whether it attacks left (-1) or right (1) as the ships attack diagonally. 11 picks a ship that attacks.
There is a trick ; as you can see from the picture, aliens can also attack in groups of up to four. To make a whole group of four attack at once, the LaunchCollider is used - this is created and looks like an upside down yellow "T". Collision with this, in 14, sets the "shouldLaunchAttack" flag for each of the ships the LaunchCollider covers.
Event 15 checks to see if any ships have their "shouldLaunchAttack" flag set, and if so they are put into "isAttacking" mode (e.g. they will come down the screen). The bullet is disabled (the bullet is used to do the left/right movement) and the attack direction is stored. Finally it makes a screechy attacking noise.
The attacking is continued in 16 and 17, where it moves according to the attack direction and the speed scalar (how fast the game is running) and it reverses direction if it goes off either edge.
Event 18 is a bit of a cheat. In real Galaxians, if an attacker drops off the bottom, it comes back on the top, dropping down into position. Here, I've faded it into the correct position and set the instance variables/bullet enabling accordingly.
Ah, now how does it know where to go. Well this is what the marker is for. It is a red X at the top left of the "alien pack". The aliens position relative to this is stored in startX and startY (in Event 8) and even if the whole alien pack moves, this relative position to the red "X" (you can see it in the CapX) tells it where to go back to.
This (and the LaunchCollider) are the two tricks that make Galaxians operate. The use of the 'shouldLaunchAttack' instance variable is quite useful too, as it allows more than one action to cause an attack - it can be done individually on the random choice, or through collision with the LaunchCollider.
Beyond that it is fairly straightforwards. Players and Aliens can fire bullets (19-32) and if the player is hit, then he loses a life and the game is over if there are no lives left.
The bug was in Events 26 and 27. Originally the Condition 26 (hitting ships or enemy bullet) fired Event 28 (reduce lives). If the player got hit by more than one thing at the same time, more than one life was lost - it was possible to lose all three lives in the one 'hit'. Using the "playerHit" global works round this - it only occurs once. I was going to use Pick 0th Instance but don't think this works with the "or"
It still has faults ; the speed adjustment stuff doesn't work very well and doesn't reset properly :) Really, I need to move all this into a function, it gets rather fast rather fast.
Subscribe to:
Posts (Atom)