Game Design 2 - Projectiles
Implementing a variation of projectiles.
Written the by Thomas Cairns.
Introduction
This post is part four of a five part long series regarding the development of game for a school project. For more details about the project please read the previous posts.
Previous posts:
- Game Design 2 - Movement Patterns // 23rd Feb
- Game Design 2 - State Manager // 16th Feb
- Game Design 2 - Bounce Mechanic // 8th Feb
Tech Heavy
The posts for this project so far have been about technical problems, approaches and solutions. They are topics that I love, read about all the time and are of course what I want to share as well. With that said I still want all my posts to be as approachable as possible because I want to share it with as many as I can, so I try to explain concepts and give references. I have tried to do so this time as well but there is the discussion about different solution where I use terminology and concepts that requires previous knowledge and would be entire posts of their own if explained.
Task
Recently I have been working with implementing different projectile types and I would like to share what I have learned while trying to create behaviour modifiers for our scripts in Unity. Before I proceed with the details about each projectile let me first explain the core projectile behaviour.
Projectile Behaviour
For the game concept we are working on the player doesn't have the ability to fire any projectiles, rather the enemies fire all projectiles. The player instead has a shield that can deflect/bounce projectiles back towards enemies and destroy them that way. When a projectile is deflected it will change ownership, meaning that a projectile can either be owned by the enemies or by the player. Either side aren't effected by projectiles that they own.
As an extension of the mechanic above, the player and enemies are locked within a box making it possible for projectiles to bounce against the boundary. The idea is to create a field where projectiles are bouncing all over the place so the player will have to dodge incoming projectiles while trying to find good angles to deflect them back towards the enemies.
The basic projectile behaves according to the information above and they just travel in a straight path until they collide with something that makes them bounce.
Variations
During our pre-production we thought about three different projectile type we thought would change the behaviour of the player while at the same time not straying to far from the bounce mechanic.
Linked
First up is a very simple variation that links two basic projectiles together in order to create a larger projectile body. The idea is also to create a bounce that will behave a bit different as one projectile will bring the other into the wall as well and make them deflect in a different angle.
Split
Travels in a straight path just like a basic projectile. The difference is that on a impact the projectile will split into two new projectiles that bounce out at 45 degrees of each side of the original projectile while the original projectile is removed. Note that the angle and number of projectiles are not fully established yet, these are just what we are going with for now.
Homing
These projectiles will home towards a target, meaning that its trajectory will change even though it hasn't collided with anything. When a enemy fires the projectile it will home towards the player. However if the player deflects a homing projectile it will change ownership and lock onto the closest enemy within range.
Implementation Process
We already had the basic projectile behaviour established and implemented so my task become figuring out how to attach a modification while still making it easy to maintain possible changes to the core. Let me bring you through my thought process:
- Think about a options
- Discover a con of the first option
- Think of an alternative option
- Discover a con with the second option
- Move back to thinking that the first option has a less severe con then the second
- Discover another con with the first option
- Think of a third option
- Discover a con with the third option and there my be the same con in the second option by not the first
- Get confused by which option has what pro/con
- Do a mental table flip
Really got lost in my thoughts along the way so I had to open up a text editor and start documenting each option. The end result became a table of comparison.
Inheritance | Variable | Component | |
---|---|---|---|
Multiple behaviours at once | No | Yes | Yes* |
Can be changed at runtime | No | Yes | Yes |
Part of the Unity call chain | Yes | No | Yes |
Execution order is stable | Yes | No | Yes* |
Will execute at the same conditions as base | Yes | Yes | No* |
Result
The component option doesn't really have a pitch perfect solution for anything but has a work around for everything instead, and since Unity objects are based on components it seems like the natural course of action.
The Component Option
I will try to explain in more detail what each header in the table above actually mean, but I will only go through it from the view of the component option. I will try to answer the three following questions for each part: what does the title/feature actually mean, what is the reason for wanting the specific feature and what does the yes/no/asterisk mean for the component system.
Multiple behaviours at once
The question is, "Can a projectile have different behaviours at once?". The reason for me to want this features is to make it possible to reuse script in order to create new and more advance projectile without having to write more scripts but instead simply allow them to be combined.
The answer for the component system is a yes, however in order to do it an execution order of scripts have to be established, more information about this will follow below in the section.
Can be changed at runtime
The question is, "Can the behaviour of projectile be changed at runtime, after a projectile has been created?". The reason I wanted this feature was in order to allow for projectiles to have a fall back behaviour. For example the homing missile needs to be able to handle cases where its target is destroyed in advance by another projectile. Then it would be very convenient if it could change behaviour to maybe circulate until a new target appears or that it become a basic projectile and starts travelling in a straight path instead.
The answer is a resounding, yes. The component system in Unity is built to handle components being added and removed from an object at runtime. There is also the option to turn component off and on temporarily instead of adding or removing them completely.
Part of the Unity call chain
The question is, "Can the script be of the type MonoBehaviour and be part of the existing call chain through that?". The reason for this feature is purely convenience based because it can be substituted since in my case we have a base script to work from. However being able to utilize the standard library and have the basic call functions is nothing to take lightly.
The answer is also a resounding yes here, because any script that needs to be component has to inherit from the MonoBehaviour-class.
Execution order is stable
The question, "Is the execution order consistent?". This feature is real deal breaker because in this case I need to know that the base behaviour of the projectile has been executed before any other behaviour takes place.
The answer is both yes and no for the component system. Let's start with the no-part, the reason is that the Unity component system executes components in an object in an arbitrary order. For our case that would mean that sometime the specific type script could potentially be called before the base. However Unity exposes a system that allows us to specify relative execution order of certain components, which means the answer turn to a yes if we always need the exact same order for all objects.
Will execute at the same conditions as base
The question, "Will the special behaviour only execute during the same conditions as the base behaviour?". The reason for this is that the bounce of the base doesn't always execute even though a collision takes place.
The answer here is no. Because every method of the MonoBehaviour is part of the Unity call chain as we already talked about previously, so there can't be any conditional dependencies from a script to another without creating a strong coupling. Strong coupling is also how I worked around this problem for now. The projectile base behaviour looks for a component that is type specific and calls an exposed method when appropriate.
Conclusion
In order to sum it all up , something that seemed trivial became a bit more complex then expected while searching for possible solutions and then simple again after settling for a specific option. I feel that I have learned a lot about how Unity intends scripting to work and what workarounds they have created for common problems.
Random Reflection
While writing this post it really hit me that we really haven't tested any of our ideas except for the very core so far. I wouldn't say that it's a bad thing that we have focused on getting the core working however I find it to be potentially dangerous that we haven't expanded and test everything, even in their simplest forms, because discovering results of the variations might potentially change our opinions. As an arbitrary example testing the homing projectile might have made it so we wanted to focus more on the chase and timing. Another arbitrary example could be that the slit projectiles make us interested in the exponential growth of projectile to create a real projectile maze.