Multiplayer Interactive-Fiction Game-Design Blog


Documentation – Basic Artificial Intelligence Library



Download 8.87 Mb.
Page124/151
Date02.02.2017
Size8.87 Mb.
#15199
1   ...   120   121   122   123   124   125   126   127   ...   151

Documentation – Basic Artificial Intelligence Library


This library provides artificial intelligence routines for the interactive fiction title. It requires the Basic RPG library, and well as the Basic IF library to run.

01 cAI – Artificial intelligence


Information about what storylines are and how to create them.


cAI - Artificial intellgience

Circumreality supports more complex artificial intelligence abilities

than are found in most IF or MUD authoring kits.

All of the AI programming is placed in this library, the

artificial intelligence library. Most of the AI code is

accessed from cAI, which is automatically included in

the cCharacter class, so all characters will have AI.

The Circumreality AI system includes the following features:







  • Goals - The ability for an AI to have goals,

    and the use of a finite state machine to complete those

    goals. Goals might be as simple as the desire to another

    place in the world or to attack another character.







  • Memories - AIs remember information about

    other characters. This information could include the

    character's names, how much they liked the last character,

    when they last saw them, etc.







  • Conversations - AIs support primitive natural

    language parsing and conversation abilities. (Although it's

    primitive, it's infinitely more complex than conversations

    in most IF titles and MUDs though.)







Important: If your game is a multiplayer game

and you want a character (based off cCharacter, which

is based off cAI) to remember players across reboots,

then that character must also be based off

cSaveToDatabase. However, characters that are based off

cSaveToDatabase aren't allowed to enter private

player instances, since those characters might end up trapped

in the instance of one specific player, and not be available to

other players.

02 cAIgoals – Goals


Information about what storylines are and how to create them.


cAIGoals - Goals and finite state machines

When a game developer creates the code to handle the intelligence

behind a computer-controlled character they usually use an algorithmic

technique called a "Finite State Machine" (FSM). The FSM provides

a semblence of intelligence, and allows the character to walk

around the world, attack any enemies that come into range,

and run away if it's wounded. FSMs don't handle conversations

though.

A FSM works by defining several "states" for a character's AI.

A state is, essentially, a state of mind. Enemies in most

first-person shooter games (FPS), have the following states:





  • Partol - The NPC wanders around on a predefined

    route. If the NPC sees an enemy it switch to the "Attack"

    state.





  • Attack - The NPC sees an enemy (such as the

    player) and attacks. If the player dies or manages to run

    away, the AI returns to the "Patrol" state. If the NPC

    is badly wounded the AI goes into the "Flee" state.







  • Flee - The NPC runs away from the enemy

    and finds a space to hide. It then heals up over time, and

    returns to the "Patrol" state.



Finite state machines work pretty well for FPS games because

the player to NPC interactions are pretty simple: Kill or be

killed.

FSMs do start showing their weaknesses though. One major

weakness in a simple FSM is that sub-FSMs are needed.

For example: The "Attack" state really requires a number of

sub-states, such as "Load weapon", "Fire", "Run towards enemy",

"Take cover", etc. Each of these sub-states are a FSM by

themselves. In effect, the ability for a FSM to have sub-FSMs

(and sub-states) is like the C++ main() function being able

to call other functions.


The other weakness about FSMs is that they have a single

track mind. If the programmer didn't program in a state

transition, there's AI will never diverge from the state.

This is especially noticable when FSMs are applied to

AIs for IF titles or MUDs.


You might want to design a character that wanders around

the world. If the character sees something valuable it picks it

up. If it happens by a merchant, it'll sell the object. If

it's attacked, it will attack back. You can impliment this in

a FSM, but the number of states and complexities of transitioning

between states is huge. What you really need is a FSM for

wandering around, a FSM for picking things up, a FSM for selling

items to a merchant, and a FSM for attacking. However, a normal

FSM system cannot handle four FSMs running at once.


Circumreality's AI system can handle both sub-FSMs and multiple FSMs running

at once.

If you want to learn more about Finite State Machines, do a search

on the Internet, or read some books about game AI.


cAIGoal

A finite state machine in Circumreality is called a "goal", because it

does a lot more than a traditional FSM. Each goal is an object

derived from cAIGoal. The object construct is used to group

the code together rather to provide a common storage method.

(In fact, you shouldn't store any information in your goal object

because the same object will be used for several AIs, all running

at the same time.)


