I am supposedly on a course to make the Orc stay within bounds of our little world. Today might be the day …

Between Monday and Tuesday, I got the Creature (Orc) to walk around a bit, turning randomly from time to time. The implementation is fairly good, I think, for what it does, but our creatures will need a lot more intelligence than they have now.

Issues that come to mind include:

  • Creation of, um, creatures at different locations
  • with different appearance
  • with different but shared behaviors
  • with different rates of consuming and gaining energy
  • with different food preferences
  • with different appeal to creatures of their gender preference

You get the idea: there’ll need to be lots of variability in our creatures, most of it represented in their behavior. We can – and should – speculate about how this might be done.

Surely there will be some number of behaviors, and many of those will have parameters. There might be a food-seeking behavior, triggered when the creature’s energy falls below some parameter value. Creatures that wait too long to eat may starve. Creatures that eat too often are more subject to being attacked while chowing down.

Behaviors themselves, taking the long view, will be specified by some kind of user interface, even though now we specify them in code. That interface might be some kind of table, behavior name in the first column, parameters in subsequent columns. (It might be something entirely different. I am not a clever man.)

From a conventional object-oriented viewpoint, I might expect that a Creature would be an object with member variables representing things like energy, and methods on the object to get and set energy. The particular food-seeking algorithm might be an instance of “Behavior” from a collection of Behaviors that the Creature “has”. The behavior would use the Creature’s primitive methods to decide what to do and cause the creature to do it.

We could also imagine just building everything in the Creature, with even the most esoteric behaviors just being methods on Creature. The creature might still have a list of the behaviors it uses and the parameters for those, but the behavior would all be inside Creature.

And we might do it some other way entirely.

Our mission as we go forward is to balance this design thinking with a very limited but clean implementation of what we need right now. We’d like to decide things based on our real needs rather than our anticipated needs. Kent Beck used to call this “letting the code participate in the design”. It has been my practice for the past two decades to try to work entirely in this mode of discovering the right design, while thinking actively all the time about design alternatives. All in all, it has worked out pretty well, but of course everything I do is small. YMMV, but this is my game, so we’ll do it my way.

For today, I’ll work on having the Creature avoid going over the edge of the world. One way to do that would be to build an edge on the world and have the creature detect it and move away from it. Another way would be to have the Creature “just know” the limits of the world and tend away from them based on that internal knowledge.

In my 2D Braitenberg articles I did the latter: the avoidWalls capability knew where the edges were. I think putting an edge on the world is probably “better” in that it keeps our focus on the creature’s senses and responses. So we’ll push toward that.

However … we want to go bit by bit, so depending on what the code tells me, I might hard code the detection of the edge of the world and then create some kind of detectable object to avoid as a second (or nth) phase of edge avoidance.

We’ll see. Time to ask the code.

Wall avoidance - first notions

Our present movement scheme is that behaviors return a table containing a speed and/or a turn value. The values are “summed up” and applied at the end of running all the behaviors.

So … what if, when we are “too close” to a wall, we were to turn away from it by some amount? That might work, and might be interesting.

I’ll start by defaulting the Creature to have two behaviors. It has self.walk now, and I’ll give it self.avoid, then see if I can build the avoid method.

My first attempt is ugly and doesn’t quite work:

