Last time I managed to make a fairly simple Codea Craft program draw an Orc standing on (or near) the floor. Today I plan to make him move a bit, and then to see what I’ve learned.

I’m starting to get a sense of what I’d say if someone were to ask me about Codea Craft, at least referring to the simplest part, that I’ve been playing with here. Not counting the AR and physics parts, about which about all I can say is what’s in this sentence.

Today, I want to add the necessary code to move our Orc from side to side in the picture, similarly to what it was doing in our first big example. The idea today is to duplicate that capability, first in line, and then to refactor toward a design that’s better (by my standards).

Let’s go for super simple. We’ll move the Orc back and forth in small steps, modifying X only, between -2 and +2, the bounds of the floor. You may recall that the floor is 4 on a side. I suspect that to begin with our variables may need to be global. We’ll see.

The Orc code presently looks like this:

``````    myEntity = scene:entity()
myEntity.model = craft.model("Blocky Characters:Orc")
myEntity.y = -1
myEntity.z = 0
myEntity.scale = vec3(1,1,1) / 8
myEntity.eulerAngles = vec3(0, 180, 0)
``````

The variable `myEntity` is our Orc. I think I’ll rename that, to reflect its Orcness and its global character, to MyOrc. I should then be able to update it more or less directly in the `update` function.

OK, that went surprisingly well, given who we’re dealing with here. In setup, I did the predicted rename, initialized X directly (it was defaulted in the original), and saved a new global to represent the step size. It turns out that 0.02 results in a reasonable speed.

``````function setup()
scene = craft.scene()
scene.sky.active = false
createGround(-1.125)

MyOrc = scene:entity()
MyOrc.model = craft.model("Blocky Characters:Orc")
MyOrc.x = 0
MyOrcStep = 0.02
MyOrc.y = -1
MyOrc.z = 0
MyOrc.scale = vec3(1,1,1) / 8
MyOrc.eulerAngles = vec3(0, 180, 0)

scene.camera.z = -4
CameraSettings = scene.camera:get(craft.camera)
CameraX = 0
CameraY = 0
FieldOfView = 60
Ortho = false
OrthoSize = 5
end
``````

Then in update, I just added this:

``````    local x = MyOrc.x + MyOrcStep
if math.abs(x) >= 2 then MyOrcStep = -MyOrcStep end
MyOrc.x = x
``````

And behold! It moves. No real surprise of course. Unless you’re me.

Now it’s cleanup time

What we have here, in my view, is not unlike the typical code we’re shown when someone is writing about how to use an API like Craft. Everything is in line and rather procedural. Arguably this form is a bit easier for beginners to understand, especially if judiciously sprinkled with explanatory comments like `-- move the Orc along X`.

The problem is, this is terrible code, by my standards, and because it’s the code we are taught, we tend to copy it and bash it until it does whatever new thing we want to accomplish. This is especially true for relatively inexperienced programmers, who may have never seen code that looks any different from this.

My mission is not just to explain Craft, but to show how at least one experienced practitioner would express ideas in the code.

Digression: Beck’s Four Rules

Kent Beck has described “four rules of simple code”. Code is simple when it:

1. Runs all the tests (works correctly);
2. Expresses all the programmer’s design ideas;
3. Contains no duplication;
4. Minimizes the number of artifacts.

These four notions alone can drive us from a poor design to a better one. We do, of course, need practice in recognizing the issues and improving things.

Note that I have no tests. As I’ve written elsewhere, I do not as yet know good ways to test code like this, whose purpose is to put things directly on the screen, other than by looking at the screen. Maybe as we go forward here, some ways will present themselves.

For now, my concern is primarily with Rule 2. This code does not express all my design ideas. Let’s look at update and talk about the ideas that are not very well expressed in the code.

``````function update(dt)
local x = MyOrc.x + MyOrcStep
if math.abs(x) >= 2 then MyOrcStep = -MyOrcStep end
MyOrc.x = x

