Roger Firth's IF pages

Home

InfAct -- about Inform NPCs

Email
Back up

An NPC -- Non-Player Character -- is just another Inform object. Well, ok, that "just" is maybe a little glib; it's probably truer to say that creating good NPCs is one of the hardest part of any game. Tricky, certainly; impossible, no. And many players would agree that the ability to interact with a well-written NPC is one of the characteristics making a memorable and satisfying game. So let's get started.

The features that distinguish an NPC from any other Inform object are easily listed:

You may also need other properties like daemon and each_turn, but they aren't so closely tied to NPC-specific behaviour.

Starting from scratch

Creating the bare bones of an NPC is really easy.

 
Object  usher "gentleman usher" cloakroom
  with  name 'usher' 'gentleman' 'gentle' 'man',
        description "The usher is smartly uniformed.",
  has   animate male;

From these humble beginnings, anything is possible. What currently happens is fairly modest, but still a tribute to the power of the Inform library.

 
>SHOW CLOAK TO THE USHER
(the velvet cloak to the usher)
The usher is unimpressed.

>ASK USHER ABOUT THE THEATRE
There is no reply.

Simple reactions

One easy way to inject some life is to have the NPC notice your comings and goings.

 
Object  usher "gentleman usher" cloakroom
  with  name 'usher' 'gentleman' 'gentle' 'man',
        description "The usher is smartly uniformed.",
        react_before [;
            Go: print "The usher nods politely as you leave.^"; rfalse;
            Examine: if (noun == player)
                "~If I may say so, as good-looking as ever.~";
            ],
        react_after [;
            Go: print "~Good evening.~^"; rfalse;
            Drop: if (noun == cloak) {
                move cloak to player;
                "~Oops - let me pick that up for you.~";
                }
            ],
  has   animate male;

The react_ properties enable the NPC to passively comment on (with the rfalse) or actively affect (with the rtrue) actions that would happen normally in another location.

 
>WEST
"Good evening."

Cloakroom
The walls of this small room were clearly once lined with hooks, though now only
one remains. The exit is a door to the east.

You can see a gentleman usher here.

>EXAMINE USHER
The usher is smartly uniformed.

>EXAMINE ME
"If I may say so, as good-looking as ever."

>REMOVE CLOAK. DROP IT
(the velvet cloak)
You take off the velvet cloak.
"Oops - let me pick that up for you."

>EAST
The usher nods politely as you leave.

Unsolicited interjections

Another straightforward approach is to use a daemon or timer of some sort which causes the NPC to make his presence felt. Here's a first attempt, using the each_turn property:

 
Object  usher "gentleman usher" cloakroom
  with  name 'usher' 'gentleman' 'gentle' 'man',
        description "The usher is smartly uniformed.",
        each_turn [; switch(random(10)) {
            1: "^~Welcome to the Opera.~";
            2: "^~Shall I hang your cloak on this hook?~";
            3: "^~Still raining, I see?~";
            4: "^The usher clears his throat apologetically.~";
            5: "^The usher shifts position slightly.";
            default: ;      ! values 6-10 are ignored
            }
            ],
  has   animate male;

Using this simple method, the NPC will make a random remark roughly five turns out of ten; you can easily add to the repertoire of remarks and adjust the probability of one being made. However, hearing the same remark more than once soon gives the game away, so a better idea uses an array of logical flags to prevent this:

 
Array   UsherSaid -> 6;     ! 0=unused, 1-5=true/false flags

[ UsherSays num str;
        if (UsherSaid->num) rfalse;
        UsherSaid->num = true;
        print_ret (string) str;
        ];

Object  usher "gentleman usher" cloakroom
  with  name 'usher' 'gentleman' 'gentle' 'man',
        description "The usher is smartly uniformed.",
        each_turn [; switch(random(10)) {
            1: UsherSays(1, "^~Welcome to the Opera.~");
            2: UsherSays(2, "^~Shall I hang your cloak on this hook?~");
            3: UsherSays(3, "^~Still raining, I see?~");
            4: UsherSays(4, "^The usher clears his throat apologetically.");
            5: UsherSays(5, "^The usher shifts position slightly.");
            default: ;      ! values 6-10 are ignored
            }
            ],
  has   animate male;

You could use the library package Flags.h to manage the logical flags as bits rather than bytes. Also, a more sophisticated implementation would permit random NPC remarks only in turns when the player hasn't already interacted with the NPC.


Next, other forms of silent interaction.