Today I’ll review the code, and think about whether to build in some object thinking. I want to … but should I?

I haven’t looked yet this morning, but my recollection is that the current code for my moving Orc is pretty clean. But I have questions.

In the first sample that I copied, there was a small object, a “RonMover” associated with the entity that represented the Orc. The comment in the source code where I stole that idea was this:

-- Create an entity and attach a Mover to make it move automatically

The Mover class only contained an init method and an update method, the former recording the entity and speed, and the latter moving the entity.

To me, that seems odd. Here are two ways one might imagine doing it, first the Mover way, then what I’d have expected.

Mover
There’s an entity for every object in the scene. Entities with behavior have an attached object, such as a “Mover”, that give them behavior.
Object
There are objects in the scene. Some have behavior, some do not.

The problem with the second is that Codea Craft just doesn’t work that way. Codea manages a collection of entity objects, not a collection of Orcs and Castles and the like. An entity can have (only) one associated component (of a given type). The word “type” here isn’t defined very well. Just “the type of component to add to this entity”. We may have to do an experiment to figure out just what this means. For now, I’m supposing it means you can only add one Lua class, though you can also add a renderer or a shape;box or the like.

As I think about it, it seems that I’ll need to think of any classes I write as being attached to the entity, sort of in the style of the Decorator pattern. However, that still leaves a design question open, I think.

Who constructs the entity?

In our present code, the Orc is built this way:

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)
    --MyOrc:add(RonMover)
end

The comment at the bottom shows where we’d add in our Mover. But what about all that other stuff? Right now, that’s done at a higher level than our mover, a higher level than “the Orc”, in my mind.

We could continue to do that, but suppose we create some newer kind of faster-moving Orc? Its creation would look just like the above, except for something like

MyOrcStep = 0.04

Furthermore, as written, we need to create a global, like MyOrc for every Orc, and add code to our update method for each one. That’ll never do.

We could build a mover class to deal with that global, but we’d still have all the creation logic written in line and at least one level above where it should be.

So I think a better design is to have a new class, Orc, that does all the entity creation and hooking up. We’ll work in that direction this morning.

This is a refactoring. When we’re done, we will have exactly the same scene and motion as before. No external behavior change. However, it’s a big refactoring.

What? You call that big??{: .pull-quote}

Yes, big. You probably think a big refactoring is one that takes a week or a month. I think that all refactorings can be made small, and certainly whenever I can see how to make them small, I do so. In this case, I see a split around moving, and setting up … in that order.

New Orc Class

My plan is to build a class named Orc, not Creature or MovableObject or anything more general. Of course I see the future need for that. Of course I’m tempted to do it. But I’ve learned that my vision for a generalized class is pretty weak when I’ve never even built a simple concrete class. And I’ve almost successfully disciplined myself to always start with the simple concrete implementation.

Of course the argument is that we’re gonna need the general stuff and we might as well write it now. I’ve made that argument, and I think it’s generally mistaken. Since I’m writing all this up, you’ll get to see what happens.

My first step is to add an Orc instance to the entity. (I’ll move entity creation into Orc as a later step. My mission now is to get this thing working quickly and cleanly.

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)
    MyOrc:add(Orc,MyOrc)
end

This requires me to build an Orc class:

Orc = class()

function Orc:init(entity)
    self.entity = entity
    self.step = 0.02
end

function Orc:update(dt)
    local x = self.entity.x + self.step
    if math.abs(x) >= 2 then self.step = -self.step end
    self.entity.x = x
end

I also commented out the code in updateOrc:

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

I saved it because I was afraid I’d have to refer to it again. The Orc moves fine, and I can delete that code, and the call to it, entirely.

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

I checked to be sure it was working by changing step to be much larger, and sure enough the Orc moved much more rapidly.

Now let’s move creation and population of the entity into the Orc class. We could do this in a few steps, but I’m feeling strong, so I’ll try to do it in one go.

Arrgh!

