Replication Tutorial

From HEWIKI
Jump to: navigation, search

Contents

He advanced.png This is an advanced tutorial covering a method of replicating objects from server to the client.

Overview

ReplicationTutorialOverviewFlower.gif

This tutorial is meant to familiarize the user HeroEngine's Replication technology as a means of Inter-Process Communication, in particular client/server data synchronization. During this tutorial you will learn how to create replicated fields/classes using the DOM Editor, setup replication for a node that exists in a server GOM, initialize replication traffic based on Spatial Awareness, process replication events, and use a Prop Bucket to visualize an asset on the client.

Essentially the goal is to create something on the area server and have it appear on any clients that are connected to that area server. Appropriate changes to data (such as position) about the object can be automatically updated on any attached client.

By now you are probably familiar with building areas using HeroBlade; Adding an asset to the area, creating an instance and then adjusting the properties of the instance using the placement tools and/or Properties Panel. A fundamental characteristic of this type of area building is that all users in a particular area instance will see exactly the same area geometry. Imagine you wanted the capability to display an asset to only users within a particular range or limited to specific users based on any arbitrary game logic, modifying the area's geometry simply is not the answer for this kind of need.

We could of course use Remote Calls in conjunction with a trigger or an entity in a Spatial Awareness System, but we lose many of the benefits of the replication technology including bandwidth shaping and prioritization for the communication.

Step By Step

Step 1: Create DOM Definitions

ReplicationTutorialNodes.png
Server Class: DynamicReplicatedObject
Field Name Type Description
DRO_Position Vector3 The location of the object in the area.
DRO_Rotation Vector3 The rotation of the object in the area.
DRO_AssetFQN String The FQN of the model in the repository.
_replicationGroupRef noderef This field already exists and needs only be added to the DynamicReplicatedObject class.
Client Class: DynamicReplicatedObject
Field Name Type Description
DRO_Position Vector3 The location of the object in the area.
DRO_Rotation Vector3 The rotation of the object in the area.
DRO_AssetFQN String The FQN of the model in the repository.
DRO_AssetID ID ID of the asset spec in the DynamicReplicatedObject prop bucket.
DRO_InstanceID ID ID of the instance in the DynamicReplicatedObject prop bucket created from DRO_AssetID.

The object will need definitions in both the server and client DOMs to represent its state. To start with, a very simple set of classes and fields will be created using the DOM Editor. The server class will be instantiated as a node in the server GOM by virtue of a script call. The client class will be instantiated via replication by virtue of an entity (a player) entering the awareness range of the entity representing the DynamicReplicatedObject in the Area's Spatial Awareness System.




Step 2: Set Replication Parameters

Set Field Replication Parameters

HeroEngine's replication technology allows for a very sophisticated and fine grained control of the replication of classes and their member field(s). Consequently, it is necessary to take a moment using the DOM Editor to define the behavior you wish the engine to use.

ReplicationTutorialFieldReplicationParameters.png
To be used in replication, each field definition needs to have its replication parameters set. For this tutorial, our three DRO_ fields defined in the server class need to have Initial Priority, Delta Priority, Lifetime, Distance Factor, Change Callback, and Destination Field (Client) set.

Ultimately when determining the priority settings, it is important to consider all replicated traffic and the relative priorities of this traffic but for the purposes of this tutorial it is not important.


Establish Destination Class Mapping

ReplicationTutorialDestinationClassMapping.gif

Replication requires that you define a mapping of the server class to its destination class when replicated. Using the DOM Editor, select the server class DynamicReplicatedObject and set its Destination Class (Client) to the matching client class. Then for each field in the server side class which is to be replicated you must check the replicated flag, and (optionally) the initial set flag set. The initial set flag indicates that the current value of the fields will be transmitted to the client when the replication is introduced.

Check both the R and IS checkboxes for all three DRO_ fields which are members of our DynamicReplicatedObject class.


Step 3: Create Class Method Scripts

Using the DOM Editor ReplicationTutorialCreateClassMethodsScript.gif, create both the server and client class method scripts for our DynamicReplicatedObject class.

Included in the Reference section is the full source for both server/client scripts and it is recommended that you start out by pasting the code in the appropriate scripts (i.e. DynamicReplicatedObjectClassMethods). Be sure to compile/Submit the server script first, then the client script before continuing.

