Replication Tutorial
|
This is an advanced tutorial covering a method of replicating objects from server to the client.
Overview
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
- Step 2: Set Replication Parameters
- Step 3: Create Class Method Scripts
- Step 4: Test it out!
Step 1: Create DOM Definitions
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.
- Create a new server class from the
data
archetype named: DynamicReplicatedObject - Create the fields specified in the table for the server class located on the right
- Add the server fields you created to the class DynamicReplicatedObject
- Create a new client class from the
data
archetype named: DynamicReplicatedObject - Create the fields specified in the table for the client class located to the right
- Add the client fields you created to the class DynamicReplicatedObject
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.
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.- Initial Priority: 100
- Delta Priority: 1 (priority increases by one per frame)
- Lifetime: 0 (field update does not age out over time)
- Change Callback: Yes (Script callback is made when the field is updated in the destination gom)
- Destination Field(Client): identically named field in the client DOM (i.e. DRO_Position -> DRO_Position)
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
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 , 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:
- Wrap the setting of Position/Rotation so that those updates may be performed on the spatial awareness entity representing the DRO
- Retrieve a Replication Group node which maintains a map of destinations to which replication traffic is sent and allows for the addition/removal of destinations
- Provide a convenient method to factory a DRO
- Receive Spatial Awareness events when players enter the object's awareness range so it can initiate replication to their client
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
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:
- Check if the DRO already has a replication group node instantiated for it
- There are two ways we will check this
- A cached reference in the field _replicationGroupRef which we added to our class
- If not found cached, look for a replication group node associated to the DRO
- There are two ways we will check this
- If replication group is not found and the
create
parameter was set to true. Create one- Instantiate a non-persisted node of the class _replicationGroup
- Set the replication group to replicate the DRO
- Set the spatial awareness system the replication group should use for distance calculations and the entity ID
- Add an entity to the spatial awareness system.
- Instantiate a non-persisted node of the class _replicationGroup
- Return a reference to the replication group
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.
- _SO_entered
- _SO_departed
- _SO_appeared
- _SO_disappeared
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
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:
- Create a non-persisted instance from our class DynamicReplicatedObject
- Initialize the DRO with the well known FQN of the humble white sphere
- Create a replication group
- Initialize the position of the DRO to be that of our character (i.e. dropping it at our character's feet)
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
- Use the FQN to instantiate a visualization via a Prop Bucket when introduced
- Listen for field changes to update the visualization
- Remove the visualization when replication is torn down (typically because the player left the awareness of the object)
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:
- Position
- Rotation
- LODFactor (modifier to LOD so the sphere is visible immediately when visualized)
- AmbientColor and DiffuseColor (so the spheres are colorful instead of just plain white)
// 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:
- When the DRO's position changes, update the position of the prop bucket instance
- When the DRO's rotation changes, update the rotation of the prop bucket instance
- When the DRO's FQN changes, load that asset spec into the prop bucket. This will result in visualizing a new model.
// 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!
In the console panel issue the commandcall 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
- Inter-Process Communication
- Replication Priority
- Class Replication Parameters
- Field Replication Parameters
- Replication Script Interface
- Spatial Awareness System
- Proxied node
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) .