if CurrentTouch.state == MOVING then
CameraX = CameraX - CurrentTouch.deltaX * 0.25
CameraY = CameraY - CurrentTouch.deltaY * 0.25
end

CameraSettings.fieldOfView = FieldOfView
CameraSettings.ortho = Ortho
CameraSettings.orthoSize = OrthoSize

--Set the camera rotation and position to look at the center of the scene
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5

--    print("update", dt)
scene:update(dt)
end
``````

This code could be described this way:

• Move the Orc
• Set Camera parameters (redundantly?)
• Set Camera Angles
• Set camera position
• Update the scene.

The redundancy in the camera settings is due to the fact that I removed the parameters that appeared in the Learn Craft example, and just initialized various values to the defaults of those parameters. In the original, those three `CameraSettings` references included variables that could change. Now they can’t.

Now I will argue that that code should be moved to the setup. Some would say, well, maybe in the future we’ll want to make those items parameters, and we’ll just have to move them back. My practice is to make the code as clean and modular now as I can, with the experienced expectation that that sets me up better for future change than leaving things lying around where they don’t really belong.

Your mileage may vary, of course, but this is what I’ve learned to do, and it works well for me.

So I want to move those camera bits into setup and work from there. I move them to the end of `setup()`, where everything still works fine. Now we have this in update:

``````function update(dt)
local x = MyOrc.x + MyOrcStep
if math.abs(x) >= 2 then MyOrcStep = -MyOrcStep end
MyOrc.x = x

if CurrentTouch.state == MOVING then
CameraX = CameraX - CurrentTouch.deltaX * 0.25
CameraY = CameraY - CurrentTouch.deltaY * 0.25
end

--Set the camera rotation and position to look at the center of the scene
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5

--    print("update", dt)
scene:update(dt)
end
``````

Now, conveniently, all the camera stuff is isolated, as is the Orc moving. These, I would extract as functions. Let’s do the Orc first:

Update becomes:

``````function update(dt)
updateOrc(dt)

if CurrentTouch.state == MOVING then
CameraX = CameraX - CurrentTouch.deltaX * 0.25
CameraY = CameraY - CurrentTouch.deltaY * 0.25
end

--Set the camera rotation and position to look at the center of the scene
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5

--    print("update", dt)
scene:update(dt)
end
``````

``````function updateOrc(dt)
local x = MyOrc.x + MyOrcStep
if math.abs(x) >= 2 then MyOrcStep = -MyOrcStep end
MyOrc.x = x
end
``````

Note that the `update` function now says what it’s going to do: update the Orc, and the `updateOrc` function says how it’s going to be done. By my lights, that’s better.

Now I’ll do the same with the Camera stuff. I should mention that I have questions about how that works, but the process of moving it should be quite mechanical, requiring no deep knowledge:

``````function update(dt)
updateOrc(dt)
updateCamera(dt)
scene:update(dt)
end

function updateCamera(dt)
if CurrentTouch.state == MOVING then
CameraX = CameraX - CurrentTouch.deltaX * 0.25
CameraY = CameraY - CurrentTouch.deltaY * 0.25
end
--Set the camera rotation and position to look at the center of the scene
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5

end
``````

Now, the update function says exactly what it does: update the orc, update the camera, update the scene. If we care how those things are done, we can read the code for those functions. If we just care about what’s going on, we can read just the top level.

Now, we’re left with a question.

What’s up with the camera?