On the Server

Our server object needs to handle several behaviors:

Wrapping Setting Position

We are going to create some wrapper methods for setting and getting the position and rotation of our DRO. The primary purpose is to provide the option of doing some additional work when the position changes, specifically updating the position of the entity in the spatial awareness system representing the DRO. The additional methods are there simply for orthogonality for the purposes of this tutorial, but they could be used to expand the capabilities of the DRO as an expanded exercise.

method DRO_GetPosition() as Vector3
  return me.DRO_Position
.
 
method DRO_GetRotation() as Vector3
  return me.DRO_Rotation
.
 
// Set the position for the object, this also must update the area's spatial awareness system so that the object is introduced
//   appropriate as players draw near to it.
method DRO_SetPosition( pos as Vector3 )
  me.DRO_Position = pos
 
  // Update the area spatial awareness system with the new position
  $SPATIALAWARENESS_AREA._SAS_UpdateEntityPosition( me, pos )
.
 
method DRO_SetRotation( rot as Vector3 )
  me.DRO_Rotation = rot
.
Replication Group
Aside.gif It is worth noting that while this tutorial implements the replication group as a singleton, there is no requirement that a particular grouping of nodes have only one replication group. Some implementations might require the creation of multiple replication groups to achieve game specific behaviors.


A replication group is the interface between one or more GOM objects and HeroEngine's replication API. It serves to identify one or more nodes that should be replicated and the destination(s) to which the replication should be sent. The most common implementation for a replication group is as a singleton for a particular grouping of nodes and that is how we are going to implement our replication group for the DRO.

There is already a method defined for the creation of a singleton replication group used by the account hierarchy to introduce characters to clients aware of them. Rather than creating a new method, we are going to simply implement our own version of the _getReplicationGroup() method. Our _getReplicationGroup() method needs to perform the following operations:


method _getReplicationGroup(create as Boolean) as NodeRef of Class _ReplicationGroup
  replicationGroup as NodeRef of Class _ReplicationGroup = me._replicationGroupRef
 
  // During runtime, a reference is cached for speed.  If the cached reference is invalid, we do an exhaustive search in an
  //   attempt to locate an existing replication group.
  if me._replicationGroupRef = None
    foreach assoc in QueryAssociation( me, "base_hard_association", 0 )
      test as NodeRef = assoc.target
      if test is kindof _ReplicationGroup
        replicationGroup = test
        me._replicationGroupRef = replicationGroup
        break
      .
    .
  .
 
  if replicationGroup = None and create
    // Create replication group
    replicationGroup = CreateNodeFromClass( "_replicationGroup" )
    addAssociation( me, "base_hard_association", replicationGroup )
    replicationGroup._replicationPrimaryNode = me
 
    // ReplicationGroups need to know what spatial awareness to gather distances for priority.
    replicationGroup._replicationSpatialAwareness = $SPATIALAWARENESS_AREA
    replicationGroup._replicationSpatialEntity = me
 
    // Add an entity to the area's spatial awareness system
    entityInfo as Class _SAS_EntityInformation
    entityInfo._SAS_EI_awareness = 3                           // 3 unit awareness for our objects
    add back me to entityInfo._SAS_EI_event_receiving_nodes    // SAS events should be sent to our objects
    entityInfo._SAS_EI_position = me.DRO_GetPosition()
 
    $SPATIALAWARENESS_AREA._SAS_AddEntity( me, entityInfo )
 
    // cached reference for fast lookup
    me._replicationGroupRef = replicationGroup
  .
 
  return replicationGroup
.
Receiving Spatial Awareness Events

By virtue of our adding the DRO as an _SAS_EI_event_receiving_nodes, we will receive the following events from the $SPATIALAWARENESS_AREA awareness system for those events related to the entity representing the DRO.

During the entered/appeared events, we will want to add the entity entering the DRO's awareness range to the client destinations for the replication group controlling the DRO's replication when the entity entering is a player. This will cause the client to receive replication traffic introducing the DRO allowing the client to start the process of visualizing the DRO locally.