To create a goal object:







  1. Create a new object and name it "oAIGoalXXX", where

    XXX described what the goal does. A goal that wanders around

    randomly might be called "oAIGoalWanderAroundRandomly".



  2. Make it a sub-class of cAIGoal.





  3. Check the option to create as an object so

    it's an actual object, not just an abstract class.



  4. Write the AIGoalStart() method, as well as

    method for other states. See below.



  5. Optionally, write the AIGoalPriorityAdjust() method.

    See below.



  6. Optionally, you may want players to be able to ask the NPC

    what they're doing. To support this, provide

    a pAIGoalWhatDoingSpeak, pAIGoalWhatDoingLike,

    and pAIGoalWhatDoingImportance or

    write your own AIGoalWhatDoing().





Speaking in FSM terminology, AIGoalStart() is the first "state"

in the FSM. Usually AIGoalStart() doesn't even act as a state

in the traditional FSM meaning, but acts like an "AI planner"...


When an AI creates a goal, it can send a parameter (or list

with multiple parameters) to the goal just like a call to

a function or method can be passed parameters. Many goal objects

are written so the parameter doesn't contain enough information

to determine what states need to be run. Instead, the first

task of the goal object is to plan out the specifics of how

to interpret the parameters.


If this doesn't make sense (which it probably doesn't), then

let me explain in English: An AI may have a goal of walking

from its current room to the neighborhood bakery. The neighborhood

bakery is a room in the world that isn't necessarily near

the character's current position. When the oAIGoalMove goal

is invoked by the AI, it will be passed in a parameter for

the bakery's room, oRoomBakery. It's then up to the

oAIGoalMove.AIGoalStart() method to figure out exactly how to

get to the bakery, such as going north to oRoomStreetMain, then

east to oRoomStreetOriely, then south into oRoomBakery. Only

once it has a plan will AIGoalStart() pass control to another goal.


Therefore, if you wanted to write oAIGoalMove, you'd need to

write the AIGoalStart() method with the following pseudo-code:



  1. Figure out what room the AI is in.





  2. Figure out how to get from the character's current room to the new one.





  3. Remember this sequence of rooms so you don't need to recalculate it

    each time.



  4. Switch to the "walk to next room" state.





You'll also need a "Walk to next room" state which has the following

code:



  1. Look at the list of rooms to walk to. If it's empty then the AI

    has met it's goal, and should return TRUE to whatever began

    the goal.





  2. Wait 5 seconds (or whatever) so that the NPC appears to

    be waking at a normal pace. If the AI doesn't wait, the NPC

    will get to the bakery in mere microsends.





  3. Otherwise, look at the next room in the list and walk there.





  4. Remove the name from the list.





