Step 9 — Adding behaviour to avatars

In other words, adding behaviour to avatars means synching gestures between the pilot and drones.

As you may have noticed, the avatars used have some behaviour that is triggered when the avatar is idle (not moving) or by the user, e.g. when the avatar is clicked on. This of course should be synchronized between clients, so that all user see the same behaviour for an avatar.

To do so, one must distinguish between an avatar that is used to represent the local user and an avatar that represents other users. From the point of view of an avatar file one can say that one instance of the file runs in the client of the user that is associated with the avatar, and many instances of the file run in the other clients to represent the user. In the first case we call this instance "a pilot", and the other instances are called "drones". The drones represent the user associated with the avatar to other users.

Scripts running in the avatar must therefore behave different if in pilot mode or in drone mode. If in drone mode, they should not take commands by the user (e.g. mouse clicks), as controlling the avatar is the domain of the associated user. Usually drones don't trigger actions like idle movements. They only follow to perform the comands they receive from the pilot instance.

If a script runs in Pilot mode it may provide a user interface to the local user that allows him/her to trigger gestures and other actions. Also idle movements are usually triggered by the pilot and sent as commands to the drones.

Implementing Avatar Synchronization

To synchronize gestures, the pilot must send events about which gesture to perform to its drones. Custom communication between clients can be done with the EventStreamSensor node. This node can have custom fields, similar as a Script node can. If it receives an event on an eventIn field, it outputs that event on an associated eventOut field. The event is output in the same client, but also in all other clients participating the scene. This way, if the pilot is triggered to perform an gesture, it sends an event to the EventStreamSensor and all drones (as well as the pilot) receive that event from the EventStreamSensor and perform the respecitive gesture as a result.

In our example the avatars have a TouchSensor and when this is clicked, Gesture 1 is triggered and the Avatar bows. The TouchSensor is enabled only for the pilot.

PROTO Avatar
[
    exposedField SFBool isPilot FALSE

    ...

    DEF Streamer EventStreamSensor
    {
        eventIn  SFInt32 evt_Gesture
        eventOut SFInt32     Gesture_evt
    }

      ...

    DEF Welder Script
    {
        exposedField SFBool isPilot  IS  isPilot
        field        SFNode Streamer USE Streamer

        ...

        url "vrmlscript:

        function TriggerGesture(Id)
        {
            if(!isPilot) // only the pilot can trigger an action. Drones just follow what the pilot says.
        return;

            ...
            Streamer.evt_Gesture= Id;
        }

        function StartGesture(Id)
        {
            ...
            Code that initializes the animation of the gesture with the given id.
            ...
        }

        ...

        "
    }

    ROUTE Streamer.Gesture_evt TO Welder.StartGesture

    ...
}

How EventStreamSensors work, a Brief Intro

When used with BS Collaborate the EventStreamSensor node associates eventIns with eventOuts via their name. If an eventIn is named set_SomeThing then it is associated with an eventOut named SomeThing_changed. Events received on set_SomeThing are sent to the BS Collaborate server which distributes it to all other clients and also to the originating client. Then the SomeThing_changed eventOut in all clients output the event.

BS Collaborate supports statefull events as well as stateless. Statefull events are stored on the server and besides set_ there are other prefixes like add_ or inc_ that do simple calculations on the state value at the server before the result of the calculation is distributed to the corresponding ..._changed eventOuts. If a new client joins the scene the current value of all states are sent to the new client and all ..._changed eventOuts emit an event during initialization.

Stateless events on the other side use the prefix evt_ for eventIns of the EventStreamSensor and the postfix _evt for eventOuts. Stateless events are not stored on the server and not sent during the initialization phase of a newly joining client. The server just forwards them to other clients as is.

Gestures as used in this tutorial conform with stateless events, so that we add fields with the names evt_Gesture and Gesture_evt.

Adressing the Right Drones

In order to send the gestures to only those drones belonging to a certain pilot we use the streamName field of the EventStreamSensor node. BS Collaborate assigns each user a separate session id that is unique for each user, and if a user uses the same nick name for logging in multiple times - with multiple clients - this session id is unique for each session of that user. This session id is used to form a unique name for the EventStreamSensor.

PROTO Avatar
[
    eventIn SFInt32 set_sessionId

    ...

    Script
    {
        eventIn SFInt32 set_sessionId IS set_sessionId
        field SFNode Streamer USE Streamer

        url "vrmlscript:
        ...
        function set_sessionId(Id)
        {
            Streamer.streamName= 'Avatar_'+ Id;
        }
        "
    }
    ...
}