// Called on nodes in the list field _SAS_EI_event_receiving_nodes when the entity
//   is informed that a subject has entered its awareness.  In the case of an entity suddenly appearing
//   (just added to the system, logged in, was just created etc) the appeared event is called instead.
proxyLocal method _SO_entered( awarenesssystem as ID, entity_ID as ID, subject_ID as ID)
// Parameters:
//   awarenesssystem - id of the awareness system instantiation that generated this event
//   entity_ID - id of the entity
//   subject_ID - id of the thing that entered the entity's awareness
 
  subject as NodeRef = subject_ID
 
  // If the entity represents a player, then add them to the replication group for the DynamicReplicatedObject
  if subject <> None and subject is kindof _playerAccount
    var repGroup = me._getReplicationGroup( true )
 
    // This will cause replication to start using the player's client as a destination
    repGroup._addClientDestination( subject, subject )
  .
.
 
// Called on nodes in the list field _SAS_EI_event_receiving_nodes when the entity
//   is informed that a subject has appeared (just added due to node creation, login etc)
//   suddenly in its awareness range.
proxyLocal method _SO_appeared( awarenesssystem as ID, entity_ID as ID, subject_ID as ID)
// Parameters:
//   awarenesssystem - id of the awareness system instantiation that generated this event
//   entity_ID - id of the entity
//   subject_ID - id of the thing that appeared in the entity's awareness
 
   subject as NodeRef = subject_ID
 
  // If the entity represents a player, then add them to the replication group for the DynamicReplicatedObject
  if subject <> None and subject is kindof _playerAccount
    var repGroup = me._getReplicationGroup( true )
 
    // This will cause replication to start using the player's client as a destination
    repGroup._addClientDestination( subject, subject )
  .
 
.


During the departed/disappeared events, we will want to remove the entity leaving the DRO's awareness range from the client destinations for the replication group controlling the DRO's replication when the entity leaving is a player. This will cause the client to receive a replication event that the replicated node is about to be torn down, allowing us the opportunity to remove the visualization of the DRO on the client.

// Called on nodes in the list field _SAS_EI_event_receiving_nodes when the entity
//   is informed that a subject has departed (left) from its awareness.  In the case of an entity
//   suddenly disappearing (deleted, removed from the system, logged off) the disappeared event is called instead.
proxyLocal method _SO_departed( awarenesssystem as ID, entity_ID as ID, subject_ID as ID)
// Parameters:
//   awarenesssystem - id of the awareness system instantiation that generated this event
//   entity_ID - id of the entity
//   subject_ID - id of the thing that departed the entity's awareness
 
  subject as NodeRef = subject_ID
 
  // If the entity represents a player, then remove them to the replication group for the DynamicReplicatedObject
  if subject <> None and subject is kindof _playerAccount
    var repGroup = me._getReplicationGroup( true )
 
    // This will cause replication to stop using the player's client as a destination
    repGroup._removeClientDestination( subject )
  .
.
 
// Called on nodes in the list field _SAS_EI_event_receiving_nodes when the entity
//   is informed that a subject has disappeared suddenly from awareness (node deletion, logoff etc)
proxyLocal method _SO_disappeared(   awarenesssystem as ID, entity_ID as ID, subject_ID as ID)
// Parameters:
//   awarenesssystem - id of the awareness system instantiation that generated this event
//   entity_ID - id of the entity
//   subject_ID - id of the thing that entered the entity's awareness
 
  subject as NodeRef = subject_ID
 
  // If the entity represents a player, then remove them to the replication group for the DynamicReplicatedObject
  if subject <> None and subject is kindof _playerAccount
    var repGroup = me._getReplicationGroup( true )
 
    // This will cause replication to stop using the player's client as a destination
    repGroup._removeClientDestination( subject )
  .
.
Factorying a DRO
MMOFoundationFrameworkWhiteSphere.gif

We need a simple way to test our implementation of the DRO to see if things are working. A quick way of doing this is to implement a function on the client that can be called via the CLI call command used to invoke scripts. To facilitate that, we are going to create an untrusted function on the server to receive a remote call and perform the actual work of factorying a DRO from a well known asset. This asset is the humble white sphere, and it is included as a part of the MMO Foundation Framework.

In a real game implementation, you might choose to use the Spec System to define the countless types of DROs your game has and handle the behaviors of factorying instances of them.