function Creature:avoid()
    local twist = 100
    local max = 15
    local min = 1
    local tab = {speed=5, turn=0}
    local eyeR = self:eyePos(vec3(0.5, 0, 0.5))
    local eyeL = self:eyePos(vec3(-0.5, 0, 0.5))
    if (eyeR.x > max or eyeL.x > max) then
        print("xmax")
        if ( eyeR.x > eyeL.x ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
    end
    if (eyeR.z > max or eyeL.z > max) then
        print("zmax")
        if ( eyeR.z > eyeL.z ) then
            tab.turn = tab.turn + twist
        else
            tab.turn = tab.turn - twist
        end
    end
    if (eyeR.x < min or eyeL.x < min) then
        print("xmin")
        if ( eyeR.x > eyeL.x ) then
            tab.turn = tab.turn + twist
        else
            tab.turn = tab.turn - twist
        end
    end
    if (eyeR.z < min or eyeL.z < min) then
        print("zmin")
        if ( eyeR.z > eyeL.z ) then
            tab.turn = tab.turn + twist
        else
            tab.turn = tab.turn - twist
        end
    end
    return tab
end

function Creature:eyePos(eyeRel)
    return self.entity:transformPoint(eyeRel)
end

Summing that up, it computes the distance of the eyes (0.5 ahead and 0.5 left or right of Orc center) from the wall and makes a big turn based on which eye is closer to the wall. This often works but something oscillates and we wander over the edge vibrating.

And anyway, it’s ugly.

We do want to tend away from danger, and (I think) we want to determine danger based on whether it’s to our left or our right, which is what the eye stuff is about. I could use a distance approach there and probably should. I tried various values of twist and always seemed to be able to get to a point where the Creature would oscillate, presumably because the twist was enough to change the eye that was closer. It usually happened when heading more or less straight at the wall. Now it could be that one of the turns is backward: it’s so ad hoc that I really don’t know.

I want to see what a smaller twist might do. We might veer away slowly. Or fall off sooner.

OK, in testing a smaller twist, I detected another place where he turned the wrong way. This code seems to “work”:

function Creature:avoid()
    local twist = 21
    local max = 15
    local min = 1
    local tab = {speed=10, turn=0}
    local eyeR = self:eyePos(vec3(0.5, 0, 0.5))
    local eyeL = self:eyePos(vec3(-0.5, 0, 0.5))
    if (eyeR.x > max or eyeL.x > max) then
        print("xmax")
        if ( eyeR.x > eyeL.x ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
        return tab
    end
    if (eyeR.z > max or eyeL.z > max) then
        print("zmax")
        if ( eyeR.z > eyeL.z ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
        return tab
    end
    if (eyeR.x < min or eyeL.x < min) then
        print("xmin")
        if ( eyeR.x > eyeL.x ) then
            tab.turn = tab.turn + twist
        else
            tab.turn = tab.turn - twist
        end
        return tab
    end
    if (eyeR.z < min or eyeL.z < min) then
        print("zmin")
        if ( eyeR.z > eyeL.z ) then
            tab.turn = tab.turn + twist
        else
            tab.turn = tab.turn - twist
        end
        return tab
    end
    return tab
end

Of course it’s not obvious to the casual observer why it works, and it’s rife with duplication. But it works like this:

So that’s good. Shall we rethink the solution or refactor it? We could remove the duplication between x and z min with a function: those blocks are otherwise the same. We could do the same to the max ones. We’d still have some duplication but maybe it’d be clear how to remove it. (I think we’d like to go to a distance calculation to do that, but I’m not sure.)

There might, however, be some way to do the whole thing in 3D or at least 2D with distance calculations right out of the box.

Or, we could go wild. Craft includes two interesting functions, rayCast and sphereCast. These can tell you whether there’s something in front of you. However, that requires using at least part of Craft’s physics capability. You’d create a STATIC rigidbody, e.g. a wall, and then your cast could detect it. That’s something we’ll need to explore but it seems too big for right now. We’ll do a physics spike later.

Another possibility that could help collapse the code here would be to compute the distance from our eyes to a line, that is, the line representing the edge. We’d still have four cases, but they might collapse more easily.

That’s a larger step, however, and I am inclined just to refactor and see what happens. Usually that leads to good things.

Ah. This works:

function Creature:avoid()
    local twist = 21
    local max = 15
    local min = 1
    local tab = {speed=10, turn=0}
    local eyeR = self:eyePos(vec3(0.5, 0, 0.5))
    local eyeL = self:eyePos(vec3(-0.5, 0, 0.5))
    if (eyeR.x > max or eyeL.x > max) then
        print("xmax")
        if ( eyeR.x > eyeL.x ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
        return tab
    end
    if (eyeR.z > max or eyeL.z > max) then
        print("zmax")
        if ( eyeR.z > eyeL.z ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
        return tab
    end
    tab.turn = tab.turn + self:twistForMin(eyeR.x, eyeL.x, min)*twist
    tab.turn = tab.turn + self:twistForMin(eyeR.z, eyeL.z, min)*twist
    return tab
end

function Creature:twistForMin(eyeR, eyeL, min)
    if eyeR < min or eyeL < min then
        return self:sign(eyeR-eyeL)
    else
        return 0
    end
end

function Creature:eyePos(eyeRel)
    return self.entity:transformPoint(eyeRel)
end

function Creature:sign(anInteger)
    if anInteger >= 0 then
        return 1
    else 
        return -1
    end
    
end

The new function twistForMin handles the two min cases. A similar function can be written for max. However …

Suppose we had four arguments, x value, x min, y value, y min. If we were to call with eyeR.x, min, eyeL.x, min that would be the same as we have now. What if we called with max,eyeR.x,max,eyeL.x? Would that handle the max case, reducing the two functions to one? It seems to me that it might.

But that is for another day, because it is 10:59 AM and my time here is up.

See you next time!

Here’s all of Creature. The other tabs are unchanged.

Creature = class()

local CreatureMaxTime = 300

function Creature:init(entity, x, z, behaviors)
    self.entity = entity
    self.behaviors = behaviors or {self.avoid}
    self.time = 0
    entity.x = x
    entity.z = z
    entity.y = 2 -- just above the ground
    entity.model = craft.model("Blocky Characters:Orc")
    entity.scale = vec3(1,1,1)/8
end

function Creature:update(dt)
    local divisor = 100
    local speed = 0
    local turn = 0
    for _,b in pairs(self.behaviors) do
        local tab = b(self)
        speed = speed + (tab.speed or 0)
        turn = turn + (tab.turn or 0)
    end
    local rotor = quat.eulerAngles(0,turn,0)
    self.entity.rotation = self.entity.rotation*rotor
    local move = self.entity.forward
    self.entity.position = self.entity.position + move*speed/divisor
end

function Creature:walk()
    local tab = {speed=1}
    self.time = self.time + 1
    local speed = 200
    if self.time == 100 then
        self.time = 0
        if math.random() < 0.5 then
            tab.turn = 45
        else
            tab.turn = -45
        end
    end
    return tab
end

function Creature:avoid()
    local twist = 21
    local max = 15
    local min = 1
    local tab = {speed=10, turn=0}
    local eyeR = self:eyePos(vec3(0.5, 0, 0.5))
    local eyeL = self:eyePos(vec3(-0.5, 0, 0.5))
    if (eyeR.x > max or eyeL.x > max) then
        print("xmax")
        if ( eyeR.x > eyeL.x ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
        return tab
    end
    if (eyeR.z > max or eyeL.z > max) then
        print("zmax")
        if ( eyeR.z > eyeL.z ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
        return tab
    end
    tab.turn = tab.turn + self:twistForMin(eyeR.x, eyeL.x, min)*twist
    tab.turn = tab.turn + self:twistForMin(eyeR.z, eyeL.z, min)*twist
    return tab
end

function Creature:twistForMin(eyeR, eyeL, min)
    if eyeR < min or eyeL < min then
        return self:sign(eyeR-eyeL)
    else
        return 0
    end
end

function Creature:eyePos(eyeRel)
    return self.entity:transformPoint(eyeRel)
end

function Creature:sign(anInteger)
    if anInteger >= 0 then
        return 1
    else 
        return -1
    end
    
end

Monday …

Yes, well. The above was done on Wednesday, 11 March. Today is Monday, 16 March. Starting Thursday I began “social distancing”, i.e. Stay The F___ Home. There are two reasons for doing that. One is that I’m quite old and therefore vulnerable should I get this thing. The other is that it seems to me to be the right thing to do.

Anyway, it threw off my rhythm a bit so I’m trying to create a new one. We’ll see how that goes.

Hm. I’m typing this with my iPad on the keyboard tray of my desk, with the iMac keyboard right beside the iPad. I could use the Mac Scrivener, and keep the article on the other keyboard. I wonder if that would be better or worse. I’ll find out later. For now, let’s do some stuff.

I’ve got one Creature-Orc doing a netflix sort of run around the plain. I think the next few steps might be:

  • Give the Creature random motion rather than just straight
  • Set a few of them loose, probably on a larger plane
  • Give them things to do, like fight or mate or eat food.

That’s how it looks today. And today will be random motion, which will lead into a technical story that allows different Creatures to have different configurations. I solved that in my Braitenberg articles with partial functions (Currying). I’m looking toward configuring these guys with tables, so that it’s easier to build a GUI to configure them, which I vaguely foresee might be desirable.

Anyway, today, random motion.

When it updates, our creature considers two variables, speed and turn. It loops through its behaviors, which all return a table which can contain values for speed and turn, which are accumulated until all the behaviors run, and then applied, rotating our guy and setting his speed.

Hmm. Presently, it does set the speed. That is, the speed at one moment can’t be based on the speed the previous moment. We may need to change that, giving the Creature a current speed. We may want to treat the speed as an adjustment to current speed. The turn is an adjustment because it is added to the current rotation of the creature. We’ll see.

OK. I guess I’ll create a random motion behavior. At a guess, it should return a constant speed, and an occasionally varying angle. Here goes for a first cut.

Hmm again! I found this code, which seems interesting:

function Creature:walk()
    local tab = {speed=1}
    self.time = self.time + 1
    local speed = 200
    if self.time == 100 then
        self.time = 0
        if math.random() < 0.5 then
            tab.turn = 45
        else
            tab.turn = -45
        end
    end
    return tab
end

There are no references to this code. It was used in an earlier experiment where the Creature turned 45 degrees (randomly) every 100 steps. The line local speed = 200 is amusing, as it’s an orphan.

Anyway this is a good start so I’ll use it as a basis.

I also note that the method avoid is setting the speed to 10. I think for now we’ll move that to the initialization of speed in the update, and it’ll serve for now as a default speed. Then both avoid and walk will just need to adjust turn.

I’m going to do that all at once. This is a very bad idea: one should surely limit changes to one at a time, but I’m feeling strong. Let’s see how it goes.

Oh wait! I should read the prior articles, shouldn’t I? Might remind me what’s going on. The avoid method is only half refactored:

function Creature:avoid()
    local twist = 21
    local max = 15
    local min = 1
    local tab = {turn=0}
    local eyeR = self:eyePos(vec3(0.5, 0, 0.5))
    local eyeL = self:eyePos(vec3(-0.5, 0, 0.5))
    if (eyeR.x > max or eyeL.x > max) then
        print("xmax")
        if ( eyeR.x > eyeL.x ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
        return tab
    end
    if (eyeR.z > max or eyeL.z > max) then
        print("zmax")
        if ( eyeR.z > eyeL.z ) then
            tab.turn = tab.turn - twist
        else
            tab.turn = tab.turn + twist
        end
        return tab
    end
    tab.turn = tab.turn + self:twistForMin(eyeR.x, eyeL.x, min)*twist
    tab.turn = tab.turn + self:twistForMin(eyeR.z, eyeL.z, min)*twist
    return tab
end

function Creature:twistForMin(eyeR, eyeL, min)
    if eyeR < min or eyeL < min then
        return self:sign(eyeR-eyeL)
    else
        return 0
    end
end

We had pulled out the handling of the minimum walls. The plan was to pull out the maximum walls similarly, then combine the min and max cases, probably by doubling the control variables and reversing the order of arguments in the call.

I’d better complete that first. It would be bad to leave this messy code around.

Hey! I just noticed that the calling sequence for twistForMin is twistForMin(eyeR,eyeL,min). Surely the eyes should go eyeL, eyeR, because then the left eye would be the left parameter.

We’ll hold off on that for now …

WOW! Just had a big scare: when I put in the twistForMax code, it didn’t work, and my quick check led me to believe that the transform code that tells me eye positions in the world didn’t work, and if it didn’t work, then the whole scheme was broken and could only be working by accident.

Fortunately, after a minute or so of panic, I backed out the change and determined that, yes, he was really still bouncing off the max and therefore my panic was misplaced. There was just something wrong with my changes. So I did them again …

This time, I realized that I had probably been checking x and y, not x an z like one needs to, because I reflexively type x and y, not x and z. With z, it nearly worked … except that because we are over the max, instead of under the min, we need to turn the opposite way in the max case compared to the min.

Here’s where we are:

function Creature:avoid()
    local twist = 21
    local max = 15
    local min = 1
    local tab = {speed=3, turn=0}
    local eyeR = self:eyePos(vec3(0.5, 0, 0.5))
    local eyeL = self:eyePos(vec3(-0.5, 0, 0.5))
    tab.turn = tab.turn + self:twistForMax(eyeR.x, eyeL.x, max)*twist
    tab.turn = tab.turn + self:twistForMax(eyeR.z, eyeL.z, max)*twist
    tab.turn = tab.turn + self:twistForMin(eyeR.x, eyeL.x, min)*twist
    tab.turn = tab.turn + self:twistForMin(eyeR.z, eyeL.z, min)*twist
    return tab
end

function Creature:twistForMin(eyeR, eyeL, min)
    if eyeR < min or eyeL < min then
        return self:sign(eyeR-eyeL)
    else
        return 0
    end
end

function Creature:twistForMax(eyeR, eyeL, max)
    if eyeR > max or eyeL > max then
        return self:sign(eyeL-eyeR)
    else
        return 0
    end
end

function Creature:eyePos(eyeRel)
    return self.entity:transformPoint(eyeRel)
end

function Creature:sign(anInteger)
    if anInteger >= 0 then
        return 1
    else 
        return -1
    end
end

I need to think about this a bit. There’s a lot not to like.

I can certainly combine those two functions (I think) with judicious mixing and matching of the parameters. If I do so, it will become entirely unclear what’s going on. There will be four cryptic calls with parameters going every which way. This is not good.

It’s also not representing the “psychology” I’d like to see. What should happen is that the creature should tend to keep away from the edges. There might be a system-level check in case his tendencies don’t save him, but this instant-on approach isn’t very Creature like. Time to take a little break and think.

First, I’ll commit the code of course.

Afternoon

So, I thought a bit, read a bit, listened to music a bit, napped a bit.

It seems to me that the fundamental idea here will be one of influence, either repelling or attracting, usually from a point, but sometimes (as with the edges of the world) from a line. The only lines I have right now are parallel with the axes of the world, but I suppose in principle there could be other lines to avoid. I suppose they’d be line segments, really. Most of that’s too speculative for now.

What I have now really won’t do, it amounts to a test that says one has gone too far, inset from the edge a bit. What we “really” need is influence, which will fall off with distance. I can imagine it falls off at one over distance, or one over distance-squared. And there might be a cutoff, that says the influence can’t be felt at all further away than some given distance.

All pretty speculative at this point. I always recommend thinking, though, and imagining what might come up. I just try not to build anything that I don’t really need.

Which reminds me. Some of this code, the distance calculations at least, looks pretty testable to me. It seems that I owe it to myself to link in the testing framework and write some tests.

Since I’m at a stable point, I’ll start with that. A bit boring but shouldn’t take long.

Nor did it. Set “RJ CodeaUnit” as a dependency, then add this test tab:

function testGame1()
    CodeaUnit.detailed = true

    _:describe("Game1 Tests", function()

        _:before(function()
            -- Some setup
        end)

        _:after(function()
            -- Some teardown
        end)
        
        _:test("Try again", function()
            _:expect( 2+2).is(3)
        end)

    end)
end

Now when I run the program, it runs, but there’s a button to run tests and when I run them, my 2+2 test fails:

After a bit of study I see my bug. The implementation of 3 should be 2+1, not 2+2. Making that change … and the test passes. CodeaUnit is hooked up. Best commit the code.

Now I’d better get down to it.

I’ve looked up the general formula for distance to a line and distance to a line segment, and they’re a bit calculation-heavy for my purposes. This is making me want to skip over the line avoidance, which I only need for the edge of the world, and which could be considered a part of the universe. We might even wrap the player around or something if he goes off the edge. So I think I’ll work on avoiding points, or objects at points.

What would be an easy way to start? Let’s make him avoid the point at x=7, z=10, which is a bit in front of where he starts. I’ll hard code a new method at first, adding it to the default behavior. That’s as simple as I can think of right now.

Well. I just typed this in:

function Creature:avoidPoint(point, force)
    local point = point or vec3(7,0,10)
    local force = force or 1
    local tab = {}
    local eyeL = self:eyePos(self.leftEye)
    local eyeR = self:eyePos(self.rightEye)
    local forceL = force/eyeL:dist(point)
    local forceR = force/eyeR:dist(point)
    if forceL > forceR then
        tab.turn = -force
    else
        tab.turn = force
    end
    return tab
end

The effect is … interesting:

He circles, once in a while switching directions. I’m not sure if that means I’ve got the signs switched or what. But I suspect I need to have a better dropoff than one over distance. I’ll try that first, but I may have to do some math if this doesn’t fall into place shortly.

This is odd. Changing to the square of the distance makes no visible change to his behavior:

function Creature:avoidPoint(point, force)
    local point = point or vec3(7,0,10)
    local force = force or 1
    local tab = {}
    local eyeL = self:eyePos(self.leftEye)
    local eyeR = self:eyePos(self.rightEye)
    local dL = eyeL:dist(point)
    local dR = eyeR:dist(point)
    local forceL = force/(dL*dL)
    local forceR = force/(dR*dR)
    if forceL > forceR then
        tab.turn = force
    else
        tab.turn = -force
    end
    return tab
end

Ah. We always apply the same force. We should adjust it by the distance, shouldn’t we. Which says to me we can probably just add the two forces (well, subtract them) to get the net effect. We’ll do that as a second step though.

function Creature:avoidPoint(point, force)
    local point = point or vec3(7,0,10)
    local force = force or 1
    local tab = {}
    local eyeL = self:eyePos(self.leftEye)
    local eyeR = self:eyePos(self.rightEye)
    local dL = eyeL:dist(point)
    local dR = eyeR:dist(point)
    local forceL = force/(dL*dL)
    local forceR = force/(dR*dR)
    if forceL > forceR then
        tab.turn = forceL
    else
        tab.turn = -forceR
    end
    return tab
end

OK this looks more natural, to the point where I think I’ll place a cube at the danger point so we can better see what’s going on.

In main, I add a call to this during setup:

function setupObstacle(scene, x, z)
    scene.voxels:fill("Bedrock")
    scene.voxels:box(x,2,z, x,4,z)
end

The result is a column of bedrock at 7,y,10, and our guy does avoid it. He’s always veering away from it a bit, which again makes me think we may need a cutoff. I’ll fiddle a bit and then give you another video, to keep the bandwidth down.

I think I’ll try setting the force to the difference of the two.

That didn’t work well at the beginning, because the two forces were equal, as we are heading directly at the column. So we never turn. There has been a bit of subtlety in these implementations, which has been that either with care in the inequalities, or my special implementation of sign, I ensured that we never got a zero effect.

Such subtle bits are not good, not least because you forget them by next Monday.

I also tried dividing the force by two, just to see if a smaller force was better, but I think I prefer the larger value. It does make me think we need a cutoff.

Also there’s a bit of an issue with the graphics. Voxels can only be created at unit coordinates, so our column, as built, has its corner at 7,y,10, not its center. I can build an explicit object and set it there, in the same way that our Orc moves to fractional positions.

Not too difficult, let’s try it.

I left the old post and the new. Creation is with this code:

function setupObstacle(scene, x, z)
    scene.voxels:fill("Bedrock")
    scene.voxels:box(7,1,10, 7,4,10)
    local entity = scene:entity()
    entity.x = x
    entity.y = 2
    entity.z = z
    entity.model = craft.model.cube(vec3(1,1,1))
    entity.material = craft.material("Materials:Standard")
end

I think the white cube is centered on 7,y,10, because with any luck model cubes would work that way, and the column has to fill a unit cube.

I still don’t like the responsiveness of the turning, as he kind of nicks the cube on the first pass. I tried one of dist instead of dist-squared, and that wasn’t notably better, plus it made the effect of avoidance more visible all across the plane.

OK, after some fiddling, I’ve got this:

function Creature:avoidPoint(point, force)
    local point = point or vec3(7,0,10)
    local force = force or 1
    local tab = {}
    local eyeL = self:eyePos(self.leftEye)
    local eyeR = self:eyePos(self.rightEye)
    local p = self.entity.position
    p.y = 0
    local d = p:dist(point)
    if d > 2 then return tab end
    local dL = eyeL:dist(point)
    local dR = eyeR:dist(point)
    local forceL = force/(dL*dL)
    local forceR = force/(dR*dR)
    if forceL > forceR then
        tab.turn = 5*forceL
    else
        tab.turn = -5*forceR
    end
    return tab
end

We ignore the obstacle unless we are within a distance of two from it. (To compute that, we had to force the entity position to the same y coordinate as the obstacle position, which happens to be zero. We’re really only paying attention to the x and z coordinates … note that the eyes are at y = 0 already.)

It’s getting close to supper time, so I’ll call it a day. I’ll start a new article next time, so what follows is the whole program as it now stands.

See you next time!


-- Game-1
-- RJ 20200222 initial plan, make a floor
-- RJ 20200226 camera object works w/o touch
-- RJ 20200227 cleanup
-- RJ 20200307 clean a bit for bugs

function setup()
    scene = craft.scene()

    -- Setup camera and lighting
    scene.sun.rotation = quat.eulerAngles(25, 125, 0)

    -- Set the scenes ambient lighting
    scene.ambientColor = color(127, 127, 127, 255)   
    
    allBlocks = blocks()
    
    -- Setup voxel terrain
    local m = 1 -- 5
    scene.voxels:resize(vec3(5,1,5))      
    scene.voxels.coordinates = vec3(-16*m,0,-16*m)    
    scene.voxels:fill("Dirt Grass")
    scene.voxels:box(0,1,0,16*m,1,16*m)
    scene.voxels:fill("Bedrock")
    scene.voxels:box(0,0,0, 16*m,0,16*m)
    
    setupCreature(scene)
    setupObstacle(scene, 7,10)
    setupCamera(scene)
end

function setupCamera(scene)
    scene.camera:add(GameCamera)
end

function setupCreature(scene)
    scene:entity():add(Creature, 7, 7, actions)
end

function setupObstacle(scene, x, z)
    scene.voxels:fill("Bedrock")
    scene.voxels:box(7,1,10, 7,4,10)
    local entity = scene:entity()
    entity.x = x
    entity.y = 2
    entity.z = z
    entity.model = craft.model.cube(vec3(1,1,1))
    entity.material = craft.material("Materials:Standard")
end

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


function draw()
    update(DeltaTime)
    scene:draw()	
end

-- blocks
-- RJ 20200222
-- ERJ 20200226 fixed dirt grass

function blocks()
    scene.voxels.blocks:addAssetPack("Blocks")
    local dirt = scene.voxels.blocks:new("Dirt Grass")
    dirt.setTexture(ALL, "Blocks:Dirt Grass")
    dirt.setTexture(UP, "Blocks:Grass Top")
    dirt.setTexture(DOWN, "Blocks:Dirt")
    local bedrock = scene.voxels.blocks:new("Bedrock")
    bedrock.setTexture(ALL, "Blocks:Greystone")
    local allBlocks = scene.voxels.blocks:all()
    return allBlocks
end


-- GameCamera
-- RJ 20200226
-- RJ 20200227 move camera rotation inside; cleanup
-- RJ 20200307 change starting rx ry for fun

GameCamera = class()

function GameCamera:init(entity)
    self.entity = entity
    self.camera = entity:get(craft.camera)
    self.rx = 45
    self.ry = 45
    self.rz = 0
    self.distance = 20
    self.target = vec3(8,0,8)
    self.camera.ortho = false
    self.camera.orthoSize = 5
    self.camera.fieldOfView = 60
end

function GameCamera:update(dt)
    if CurrentTouch.state == MOVING then 
        self.rx = self.rx - CurrentTouch.deltaY * 0.25 -- note XY inversion
        self.ry = self.ry - CurrentTouch.deltaX * 0.25
    end
    local rotation = quat.eulerAngles(self.rx, self.ry, self.rz)
    self.entity.rotation = rotation
    self.entity.position = -self.entity.forward * self.distance + self.target
end


-- TestGame
-- RJ 20200316

function testGame1()
    CodeaUnit.detailed = true

    _:describe("Game1 Tests", function()

        _:before(function()
            -- Some setup
        end)

        _:after(function()
            -- Some teardown
        end)
        
        _:test("Try again", function()
            _:expect(2+1).is(3)
        end)

    end)
end