== Lesson 26: Characters That Move Themselves image::learn/26-navigation.png[The finished lesson, 480] In this lesson you build a room with a wall across the middle of it and put two characters in it. One of them you send to a corner with a button press. The other picks somewhere at random, walks there, and when it arrives picks somewhere else, forever. Neither of them walks into the wall, and you never write a line that says how to go round it. That is new. Every lesson up to this one has moved things where you told them to go. This is the first lesson where the game works out something for itself. === The Problem with Walking Toward Something Here is the obvious way to make a character walk to a point, and it is the way everybody writes first. Take the target, subtract where you are, and step a little along the line between them, every frame, until you get there. It works beautifully in an empty room. Put a wall in the room and it stops working, and it stops working in a way that looks stupid rather than broken: the character marches into the wall and stands there grinding against it, a metre from a doorway it cannot see, because nothing in "step toward the target" has any idea that the world has shapes in it. You cannot patch your way out of this. Turning aside when you touch a wall gets your character stuck in the first corner it finds. Nor is it enough to know where the walls are, because the route round a wall may be long, may go away from the target before it comes back, and may not exist at all. What is needed is a way to ask a real question: *how do I walk from here to there?* Answering it needs something that knows the shape of the floor. === What a Navigation Mesh Is Stand in a room and look at the floor. Some of it you could walk on. Some of it you could not: it is under a table, inside a wall, off the edge of a balcony, or up a step too high to climb. Now imagine painting the part you could walk on, and throwing away everything else -- the walls, the furniture, the ceiling, the scenery. What you are left with is a flat shape, or several, with holes in it where the pillars are and gaps where the drops are. That painted shape is a *navigation mesh*, and it is all a character needs in order to plan a walk. It is not the level. It is a simplified map of the parts of the level a person can stand on, and because it is simple, questions about it can be answered quickly. Singe makes it for you from the level you already have. You say how big the characters are, hand it the geometry, and it works out what is walkable: floors and ramps within the slope you allow, steps no higher than you allow, with a margin trimmed off around every wall and every drop so a character with width does not clip a corner. That is called *baking*, it happens once, and the result can be saved to a file and loaded next time. === A Room with a Wall in It Start a new file. This part is a three dimensional scene, which is lesson twenty-two's subject: a *scene* is a world drawn in three dimensions, and everything in it hangs on a *node*, which is a position, a rotation, and a scale that you can attach a shape to. If you skipped that lesson you can still follow this one. Take the next two blocks as "a grey floor twenty units square, three tan walls standing on it, and a camera looking down at the lot". [source,lua] ---- local ROOM = 20 local WALL_HEIGHT = 2 local AGENT_RADIUS = 0.4 local AGENT_HEIGHT = 1.8 local WALK_SPEED = 2.2 sceneEnable(true) sceneSetBackground(28, 32, 42, 255) sceneSetAmbient(80, 86, 100) local floorLook = materialNew() materialSetColor(floorLook, 96, 102, 110) materialSetRoughness(floorLook, 0.9) local wallLook = materialNew() materialSetColor(wallLook, 150, 128, 104) materialSetRoughness(wallLook, 0.8) local ground = nodeNew() nodeSetMesh(ground, meshPlane(ROOM, ROOM), floorLook) local wallMesh = meshBox(1, WALL_HEIGHT, 1) local walls = {} function addWall(x, z, width, depth) local wall = nodeNew() nodeSetMesh(wall, wallMesh, wallLook) nodeSetScale(wall, width, 1, depth) nodeSetPosition(wall, x, WALL_HEIGHT / 2, z) walls[#walls + 1] = wall end addWall(-4, 0, 12, 0.6) addWall(7.5, 0, 5, 0.6) addWall(-3, -6, 0.6, 8) ---- The units here are metres, because that is what Singe's three dimensional world assumes. The first two walls run across the middle of the room with a three metre gap between them: a doorway. The third is a stub sticking into the far half, to give the characters something to walk round once they are through. Add a light and a camera so there is something to see: [source,lua] ---- local sun = lightNew(LIGHT_DIRECTIONAL) nodeSetPosition(sun, 8, 14, 6) nodeLookAt(sun, 0, 0, 0) lightSetIntensity(sun, 3.5) lightSetShadow(sun, true) local camera = nodeNew() nodeSetPosition(camera, 0, 17, 19) nodeLookAt(camera, 0, 0, 0) cameraSet(camera) cameraSetPerspective(55, 0.1, 120) function onOverlayUpdate() overlayClear() return OVERLAY_UPDATED end ---- Run it. A grey floor, a wall with a gap, and a stub, seen from above and behind. === Bake the Floor Three lines turn that room into a map: [source,lua] ---- local nav = navNew(AGENT_RADIUS, AGENT_HEIGHT, 45, 0.3) navAddNode(nav, ground) for _, wall in ipairs(walls) do navAddNode(nav, wall) end navBuild(nav) ---- Put them after the walls and before the light. Then add one line to `onOverlayUpdate`, under `overlayClear`, so you can see what you got: [source,lua] ---- navDraw(nav, 0, 170, 190) ---- Run it again. A web of blue-green triangles is laid over the floor, and it stops short of the walls. The gap it leaves is not decoration: it is the 0.4 metres of clearance a character with a radius of 0.4 metres needs so that its shoulders do not go through the stone. The doorway is still a doorway in the mesh, because three metres is wide enough to walk through; make the gap half a metre and the blue would close across it, and as far as every character in your game was concerned there would be no door there at all. Look at it for a moment, because `navDraw` is the tool you will reach for every time a character does something inexplicable. Nine times in ten the answer is on the floor in front of you. === Somebody to Walk It A character is a node. What it looks like is not navigation's business, so build the cheapest thing that shows which way it is facing: a box with a nose on it. [source,lua] ---- function makeWalker(x, z, look) local walker = nodeNew() nodeSetPosition(walker, x, 0, z) local body = nodeNew() nodeSetParent(body, walker) nodeSetMesh(body, meshBox(0.7, AGENT_HEIGHT, 0.7), look) nodeSetPosition(body, 0, AGENT_HEIGHT / 2, 0) local nose = nodeNew() nodeSetParent(nose, body) nodeSetMesh(nose, meshSphere(0.22, 16), look) nodeSetPosition(nose, 0, 0.4, -0.45) return walker end ---- That goes with `addWall`. It needs a colour, so add one beside the other materials: [source,lua] ---- local walkerLook = materialNew() materialSetColor(walkerLook, 225, 85, 60) materialSetEmissive(walkerLook, 70, 18, 10) ---- And now the line this lesson exists for. After `navBuild`: [source,lua] ---- local walker = makeWalker(-7, 7, walkerLook) local walkerAgent = navAgentNew(nav, walker, AGENT_RADIUS, AGENT_HEIGHT, WALK_SPEED) ---- `navAgentNew` puts the node on the mesh and hands back an *agent*: the engine's word for something it is willing to walk around for you. From this moment the engine owns that node's position. Every frame it moves it, and turns it to face the way it is going, and it will keep doing that until you tell it where to go or take the agent away. Run it. A red box with a nose appears in the near corner and does absolutely nothing, because you have not asked for anything yet. === Sending It Somewhere Give it a list of places and a button. [source,lua] ---- local posts = { { 7, 0, 7 }, { 7, 0, -7 }, { -7, 0, -7 }, { -7, 0, 7 }, { 30, 0, 30 } } local post = 0 local message = "Button 1 sends the red one somewhere." function onInputPressed(what) if what ~= SWITCH_BUTTON1 then return end post = post % #posts + 1 local target = posts[post] if navAgentMoveTo(walkerAgent, target[1], target[2], target[3]) then message = string.format("Walking to post %d.", post) else message = string.format("No path to post %d. Staying put.", post) end end ---- Print the message so you can see what is going on. In `onOverlayUpdate`, after `navDraw`: [source,lua] ---- overlayPrint(2, 2, message) ---- Run it and press button one -- the *space bar* by default. The box turns, walks to the far corner of its own half of the room, and stops. Press again. It walks along the wall, finds the doorway, goes through it, comes out the other side, goes round the stub, and stops in the far corner. You did not tell it about the doorway. Nothing in your script mentions the doorway. It found it, because the doorway is a hole in a shape and going round holes is what the mesh is for. Each `posts` entry is three numbers, an x, a y, and a z, because a point in a three dimensional world needs three. Y is up, and yours are all zero because everything here is on the ground. The `posts` table is a table of tables, from lesson six, and `posts[post]` is one of the inner ones. === Knowing When It Arrives The box stops when it gets there, but your script does not know that it has. Ask to be told: [source,lua] ---- function onNavArrived(agent) message = string.format("Arrived at post %d.", post) end ---- This is a callback, the same idea as `onOverlayUpdate` in lesson one: you write it, the engine calls it. This one runs once, the moment an agent reaches the target it was given. Not when it is near, and not every frame afterward -- once, on arrival. It is handed the agent, because a game has more than one. That argument is the number `navAgentNew` gave back, and comparing it is how you tell which of your characters has just got where it was going. === When There Is No Path Press the button a fifth time and the message reads `No path to post 5. Staying put.`, and the box does not move. Post five is at `{ 30, 0, 30 }`, which is ten metres outside a room twenty metres across. There is no walkable floor there, there is no route to it, and there is nothing sensible for a character to do about it. `navAgentMoveTo` says so by handing back `false`, and it leaves the agent exactly as it was. This is the part beginners skip, and it is the part that bites. Real games ask for impossible walks constantly, because the target is usually the player and the player gets everywhere: standing on a crate, mid-jump, in the water, behind a door that closed, or through a gap the nav mesh trimmed away as too narrow. Every one of those makes a perfectly reasonable `navAgentMoveTo` return `false`. Your script decides what that means. A guard who cannot reach you should go back to patrolling, or walk to the nearest point it *can* reach and wait there, or shout. What it must not do is nothing while your code quietly ignores a returned `false` and you wonder for an hour why one enemy in twenty is frozen. Two other calls help here, and they are worth knowing before you need them. `navNearest` takes any point and hands back the closest spot on the mesh, or `nil` if there is nothing near it, which is how you turn "where the player is" into "somewhere a character can actually stand". And `navPath` asks the same question as `navAgentMoveTo` without moving anybody: it hands back the corners of the route as a table of points, or `nil` when there is no route. You can measure it, draw it, or decide the walk is too long to bother with. Drawing it is the useful trick. Add this to `onOverlayUpdate`: [source,lua] ---- local target = posts[post] if target ~= nil then local x, y, z = nodeGetWorldPosition(walker) local route = navPath(nav, x, y, z, target[1], target[2], target[3]) if route ~= nil then for i = 2, #route do local a = route[i - 1] local b = route[i] lineDraw(a[1], a[2] + 0.1, a[3], b[1], b[2] + 0.1, b[3], 255, 215, 60) end end end ---- Now a yellow line runs from the box to wherever it is going, bending through the doorway, shortening as it walks. Send it to post five and the line is not drawn at all, because `navPath` returned `nil` and the `if` caught it. You are watching the decision that the agent is making, drawn on the floor. === A Second Walker One character avoiding walls is navigation. Two characters avoiding walls *and each other* is what the engine actually gives you, and it costs four more lines. Add a blue material beside the red one, then this under the first agent: [source,lua] ---- local wanderer = makeWalker(6, -6, wandererLook) local wandererAgent = navAgentNew(nav, wanderer, AGENT_RADIUS, AGENT_HEIGHT, 1.4) ---- Send it somewhere to start it off, after the camera: [source,lua] ---- navAgentMoveTo(wandererAgent, navRandomPoint(nav)) ---- And teach `onNavArrived` to tell the two apart: [source,lua] ---- function onNavArrived(agent) if agent == wandererAgent then navAgentMoveTo(agent, navRandomPoint(nav)) else message = string.format("Arrived at post %d.", post) end end ---- `navRandomPoint` picks somewhere on the walkable mesh -- anywhere on it, never inside a wall -- and hands back three numbers, which is exactly what `navAgentMoveTo` wants next. Because the callback fires on arrival and its answer is another journey, the blue box now wanders the room for as long as the game runs, out of four lines and no state at all. Watch the two of them meet in the doorway. Neither one stops dead and neither one walks through the other: they both lean aside a little, in good time, and pass. That is the agents steering round each other, and it is the difference between characters and furniture. The finished script is `learn/26-navigation.singe`. === What Just Happened [source,lua] ---- local nav = navNew(AGENT_RADIUS, AGENT_HEIGHT, 45, 0.3) ---- Starts a navigation mesh, and the four numbers are a description of the person it is for: 0.4 metres wide, 1.8 metres tall, able to walk a slope of up to forty-five degrees, and able to climb a step of up to 0.3 metres. Every one of those changes the shape that comes out. A wider character loses more floor to the margin around walls and may find a doorway closed. A taller one cannot walk under a low beam. Allow a steeper slope and a ramp becomes a road; allow less and it becomes a cliff. The radius also decides how finely the floor is measured, so a very small radius over a very large level takes a long time to bake. The manual's entry for `navNew` is worth reading before you pick your numbers. [source,lua] ---- navAddNode(nav, ground) navBuild(nav) ---- `navAddNode` hands over geometry -- a node and everything hanging under it, as it stands in the world at that moment, so place things before you add them. One call takes a whole loaded model. Only actual shapes count: a light, a sound, or a physics body on its own adds nothing. `navBuild` does the baking, and it is the slow call in this lesson: a fraction of a second for a room like this one, a tenth of a second for a cathedral on a desktop machine, several seconds on a Raspberry Pi. Do it while a title screen is up, not while the player waits. Afterward the geometry is thrown away and the mesh is closed. You cannot add more. If your level changes shape -- a door opens, a bridge falls -- you bake another mesh, and `navDelete` frees the old one. On a slow machine you would rather not bake at all, so `navSave` writes the finished mesh to a file and `navLoad` reads it back, built and ready. Bake once on your own machine, ship the file with the game, and the player never waits. [source,lua] ---- local walkerAgent = navAgentNew(nav, walker, AGENT_RADIUS, AGENT_HEIGHT, WALK_SPEED) ---- An agent is a node the engine has agreed to walk about for you. The radius and height here are what it uses to keep clear of other agents, and the last number is its top speed in metres a second -- 2.2 is a brisk walk, 1.4 is an amble, 6 is a sprint. From here on, do not set that node's position yourself. Two things moving one node fight, and the fight looks like stuttering. If your character needs to be shoved about by physics as well as walk, there is a call for that -- `navAgentSetPlayer` hands the steering to a physics controller instead -- and the reference's entry for it explains when you want it. [source,lua] ---- local ok = navAgentMoveTo(walkerAgent, x, y, z) ---- Sets the target. It does not walk there; it says where "there" is, and the engine gets on with it over the following frames. You may call it again at any moment and the new target replaces the old one, which is how a guard follows a player who is running: re-aim it once or twice a second, not sixty times. And it hands back `true` or `false`, and you must look. [source,lua] ---- navAgentStop(walkerAgent) ---- Not used above, but the other half of the pair: it forgets the target and the agent slows to a stand where it is. `onNavArrived` does not fire, because it did not arrive. Use it when the reason for the journey has gone away. === Fast Is Not the Point One more call, because it is what turns this from a demonstration into something that looks like a game. [source,lua] ---- local vx, vy, vz = navAgentGetVelocity(walkerAgent) local speed = math.sqrt(vx * vx + vz * vz) overlayPrint(2, 4, string.format("Speed %.2f", speed)) ---- `navAgentGetVelocity` tells you how the agent is moving right now: not where it is going, but how fast and in which direction it is actually travelling this frame. Watch the number as the box sets off and as it comes to a halt. It eases in and eases out. That number is what you hang an animation on. A character whose legs move at the speed it is actually travelling looks alive; one whose walk cycle runs at a fixed rate while it accelerates looks like it is skating. With a real model in place of the box you would compare that speed against a couple of thresholds and play an idle, a walk, or a run -- which is lesson twenty-three's subject, and the reference's example under `navAgentGetVelocity` is written for exactly that. Which is the real point of this lesson. A character that takes the fast straight line and grinds along a wall is worse than one that sets off a second later, turns properly, goes round through the door, and slows down when it arrives. Nobody watching a game measures how quickly an enemy reached them. They notice, immediately and without being able to say why, when something moves in a way a living thing would not. === Try It . *Close the door.* Change the first two walls so the gap between them is half a metre instead of three: `addWall(-4, 0, 13, 0.6)` and `addWall(6.5, 0, 7, 0.6)`. Run it and look at what `navDraw` shows before you press anything. Then send the box across and see what it does. . *Make everybody fat.* Set `AGENT_RADIUS` to `2.0` and run it. The doorway is still three metres wide and the blue triangles are gone from it. Nothing is broken; the mesh is answering a different question now. . *Send it to the mouse.* Take the point under the mouse pointer with `sceneUnproject`, snap it onto the mesh with `navNearest`, and send the box there when the button is pressed. `navNearest` returning `nil` is the case you must handle, and it happens whenever the pointer is off the floor. . *Add four more wanderers.* Put the blue walker's four lines in a `for` loop from lesson four, keep the agents in a table, and let them all wander. Watch the doorway when three of them want it at once. . *Bake it once.* Call `navSave(nav, "room.nav")` after `navBuild`, then replace `navNew`, the `navAddNode` calls, and `navBuild` with a single `navLoad("room.nav", AGENT_RADIUS, AGENT_HEIGHT)`. Now change a wall's position and run it again. What you see is the trap that comes with baked meshes, and it is worth meeting once on purpose. === Break It on Purpose Baking closes the mesh. Try to add one more thing after the fact -- a crate you placed late, or a wall you forgot -- by putting this line straight after `navBuild(nav)`: [source,lua] ---- navAddNode(nav, ground) ---- Singe stops before the window opens, naming the line that call is on in your file: ---- 69:navAddNode: The mesh is already built. ---- The shape of an engine error is the line, the call that objected, and the complaint, and this complaint means what it says: `navBuild` consumed the geometry it was given and there is no longer anything to add to. The fix is never to move the `navAddNode` call. It is to notice that you have two levels' worth of geometry and want two meshes, or that you baked too early. Add everything, then build, and build last. === What You Learned * Stepping toward a target works in an empty room and fails the moment there is a wall, and no amount of patching fixes it. * A navigation mesh is a simplified map of the floor a character can stand on, with the walls, the drops, and the clearance around them taken out. * `navNew` describes the character the mesh is for, `navAddNode` hands over the level's geometry, and `navBuild` bakes it. After the bake, nothing more can be added. * `navSave` and `navLoad` keep a baked mesh in a file so the player never waits for it. * `navDraw` shows you the mesh, and it is the first thing to look at when a character behaves strangely. * `navAgentNew` gives a node to the engine to walk about; from then on, do not move that node yourself. * `navAgentMoveTo` sets a target and hands back `false` when there is no way to get there. Handling that is your job, and it happens often. * `onNavArrived` is called once when an agent reaches its target, and is told which agent. * `navPath` answers the same question without moving anyone, and `nil` means no route. * Agents steer round each other as well as round the level. * `navAgentGetVelocity` is how a walk cycle matches the ground being covered. === Next Time Your two boxes now walk around a room in silence. Lesson twenty-seven is about what you put behind them: music that loops properly and stays in step with video, and MIDI, which lets a game play an instrument -- or lets a real instrument play the game.