How to make a simple physics-based cannon in Unity/C#

Let’s see how to implement a basic 3D cannon throwing balls with rigidbodies and colliders!

🚀 Find all of my Unity tutorials on Github!

This article is also available on Medium.

This tutorial is available either in video format or in text format – see below 🙂

Get the scripts and assets for this tutorial on my Github 🙂

A quick overview

In this episode, we are going to create a basic physics-based cannon that can shoot cannon balls into some obstacles, and we’ll see how to show the expected trajectory of the projectiles.

We will use Unity colliders and rigidbodies to compute the collisions and a LineRenderer to display the cannon ball trajectory.

Are you ready? Then, let’s dive in! 🙂

Setting up the scene

To begin with, let’s prepare our 3D scene.

The main element will be our cannon. If you want, you can download the model I’ll be using over here, or you can make your own. The 3D object I’m sharing was made in Blender, and exported to the common FBX format.

To import it in Unity, simply copy it to your project assets folder (I usually create a subfolder called “Imports” first, and paste it inside).

To preview your imported object, just drag the file to the scene view or the hierarchy:

By selecting your asset in the “Imports” folder, you can then tweak some import parameters, according to the data you need (or need not) to be retrieved by Unity from the file. Any change you make in this panel will automatically update the preview object in your scene.

For example, here, I want to do a few things:

  • first, in the “Model” tab, I want to make sure that I bake the axis conversion – that’s important because Blender and Unity’s coordinate system are not exactly in sync, so, if you don’t tick this box, you might have some weird rotation on your object by default.

If I bake the axis conversion, I’m back to a zeroed-out transform:

The cannon model before baking the axis conversion (on the left) and after (on the right)

Now you see that, for now, my cannon is not looking to the side as expected (I mean: with its barrel along the world X-axis). Ultimately, we’ll be re-updating the rotation of the cannon and force it to look to the right, so we could leave it as-is; but to insure we get a nice initial rotation in our game, let’s tweak its rotation along the Y axis and set it to 90 degrees.

  • then, in the other “Rig”, “Animation” and “Materials” tabs, I just want to disable everything because I don’t need a rig, nor animations, and I won’t be importing materials from Blender – I will recreate those inside Unity itself.

Something important for our tutorial is that we want the forward axis of our object (in local mode), so the Z blue arrow, to be pointing in the direction of the cannon barrel. So if you’re making your own object, make sure to export and import it properly to have this forward direction nicely set up.

I’ll set up a few basic materials, one for the body of the cannon and another for the wheels:

I can then add a few additional objects in my scene like the ground (with a basic green grass colour) or a quad in the background to put my skybox on. This skybox will be rendered using a custom shader, a very simple script that basically lerps between two colours based on the UVs Y position.

I won’t dive into the details of shaders now – you can download my shader file from Github and add it to your project. Then, just create a new material, assign it this shader and drag the material to your quad:

You should get a basic black to white gradient that you can tweak using the colour parameters in the Inspector to make a nice day sky gradient, like this:

Ok – we’re now finished preparing the scene, and we’re ready to start coding! 🙂

Implementing the cannon logic

What we want to do is have the cannon move and aim at our mouse position while we’re pressing the left mouse button, and then fire when we release this mouse button.

All of this will be handled by a C# script called the CannonManager, that we’ll put on our “Cannon” object.

Now, let’s open this script – we can keep the default Unity methods, Start() and Update(), but I’ll clean up the comments a little:

Having the cannon follow the mouse (if LMB is pressed)

Ok so – first, let’s make the cannon look at our mouse position whenever we press the left mouse button (LMB).

We can store whether we’re currently pressing the mouse or not in a little boolean variable called _pressingMouse. We’ll modify this value in our Update() method.

When we get the mouse down event, with the mouse button of index 0 (that’s the left button), we’ll set this flag to true. When we get the mouse up event, meaning we’re releasing the button, we’ll set the flag back to false:

And then, still in our Update(), we can have a little if-check on this flag: if we’re pressing the mouse, then we want to make the cannon look at the mouse position.

This can be done very easily using two Unity features: first, the quick coordinate transform from screen to world space; second, the LookAt() method of Transforms.

Let’s start by caching the reference to our main camera in a _cam variable. This is a little optimisation trick that avoids constantly recomputing who the main camera is… – because, no, Unity does NOT do this caching for us and actually needs to re-iterate through all tagged objects every time you call Camera.main! This can be long in a big scene and it is actually a pretty common case of slowdowns in Unity game dev, as explained in this amazing Unite talk by Ian Dundore. Here, obviously, it wouldn’t be an issue, but it’s good practice to remember to cache this value, especially when it’s never going to change like in this basic scene 😉

Now that we’ve got our reference to the main camera, we can use its ScreenToWorldPoint() method to convert the current mouse position on the screen to its equivalent in world coordinates. But we also need to make sure that it is on the same Z-plane as the cannon by resetting its Z position to 0. If we don’t re-assign this value, the projected position will be on the camera clip plane; and since my camera is somewhere about -12 on the Z axis, the clip plane is also back there around -12, far away from the cannon that’s at z = 0!

This mousePos Vector3 is the point that we want our cannon to look at while we’re pressing the mouse – we just need to call the transform.LookAt() method on this position to orient the cannon properly:

If you save this and run your game, you’ll see that when you press the left mouse button, the cannon will follow your mouse smoothly and, as soon as you release the button, the cannon will stop and freeze.

Firing cannon balls!

Let’s take care of firing, now! 🙂

This will happen when we release the left mouse button, so in the mouse up event check. In addition to resetting the _pressingMouse flag, we’ll also call a new private method, _Fire():

In this method, we want to do two things: one, we want to instantiate a cannon ball; two, we want to apply some force to it so that it’s projected from the cannon.

First things first, we have to prepare our cannon ball prefab. I’ll just use a little sphere, slightly scaled down, and make sure that it has both a SphereCollider and Rigidbody components:

This Rigidbody component makes the cannon ball a dynamic physical object that can be affected by forces and that has a velocity.

Then, let’s create a reference for this prefab game object in our CannonManager script, and drag it in the Inspector.

While we’re at it, we’ll also add an empty game object at the end of our cannon barrel to mark the point that the cannon balls should be spawned at. We’ll also reference this “FirePoint” Transform in our script, and assign it in the Inspector:

Finally, it’s time to go back to our _Fire() method and use all of this!

We’ll use Unity’s Instantiate() function to create a new instance of our cannonBallPrefab, and place it at our newly created firePoint position. Since we don’t want any initial rotation, I’ll use the Quaternion.identity value for the initial rotation:

And then, we’ll get the Rigidbody component from our cannonBall instance and add some force to it. The force we’ll want to add will be the initial velocity of the cannon ball, and I’ll store it as a private variable in my script so that I can update it from other methods later on:

Also, I want to change the AddForce() force mode from the default ForceMode.Force to a ForceMode.Impulse:

It can be a bit tricky to know which force mode to use when working with Unity physics but, basically, it depends on whether or not you want to take the mass of the object into account and whether or not you want the force to be applied continuously or just instantly.

You can take a look at the description of each force mode in the Unity’s docs, or in your IDE; the Impulse force mode is one where the force is applied instantly and with the mass of the object.

There is one question that remains, though: how are we going to compute this initial velocity?

At the moment, it’s zero, so if I try and fire… the ball will just fall on the ground!

What I’d like is for this velocity to be directed towards my mouse position, just like the cannon barrel, so that the cannon ball logically travels along the line that goes from the tip of the barrel to the mouse.

I also want the velocity strength to depend on how far away from the cannon my mouse is. So, if I’m very close to the barrel, I want to have a low initial velocity and if I’m further away, I want my velocity to increase.

