Yo Frankie! | Crystal Space Tutorials

Scripting Non-Player Character

Using Crystal Space + CEL + B2CS + Blender

by Dariusz Dawidowski

Introduction

This tutorial shows solution how to make something practical using Apricot pipeline but doesn't teach details. Also note that tools are under heavy developing and some names or techniques can be outdated when you reading this. Always please refer Crystal Space , CEL , Blender2crystal and Blender manuals.


Step 1 - Settings

To know what we want to do - we need to make a list of performing actions. Usually every Non Player Character (NPC) is just our enemy. But what is the most frequently performed action ? Wandering. Let's focus on it. NPC needs to wander around but it can't be just random movement. NPC needs to avoid all obstacles (static level decorations and dynamic objects) also shouldn't try go into walls. Solution for all of this things except dynamic objects is moving on navigation graph which is predefined zone for safe route.

We need below Property Classes:

  • cel->move->PathFinder which is for moving on navigation graph (and this is component which we really want to use, below components are used by this one so they need to be present)
  • cel->move->Steer which is for steerng of the character
  • cel->move->actor->ActorMove which is for rotational control of the character
  • cel->move->LinearMovement which is for general component for movement in space

> Use the file in SVN: pro/tutorials/scripting_npc_01.blend


Step 2 - Navigation graph

In first example NPC needs to omit obstacle using simple graph. To make a graph we need vertices and edges from any mesh (1). It's not obligatory but better is to strip mesh to edges and vertices only by selecting all vertices, pushing DEL (Erase) and choose 'Only Faces'. This mesh needs to be tagged as 'celgraph' (2). Very easy method for building graph is extruding vertex after vertex also. Last tip that I can give about making graph is very useful retopo function in Blender - you can align graph to complicated terrain surface very easily.

> Use the file in SVN: pro/tutorials/scripting_npc_02.blend


Step 3 - Quest

Select enemy object and create new quest, say 'NpcTutorial'. Open editor and rename main state to 'wandering' it's better explanatory than 'default', after this change default state to 'wandering'.

First action should not be too complex, so let's just write text on console. You using text console, do you ? :)

> Use the file in SVN: pro/tutorials/scripting_npc_03.blend

No time for final result yet, in next step we will try to reproduce this in custom Python action.


Step 4 - Custom Python Property Class

It's a good time to make own Property Class in Python. You can put it in subdirectory inside defined 'scripts'. Name it say 'components'. Refer YoFrankie directory structure. Inside components we need __init__.py, which includes all necessary files in the directory. Also it's very useful to put there a couple of helpler classes like amhelpler.py and animation.py - for animation, csproperties.py - for list of properties, and main wrapper for each custom Property Class - apricotpc.py. You can modify them for own purposes.

Now lets create custom Property Class named say npctutorial.py, don't forget to add it to __init__.py as well as other files.

1 - First simplest template. It uses mentioned wrapper apricot.py to make things easier and cleaner.

from pycel import *
from apricotpc import ApricotPropertyClass, NoneParBlock

class MyClass(ApricotPropertyClass):

    def __init__(self, oreg):
        ApricotPropertyClass.__init__(self, oreg)

    def SetEntity(self, entity):
        ApricotPropertyClass.SetEntity(self, entity)

CEL_IMPLEMENT_FACTORY(MyClass,"mygame.class")
First two lines - necessary importing.
'MyClass' is a custom name of the class depends what we want to do for example Shoot, Kick or Throw.
Next - standard initializing in Python in the beggining we executing ApricotPropertyClass wrapper.
SetEntity - time to create aal resources like other Property Classes, variables etc.
Lastest line is special macro to register this class in all systems. We need to use class name and string representing this class in graphicall interfaces. It's a good habit to use dots for hierarchy eg. "apricot.move.run".

2 - Hello world. Let's name it properly for our purpose, and lets add one action printing on console "Hello world". You using text console, do you ? :)

from pycel import *
from apricotpc import ApricotPropertyClass, NoneParBlock