Our factory is going to:

untrusted function AddDRO()
  dro as NodeRef of Class DynamicReplicatedObject = createnodefromclass( "DynamicReplicatedObject" )
  dro.DRO_AssetFQN = "\engine\cleangame\resources\common\utility_sphere_white01.gr2"
  var repGroup = dro._getReplicationGroup( true )
  dro.DRO_SetPosition( SYSTEM.REMOTE.CLIENT.GetPosition() )
.

On the Client

Request Server Factory a DRO

If you recall, in the server script we implemented an untrusted function to receive a remote call from the client to factory a DRO instance. The client script needs a function to make a remote call to that function when called from the CLI using the call command.

// Calls the server requesting it add a non-persisted dynamic replicated object positioned at the player's location.
function AddDRO()
  call server DynamicReplicatedObjectclassMethods:AddDRO()
.
Instantiate a Visualization of the DRO During Introduction

When the DRO is introduced to the client by replication, we wish to use the replicated data to instantiate a visualization of the DRO based on the specified FQN setting the position and rotation properties. Replication causes a number of events (i.e. _OnReplicationNodeAdded() and _OnReplicationGroupAdded()) to be called on the "primary" node of a replication, which for our purposes is the DRO itself. During the _OnReplicationGroupAdded() event, we are going to use the now initialized data of the client DRO instance to add an asset to a prop bucket.

// Called when all of the nodes that are part of the replication group have been added.  At this point, all nodes are present
//   with those of their fields marked "Initial Set" set to the replicated values
method _OnReplicationGroupAdded()
 
  if me.DRO_AssetFQN <> ""
    CreatePropBucket( "DYNAMICREPLICATEDOBJECT" )    // Ensures the prop bucket exists; there is no harm in creating when it already exists
 
    me.DRO_AssetID = AddAssetSpecToPropBucketCB( "DYNAMICREPLICATEDOBJECT", me.DRO_AssetFQN, me )
  .
.

The addition of an asset to a prop bucket is an asynchronous process which results (when you use the AddAssetSpecToPropBucketCB() external function) in a callback being made to a node you specified at the AssetSpecReady() method. Once the asset is ready, we can use that asset to instantiate an instance and set properties as needed.

The Properties we will modify are:


// Asynchronous callback resulting from calling the external function AddAssetSpecToPropBucketCB()
method AssetSpecReady(spec as NodeRef of Class HBSpec, loadFailed as Boolean)
  println( "AssetSpecReady: " + spec.LW_FIELD_sFQN + " IsReady: " + IsAssetSpecReady( "DYNAMICREPLICATEDOBJECT", me.DRO_AssetID ) + " IsBroken: " + IsAssetSpecBroken( "DYNAMICREPLICATEDOBJECT", me.DRO_AssetID ))
  if not loadFailed
    // Remove existing instance, because we are about to create a new one using this asset spec
    instance as NodeRef of Class HBNode = me.DRO_InstanceID
    if instance <> None
      me.DRO_InstanceID = 0
      RemoveInstanceFromPropBucket( "DYNAMICREPLICATEDOBJECT", instance )
    .
 
    instance = CreateInstanceFromPropBucket( "DYNAMICREPLICATEDOBJECT", me.DRO_AssetID )
    me.DRO_InstanceID = instance
    SetNodePosition( instance, me.DRO_Position)
    SetNodeRotation( instance, me.DRO_Rotation)
    instance["LODFactor"] = 10
    instance["DiffuseColor"] = "#.267,.0,1.0,1.0"
    instance["AmbientColor"] = "#.267,.0,1.0,1.0"
    ActivateInstance( instance, "")
  .
.

Finally, while we are not actually doing anything in this replication callback I have included it for informational purposes. During this callback it is common that you might choose to associate the newly introduced node to the primary node for the replication.

// As each node is added by replication, a callback is made to the primary node notifying it that a new node
//   has arrived with its initial set data.
method _OnReplicationNodeAdded(addedNode as NodeRef)
 
.
Listen for Field Changes to Update the Visualization