In other words, I’d like for my initial velocity to be just the difference between the mouse position with its zeroed-out z position (that’s our mousePos variable) and the fire point of my cannon:

If you save your file, you can now throw balls from the cannon with varying velocities depending on the position of your mouse when you release the left button!

Showing the trajectory of the cannon ball

To finish this off, let’s see how to show the expected trajectory of the cannon ball – this will help us aim… because, for now, it’s a bit hard to compute the parabolic trajectory in our head 😉

To display this trajectory, I’m going to use a LineRenderer: this Unity component allows you to quickly draw a set of segments from a list of positions and even apply some gradient colours on the line with alpha transparency.

Here, I’ll set it up to go from solid to transparent white, with a width of 0.2. Also, make sure to use world positions because our script will compute global coordinates:

Now, as you can see, if I just create some sample points and move them around in my scene view, I instantly get a line between them that fades away as it goes towards the last point:

I’ll place the LineRenderer inside my “Cannon” object but, in truth, its position doesn’t really matter: what is important is the position of the points that we’ll compute in our C# script.

So the idea will be to add more intermediary points to get a smooth curve and then “simulate” just a few time steps to get the future position of the cannon ball if I were to throw it with this specific initial velocity.

I’ll set this number of points as a constant integer, N_TRAJECTORY_POINTS, and create a new private method _UpdateLineRenderer() that I’ll call from my if-_pressingMouse check in the Update():

I’ve also added a reference to the LineRenderer – don’t forget to drag it in the Inspector! 🙂

In the Start() method, we need to setup the number of points in our LineRenderer, using our N_TRAJECTORY_POINTS constant:

Now, in the _UpdateLineRenderer() function, we want to simulate the movement of the cannon balls for a small amount of time.

To do this, we are going to rely on the classic formulas from Newtonian physics that tell us the acceleration is the opposite of the gravity, and therefore via integration we get a formula for the speed depending on the time. Since the speed is the rate of change of the position (it’s its derivative), we can easily compute the little position delta we get at each time step. I’ll skip the math details but we directly use our initial velocity to get the integration constants, both the initial strength and the initial angle, which we can then plug in our position delta formulas.

And finally, we add this little move delta to our start position, namely the position of the firePoint, at each simulated time step:

This final position pos is the expected world position of the cannon ball at that time t, where t is a few timesteps in the future from the firing timestamp; and so this is the position we are going to assign to the i-th point in our LineRenderer, using the SetPosition() method:

Then, we simply increase the simulated time by the given time step to get the next expected position, and so on, and so on, until we’ve filled all the N_TRAJECTORY_POINTS slots:

One last improvement we can make is to toggle this renderer on or off when we click or release the mouse to avoid showing outdated data. We just need to add this logic on our mouse down and up events, in the Update() (and initially in the Start(), too, to hide the renderer at the beginning):

Save this, and you’ll see that you get a simple curved line showing you the trajectory that the cannon ball will follow if you release your mouse and give it this precise initial velocity! 🙂

Now, you can go ahead and add a few simple obstacles in the scene. Make sure that they have box colliders and rigidbodies (to be physical objects that can receive collisions), and you’ll have some objects to shoot at!

Conclusion

That’s it for today folks!

In this tutorial, we saw how to create a simple physics-based cannon that throws balls at obstacles. It is controlled entirely by mouse – the position of the mouse determines both the angle and the force of the cannon ball impulsion. Finally, we’ve seen how to use a LineRenderer to show the expected trajectory of the projectile.

This was a very light overview of Unity’s physics system – if you want to learn about the basics and in particular the notion of triggers, I really recommend you check out Unity’s official tutorial on their Physics Engine.

I hope you enjoyed this quick Unity tutorial and the dual video/text versions. Feel free to react in the comments and tell me if you like this format — and of course, go ahead and share your ideas for future topics you’d like me to make Unity tutorials on!

Leave a Reply

Your email address will not be published.