The Prop System, a part of the Foundation Framework, provides a set of tools which may be used to manage interactive and non-interactive objects. These objects may be introduced to players for the purposes of visualization and interaction, and this introduction may be restricted such that only players who meet certain criteria will be included (e.g. only players with the 'collect acorns' quest will be allowed to see and interact with acorn objects). From the perspective of the world-builder, these objects - once added to a scene - are seamlessly integrated with an area's geometry and may be manipulated with the standard HeroBlade translation tools. Unlike standard area geometry, however, these prop objects may have additional functionality attached to them to restrict visibility, provide interactivity, or to provide any other game-specific behavior required.
The Prop System utilizes the Spec System to store prop type definitions. These definitions are used during construction of prop objects to define their behavior, and each can be overridden, extended or decorated in order to provide game-specific functionality for props. Overriding default functionality and providing restrictions or augmentations to visibility, interactivity, etc, is accomplished either by extending a spec via inheritance or by glomming decorator classes onto the prop spec which offer the desired functionality. An example of a decorator that might extend the default prop functionality is the 'MouseInteractionDecorator' which responds to mouse input events. Additional game-specific decorators may be created, and the base prop spec may be overridden or extended as desired.
By default, the MMO Foundation Framework offers the following functionality for props:
- Mouse interaction
- Activation (not yet implemented)
- Spatial perception
- (more) (pending further FF work)
What problem(s) does this solve?
- Selectively introduce and visualize objects for players based on an arbitrary set of criteria
- Provide interactions between props and other objects (not necessarily restricted to just players)
- Provide a framework for extending the default prop behavior with game-specific code
- Provide an intuitive interface for editing prop objects (namely, the HeroBlade translation tools and prop context menus)
What problem(s) does this not solve?
- Implementing visualization- and selection- restriction logic (e.g. due to quest possession, faction standing, etc)
- Implementing the game logic to respond to interactions between props and players (or other entities)
- Providing an all-encompassing set of non-standard behavior that may be specific to your game's systems
For World Builders
From the perspective of the world-builder, the prop system is fairly seamlessly integrated into the tools they are already familiar with using. Prop type definitions are established in the prop spec editor, individual prop objects are instantiated by adding them to the asset library, and these objects are manipulated in the world using the standard HeroBlade translation tools and prop context menus.
The process of setting up a prop to be instantiated in the world and manipulating it involves the following steps:
- Choose a pre-existing prop spec from which you want to instantiate props
- Create an entry in the Asset Library which will be used to add a prop to an area
- Add a prop to an area using the Asset Library entry
- Manipulate the prop using the HeroBlade translation tools as well as the right-click context menu
Choosing a Prop Spec
image All prop objects are instantiated from prop specs, each of which may extend or override the base behavior. These specs can also be decorated with prop spec decorators to provide additional functionality on a per-spec basis. (example: A sign that can be clicked on in order to be read may be instantiated from one spec; while a an ancient relic that must be discovered, approached and then clicked on to collect may be instantiated from a different one)
In order to determine which spec you want to use for your prop (or to edit an existing spec), you'll first want to open the Prop Spec Selector from the Utilities Interface's "Tools" menu. There, you'll see a list of all prop specs currently defined. Many, such as the "Visible Prop" and "Interactive Prop" specs, are pre-defined and offered as part of the base MMOFF implementation. Others, which your team may implement, will also be listed here.
To use a pre-existing prop spec, simply note the ID of the spec and write it down for use in later steps. To add, edit, or decorate a spec, click either the 'Add', 'Edit' or 'Glom' buttons, respectively.
Adding the Library Command
image Once you know the spec from which you want to instantiate your prop, create a Library entry with an /heprops command similar to the one below:
/heprops library #spec='1' #fqn='\engine\cleangame\resources\common\utility_box_white01.gr2' #position='$POSITION' #lod='10'
Instantiating and Manipulating Props
image Once the prop has been added to the library, props may be instantiated by selecting the prop in the library and clicking 'Add'. The prop - once instantiated in the world - may be treated like any other asset and repositioned, rotated, scaled or interacted with via the HeroBlade translation tool widgets.
Props are - at base - dynamic server objects which respond to spatial awareness events by performing some action; usually, this is simply introduction or removal from a player's awareness (e.g. you've moved within range of a resource node or dynamic object which you should now be able to see). Props can also be used, though, to drive custom game logic which uses spatial awareness (e.g. king-of-the-hill or capture-the-flag logic for a dynamic object; or determining proximity to objects to determine whether interactions are valid). There are several moving parts that are required to work together in order to facilitate this behavior, and - as such - changing the logic for a type of prop (or defining your own) often requires touching multiple classes and multiple scripts.
The pieces involved in creating a new prop from scratch (all of which have both server and client components):
- The Prop Spec
- The Prop Spec-derived Object
- The Prop Spec Decorators
- The Prop Spec-derived Object Decorators
Understanding Prop Instantiation
Props may be instantiated on the server either in the Edit instance of an area or a Play instance. Props instantiated in the Edit instance persist with the area, whereas props instantiated in a play instance are destroyed the moment the area spins down.
The process for instantiating a prop in script is as follows:
specKey as ID = 1 //use any valid specKey for a spec within the _PropSpecOracle propSpecOracle as NodeRef of Class _PropSpecOracle = $_PROPS._getPropSpecOracle() //get our prop spec oracle propSpec as NodeRef of Class _PropSpec = propSpecOracle.GetSpecByKey(specKey) //get the spec we're interested in creating a prop from prop as NodeRef of Class _Prop = propSpec.CreateFromSpec() //create our prop
After the call to 'propSpec.CreateFromSpec()', the following will be true:
- The prop object will have been created from the spec and default values will have been populated
- The prop object will have notified all decorators of its creation, allowing those decorators to perform their own synchronous initialization
- The prop object will have been registered with the area and either linked with the area root node (if we're in an Edit instance) or stored in a temporary collection (if we're in a Play instance)
- In the case of the standard 'spatially aware' prop, the process for replication to clients within the prop's awareness range will have begun
That's it! You can now access, modify and use the prop at will.
Understanding Prop Instance Destruction
The process for destroying a prop in script is as follows:
myProp as NodeRef of Class _Prop = 2938717654872 $_PROPS._RemoveProp(myProp)
At this point, the prop will have been removed from the area and any spatial awareness tracking or replication will have ceased for it.
Querying For Props
To query for props (perhaps because they were created during a previous session or because you don't store references to them elsewhere), simply make a call to one of the following methods on the $_PROPS system node:
method _FindPropsBySpec(propSpec as NodeRef of Class _PropSpec) as List of NodeRef of Class _Prop method _GetAllProps() as List of NodeRef of Class _Prop method _GetAllProxyProps() as List of NodeRef of Class _Prop
Understanding Client/Server Props
The default prop spec is designed to provide an example of creating objects that will only become visible to the player when s/he approaches an object. This is accomplished through clever use of spatial awareness, replication and local client visualizations to give the illusion of permanence for temporary, dynamic objects.
The sequence of events for one of these dynamic replicated objects (all of which is handled within the prop spec and prop classes) is:
- Create the server-side dynamic replicated prop and attach it to the area root
- Add the server prop to a spatial awareness system and establish configuration values such as 'awareness range'
- When players get within the awareness range of the server prop and trigger the prop's spatial awareness event handler, add the prop to the player's replication group
- When players leave the awareness range of the server prop, remove the prop from the player's replication group
Once a dynamic prop is replicated to a player's client, the client may choose to respond by rendering the prop on-screen or triggering some other client-side action.
With this setup, any number of dynamic objects may be created that will respond to spatial awareness events by introducing themselves to players. Additional behavior could be added to these props to - for instance - make the props selective and only introduce themselves to certain players.
Understanding The Prop Spec
The prop spec is comprised of - at minimum - the following methods (in addition to those found on a BaseSpec object):
CreateFromSpec is what handles the physical instantiation of the prop from the _Prop class. After instantiation, this method allows any decorators glommed onto the prop spec to perform any desired initialization, then makes a call to $_PROPS._AddProp to add the prop to the prop system.
At base, a simple implementation of CreateFromSpec in a prop spec looks something like this:
method CreateFromSpec() as NodeRef aInstantiatedNode as NodeRef if $AREA._IsEditInstance() aInstantiatedNode = CreatePersistedNodeFromClass("_Prop") else aInstantiatedNode = CreateNodeFromClass("_Prop") . me.OnCreateNotifySpecDecoratorClasses( aInstantiatedNode ) where aInstantiatedNode is kindof _Prop $_PROPS._AddProp(aInstantiatedNode) . return aInstantiatedNode .
In order to support Library instantiation, a prop spec must implement the _LibraryCreateFromSpec method. This method will wrap both the creation of the prop from the spec as well as the interpretation of the library command associated with the Library asset. Additionally, it will give any spec decorators the opportunity to perform initialization on the instantiated prop.
The default implementation of this method (shown below) looks for a '#position' tag embedded in the Library command string and sets the position of the newly instantiated prop to the specified position. It also lets any spec decorators perform initialization by passing the Library command string down into any that are glommed onto the spec.
method _LibraryCreateFromSpec( libraryCmd as String ) as NodeRef aInstantiatedNode as NodeRef tokens as LookupList indexed by String of String = me._parseTokensFromLibraryCmd( librarycmd ) pos as Vector3 = tokens["#position"] aInstantiatedNode = me.CreateFromSpec() where aInstantiatedNode is kindof _Prop aInstantiatedNode._SetPosition(pos) . me._OnLibraryCreateNotifySpecDecoratorClasses( aInstantiatedNode, libraryCmd ) return aInstantiatedNode .
Creating a New Spec Decorator
The great flexibility of the prop system comes from the fact that props and their specs may be decorated with virtually any behavior desired: different visualization methods, restrictions on introduction/interaction, connections to other game systems - the possibilities are vast.
New prop spec decorators must implement the following methods:
One of these two shared functions will be called depending upon whether the prop is instantiated from the Library or from script. The primary purpose of these methods is to perform operations on the newly instantiated prop to initialize it (such as glomming a decorator class onto the prop and initializing the fields belonging to the glommed class to the correct values).
An example implementation of OnInstantiationFromSpec might be:
shared function OnInstantiationFromSpec( specDerivedObject as NodeRef ) if not ( specDerivedObject is kindof _VisibleProp ) GlomClass( "_VisibleProp", specDerivedObject ) . where specDerivedObject is kindof _VisibleProp where specDerivedObject is kindof _Prop spec as NodeRef of Class _PropSpec = specDerivedObject.GetMySpec() where spec is kindof _PropVisualizationDecorator specDerivedObject._PropLoD = spec._PropLoD . . .
In the above example, a new class - _VisibleProp - is glommed onto the instantiated prop. This class implements all the functionality required to visualize the prop on the client and will have the opportunity to respond to all events that the prop would normally respond to.