219. World of Warships #2 – shooting
I’ve had a lot of fun (and some frustrating debugging) in getting the shooting working properly. Below is where I’m up to. This post covers some great techniques.
Trajectories
The first thing I did was look up the formula for trajectories, ie how a shell flies when fired from a gun. It depends on the initial height, the muzzle speed, the angle of the barrel, the force of gravity, and other factors we can ignore, like wind resistance. Wikipedia had a great article with all sorts of formulae for distance travelled, time taken, etc, but I need to draw the shell every frame as it flies.
It turns out we can do this very simply indeed.
--when we fire a shot, we define.. --pos = the initial position of the gun muzzle --grav = vec3(0,-42,0) --the force of gravity --velocity - see note below --then each frame we update the position pos=pos+velocity/60 velocity=velocity+grav/60 --adjust for gravity if pos.y<0 then --the shell hit the water
So we simply add the velocity to the position each frame, and reduce velocity by gravity at the same time. And choosing a suitable figure for gravity gives the same answers as all the fancy formulae.
I spent some time on Wikipedia, finding out how far destroyer guns could shoot, and their muzzle velocities, and I did a lot of testing to ensure my results were realistic for any angle of the barrel.
Calculating initial velocity of projectiles
But how do we calculate the initial velocity (direction and speed), if we know
- the sideways (y axis) rotation (the rotation of the ship plus any rotation of the gun turret),
- the up/down (z axis) rotation of the gun barrels
- the muzzle speed
What we need to do is turn the rotation angles into a direction vector, and I have a function to do this. It takes a 3D vector and y,z angles, and returns a rotation matrix.
function RotateVector(v,y,z) local m=matrix(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1) if y~=0 then m=m:rotate(y,0,1,0) end if z~=0 then m=m:rotate(z,0,0,1) end return m*v end
The way I use this, given a ship rotation (left/right) angle of y, and a barrel rotation (up/down) of z, is shown below.
velocity = RotateVector(vec3(1,0,0), y, z) * MuzzleSpeed
Why am I rotating vec3(1,0,0)? Because I have to start with the direction my ship was facing when I built it, which was facing to the right.
So with just a few lines of code, my gun shells accurately simulate actual projectile trajectories. (But I can see one use for at least one of the Wikipedia formulae, which tells you what angle to use if you want shoot a certain distance – that will come in useful for automatic aiming).
Showing the shell in flight
In real life, you wouldn’t see shells coming in, you might hear the whine just before they blew you to bits. But all games like to show you where your shots are going (or where they are coming from).
I planned to show a streak of light for the shell – not too long, just enough so you can see it, but not so long you would notice it wasn’t curved to follow the trajectory.
I realised that my muzzle flash in my last post was no longer necessary if I have this streak, so I got rid of it.
I have two ways I can do the streak – either
- a 3D object, like a long thin block, which turns to follow the trajectory path, or
- a 2D line, which is simpler to draw but difficult to place
The problem with putting a 2D line on a 3D screen is figuring out where to put it. I know how to do this, because I’ve posted about it, and I started writing some code for it. But it means drawing the shell streaks last, because you have to convert back to 2D first.
In the end, I simply used a 3D block stretched so long that it looks like a streak. Now, how do I position it?
My first problem is getting the starting point – the position of the end of the gun when it fires.This is a combination of the ship position, the ship rotation, the gun rotation, and the barrel rotation, and the length of the barrel. However, when we draw the gun barrels every frame, we do nearly all the translation work required. We just need to translate from the start of the barrel to the end. So when I fire, I do all the normal translating to draw the gun and the barrel, and then I add a translation to get me to the end of the barrel to give me the starting point of the trajectory.
My second problem is angling the streak in flight, so it follows the angle of the trajectory. For this, I use my LookAtMatrix function, which turns an object to face another object. So I give it the starting point (the position of the shell), and the point I want to look at (I simply add current velocity to the shell position). The function returns a rotation matrix (including a translation to the shell position) which I can use to translate and rotate the shell before drawing it.
local m=LookAtMatrix(shellPos,shellPos+velocity) pushMatrix() modelMatrix(m) shell:draw() popMatrix()
Splash!
If a shell hits the water, I’d like to make a splash. I spent quite some time getting Codea to draw a 2D splash image that looked sort of realistic (all the images in this project are created by Codea at the start of the program). Then I spent some time figuring out how to make the splash appear and disappear, using a timer variable.
My final problem was turning the 2D splash so it always faces the camera. Again, I used the LookAtMatrix function, giving it the splash position and the camera position.
local m=LookAtMatrix(splashPos,cameraPos) --then use modelMatrix(m) to rotate the image
We’re not quite done, because the splash image has transparent pixels, and if you draw one behind the other, you get strange effects. So I need to sort the splashes so I draw them from furthest to nearest. No kidding.
Realism
So far, this is quite a realistic simulation. However, the ship can fire so far that you can barely see the splash, meaning it could probably reach anywhere on the map. I may have to shorten the range. Also, in real destroyers, the guns can fire as quickly as every 4 seconds. That’s too quick for a game. So I’ll have to tune those things.
Skills
Now if some of the techniques in this post look a bit scary or strange, that’s OK. They were for me, too, when I first saw them. But the value of exploring all the different things you can do with 3D, is that it gives you more options to choose from, and as you can see above, even very difficult tasks can sometimes be done in a couple of lines, if you know how.
This is why I try to discourage people from just launching straight into 3D games, because you are just going to make a horrible mess if you don’t know any of this stuff. It pays to spend time learning some skills first.
Having said that, I still spent a lot of time fixing tricky little bugs. The worst was that if I fired several times in a row, the shell streak started off further and further from the gun barrel. I couldn’t understand why there was an increasing gap. It probably took me two hours to realise that multiple shots slow down the frame rate, so if you update the position based on DeltaTime, the first update will be take longer (and DeltaTime larger)if there are multiple shots, so the streak will move further before it is drawn the first time. So in the code above, I update position using a constant 1/60 of a second, and that removes the problem.
I will probably write about the islands next, but I need to tidy them up first.
PS At various points, I will be happy to share the code. I will try to have it ready for the next post.
Hey I stumbled apon your tuts and think there great, keep it up. But I have a question, in my whack a bug program I can’t make the bugs disappear after x amount of seconds. Do you know any code to make things disappear after x amount of seconds.
THe place to ask that question is on the codea forum. Try to explain clearly and show some code if possible.