If you recall, we used the DOM Editor to mark the DRO_field definitions to perform a change callback when updated. The callback is made to a shared function in any of the class method scripts that implement it for the classes that comprise the destination instance. For this tutorial, there is only one class involved DynamicReplicatedObject so we need implement it only in the client DynamicReplicatedObjectClassMethods script.

We want to take the following actions when fields change:

// Called when fields whose Client Callback propery was set to YES are updated
shared function _OnReplicationFieldUpdated(updateNode as NodeRef, updateField as String)
  where updateNode is kindof DynamicReplicatedObject
    instance as NodeRef of Class HBNode = updateNode.DRO_InstanceID
    when tolower( updateField )
      is "dro_position"
        SetNodePosition( instance, updateNode.DRO_Position )
      .
      is "dro_rotation"
        SetNodeRotation( instance, updateNode.DRO_Rotation )
      .
      is "dro_assetfqn"
        updateNode.DRO_AssetID = AddAssetSpecToPropBucketCB( "DYNAMICREPLICATEDOBJECT", updateNode.DRO_AssetFQN, updateNode )
      .
    .
  .
.
Remove the Visualization When Replication is Torn Down

When the client DRO receives notification that the replication is about to be torn down, we want to remove the visualization on the client so the user will no longer see it. The tear down is triggered by a spatial awareness event notifying the server node that the player has left its awareness range causing the DRO to remove the player from the destinations of its replication group.

// Teardown
method _OnReplicationNodeRelease(releasedNode as NodeRef) as Boolean
  instance as NodeRef = me.DRO_InstanceID
  if instance <> 0
    RemoveInstanceFromPropBucket( "DYNAMICREPLICATEDOBJECT", instance )
  .
 
  return true
.
 
// Only important if there are fields that are reference frame adjusted for seamless world as a part of this class, which there are not
method _OnReplicationGroupRehomed(oldArea as ID, oldInstance as ID, oldOffset as Vector3, newArea as ID, newInstance as ID, newOffset as Vector3)
.

Step 4: Test it out!

ReplicationTutorialConsoleCommand.png
In the console panel issue the command call dynamicreplicatedobjectclassmethods adddro, which will result in a remote call to the area server requesting the creation of a non-persisted Dynamic Replicated Object and introduce it to spatial awareness at your character's position. You should observe a purple sphere appear near your character's feet.

Reference

Source Code

Server Script

// DynamicReplicated Objects represent one of many alternate ways in which to cause the visualization of an asset
//   to appear on zero or more clients.  They are composed of both server and client classes and behaviors.
 
 
untrusted function AddDRO()
  dro as NodeRef of Class DynamicReplicatedObject = createnodefromclass( "DynamicReplicatedObject" )
  dro.DRO_AssetFQN = "\engine\cleangame\resources\common\utility_sphere_white01.gr2"
  var repGroup = dro._getReplicationGroup( true )
  dro.DRO_SetPosition( SYSTEM.REMOTE.CLIENT.GetPosition() )
.
 
method _getReplicationGroup(create as Boolean) as NodeRef of Class _ReplicationGroup
  replicationGroup as NodeRef of Class _ReplicationGroup = me._replicationGroupRef
 
  // During runtime, a reference is cached for speed.  If the cached reference is invalid, we do an exhaustive search in an
  //   attempt to locate an existing replication group.
  if me._replicationGroupRef = None
    foreach assoc in QueryAssociation( me, "base_hard_association", 0 )
      test as NodeRef = assoc.target
      if test is kindof _ReplicationGroup
        replicationGroup = test
        me._replicationGroupRef = replicationGroup
        break
      .
    .
  .
 
  if replicationGroup = None and create
    // Create replication group
    replicationGroup = CreateNodeFromClass( "_replicationGroup" )
    addAssociation( GetRootNode(), "base_hard_association", replicationGroup )
    replicationGroup._replicationPrimaryNode = me
 
    // ReplicationGroups need to know what spatial awareness to gather distances for priority.
    replicationGroup._replicationSpatialAwareness = $SPATIALAWARENESS_AREA
    replicationGroup._replicationSpatialEntity = me
 
    // Add an entity to the area's spatial awareness system
    entityInfo as Class _SAS_EntityInformation
    entityInfo._SAS_EI_awareness = 3                           // 3 unit awareness for our objects
    add back me to entityInfo._SAS_EI_event_receiving_nodes    // SAS events should be sent to our objects
    entityInfo._SAS_EI_position = me.DRO_GetPosition()
 
    $SPATIALAWARENESS_AREA._SAS_AddEntity( me, entityInfo )
 
    // cached reference for fast lookup
    me._replicationGroupRef = replicationGroup
  .
 
  return replicationGroup
