Yesterday, I had an idea of making a simple 2D game in 1 hours, and I wanted to challenge myself to see whether I can actually do such thing or not. It turns about, I could! Yesterday, I made a very simple 2D Archery game. But I wanted the game to be interesting as well, so I decided that I would implement something like Angry Birds. You see, when you pull the bird on the slingshot the game kind of display to you a predicted trajectory of the bird when released and the further you pull the bird, the further it goes. So, lets break down the game into smaller problems and tackle them one by one:
- I want the bow to rotate with the mouse position so we can aim.
2. To make things easy, regardless of whether the player is holder the right click button or not, I want the game to always display a minimum trajectory prediction from the bow. The trajectory should be simulated.
3. If the player holds down the left click, the bow should charge power to shoot the arrow.
4. When the player releases the mouse left button, the arrow should shoot and follow the predicted trajectory.
5. The arrow should trigger an event if it hits a target.
6. A target that is moving up and down as a challenge to the player
7. If the arrow hits the target the following should happen: A) Increase the speed of target movement for added challenge. B) Play a particle effect at target position.
Now, lets start tackling one problem at a time.
- Rotate the bow with the mouse position.
I started by creating a 2D sprite and I did some search online for a cool bow and arrows to use in the game. I then assigned the bow image to the sprite renderer component in the game object and was ready to start coding.
I created a script called Bow, this script will be the main driver for all the bow functions. In the Update() function I started by calling a function CalculateBowRotation(). This function will calculate the rotation of the bow so that it is always pointing towards the mouse position. Here is the function code:
The first need I needed to do is to calculate the mouse position with respect to the world space. As you Input.MousePosition give the position of the mouse in screen space. That will not give an accurate rotation of the bow because its coordinate system is in world space. Once this is done, I calculated the direction of the aim by subtracting the position of the mouse from the position of the bow and then normalize it. When this is done, I can now use the Atan2 function to calculate the new angle of the bow based on the position change of the mouse. When calculating the angle, don’t forget to convert the angle from radiant to degrees as the Atan2 function gives the result in radiant. Finally, just multiply the forward of the bow, because I want to rotate around the Z axis with the newly calculated angle and you got a rotating bow.
2. Calculate and display the trajectory prediction points
This is the hardest part in the entire game; but really, it is not that hard. First of all, we need to have an equation that calculates the position of each point across the trajectory. You can refer to this YouTube video to learn more about the equation that I am about to use.
The first thing I did is that I created a 2D circle with RigidBody2D component in it. Then I created a prefab out of that object. Then what you need to do is to instantiate the points into the game and then position them accordingly. Since we will be placing multiple points, then we need a list or an array to hold the generated points and then we need to instantiate those points at the same position. This should be done in the start function of the Bow script as you want those points to be added to the game once the Bow object starts. The code will look like this:
You can see in figure 2 that we have initialized an array of the prediction ball count and then just looped through it and instantiated prediction balls at the bow position. Now, we need to calculate each point’s position in the predicted trajectory.
In the Update function of the bow script, I created a function called CalculateArrowPrediction(). This function will calculate and place each point in on the predicted trajectory. The function looks like this:
The only trick in this function is just to code the equation; and as you can see all I did is just to calculate each parameter and then plug them all into the equation and update the position of each point in the array. Let me, however, explain the rationale behind the calculation of t. t represents the correlation of the position of the point with respect to time. So basically, I want to say at time T, the point should be at that position. That’s why I am dividing the index of each point with the total length of the array so I can distribute them evenly across the path. Another thing to note in the function is V0 which is the initial velocity. It is calculated with respect to the aim direction. Keep the initial velocity in mind as we will refer later to it in this article. With that, we have plotted the prediction points correctly on the trajectory path.
3. Charge the Bow power when the player holds down the left click
The power of the bow is the initial velocity of the arrow. So basically, we want to increase the initial velocity with time to some maximum velocity if the player holds down the left click. This is done in the Update() function of the Bow script. The code looks like this:
As you can see in figure 4, the Start() function has been updated with a line to initialize the initial velocity with the minimum velocity. In the update function when the left click is held down, the initial velocity in incremented with time and according to a charge speed. Pretty straight forward, right?
This takes us to the next step…
4. Shoot the arrow when the player releases the left click
I created a function called ShootArrow() that instantiates the arrow prefab and just shoots it. This function is called in the Update() function. The code looks like this:
You can see in figure 5 that the implementation is straight forward. In the update function, I check for left button release, if that’s the case, the ShootArrow() function is called and the initial velocity of the arrow in reset to the minimum position. Then, in the ShootArrow() function, the arrow prefab is instantiated and then its velocity is set to the calculated initial velocity so it can move forward. You can also notice that I have implemented a fire rate functionality in order to limit the spamming of arrows in the game.
With that we have a functioning Bow. Let’s move on to the next step!
5. The arrow should trigger an event if it hits a target.
First of all, I created two additional sprites. One for the arrow and another for the target. Both have colliders set as triggers and both have RigidBody2D components. The arrow script should have an OnTriggerEnter2D function that gets called when the arrow hits the target and raise an event so other objects can act accordingly. The code for the arrow script looks like this:
As you can see, I have defined an event called onTargetHit. this event is trigged in the OnTriggerEnter2D function if the arrow hits an object with a tag “Target”. Other objects can subscribe to that event to execute logic based on that event (We will see that later). Once the arrow hits the target, I disable the collider and change the velocity and gravity scale to zero because I want the arrow to stay stationary. Then I destroy the object. If the arrow did not hit anything, then it gets destroyed after 5 seconds from the call in the Start() function. That way, the hierarchy of the game is clean.
One other thing that I did the arrow to make it look better is that I created an additional script called RotateOnDirection. That script will rotate the arrow to align it to the direction it is moving towards. The script looks like this:
As you can see in figure 7, in the Start() function the initial position of the arrow is assigned to the position on instantiation. Then in the update function the direction of movement is calculated. Finally the angle is calculating by the Atan2 function, just like the bow’s rotation.
6. A target moves up and down to challenge the player
For this one to work, I created two empty game objects and made them children to the target. I also made the target sprite object a child of an empty game object that holds the sprite and the two points.
Them I created a script called Target and assigned it to the Target_sprite object to control the movement of the target. In the Update() function of that script a Movement() function is called every frame to move the target between both points. Here is the function code
As you can see in figure 9, we reference both points and have a movement speed. Then I initialized a variable to hold the position in which the target should move towards as point A. In the Update() function then called the movement function which in turn moves the target between point A and point B.
7. Execute actions when the arrow hits the target.
Remember the onTargetHit event that I created in the Arrow script? This is where it comes in handy. What I need to do is to make the Target script subscribe to that event so that if raise, the Target script executes a function that then do some logic based on that. The updated code of the Target script looks like this:
As you can see, I have created a function called TargetHit. This is the function that will be called when the event onTargetHit is raised. You can see that in the Start() function the Target script is subscribing to the event and in the OnDisabled() it is unsubscribing. All what TargetHit() does, is that it plays a particle effect that is a child of the Target_sprite object and increases the movement speed by 1. Now, of course there has to be a maximum speed at the end, otherwise things will get crazy later on.
With that we have a functioning 2D Archery game that can be done in 2 hours.
You can try the game I created by clicking here.