That doesn’t work. After a bit of confusion I realize that when we call entity:add, we pass a class name, not an instance. The add then instantiates our class, passing … [reads docs] … the entity plus whatever other parameters we include on the call to add.

OK. So to build an object our minimum call in setup will be, roughly:

scene:entity():add(Orc)

At least that’s what I’ll try. Then we can init the rest of the entity as planned in the Orc:init(). Trying again:

function setupOrc(scene)
    scene:entity():add(Orc)
end

Orc = class()

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

function Orc:update(dt)
    local x = self.entity.x + self.step
    if math.abs(x) >= 2 then self.step = -self.step end
    self.entity.x = x
end

A minor bug was that even though I just said to use scene:entity():add(Orc), I forgot to edit setupOrc.

Anyway, now the Orc moves again. It’s 1040 here, and I’d like to leave by 1100, so we’ll stop for now, right after lessons learned, and the code.

First the code in Main. (Orc is as above.)

-- Orc-1
-- RJ 20200203
-- upper case variables are global
-- 20200203: Orc Class

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)
    scene:entity():add(Orc)
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)
    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.map = readImage("Blocks:Dirt")
    ground.material.specular = color(0, 0, 0, 255)
    ground.material.offsetRepeat = vec4(0,0,5,5)
    ground.y = y
    return ground
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

Lessons Learned

Even though I broke Orc up into two steps, first attaching and moving, and then putting the Orc creation parameters inside the class, the second step still got me in trouble.

Now in the old days, I’d bang my forehead and tell myself to think better or work harder or something. These days, I try to think of a smaller step.

However, I had a fundamental misconception about creating the Orc, even though I “knew” that you pass a class name, not an instance, to entity:add. The desire to create the object and then add it, which to me is a more conventional structure, was too strong, and I forgot.

It’s possible that I need some micro-tests here, as described in my earlier TDD article. But I didn’t see then what those tests would be like, and at this moment, I still don’t.

Was there a smaller step than moving all the Orc setup into init in one go? Surely there was. We had this code:

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)
    MyOrc:add(Orc,MyOrc)
end

I note that MyOrc is an entity, not an Orc. Possibly that confused me. As for a smaller step, what if I had just moved one line out of setupOrc into Orc:init()? The model line would have been ideal, but almost any line would have worked.

It would still require me to begin accessing the entity inside Orc, and it would have just as readily discovered any mistakes like expecting scene and getting entity, which I think happened at one point.

So my big lesson here is that smaller steps, even smaller than I can at first imagine, and still a good idea.

Other lessons are just hammering Craft concepts into my head, like the fact that you add a class name, not an instance, and that you can’t create the entity inside the Orc class because of that. So the start, to me, is a bit odd, but it should always be the same.

One more thing: “Ron, but WHY???”

I want to address (again) why I think it’s worth-while to spend this much effort on a trivial throw-away program.

The more obvious reason is that in these articles, I’m trying to give the reader insight into what I think about, my reasons for doing things, and a sense of when things go well and when they don’t. This is in contrast to articles that just go BEHOLD! and there’s the code. Maybe some programmer somewhere is good enough to do the behold trick, but I’ve never met them.

Perhaps less obvious is that I’m trying to work out, on a small stage, good ways of building things. I’ve mentioned before that the programming part of these articles is a small fraction of the time used. Most of it is in the writing of the articles. The time spent doing and learning is really quite small, and the payoff is large.

Whatever my “real” Codea Craft application, or whatever real application I might be working on, I need its design to be very good, because poor design slows me down. So working in a small example, and growing it in directions that are next steps for a “real” program, gives me a solid and visible sense of how to do things.

I expect that we’ll see a bit of that happening when I move to more complex motion, and motion of more than one kind of creature.

Finally, I have a fantasy of writing up at least some of Codea Craft with examples that are useful, not just for exercising its capabilities, but that show a learner what a good design is like.

OK, I also admit that I really enjoy making code nicer. That’s in there too.

See you next time!