We “know” that there is a camera, and that it has a position that it’s at, and that it has some direction it points in. We know that from reading the documentation. What confuses me is how that code gets the effect we see, of the camera rotating around the scene (or the scene rotating within the camera’s view. Look at this movie again:

Our view rotates around the Orc and moves up and down a bit, while apparently staying at the same distance away and looking right at the center of the floor. Clearly the code that does this is:

``````    --Set the camera rotation and position to look at the center of the scene
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5
``````

That’s all there is. I would expect that setting the angles on the camera would rotate it. I think I’ll check that by commenting out the position part.

Sure enough, now when I drag on the screen, the picture moves off screen, as if the camera is looking away.

``````    scene.camera.position = -scene.camera.forward * 5
``````

(Yes, I’m about to read the documentation, but I often prefer to figure things out). I’m guessing that the 5 is the distance of the camera away from the center, because it seems like we’re about 5 away from the middle of the floor. So making that larger would move the camera back. A quick check verifies that for me.

`camera.position` is going to be a 3-vector, certainly, and so, very likely, `camera.forward` is also a vector. What is it likely to be?

Based on years of experience, I suspect `camera.forward` is a unit vector pointing in front of the camera, however it’s currently rotated. If that’s the case, then the negative of that, times five, is a point 5 meters behind where the camera is now.

This is all speculation. Earlier you were telling me to read the documentation. You’ll be happy to hear that neither `camera.position` nor `camera.forward` are documented at all. We’re on our own unless we can find the source code somewhere.

My next step is going to be to display some values to see what happens. I think I’d like to display camera position and camera forward.

Hm, something just occurred to me. That update code runs every time. So even if we’re not moving our finger, the camera angles will be set, and then position will be set. Yet the camera doesn’t move. Interesting. Anyway I printed some values. Doing so didn’t help me much.

Most confusing. More study required. I’ll do that and report back.

Camera Explained

After my lunch break I mentioned to a friend in another world that I didn’t understand how the camera worked. No sooner had I said that than I did realize how it works. Here’s a picture I just drew:

In the left frame, the camera is 5 back from the floor center, looking at the wide front of the orc. The view is shown below, some details left to the reader.

Then we move our finger on the screen. As we can “readily see” in this code, that rotates the camera to the right.

``````    if CurrentTouch.state == MOVING then
CameraX = CameraX - CurrentTouch.deltaX * 0.25
CameraY = CameraY - CurrentTouch.deltaY * 0.25
end
--Set the camera rotation and position to look at the center of the scene
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
``````

With the camera rotated exactly 90 degrees right, for convenience, we see that `camera.forward` will be (1,0). So this code …

``````    scene.camera.position = -scene.camera.forward * 5
``````

… calculates (-5,0) and moves the camera to that position. That leaves it pointing at the side of the orc, which is what we expect.

This code assumes that the nominal target of the camera is at the zero point. So, in essence, the move really does two things, move to zero and then back off by -5. Since `camera.forward` is really a vector offset, not a position, our (-5,0) is also an offset. If we calculated `target + offset`, where target is a position, we’d wind up with a new position. Strictly speaking, we’ve swapped in a new datatype by just moving to the (-5,0) but to Codea a vector is a vector and it “just works”.

Simple trick. A bit obscure, and as written only works if you are essentially aiming at the zero point of your scene. But it does work, and now I understand it. I hope this explanation has worked for you.

If it doesn’t, and you have a better one, let me know and I’ll update this article.

Critiquing our code …

Let’s take a look at this code and see what improvement it might need. Here’s the whole program:

``````-- Orc-1
-- RJ 20200130

function setup()
scene = craft.scene()
scene.sky.active = false
createGround(-1.125)

MyOrc = scene:entity()
MyOrc.model = craft.model("Blocky Characters:Orc")
MyOrc.x = 0
MyOrcStep = 0.02
MyOrc.y = -1
MyOrc.z = 0
MyOrc.scale = vec3(1,1,1) / 8
MyOrc.eulerAngles = vec3(0, 180, 0)

scene.camera.z = -4
CameraSettings = scene.camera:get(craft.camera)
CameraX = 0
CameraY = 0
FieldOfView = 60
Ortho = false
OrthoSize = 5
CameraSettings.fieldOfView = FieldOfView
CameraSettings.ortho = Ortho
CameraSettings.orthoSize = OrthoSize
end

function update(dt)
updateOrc(dt)
updateCamera(dt)
scene:update(dt)
end

-- Called automatically by codea
function draw()
update(DeltaTime)

-- Draw the scene
scene:draw()
end

-- Creates the ground using a box model and applies a simple textured material
function createGround(y)
local ground = scene:entity()
ground.model = craft.model.cube(vec3(4,0.125,4))
ground.material = craft.material("Materials:Specular")
ground.material.specular = color(0, 0, 0, 255)
ground.material.offsetRepeat = vec4(0,0,5,5)
ground.y = y
return ground
end

function updateOrc(dt)
local x = MyOrc.x + MyOrcStep
if math.abs(x) >= 2 then MyOrcStep = -MyOrcStep end
MyOrc.x = x
end

function updateCamera(dt)
if CurrentTouch.state == MOVING then
CameraX = CameraX - CurrentTouch.deltaX * 0.25
CameraY = CameraY - CurrentTouch.deltaY * 0.25
end
--Set the camera rotation and position to look at the center of the scene
print("pos-in", scene.camera.position)
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
print("angles", scene.camera.eulerAngles, "forward", scene.camera.forward)
scene.camera.position = -scene.camera.forward * 5
print("pos-out", scene.camera.position)
end
``````

Well, I hate that the comments are folding on my screen. If they fold on yours, I apologize. But “comments are the code’s way of asking to be made more clear”, so with luck they’ll go away soon,

LOL, here’s a quick fix in waiting:

``````-- Called automatically by codea
function draw()
update(DeltaTime)

-- Draw the scene
scene:draw()
end
``````

Love the comment before `scene:draw`, in case you didn’t know what that meant. I’ll remove that, and the blank lines. I’m inclined to leave the “called automatically” comment, not because I need it, but because some new folks might appreciate the reminder. So:

``````-- Called automatically by codea
function draw()
update(DeltaTime)
scene:draw()
end
``````

Ship it, it’s better.

Seriously, many folks would check into trunk with this change. Trunk-based development is great, and works best if we check in frequently. Topic for another day, perhaps, but consider it done.

Truth is, I’ve not put this code under version control, because despite that it’ll fill up four or five articles, it’s essentially a trivial throw-away. I’ll surely regret not putting it under version control anyway, and when that happens I’ll be sure to tell you.

What’s next? Well, obviously, it’s that `setup:`

``````function setup()
scene = craft.scene()
scene.sky.active = false
createGround(-1.125)

MyOrc = scene:entity()
MyOrc.model = craft.model("Blocky Characters:Orc")
MyOrc.x = 0
MyOrcStep = 0.02
MyOrc.y = -1
MyOrc.z = 0
MyOrc.scale = vec3(1,1,1) / 8
MyOrc.eulerAngles = vec3(0, 180, 0)

scene.camera.z = -4
CameraSettings = scene.camera:get(craft.camera)
CameraX = 0
CameraY = 0
FieldOfView = 60
Ortho = false
OrthoSize = 5
CameraSettings.fieldOfView = FieldOfView
CameraSettings.ortho = Ortho
CameraSettings.orthoSize = OrthoSize
end
``````

We need to give `setup` the same treatment we gave `update`, using the Composed Method pattern (Composed Function in this case, actually), by extracting some functions:

``````function setup()
scene = craft.scene()
setupSky(scene)
setupOrc(scene)
setupCam(scene)
end

function setupSky(scene)
scene.sky.active = false
createGround(-1.125)
end

function setupOrc(scene)
MyOrc = scene:entity()
MyOrc.model = craft.model("Blocky Characters:Orc")
MyOrc.x = 0
MyOrcStep = 0.02
MyOrc.y = -1
MyOrc.z = 0
MyOrc.scale = vec3(1,1,1) / 8
MyOrc.eulerAngles = vec3(0, 180, 0)
end

function setupCam(scene)
scene.camera.z = -4
CameraSettings = scene.camera:get(craft.camera)
CameraX = 0
CameraY = 0
FieldOfView = 60
Ortho = false
OrthoSize = 5
CameraSettings.fieldOfView = FieldOfView
CameraSettings.ortho = Ortho
CameraSettings.orthoSize = OrthoSize
end
``````

Here again, I prefer this arrangement. I can look at setup and see that it creates a scene and then sets up three items, ground, orc, and camera. (I should rename that setupCamera. Doing it right now.) Then if I want to see how the camera is set up, I can read that bit. And if I don’t (and I really don’t), I can just trust that that’s what goes on in `setupCamera`.

It appears to me that `CameraSettings` doesn’t need to be global (though CameraX and CameraY may need to be, as they are referred to in both `setup` and `update`). I’ll try making that local, and if that works demote it to lower case, as I’m using upper case for globals (mostly).

``````function setupCamera(scene)
scene.camera.z = -4
local cameraSettings = scene.camera:get(craft.camera)
CameraX = 0
CameraY = 0
local fieldOfView = 60
local ortho = false
local orthoSize = 5
cameraSettings.fieldOfView = fieldOfView
cameraSettings.ortho = ortho
cameraSettings.orthoSize = orthoSize
end
``````

I also made `fieldOfView`, `ortho`, and `orthoSize` local. They were global because I copied and pasted from the Learn Craft example, which had `parameters` looking at them.

This is why copy/paste is so dangerous: this code injected four global variables into our tiny little application, which increases the code complexity by a disproportionate amount, since we have to wonder whenever we see a global, who else is looking at it.

Relatedly, note that I pass the scene to all the setup functions. I prefer that to letting them all fiddle with globals. I should make that practice part of my coding standard.

There’s no way around making the scene a global (at least not right now) but I can at least limit access to it. Let’s do that, and name it upper-case `Scene` as well, which should help me catch any references to the global.

That went … almost well

I did the scene conversion all in one go. Renaming the `scene` global to `Scene` was a good idea, as it meant that all the references I missed were flagged at run time. However, if this program were more complex, so that some objects or actions only happened part of the time, a missed scene reference might not have shown up so readily. Lua defaults variable names to global and initializes them to nil. That’s not always ideal.

In any event, in a few minutes I got down to this version, which now has only a few limited occurrences of the `Scene` global.

`````` -- Orc-1
-- RJ 20200130

function setup()
Scene = craft.scene()
setupSky(Scene)
setupOrc(Scene)
setupCamera(Scene)
end

function setupSky(scene)
scene.sky.active = false
createGround(-1.125, scene)
end

function setupOrc(scene)
MyOrc = scene:entity()
MyOrc.model = craft.model("Blocky Characters:Orc")
MyOrc.x = 0
MyOrcStep = 0.02
MyOrc.y = -1
MyOrc.z = 0
MyOrc.scale = vec3(1,1,1) / 8
MyOrc.eulerAngles = vec3(0, 180, 0)
end

function setupCamera(scene)
Scene.camera.z = -4
local cameraSettings = scene.camera:get(craft.camera)
CameraX = 0
CameraY = 0
local fieldOfView = 60
local ortho = false
local orthoSize = 5
cameraSettings.fieldOfView = fieldOfView
cameraSettings.ortho = ortho
cameraSettings.orthoSize = orthoSize
end

function update(dt, scene)
updateOrc(dt, scene)
updateCamera(dt, scene)
scene:update(dt)
end

-- Called automatically by codea
function draw()
update(DeltaTime, Scene)
Scene:draw()
end

-- Creates the ground using a box model and applies a simple textured material
function createGround(y, scene)
local ground = scene:entity()
ground.model = craft.model.cube(vec3(4,0.125,4))
ground.material = craft.material("Materials:Specular")
ground.material.specular = color(0, 0, 0, 255)
ground.material.offsetRepeat = vec4(0,0,5,5)
ground.y = y
return ground
end

function updateOrc(dt)
local x = MyOrc.x + MyOrcStep
if math.abs(x) >= 2 then MyOrcStep = -MyOrcStep end
MyOrc.x = x
end

function updateCamera(dt, scene)
if CurrentTouch.state == MOVING then
CameraX = CameraX - CurrentTouch.deltaX * 0.25
CameraY = CameraY - CurrentTouch.deltaY * 0.25
end
--Set the camera rotation and position to look at the center of the scene
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5
end
``````

“One more thing …” – Columbo1

The variables `CameraX` and `CameraY` need to be global, I think, but I don’t need to init them to zero in setup if I do this:

``````function updateCamera(dt, scene)
CameraX = CameraX or 0
CameraY = CameraY or 0
if CurrentTouch.state == MOVING then
CameraX = CameraX - CurrentTouch.deltaX * 0.25
CameraY = CameraY - CurrentTouch.deltaY * 0.25
end
--Set the camera rotation and position to look at the center of the scene
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5
end
``````

That’s a Lua idiom for “if CameraX isn’t nil, use it, otherwise use zero. So at a little cost in update, I can limit the references to those two globals to this one function. I’m willing to pay that price. YMMV of course.

I wonder if we can simplify further, by not updating the camera outside the if, and moving the `or` bits inside. Only one way to be sure: I’ll try it.

Oh yeah, that’s better:

``````function updateCamera(dt, scene)
if CurrentTouch.state == MOVING then
CameraX = (CameraX or 0) - CurrentTouch.deltaX * 0.25
CameraY = (CameraY or 0) - CurrentTouch.deltaY * 0.25
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5
end
end
``````

If we had a camera object, we could keep those variables as member variables, but for now this localizes the global references to this one small method. One does have to know that Lua idiom, but we’re supposed to know our programming language, so that’s OK in my book.

Summing Up

I think this is about as clean as we can make a basically procedural solution. Next time, I’ll see about moving the Orc’s motion into an object. I see two steps to doing that but I could be wrong. We’ll see.

I kind of suspect that maybe three people in the whole world are actually interested in this. If so, it’s not worth the effort writing these articles: it takes longer to write and edit them than it does to write the code, by a factor of probably five. So if you are enjoying these and find them useful, please tweet me or DM me or drop me an email. Thanks!

Here’s the whole program:

``````-- Orc-1
-- RJ 20200201
-- upper case variables are global

function setup()
Scene = craft.scene()
setupSky(Scene)
setupOrc(Scene)
setupCamera(Scene)
end

function setupSky(scene)
scene.sky.active = false
createGround(-1.125, scene)
end

function setupOrc(scene)
MyOrc = scene:entity()
MyOrc.model = craft.model("Blocky Characters:Orc")
MyOrc.x = 0
MyOrcStep = 0.02
MyOrc.y = -1
MyOrc.z = 0
MyOrc.scale = vec3(1,1,1) / 8
MyOrc.eulerAngles = vec3(0, 180, 0)
end

function setupCamera(scene)
Scene.camera.z = -4
local cameraSettings = scene.camera:get(craft.camera)
local fieldOfView = 60
local ortho = false
local orthoSize = 5
cameraSettings.fieldOfView = fieldOfView
cameraSettings.ortho = ortho
cameraSettings.orthoSize = orthoSize
end

function update(dt, scene)
updateOrc(dt, scene)
updateCamera(dt, scene)
scene:update(dt)
end

-- Called automatically by codea
function draw()
update(DeltaTime, Scene)
Scene:draw()
end

-- Creates the ground using a box model and applies a simple textured material
function createGround(y, scene)
local ground = scene:entity()
ground.model = craft.model.cube(vec3(4,0.125,4))
ground.material = craft.material("Materials:Specular")
ground.material.specular = color(0, 0, 0, 255)
ground.material.offsetRepeat = vec4(0,0,5,5)
ground.y = y
return ground
end

function updateOrc(dt)
local x = MyOrc.x + MyOrcStep
if math.abs(x) >= 2 then MyOrcStep = -MyOrcStep end
MyOrc.x = x
end

function updateCamera(dt, scene)
if CurrentTouch.state == MOVING then
CameraX = (CameraX or 0) - CurrentTouch.deltaX * 0.25
CameraY = (CameraY or 0) - CurrentTouch.deltaY * 0.25
scene.camera.eulerAngles = vec3(CameraY, CameraX, 0)
scene.camera.position = -scene.camera.forward * 5
end
end
``````

1. Jeffries shows his age.