Here we are, at last. At the last article about this journey. This is not going to be a tutorial, as the title suggests.
Many things have happened since I started, both on my repo and in my life. Trying and building this game was an excellent way of learning about Unity, any day something more.
And as I was learning, whether willingly or by accident, the game itself changed and evolved with me, going through many reworks and metamorphoses.
We started with blocky sprites, acting as player, enemy and laser and arrived to homing torpedoes, unique boss fight and explosion capable of shaking your monitor… well almost.
I added many features without writing tutorials about them, as they flashed through my mind more quickly than my fingers can type. If you’re curious about them, you can always check my repo, and if needed reach out to me to ask about them or ask for a bonus tutorial.
Now I’m going to list all the feature, in a medium detailed way, grouped by topics. I used a lot the Component Design Pattern, many short scripts each of them in charge of one feature.
This wicked scripts uses the powers of an orthographic camera, at each frame detects the size of the effectively visible area and translate them to world size measures that can be used as bounds for other objects movement. I suggest to have a look, it is a couple of operations more each frame, but it allows to zoom in and out with the camera and have the bounds updated accordingly. Proud of it.
There is an animator component on the camera, and a shake animation. Basically it is really irregular out-of-phase oscillation of x and y camera coordinates that go back to 0 at the end. This animation is triggerable so I can choose when the camera shakes: for example it shakes on asteroid or enemy explosion but not on torpedo one.
The object is a prefab (so I can reuse it in different levels). Basically a 2D sprite pumped with other components such as audio sources, collider, rigid body and a bunch of scripts.
This is the core script, it manages lives, score and kills counting, collision with other damaging objects and so on. It also represent a gateway for all other script components that can rapidly access any other through it. Here is also managed the damage VFX that enables an animated sprite on the player wing, according to the side from which the damage come.
Player movement is managed by two scripts: one for input, one for movement itself. The first one is simply in charge of collecting inputs from keyboard (also for other purposes) while the second one translates those inputs into world movement, through transform translation.
Movement script also forbids the player to move outside some boundaries and applies speed effects.
A separated script manages the shooting, of any kind of weapon available to the player. It talks with the input manager and instantiate objects according to the particular current projectile. Shooting will deplete ammo resources. A firing rate is provided and can be modified by the collection of power ups.
The Player game object has a child, the thruster. It is just a VFX for its engine fire but the scripts attached to it manages its visual response corresponding to a simple acceleration, input driven, or a big one when a power up is collected. This visual response affects both the size and colour of the thruster and the depletion/recharge of a bar in the UI.
Collection of Power Ups
The script dedicated to collection manages al interaction with the power up game objects, from the trigger detection to the application of specific effects. If a key is pressed, all collectables on scene will be quickly attracted to the player at cost: reduction in gained score, the more you press the more you lose.
A simple script makes all audio sources accessible through a dictionary, so basically a key-value map, where the key is a string (such as “laser” or “explosion”) and the value is the associated audio source.
2D sprite object made prefab.
As for the player, the enemies also have a similar core script managing lives, score, damages etc. Here is also added sound management.
Same as for the player but without relying on human input. The base movement is a vertical top down. Once the bottom side is trespassed, enemies are recycled (until they are told otherwise) and respawn at top, but with different x position. Also other movement types are allowed, mostly based on random number extraction (they have a given probability to occur): x-wise oscillation, dodging player attacks, ramming into player ships.
Here is one of the part where I learnt the most. I started using Raycast in order to detect, in loops, if the player was in front of the enemy and shoot it (if rolling dice allows it). Shortly after, I wanted the same logic to work for detection of player projectiles and a simple ray was obviously not enough: I reworked everything to use Boxcast, which is (funny) nothing more than the shooting of a box of given dimension, in a given direction. In this way I was sure to detect the tiny lasers and roll the dice for a dodge move.
Based on the targeting system, the shooting system will shoot a couple of lasers only if the player is on the line of fire and if a probability throw is won. Since enemy death takes time (explosion clip length) the object will not be removed immediately, and during that dying time it can collide and shoot and be a pain in the ass. I solved the problem disabling the collider component on death, and make the enemy shoot on if it is enabled.
The fighter is a new enemy type with different visuals, it carries the same scripts as the enemy but overrides some values and adds some features. The fighter can shoot also torpedoes and can proceed top-down while going back and forth.
The boss deserves an own section. In it I, again, reused some of the enemy scripts buts also a few ones for boss-specific purposes.
As did before, here is an override of simple enemy values, plus a check on live to activate a final stage in which the boss turns red and shoots even more.
The boss doesn’t move. Once it enters the scene, it stays there and shoots everything it got at you. This script manages the entering: the boss will slowly slide top-down till it reaches its position. While doing so it makes sure that you cannot damage it until its final position is reached and the real fight begins.
Top-down laser shooting is managed by simple enemy scripts, but the boss can shoot also torpedoes and side lasers. Torpedoes are launched 2 at a time from random position (among 4 possibilities) at a given rate and speed (which increase in the final stage). Side laser shooting is treated as top-down, using the aiming Boxcast system with different origin and direction.
A simple script with all the code necessary to trigger the explosion animation. It is used by all exploding objects. Apart from triggering the animation, the collider is disabled and the gameobject is set to be destroyed at the end of the animation.
Used by player, enemy and fighter, this script ensures a tilt of the ship while moving horizontally. The tilt is carried out with a rotation around y axis. This is the feature I like less, follow me here: local rotation around y axis actually occurs around a y axis passing through the origin of the parent object. In absence of the latter, the y axis will nothing more than the world y axis, passing through the origin of world space. This means that even if I use local rotation on the player, since it is not child to anything, it will rotate in the world space and not around “itself”, thus changing also its z position. There are two way of solving this:
- Detach the sprite component from the object, attach it to another which will be child of the main object, so the local rotation will be referred to the parent transform (good way)
- Add a new Position Constraint component, forcing the z position to be always zero (an easier, worse way which I preferred due to lack of time).
A script containing all level-related values, such as enemy stats, level completion requirements etc. Here are stored spawning rates also and methods that check if the level is complete: a level is complete if a given number of enemies is killed.
The spawning of enemies, asteroids and powerups is managed here. Usually there is a range of spawning rates so the next spawn will occur after a random time, extracted in that range. There are also master switches that turn off the spawning and methods that allow it only if the player got rid of all initial asteroids.
Similar to level manager, the boss manager appears only the final level and its job is to manage the boss entering. It will clear the scene of all remaining enemies when it’s boss time, disabling their interaction and respawn-at-top feature and letting them fly down until they are removed from game.
Asteroids are simple 2D objects with a collider and a rigid body. They will interact as triggers with enemies and player, damaging them but as colliders with other asteroids: having also a 2D physical material, unity will treat them as real object (with no gravity) making them realistically bounce off each other. Their movement is actually managed through rigid body properties such as velocity and angular velocity instead of transform translations/rotations.
Basically I mean collectable objects, since not all of them are power ups: there are ammo, 1 up, weapons, fire rate power down, shields… They have sounds, can be collected or destroyed. When collected (trigger interaction) they will apply an effect on the player, carrying info such has effect magnitude and duration in time. The object tag will be used to select the specific effect.
This simple power up will give the player ability to shoot 3 parallel lasers at a time. This is a simple object with 3 laser children.
Shield is actually cool. When activate, it will protect the player from a couple of hits and, in the meantime, a UI bar will show its countdown. Shields act as rigid bodies: asteroid will bounce off them and the player can willingly use shields to push them and change their motion.
Well, it increases speed. It also applies a thruster VFX and lock the UI bar until the effect is off.
Do I need to explain?
The player can launch torpedoes. Homing ones. This means that the torpedo, after an initial sprint, searches for the closest target and pursues it until it reaches it, or the countdown is over and it self-destroys, or a closer target comes into play. Torpedoes can also be set as “enemy”, in that case they will pursue the player.
This is actually a power down: the fire rate slows down until the effect is on.
Actually the funniest weapon. Starting from laser sprite, once you shoot it, it will scale up until it reaches the size of a huge beam and then scales down, all using an animation. The box collider scales with the objects and applies damage over time while “on stay” with other triggers.
Post Processing Effects
A few methods to programmatically tweak the post process, for example the one the increases bloom and then fade it back to normal, used when an explosion occurs.
Conclusions, what’s next?
If you reached this point, you now have a very clear idea of what’s inside this little game, what is its meaning and purpose and journey it went through. I learnt a lot while building it, and I hope you found something useful too.
I am happy with it? Yes.
I am 100% happy with it? No.
It can be improved, anything can. I already have ideas but I don’t think I will apply it in the near future: this was intended as a learning journey, and it was the first big step of it. It’s time for the next one!
But it is certainly useful to state where the gam can be improved, as a mind and a honesty exercise. I’m going to leave you with a list of possible improvement, but I will see you on the next tutorial, about something totally new and different!
- Excessive use of
GameObject.Find()method. On a small game like this may not be of great impact, but the more I study Unity, the more I understand it is a performance demanding operations. So, wherever is possible it could be replace with referenced objects set in the inspector or, when necessary, through the use of static or singleton objects (think of the level manager…).
- Rework the tilt feature as explained before.
- Implement a Max Score counter in the main menu.
- Rethink the value balance.
- Add boss music.
- Make the level transition smoother.
- Do you have other ideas?