Goal objects can do easily do this. Here's how:







  1. Write the code for AIGoalStart() as described

    above.



  2. AIGoalStart() is passed the character's object

    in the Actor parameter. This can be used to determine

    where the character is.

    The Parameter parameter of AIGoalStart() should

    expect the destination room to be passed as an object.

    With the character's current room and destination room known,

    AIGoalStart() can calculate the path needed using the A*

    algorithm. I won't get to that right now

    The AIGoalStart() method is passed a list in

    the Settings paramter. You can store any information

    in this list that you like. (It acts as a substitiute for member

    variables, which you cannot use to store information.

    Remember your oAIGoalMove object may be used by more than

    one NPC simultanously.

    Once the path has been written into the "Settings" list,

    AIGoalStart() should call AIGoalNext() and return

    the value. AIGoalNext() is provided by cAIGoal,

    and wraps up the input parameters into a list, that can

    be returned from AIGoalStart(), and is interpreted by

    AIGoalStart's caller. The code might look

    like return AIGoalNext (WalkToNextRoom, 5.0,

    NULL); WalkToNextRoom is the method that handle the

    "walk to next room" state. 5.0 is the number of seconds to

    delay before reaching that state. NULL is the parameter that

    will be passed into WalkToNextRoom, but it isn't needed.




  3. Create a private method called WalkToNextRoom. It

    should accept the same paramters as AIGoalNext().




  4. WalkToNextRoom() receives the same Settings list that was

    passed into AIGoalsStart(). It can look at the list

    and see what rooms it needs to move to. If the list

    is empty then WalkToNextRoom should return

    AIGoalNextFinished (TRUE); This informs the goal

    monitoring code in cAI that the goal is finished, and

    that is has been successfully completed (by returning TRUE).

    If there are still rooms on the list,

    call ActorMove() with the room, and remove

    the room from the Settings list. Then, return

    AIGoalNextRepeat(NULL) to repeat the same state (of

    WalkToNextRoom()), 5 seconds from now.

If you want the NPC to walk to the bakery as soon as its

created you can just modify pAIGoalsEmpty in the

NPC object, and

change it to [[oAIGoalMove, 0, oRoomBakery]].

If you start up your IF title, and can log on quickly enough,

you'll see the NPC walking to the bakery. He will move to

a new room every 5 seconds.


The pAIGoalsEmpty parameter is used by cAI whenever

it comes across an AI without any goals... which basically

means when the object is first created. The list contains

a sub-list for every goal the AI should start out with. Each

sub-list starts with a goal object, followed by a priority, and

then the parameter to pass into the goal's AIGoalStart() method.

(I'll get to priority in a bit.)




Sub-goals

What happens if you want the NPC to wander from his starting

room to the bakery, and then to the local pub, and then

back to his starting location?


You could modify the oAIGoalMove object that you wrote to

handle all three locations, or you could use sub-goals, letting

oAIGoalMove handle the sub-goal of moving from one spot to another.


To create a goal that handles "Follow a path" that would

move the NPC from A to B to C and back to A, just do the following:



  1. Create a new object, oAIGoalFollowAPath just like

    you created oAIGoalMove.



  2. Write the AIGoalStart() method for the goal.




  3. It should expect that the parameter passed into it is a list

    of rooms, such as [oRoomBakery, oRoomPub, oRoomStart].

    AIGoalStart() should remember this list in the Settings list.

    You can just do Settings.ListConcat(0, Parameter);,

    which will leave Settings with [0, oRoomBakery,

    oRoomPub, oRoomStart]. The first paremter, "0", is the next

    room in the list to go to.

    AIGoalStart() then should call return AIGoalNext

    (WalkedToRoom, 0, TRUE); This causes the WalkedToRoom

    goal to be run in 0-seconds and be passed in TRUE (which means

    that no error has occurred).


  4. Write a private method, WalkedToRoom, with the

    same parameters as AIGoalStart().


  5. If the Parameter != TRUE then there has

    been an error, and WalkedToRoom() should return

    AIGoalNextFinished (FALSE); This will stop the NPC's

    wandering around from room to room.

    Otherwise, the character should walk to the next room.

    Remember var vRoom = Settings[Settings[0]+1];,

    which is the next room on the list. Advance to the next

    room by calling Settings[0] = (Settings[0] + 1) %

    (Settings.ListNumber()-1);

    Then, to walk to vRoom, just return AIGoalNextSubGoal

    (WalkedToRoom, oAIGoalMove, 0, 0, vRoom); This

    tells the AI system to run the sub-goal, oAIGoalMove, which

    you wrote previously. oAIGoalMove is run with a priority of

    0 (which I'll explain later), and a delay of 0 seconds (why

    wait?). The parameter is vRoom, which is the next room to

    walk to. The first parameter, WalkedToRoom, is the state in

    this goal that will be called with oAIGoalMove reaches its

    goal. When oAIGoalMove.WalkToNextRoom() reaches its destination

    it will call AIGoalNextFinished(TRUE). The "TRUE" parameter

    will be passed into oAIGoalFollowAPath.WalkedToRoom(), so

    the "follow a path" goal knows that everything went okay.



  6. Modify pAIGoalsEmpty to

    be [[oAIGoalFollowAPath, 0, [oRoomBakery, oRoomPub,

    oRoomStart]]. Now, the NPC will walk between the

    bakery, pub, and start room and won't ever stop.





Running multiple goals at once

What if you want your NPC to pick up any objects that it sees

along its path?

You could modify your oAIGoalMove code to also look for unclaimed

objects and pick them up. However, since not all NPCs will

be cleptomaniacs, you now need two goal objects, oAIGoalMove and

oAIGoalMoveClepto. Plus, you'd need two "follow a path" objects,

oAIGoalFollowAPath and oAIGoalFollowAPathClepto.


There is an easier way...





  1. Create a new goal, oAIGoalPickUpAnything that

    ends up looking around and picking up objects that it sees.

    That's all it does. It will require a few methods just like

    my previous examples. I won't go into detail.



  2. Modify pAIGoalsEmpty to

    contain [[oAIGoalFollowAPath, 0, [oRoomBakery, oRoomPub,

    oRoomStart], [oAIGoalPickUpAnything, -10] ]. This

    new property tells the AI for the NPC object to create two goals,

    one of which is to wander around and the other is to pick

    up objects. The list element after oAIGoalPickUpAnything is

    -10, which is the priority. I didn't put a third element,

    the parameter, because pick up anything doesn't require

    any parameters.





  3. Modify your world to include some objects

    placed along the NPC's path.





  4. Start your IF.



You'll be disappointed. The AI just wanders around the world

and doesn't ever pick any of the objects up. That's because

the priority score for oAIGoalPickUpAnything is less than

the priority for oAIGoalFollowAPath. If you make the priority

higher, you'll discover that the NPC picks up all the objects

in the starting room, but doesn't ever move.

The solution is to write a AIGoalPriorityAdjust() method

for the oAIGoalPickUpAnything object.

When the AI system realizes that more than one goal is active

for a NPC, it uses the priority to determine which goal to run.

However, you may want the priority to be dynamic, depending upon

the character's location, surroundings, or health. The AI

system automatically calls AIGoalPriorityAdjust() for each

goal and adds

the number it returns to the goal's score. Most goals don't

bother with this function, but some need to.

In the case of the cleptomaniac NPC, if oAIGoalPickUpAnything's

AIGoalPriorityAdjust() returns 20 (or any number >= 11), then

the oAIGoalPickUpAnything goal will temporarily have a higher

priority than oAIGoalFollowAPath. Therefore, write code in

oAIGoalPickUpAnything.AIGoalPriorityAdjust() that checks

the room for objects to pick up. If it finds any it

can return 20. If it doesn't find any it will just return 0.


If you make these changes the NPC will wander around its route

and pick up any objects along the way.

You can use multiple goals with dynamic priorities for lots of

things:



  • The "drink healing potion" goal would normally have a low

    priority that would increase if the character was wounded.



  • The "run away" goal could likewise have a low priority that

    increases if the character is wounded.



  • A "find the store and eat goal" could increase its priority

    based upon the hunger of the character.



  • Etc.







Temporarily suspending goals

An NPC wandering around the world and picking up anything

can also be achieved by

using AIGoalNextSuspend(). A goal which temporarily

suspends itself waits a given amount of time before waking up

and doing processing. The "pick up anything" goal could wake up

every 5-10 seconds, and see if any objects were around. If there

weren't any it would go back to sleep, suspending itself for

another 5-10 seconds. If it found an object it would initiate

the oAIGoalObjectGetDrop goal to pick up it up.





Programmatically adding new goals

So far I've been using pAIGoalsEmpty to add goals

to the AI's list of goals. If you wish to have some other

code add a new goal, you can call oNPC.AIGoalAdd() to

add one or more goals to the list.

Remember, that when a goal is added the highest priority ones

will run first, while lower priority goals will wait until

they're highest priority. (Unless, of course, the goal supports

AIGoalPriorityAdjust()).

You can use this to your advantage. For example: A NPC has the

oAIGoalFollowAPath goal running. However, if another character

speaks to the NPC, then your code to add the goal

oAIGoalHavingConversation with a higher priority

than oAIGoalFollowAPath. This would cause the NPC to stop

walking and talk to whomever has asked the question. If the NPC

weren't asked a question for 20 seconds, or if the other

character walks away, the oAIGoalHavingConversation goal could

exit by returning AIGoalNextFinished(), and the NPC would

procede to follow its path.

cCharacter objects, as well as any other object with cAI as a superclass

also supports the following methods:





  • AIGoalAdd() - As mentioned above. Add a goal to

    the AI's list of goals.







  • AIGoalFind() - Find a goal in the AI's list.





  • AIGoalNumber() - Return the number of goals.





  • AIGoalOverride() - This is a method that you write

    yourself. It lets you create a custom personality for NPCs, so

    that they use different goals than the norm. For example: If you

    want a character that uses different attack aiming tactics than normal,

    you would have the AIGoalOverride() look for

    references to the default aim handler, oAIGoalCombatAimChoose,

    and return your own goal, such as oAIGoalCombatMYAimChoose.





  • AIGoalQuery() - Get information about a goal,

    such as its callback, timing, or whether it's suspeneded until

    a sub-goal finishes.





  • AIGoalRemove() - Remove a goal from the AI's list.







Summary

Goals are the basic mechanism for getting a NPC to "do stuff"

like walk around, fight, etc. You can have goals run sub-goals,

as well as running several goals at once. By combinging sub-goals

and multiple goals (potentially with dynamic priorities) you can

create a wide variety of behaviours for your AIs.


For a complete lists of pre-programmed

goals, look for objects with the "oAIGoal" prefix.


Download 8.87 Mb.

Share with your friends:
1   ...   120   121   122   123   124   125   126   127   ...   151




The database is protected by copyright ©ininet.org 2024
send message

    Main page