.
 
method DRO_GetPosition() as Vector3
  return me.DRO_Position
.
 
method DRO_GetRotation() as Vector3
  return me.DRO_Rotation
.
 
// Set the position for the object, this also must update the area's spatial awareness system so that the object is introduced
//   appropriate as players draw near to it.
method DRO_SetPosition( pos as Vector3 )
  me.DRO_Position = pos
 
  // Update the area spatial awareness system with the new position
  $SPATIALAWARENESS_AREA._SAS_UpdateEntityPosition( me, pos )
.
 
method DRO_SetRotation( rot as Vector3 )
  me.DRO_Rotation = rot
.
 
// Called on nodes in the list field _SAS_EI_event_receiving_nodes when the entity
//   is informed that a subject has entered its awareness.  In the case of an entity suddenly appearing
//   (just added to the system, logged in, was just created etc) the appeared event is called instead.
proxyLocal method _SO_entered( awarenesssystem as ID, entity_ID as ID, subject_ID as ID)
// Parameters:
//   awarenesssystem - id of the awareness system instantiation that generated this event
//   entity_ID - id of the entity
//   subject_ID - id of the thing that entered the entity's awareness
 
  subject as NodeRef = subject_ID
 
  // If the entity represents a player, then add them to the replication group for the DynamicReplicatedObject
  if subject <> None and subject is kindof _playerAccount
    var repGroup = me._getReplicationGroup( true )
 
    // This will cause replication to start with the player's client as a destination
    repGroup._addClientDestination( subject, subject )
  .
.
 
// Called on nodes in the list field _SAS_EI_event_receiving_nodes when the entity
//   is informed that a subject has departed (left) from its awareness.  In the case of an entity
//   suddenly disappearing (deleted, removed from the system, logged off) the disappeared event is called instead.
proxyLocal method _SO_departed( awarenesssystem as ID, entity_ID as ID, subject_ID as ID)
// Parameters:
//   awarenesssystem - id of the awareness system instantiation that generated this event
//   entity_ID - id of the entity
//   subject_ID - id of the thing that departed the entity's awareness
 
  subject as NodeRef = subject_ID
 
  // If the entity represents a player, then remove them to the replication group for the DynamicReplicatedObject
  if subject <> None and subject is kindof _playerAccount
    var repGroup = me._getReplicationGroup( true )
 
    // This will cause replication to start with the player's client as a destination
    repGroup._removeClientDestination( subject )
  .
.
 
// Called on nodes in the list field _SAS_EI_event_receiving_nodes when the entity
//   is informed that a subject has appeared (just added due to node creation, login etc)
//   suddenly in its awareness range.
proxyLocal method _SO_appeared( awarenesssystem as ID, entity_ID as ID, subject_ID as ID)
// Parameters:
//   awarenesssystem - id of the awareness system instantiation that generated this event
//   entity_ID - id of the entity
//   subject_ID - id of the thing that appeared in the entity's awareness
 
   subject as NodeRef = subject_ID
 
  // If the entity represents a player, then add them to the replication group for the DynamicReplicatedObject
  if subject <> None and subject is kindof _playerAccount
    var repGroup = me._getReplicationGroup( true )
 
    // This will cause replication to start with the player's client as a destination
    repGroup._addClientDestination( subject, subject )
  .
 
.
 
// Called on nodes in the list field _SAS_EI_event_receiving_nodes when the entity
//   is informed that a subject has disappeared suddenly from awareness (node deletion, logoff etc)
proxyLocal method _SO_disappeared(   awarenesssystem as ID, entity_ID as ID, subject_ID as ID)
// Parameters:
//   awarenesssystem - id of the awareness system instantiation that generated this event
//   entity_ID - id of the entity
//   subject_ID - id of the thing that entered the entity's awareness
 
  subject as NodeRef = subject_ID
 
  // If the entity represents a player, then remove them to the replication group for the DynamicReplicatedObject
  if subject <> None and subject is kindof _playerAccount
    var repGroup = me._getReplicationGroup( true )
 
    // This will cause replication to start with the player's client as a destination
    repGroup._removeClientDestination( subject )
  .