class NpcTutorial(ApricotPropertyClass):
    """
    cel property class
    Put description of this class here.
    """

    def __init__(self, oreg):
        ApricotPropertyClass.__init__(self, oreg)

    def SetEntity(self, entity):
        ApricotPropertyClass.SetEntity(self, entity)

    def PrintHelloWorld(self, args):
        """
        cel action
        Put description of this action here.
        """
        print "Hello world"

CEL_IMPLEMENT_FACTORY(NpcTutorial,"mygame.npctutorial")
You can see new things: PrintHelloWorld method and comments. These comments are special - it's a way for registering this methods in B2CS user interface. Restart Blender and now you can use it.

If you see in example file - for tutorial purposes I made NpcTutorial2.xml to not overwrite the old one.

> Use the file in SVN: pro/tutorials/scripting_npc_04.blend

First we need add our new Property Class to the entity's list of classes.

Now go to Quest Editor and change Reward 'debugprint' to 'action'. Set Entity to $this and set ID to our Python Property Class. Run game and text console should display 'Hello World' during NPC initialization.

3 - Custom parameters.

> Use the file in SVN: pro/tutorials/scripting_npc_05.blend

from pycel import *
from apricotpc import ApricotPropertyClass, NoneParBlock

class NpcTutorial(ApricotPropertyClass):
    """
    cel property class
    Put description of this class here.
    """

    def __init__(self, oreg):
        ApricotPropertyClass.__init__(self, oreg)

    def SetEntity(self, entity):
        ApricotPropertyClass.SetEntity(self, entity)

    def PrintHelloWorld(self, args):
        """
        cel action
        Put description of this action here.
        """
        print "Hello world"

    def Print(self, args):
        """
        cel action
        Put description of this action here.
        @param text: Text for displaying
        @type text: string
        """
        print args["text"]

CEL_IMPLEMENT_FACTORY(NpcTutorial,"mygame.npctutorial")
There is new method 'Print' witch much more rich comments you can see how to describe input parameters:
@param parameter_name: description
@type parameter_name: type (use string, int or float)

You can fetch them by args["parameter_name_here"]. So in Quest Editor let's change 'PrintHelloWorld' to 'Print' and add parameter with custom text.

4 - Moving on graph.

> Use the file in SVN: pro/tutorials/scripting_npc_06.blend

Time for real Property Class which can be used in our game. As before for tutorial purposes I'm using new quest to not overwrite the old one - NpcTutorial3.xml.

from pycel import *
from apricotpc import ApricotPropertyClass, NoneParBlock

class NpcTutorial(ApricotPropertyClass):
    """
    cel property class
    Put description of this class here.
    """

    def __init__(self, oreg):
        ApricotPropertyClass.__init__(self, oreg)

    def SetEntity(self, entity):
        ApricotPropertyClass.SetEntity(self, entity)
        if not entity: return
        self.mesh = celGetMesh(entity)
        self.meshwrapper = self.mesh.Mesh
        self.movable = self.meshwrapper.GetMovable()
        self.pathfinder = celPathFinder(self.entity)
        self.steer = celSteer(entity)
        self.PropertyClassesHaveChanged()

    def PropertyClassesHaveChanged(self):
        self.sector = self.movable.GetSectors()[0]
        navigraph = self.sector.Object.GetChild(iCelGraph, "navigraph")
        if not navigraph: return
        self.pathfinder.SetGraph(navigraph)
        self.steer.CheckArrivalOn(1.0)

    def Wander(self, args):
        """
        cel action
        Wandering on navigation graph.
        """
        if self.pathfinder:
            self.pathfinder.Wander(100)

CEL_IMPLEMENT_FACTORY(NpcTutorial,"mygame.npctutorial")
In the SetEntity we initializing Property Classes. Methods celGet*(entity) can create or fetch (in case if it's created already) other Property Classes. It's typical to use other Property Classes from others.

PropertyClassesHaveChanged is special callback executed every time when any Property Class will be added or removed from entity, so it's safe time to setup things. Imagine that you have 5 Property Classes and trying to fetch mesh position but Mesh Property Class isn't created yet !

And third new thing is Wander Action. Now our NPC will wander randomly through 100 nodes close to graph with setted precision 1.0. Because we chave no main character with camera - set Blender 3dView window to operate camera.


« back