Skip to content

228. World of Warships – multiple enemies

August 24, 2015

You can now play against three ships at once, and they are placed randomly on the map, so keep a sharp lookout!

The video below shows 5 minutes of gameplay, sinking 2 of the 3 enemy ships (the last ship takes longer to find because it is a large map).

Note the FPS counter is misleading because the video recording slows things down – on my iPad3, the game runs at about 45 FPS.

Code is at the bottom, if you want to try it.

Adding more ships

This was easy, because I built it in from the very beginning. When I had just one enemy ship, I still put it in a table of enemies, and looped through the table when drawing, navigating etc. Adding more ships meant just adding more items to the ship table. All the ships use the same 3D model, and I have a Ship class so I can store information about multiple ships, so it really was easy. Hooray for classes!

Making it even

In a fair fight, you would have a 50% chance of beating one ship, and not much health left if you won, so I had to rig it so you have a chance of beating three ships. I did this by making the player’s guns more accurate than the enemy guns, and this seems to work well. In several battles, I’ve won some and lost some, and the end result has been close.

Finding enemies

I had to think about how to help the player look around easily, and in the end, I decided just to use swipes on the screen.

It’s easy to translate a swipe into a rotation angle, like this

angle=touch.deltaX/WIDTH*fov

touch.deltaX gives you the x distance your finger has moved (in pixels) since the last frame, and fov is the “field of view”, the number of degrees covered by the width of your screen (in post #220, I explained how I used changes in the field of view number to zoom in and out).

So if the screen covers an angle of 45 degrees, and your swipe is 10 pixels wide on a screen which is 1000 pixels wide, then your swipe is 10/1000 x 45 = 0.45 degrees, and you can add this to the rotation angle of the camera to turn it.

So you can swipe left and right to turn the ship to look around you, and that seems to work nicely.

(If you can’t find any enemies, just reduce the fog parameter setting, which means you – and they – can see further).

Managing touch

This bit is for people who may be relatively new to programming.

It’s important to manage touches, because they affect so much of the game

  • steering
  • speed
  • selecting enemies
  • turning to look left and right

The code would be very messy if it was full of if tests, so my touched function looks like this

function touched(t)
    if joy:touched(t) then return  
    elseif t.state==MOVING then
        local y=t.deltaX/WIDTH*viewZoom[zoom]
        if viewAngle==0 then viewAngle=S.angle end
        viewAngle=viewAngle+y*2
        camView="Scan"
    elseif t.state==ENDED then 
        if ViewConsoleTouched(t) 
        or FireButtonTouched(t) 
        or target and AimCircleTouched(t) 
        or TargetTouched(t)
        then return
        end
    end
end

It first tests if the joystick circle has been touched (and if so, stores details).

Then, if the finger is moving, it makes the camera angle change so the player can look around. I should really put this code into a little function for neatness.

After this, it tests if the player has touched one of the camera views, or the red firing button, or the aiming circle (with the little ship in it), or a target ship on the screen.

Note that each of these tests is done by a separate function, and all of them return true if there was a touch. This makes the code above very compact, and having each test in its own function makes it easy to test, if I want.

My draw function is also compact, and needs hardly any explanation. If you are new to Codea, I encourage you to think of the draw function as not simply for drawing, but as a place to manage what happens each frame, and to keep it tidy like this.

function drawPlay()
    FPS=FPS*0.9+0.1/DeltaTime
    CheckForWinner()  
    background(skyColor)
    perspective(viewZoom[zoom]) 
    SetCamera() 
    local v=joy:update() --update joystick details
    S:draw(-v.x,v.y)  --draw player's ship 
    DrawEnemyShips()
    Terrain.draw()
    S:drawTrack() --track behind our ship
    DrawShots()
    DrawSplashes()
    DrawExplosions()
    joy:draw()
    DrawHUD()
    if aiming then DrawAimingCircle() end
end

Selecting enemies

I suppose I could auto-select visible enemies, but if there’s more than one, the player will want to choose. So I’ve made it that you need to touch a ship you want to shoot at, and then the red firing button should appear.

How do you touch a 2D screen to select a ship that is in a 3D scene? My method probably isn’t in the textbooks, but it works nicely. It uses a very similar approach to the swiping above. See the notes under the code, for explanations.

local y=-(t.x/WIDTH-0.5)*fov  --[1]
local a=(RotateVector(camLook-camPos,y,0)):normalize()  --[2]
for i,e in pairs(E) do   --[3]
    if IsShipVisible(S.pos,e.pos) then  --[4]
        local d=camPos:dist(e.pos)      --[5]
        local v=(camPos+a*d):dist(e.pos) --[6]
        if v<50 then --[7]

–etc etc

[1] First, I calculate how far my x touch value is from the middle of the screen, as a proportion of screen width, and then multiply by field of view (the number of degrees covered by the screen). This tells me how many degrees I need to turn left or right, to aim at the point I touched

[2] Which way are we pointing at the moment? Not in the direction of the ship, but in the direction of the camera. The camera is looking straight ahead, so it needs to rotate by the angle in [1]. In fact, I’m not going to rotate the camera angle, but its direction vector (a vec3), which I calculate by subtracting the camera position from the point the camera is looking at. Then I rotate it by the angle from step [1], and normalise it so its length is 1 (see [6] below for the reason). So now I have a direction vector that points from our ship toward the touch point.

[3] I am going to check if any enemy ships are close to this line (at any distance), so I loop through them

[4] I first check if I can see that ship, if not, I skip it

[5] I calculate the distance to the ship

[6] I multiply this distance by the direction vector from [2] above, which (if my touch was accurate) should give me a point on the map that is very close to the ship I was trying to touch.

[7] if this point is within 50 pixels of the enemy ship, set it as the target

There are other ways you can do this, having calculated the direction vector in step [2], for each enemy ship,

  • calculate the direction vector to the enemy ship (ie position of enemy ship minus our position) and use the dot product to measure how similar the vectors are.

or

  • calculate the length of the shortest line between the enemy ship and the direction vector

and probably more….

Finished!

I think I’m finished. If I was going to make an app, it would still need a lot of fine tuning, play testing and polishing, but that’s not why I made it. I’m just having fun.

I’m really pleased with the water, the islands, the explosions, the mist, and the ships – all the visual effects.  It’s amazing being able to do this with an iPad app, and it’s why I love Codea so much.

Play the game

The code is below. There are likely to be a couple of bugs here and there, because I haven’t play tested it for hours and hours, but hopefully they are not too often to be annoying.

The instructions are pretty simple

  • there is one parameter, for fog. Reduce it for less fog and greater visibility
  • use the joystick to turn and to control speed
  • if you get too close to an island, you will get an unpleasant noise and have to back off
  • swipe on the screen to turn your head and look around
  • also select one of the camera views at the bottom if you want. The first three put the camera behind the ship or on the bridge, “target” keeps the camera pointing at an enemy (once you have selected an enemy), “zoom” gives you closeups (keep pressing to change it), and “Shot” gives you shot cam, ie the camera travels with your gun shots.
  • touch an enemy ship to select it. The red firing dot should appear
  • press the red dot to bring up the aiming circle. Touch it where you want the shots to go (generally, the front of the enemy ship is a good spot)
  • your guns will only fire if they can turn far enough (eg your back guns can’t fire if an enemy is straight in front) and if they are ready.
  • The four dots at lower right show the gun status, from front to back of the ship. Green means ready to fire, yellow means reloading, red means disabled (by enemy gunfire)
  • The green line above the dots is your health, and when you have selected an enemy, its health will be shown above yours
  • When you sink an enemy, it disappears. Sorry, no fancy sinking animations yet.
function setup()
    http.request("http://bit.ly/1NuOuqN",
    function(d) loadstring(d)() setup() end,
        function(e) print(e) end
    )
end

k

Leave a Comment

Leave a comment