StupidRPG: Bird is the Word

Today I’m working on the first indepently-acting NPC: a bird. Pretty exciting, yeah? It flies around the map looking for food, avoiding unwelcome contact from the player, and (when no longer hungry), paying a visit to Ranger Bob.

Its behavior will likely grow more complex over time, but as a proof of concept I’m pretty happy with how it’s working. I started with a basic outline of the bird’s possible actions and logic.

Behaviors
1
The bird roams the Forest, chattering away and looking for food.

Starting (Max) hunger is 100.
    Reduced by 60 when eating.
    Can go below 0.
    Increased by 1 per turn.
    
General Behaviors
    If the player tries to touch/take/hurt/socialize me, flee along loop

Hungry Behaviors
    Every turn, if the player is in the location, chirp
    Every turn, if there is something edible in the location, try to eat it
    Every 2nd turn, if there are no edibles in the location, move along loop
    If the bird feeder is here, check it for food

Full Behaviors (Hunger <= 20)
    Every turn, head toward ranger station
    If I'm at ranger station, sing

Next, I started putting together a set of functions to handle each of the bird’s actions: look for food, move along path, etc.

Actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
actions = {
'_lookForFood':function() {
var things = this.location().children;
for(var f in things) {
console.log("BIRD EYEBALLING "+things[f].name);
if(things[f].hasComponent('edible')) {
if(things[f].nutrition > 0) {
queueLocalOutput(this, "The bird pecks at the " + things[f].name + " eagerly.");
this.hunger -= things[f].nutrition;
return true;
} else {
queueLocalOutput(this, "The bird pecks at the " + things[f].name + " half-heartedly.");
return false;
}
}
}

return false;
},
'_moveAlongPath':function() {
var prev = this.location().key;
var next = this.path[prev];

this._alertPlayerToMovement(prev,next);

ECS.moveEntity(this, next);
},
'_alertPlayerToMovement':function(prev,next) {
if(player.locationIs(prev)) {
queueGMOutput("The bird flits away toward " + ECS.getEntity(next).name + ".");
} else if(player.locationIs(next)) {
queueGMOutput("The bird flits in from " + this.location().name + ".");
}
}
}

Finally, I put it together into some simple branching logic, executed on each game tick.

Bird Brain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function(){
// Look for food when hungry
if(this._hungry())
{
if(!this._lookForFood()) {
this._moveAlongPath();
}
return;
}

// Head toward ranger station when full
if(!this.locationIs('ranger-station'))
{
// Head to ranger station
if(this.locationIs('north-trail')) {
this._alertPlayerToMovement('north-trail', 'ranger-station');
ECS.moveEntity(this, 'ranger-station');
} else {
this._moveAlongPath();
}

return;
}

// Sing at random
if(random() > 0.75) {
queueLocalOutput("The bird chirps a happy tune.");
}

// Update hunger
this.hunger++;
}

With these simple behaviors and extremely simple logic, the bird will roam the map, interacting dynamically with any food it encounters (stopping to sample it, and continuing to eat if it’s still hungry and the food is nutritious). Once full, it will make its way to see Ranger Bob, and serenade him until it gets hungry again.

The code structure is a little clunky, and I’d like to turn it into something closer to a state machine, where the bird starts in one of a few states (LOOKING_FOR_FOOD, LOOKING_FOR_BOB, SINGING) and can then transition into other states. Managing AI via a state machine is a more scalable solution and will make it easier for me to assemble complex NPC behaviors.