Prototype 2D - Game Development HERTS
Full list of Changelog can be found here:
Added Final Assistant Boss
The final stage did not include a procedural generated level. Instead, it entailed a pre-built level.
Added assistant boss state behavior and narrative text which follows once the assistant has been defeated.
The narrative was coded out using a coroutine which involves waiting for a few seconds before changing the text.
Added future level
I wanted a vaporwave aesthetic for this particular level as this is the dimension where the time traveler came from. Vapor wave includes pinkish tints and cyperpunk elements. Example picture below.
Added machine and liquid canister props
The liquid canister emits a poison gas when destroyed. Player and entities standing inside the gas cloud will take damage over time.
Added Turret enemy
Turrets were the given choice as they were used for self defence. If the player is too close to the turret, the turret will go into their attack state and start firing a rapid succession of projectiles. If the player moves away from the turret detection radius, they will enter their idle mode and retract.
Added Robot Enemy
These robots possess a powerful sniper rifle each. When a player is detected, a red laser beam will appear and track the player. The robot will also change its color to red. After 3 seconds, the robot will fire a fast moving projectile. This particular enemy serves to make the game much more challenging as the enemy sometimes could be out of sight and it is up to the player to dodge these projectiles and destroy these enemies.
Added enemy spawn prevention radius
I ran a playtest with some of my friends and i realized this game breaking problem. Enemies sometimes spawned at the same position as the player and this caused the player to take damage unfairly. To remedy this issue, I ran a check on the player spawn position by drawing a physics circle overlap. Player detection was a public float in which i could control the radius of the circle.
The circle collider checks through all entities(wall and floor prefabs) and collisions which resulted in some lag. To prevent that, I gave it some conditions to check only for enemies.
if(hitColliders[i].tag == ("Enemies") && hitColliders[i]
To prevent the circle from detecting the player,
hitColliders[i] != boxCol && hitColliders[i] != edgeCol
If enemies are detected, destroy them. Hence, this created some breathable space for the player at the start of each generated scene.
Added in game timer (Source: https://www.youtube.com/watch?v=ixlIaMuhkbM)
I felt like the players needed a sense of how long they have played the game. As a result, I included a timer for the players to keep track of their playing time.
In Addition, I wanted to further the narrative concept by making the player lose certain amount of time whenever he fires his weapon. This is still in development.
Adjusted health pickups to drop only when player has lost health
Prior to this, health pickups were dropped by enemies even when the player had full health. This could be a potential exploit for the player to "conserve" health pickups and using them only when they had taken damage. To fix this, all I simply had to to do was to enable health drops only when the player's health is not full.
Further development could include increasing the drop rate of health pickups according to how much health the player has left.
Object Pooling (Source: https://www.raywenderlich.com/847-object-pooling-in-unity)
The most important part of game development is optimization. Optimization refers to the cleaning up of junk codes and making the game take up minimal yet necessary CPU memory.
Initially the game was running fine with little to no lag. However as the game progresses, more enemies are spawned which results in more projectiles and being created. Hence, the process of creating(instantiating) and destroying are two of the most intensive operations in the game. To help lighten the load on the memory, we make use of object pooling. This refers to pre-instantiating gameobjects which are going to be used in the scene itself. For example, In the forest level, we know that bandits are going to fire projectiles. As a result, I would need to create an object pooler to instantiate these projectile gameobjects at the start of the scene. During the scene, the projectiles that are supposed to be created and destroyed are now being replaced by the activating and deactivating of the pre instantiated gameobject which is now the projectile.
In this case, I assigned 30 enemyskeleton projectiles to be pre-instantiated. We can see the highlighted projectile represents an activated version of that particular gameobject.
As a result of object pooling, there are lesser instantiate and destroy executions which results in a smoother gameplay experience.
Added Boss CutScenes
This is to preempt players that the current stage they are in is a Boss Stage rather than just going in blindly and encountering the boss.
Added Yakuza Boss
Yakuza Boss is an advanced version of the mafia enemy. He has a minigun that fires a rapid succession of projectiles in the shooting state. Using a random factor, the boss may have a chance to chase the player and if the player gets within the range of the boss''s katanta, he will swing his weapon, dealing 2 damage to the player.
The minigun settings:
The low cooldown rate allows projectiles to be triggered rapidly.
Added Urban District Level
I wanted this level to portray a modern urban town to give a stark difference from the forest level. This level consists of mafia enemies.
The mafia has both ranged and melee attacks. They fire a burst of 5 projectiles and if the player gets too close to the mafia, it will instantiate a melee attack for a split second which deals 2 damage to the player.
Added Lamp Post and Car Props
Cars explode upon taking 4 damage. The explosion will damage both enemies and players, dealing 8 damage making it instant-kill. Hence this is to make the player more cautious about his/her surroundings and to use these props to their advantage in killing enemies.
Added Portal Core
Portal Cores were added to facilitate the narrative storytelling in the game. Portal cores are items that are being dropped by Bosses. When picked up, they will activate the Level portal which will then transport the player into the future (next level).
Added Level Portal
Instead of using the regular pink stage portal, I decided to introduce a different type of portal to differentiate between stage and level transitions.
Added Bandit Boss Sprites and Animation
Added States for Boss Bandit - Chase player and Shoot, Idle & Bush which spawns smaller enemy bandits. The issue with this set up is that due to the fact that the state names could not be read, I could not use the state machine to execute commands. To bypass that, I checked for the animator bools and used it as rules to execute commands.
The Boss Bandit consists of the bush state and chase state. In the bush state, the boss bandit has a 20% chance of spawning smaller bandits. This state serves as a vulnerable state for the player to attack the boss as the chase states will result in the boss bandit chasing the player and firing 5 high velocity projectiles. The Boss Bandit has a walking speed of 3000, making it slightly faster than the player.
Player Starting Invincibility
Added player invincibility of 1.5 seconds when the scene loads. This is to prevent cheap hits on the player (enemy spawns in the same location as the player and the player takes a hit).
Added Forest Level
The level generator was designed to interchange different prefabs. As such this made it easier to create and generate multiple scenes.
Added the bandit enemy. They fire two projectiles with each burst.
I wanted a surprise element in which the bandits are able to spawn randomly from bushes in the environment.
How it works:
I created a bush prop which consists of a box collider. It constantly checks whether the player enters the collider. If it does, the bush prop game object will destroy itself and spawn a bandit enemy.
Added Enemy Boss Snake
This was relatively fun to make as it involves creating transitions states (idle, walk, spit) for the snake boss. This was one of the main reasons as to why I wanted to create a a game like this - to create pattern specific behavior that gets increasingly harder.
How it works?
When the boss is initialized, it goes into the spawn state. In which, there will be a 5 second timer that activates itself. Once the timer reaches 0, the spawn state will transition into normal state in which the snake will start chasing the player and shooting a spread of five projectiles.
void Normal_Update() refers to the continuous tick when the snake is in the normal state.
These are the codes/instructions that are continuously executed in the normal state.
In the normal state, the snake basically tries to locate the player by checking if there is line of sight with the player. This is done using a raycast function which fires a ray and check to see if the collider it hits is tagged as the Player. If yes, then the snake will move towards the player's position by the speed given.
Added Loading Screen
I decided to add the loading screen because it gives some information as to what stage the player is currently in as well as serves as a short break between each stage.
Subsequently, I added in the year (2000 B.C.) to help facilitate the narrative of the game.
Added Main Menu
It's not really a game without a main menu.
I was thinking if i could remove the play button and use this main menu stage as a sort of introductory stage in which the player could explore the controls and interact with the weapons.
Something like this.
Added Audio Manager
An issue with scene changing games like this is that there is a need to have a control script that persists through the scenes. An audio manager was created to manage sounds throughout the game.
This code allows the gameobject which in this case is the audio manager to not get destroyed when the next scene loads as by default, all gameobjects without these code will get destroyed.
The Audio Manager was set to an public instance so that i can call it from any other script to trigger sounds.
The Audio Manager consists of different cyberpunk soundtracks that can be played or stopped accordingly.
Portals are entities that spawn whenever the player has killed all the enemies.
How it works?
There are two parts to the mechanics of the Portal.
The Portal script consists of a enemy checker that checks to see if enemies are still alive at every half a second.
if enemies are not alive, then the script will trigger a camera shake and plays the portal open animation.
The next part was more tricky to solve. The idea was that the portal needs to locate the last position of the last enemy that is alive. If you think about this, if all enemies were killed, the portal does not have any position to spawn with. To solve this, I ran a check to see if there is only one enemy remaining, the portal will spawn. However while spawning, it will be deactivated. Only when that last enemy is killed, only will then the portal activate itself at that spawn position.
To make sure no more than one portal spawn per stage, I used a counter to check for that. If the counter is less than one, the portal can be spawned.
I used a shader material to apply onto the portal, giving it a pink flickering look.
Particle effect was also added to the portal.
Added ammo Drops from mobs
In order to facilitate gameplay, mobs will have a chance to drop ammo pickups. These pickups contain +35 ammo.
Updated interaction with chest. Player moves over chest and automatically opens instead of interacting and pressing e to open. This is because in a fast paced game, a button press could potentially distract the player and cause them to die.
Switched UI Text to TextMeshPro
Text mesh pro is a free Unity Addon which provides better text display and customizations.
Fixed Player spawning and colliding with walls by checking for a free area space by using edge colliders Added wall depth Updated player running animation for a better feel Fixed sorting layers Increased enemy skeleton walk speed and random angle shooting Adjusted camera damp time from 0.05 to 0.1 for less quick motion
4:3 Screen Resolution Screen Resolution capped at 4:3 to allow player to have equal sight from top, bottom, left and right.
Looking at 16:9 inch, we notice that the width is relative longer as compared to the height. Imagine playing the top down shooter game, we are able to see more of what's in front and behind of the player as compared to the top and bottom. This serves as a disadvantage to the player because they are limited by the height of the screen. To balance this out, we use a 4:3 ratio to provide a more even viewing platform for the player.
Sound Effects Source: Nuclear Throne sound effects repository. https://clyp.it/hvlscdkd
new Cyberpunk soundtracks from https://assetstore.unity.com/packages/audio/music/electronic/synthwave-001-cyberpunk-music-asset-pack-84025 Added new Snake enemy
Added props to populate the environment. These props are interactive and can be destroyed by the player. The idea of props is to act as a barrier from projectiles or obstruct the player when playing the game. In addition, as the level progresses, the props vary from time to time and possesses different characteristics.
Added Ammo Counter
How it works?
Initialize the counter to start off at zero when the player picks up a weapon.
Each time the player successfully fires a projectile, the counter increments by 1.
Ammo counter number = Total ammo count (which relates to how much initial ammo the player has) minus the incremental counter.
(Originally it was done in the UI Canvas but the issue was that when the current weapon is destroyed, the script in the Canvas cannot pick up the weapon script anymore. Hence the solution was to incorporate the ammo count in the weapon script itself and get reference from the Ammo Counter script in the UI Canvas which is a better workaround.)
An issue that popped up was that the player was still able to fire their weapon despite the ammo count reaching zero. To fix this, all we had to do was to implement a condition statement to check if that the ammo count was smaller or equals to zero, do not allow the player to fire the weapon.
Added chest prop
Each level now spawns two chests. These chests contain random weapon pickups which are essential or beneficial for the player's progression towards further levels.
Adding of enemies to populate the map.
I used the skeleton and slime enemy as a placeholder (came with the asset pack) for enemy spawning.
I wanted to create a system that randomly spawns enemies based on the chance to spawn them and from a list of array.
The first problem that i encountered was that the enemies were spawning outside of the map boundaries. To fix that, I set the limits in which enemies could spawn. This fixed the issue of enemies spawning outside of the map limit but they were still spawning inside of the walls which is not what i want.
As such, I ran a forloop to check through each tile to see whether if it is a floor or shadow tile. If it is, then allow enemy to spawn with a random chance, else, do not allow enemy to spawn.
Before running the forloop.
After running the forloop.
Added procedural map generation (Source: Six Dot Studios - Nuclear Throne Procedural Map I generation https://www.youtube.com/watch?v=I74I_MhZIK8&t=2s)
Making use of Six Dot Sutdios's Procedural Generation Algorithm, I adjusted the code to fit my needs. This included spacing in between the walls, the size of the generated walls, prefabs and entities generation, Portal Spawning and player detection.
A series of randomly generated maps each time the scene restarts.
The idea behind procedural generation is to randomly generate the stage each time the player restarts or proceeds to the next level. Procgen reduces the likelihood of the player getting bored as well as memorizing the level.
How it works?
Firstly, we set limits to how big or small we want to generate the map.
int roomHeight, roomWidth;
Vector2 roomSizeWorldUnits = new Vector2(700, 700);
So this means it will generate a map within 700 by 700 tile boundary.
The best part of procedural generation is that it reduces time taken to create a new level. This means that we can create a system which you can swap out or interchange the assets freely.
To do that, we create multiple public GameObjects prefabs and then we assign them to the procedural generator.
public GameObject wallObj, wall1Obj, wall2Obj, wallShadow, floorObj, gravelObj, portalObj, cactusProp, windProp, rockProp, chestProp;
To create floors, we create an entity called a walker whose job is to "walk" randomly through the map boundary and spawns different prefabs (floor,wall, shadow etc) based on random chance. I added shadows to help contrast between the floors and walls as they felt to flat and the player would get confused as to whether they are able to walk on that.
This was how it looked like before wall shadows were added.
Added shadows to help give a sense of space.
Added collisions to the wall prefabs by using a box collider 2d. This prevents the player from moving out of the limits and serves as collision for the projectiles.
Faced an issue with the player colliding with the collider due to overlapping when flipping the character. The solution to this (thanks to Alan for pointing it out) was to make sure the player box collider was symmetrical so that it doesnt collide with the wall collider when flipping.
Starter Packs Used:
I have included the sources used from external parties in this blog. The rest of it is mostly my own personal development of the game.
Pixel Top Down Shooter
The shooter pack consists of:
-Pixel perfect collisions support & movement -Pixel Perfect Rendering -Full player movement (Idle, Run, DodgeRoll) -Responsive Controls -Weapons (Automatic, SemiAutomatic & Burst) -Projectiles/Bullets -Mouse Aiming/Shooting -Health System -Currency/Score System -Doors/Gates (Exit) -Destructibles (Crates) -Hazards (Pits and Spikes) -Pickups (Weapons, Coins and Chests) -Simple Enemy AIs (line of sight, shooting, chasing) -Damage On Touch -Customizable Screenshake + Pixel Perfect Smooth Camera Follow -Sprite Flashing with shader -Sprite Outline Shader -Player death with room fade-in/out -Fully working beautiful and professional pixel art tilesets, characters, weapons and all kinds of assets with animations. -Example Scenes created with Unitys built in TileMap & 2D Extras. -Full Documentation in PDF explaining how everything works from the player controller to full level creation and layout guide -Thousands of lines of very clean, well commented, easy to understand code and easily editable code for quick changes in "game feel"/pacing.
Composed by indie music producer Mercurius FM. 7 Original 80s cyberpunk themed synthwave level tracks. +1 title song +1 ending song Includes 2-5 small loops for each song. + 6 music stabs / sound FX for interface and gameplay Every song loops seamlessly 45 tracks in total Professionally mixed and mastered with consideration for game sound effects. High quality lossless 16-bit sample rate, 44.1 khz WAV files Royalty Free Includes: 7 stage tracks 1 title track 1 ending track 6 musical stabs Format: Stereo 16-bit sample rate 44.1 khz WAV files Credits: Music composed, recorded, and produced by Mercurius FM: http://mercuriusfm.com/epkepk
Creation of Assets and Sprites
The first few weeks involved the creation of the sprites needed for the game. Starting off with character design, I wanted something that had vivid colors, something close to neon.
(In color balance, 2019)
The first design was created to get a rough feel of how the game would work. It consists of a boy wearing a mask. I felt that this character did not like up with the narrative as it did not have a time traveler vibe to it.
The second design feels slightly more playful and it has that retro vibe to it. It felt more fitting of the overall theme and I decided that this was going to be the main character.
Subsequently I created the sprite sheet for the idle, walking, falling, rolling and death animation.
Weapon, casing & projectile Sprites
Weapons and projectile sprites were also created. Pistol and assault rifle were the first to be created so as to have a template to work with. Next up I will be working on the environment tile maps.
Creating the environment tiles were slightly tricky as they had to be modular and tileable. I started off with the sand texture by choosing a base color for the fill and adding details by using a darker shade of the chosen color. I had to trial and error a few times before getting the "details" to tile up with each other. One way to test whether the tiles are modular is to stack them in a 3 by 3 grid and make sure it looks visually acceptable.
Once the sand is done, I continued working on the walls. Similarly, the process for creating the walls was similar to the sand but this time round, I chipped the edges of the walls to reduce the blockiness when placed side by side with the sand. My initial thought was to create 9 different blocks (as shown in the figure above) with different patterns and then i could use a code to place the blocks respectively. However I felt it would be easier if i created a block that has four chipped corners and I can just simple generate the block by itself without worrying about the which one out of the nine blocks to generate.