.

Client Script

// DynamicReplicated Objects represent one of many alternate ways in which to cause the visualization of an asset
//   to appear on zero or more clients.  They are composed of both server and client classes and behaviors.
 
 
// Calls the server requesting it add a non-persisted dynamic replicated object positioned at the player's location.
function AddDRO()
  call server DynamicReplicatedObjectClassMethods:AddDRO()
.
 
// As each node is added by replication, a callback is made to the primary node notifying it that a new node
//   has arrived with its initial set data.
method _OnReplicationNodeAdded(addedNode as NodeRef)
 
.
 
// Called when all of the nodes that are part of the replication group have been added.  At this point, all nodes are present
//   with those of their fields marked "Initial Set" set to the replicated values
method _OnReplicationGroupAdded()
 
  if me.DRO_AssetFQN <> ""
    CreatePropBucket( "DYNAMICREPLICATEDOBJECT" )    // Ensures the prop bucket exists, there is no harm in creating when it already exists
 
    me.DRO_AssetID = AddAssetSpecToPropBucketCB( "DYNAMICREPLICATEDOBJECT", me.DRO_AssetFQN, me )
  .
.
 
// Asynchronous callback resulting from calling the external function AddAssetSpecToPropBucketCB()
method AssetSpecReady(spec as NodeRef of Class HBSpec, loadFailed as Boolean)
  println( "AssetSpecReady: " + spec.LW_FIELD_sFQN + " IsReady: " + IsAssetSpecReady( "DYNAMICREPLICATEDOBJECT", me.DRO_AssetID ) + " IsBroken: " + IsAssetSpecBroken( "DYNAMICREPLICATEDOBJECT", me.DRO_AssetID ))
  if not loadFailed
    // Remove existing instance, because we are about to create a new one using this asset spec
    instance as NodeRef of Class HBNode = me.DRO_InstanceID
    if instance <> None
      me.DRO_InstanceID = 0
      RemoveInstanceFromPropBucket( "DYNAMICREPLICATEDOBJECT", instance )
    .
 
    instance = CreateInstanceFromPropBucket( "DYNAMICREPLICATEDOBJECT", me.DRO_AssetID )
    me.DRO_InstanceID = instance
    SetNodePosition( instance, me.DRO_Position)
    SetNodeRotation( instance, me.DRO_Rotation)
    instance["LODFactor"] = 10
    instance["DiffuseColor"] = "#.267,.0,1.0,1.0"
    instance["AmbientColor"] = "#.267,.0,1.0,1.0"
    ActivateInstance( instance, "")
  .
.
 
// Called when fields whose Client Callback propery was set to YES are updated
shared function _OnReplicationFieldUpdated(updateNode as NodeRef, updateField as String)
  where updateNode is kindof DynamicReplicatedObject
    instance as NodeRef of Class HBNode = updateNode.DRO_InstanceID
    when tolower( updateField )
      is "dro_position"
        SetNodePosition( instance, updateNode.DRO_Position )
      .
      is "dro_rotation"
        SetNodeRotation( instance, updateNode.DRO_Rotation )
      .
      is "dro_assetfqn"
        updateNode.DRO_AssetID = AddAssetSpecToPropBucketCB( "DYNAMICREPLICATEDOBJECT", updateNode.DRO_AssetFQN, updateNode )
      .
    .
  .
.
 
// Teardown
method _OnReplicationNodeRelease(releasedNode as NodeRef) as Boolean
  instance as NodeRef = me.DRO_InstanceID
  if instance <> 0
    RemoveInstanceFromPropBucket( "DYNAMICREPLICATEDOBJECT", instance )
  .
 
  return true
.
 
// Only important there are fields that are reference frame adjusted for seamless world as a part of this class, which there are not
method _OnReplicationGroupRehomed(oldArea as ID, oldInstance as ID, oldOffset as Vector3, newArea as ID, newInstance as ID, newOffset as Vector3)
.
Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox