Skip to content
mitm001 edited this page Jun 3, 2019 · 7 revisions

Getting Started

Checklist

  1. Reference dependencies or download jars
  2. Hook up the server
  3. Hook up the client

Project Setup

The easiest way to use SimEthereal is with a build system that supports maven-style repositories. Gradle is my personal favorite. In that case, one need only include the SimEthereal dependency.

Gradle:

compile "com.simsilica:sim-ethereal:1.1.1"

Maven:

<dependency> 
  <groupId>com.simsilica</groupId> 
  <artifactId>sim-ethereal</artifactId> 
  <version>1.1.1</version> 
  <type>pom</type> 
</dependency>

For direct IDE setup as in the JME SDK, you will need the SimEthereal jars as well as the related dependencies.

Download the latest release of SimEthereal from: https://github.com/Simsilica/SimEthereal/releases

Dependencies:

Basic Usage

Setup Server

  1. Setup the game-specific constants that the client and server will share:
    private static final int gridSize = 64;
    
    /**
     *  The 3D zone grid definition that defines how space is broken
     *  up into network zones.  
     */
    public static final ZoneGrid ZONE_GRID = new ZoneGrid(gridSize, gridSize, gridSize);
 
    public static final float MAX_OBJECT_RADIUS = 5;
    
    /**
     *  Defines how many network message bits to encode the elements of position 
     *  fields.
     */   
    public static final Vec3Bits POSITION_BITS = new Vec3Bits(-MAX_OBJECT_RADIUS, 
                                                              gridSize + MAX_OBJECT_RADIUS,
                                                              16);
 
    /** 
     *  Defines how many network message bits to encode the elements of rotation
     *  fields.  
     */
    public static final QuatBits ROTATION_BITS = new QuatBits(12);
 
    /**
     *  Defines the overall object protocol parameters for how many bits ar used
     *  to encode the various parts of an object update message.  
     */   
    public static final ObjectStateProtocol OBJECT_PROTOCOL 
                = new ObjectStateProtocol(8, 64, POSITION_BITS, ROTATION_BITS);
 
    /**
     *  Defines the 3D zone radius around which updates will be sent.
     */           
    public static final Vec3i ZONE_RADIUS = new Vec3i(1, 1, 1);

For a complete example, see: GameConstants.java

  1. Add the EtherealHost to SpiderMonkey's Server services:
    EtherealHost ethereal = new EtherealHost(OBJECT_PROTOCOL, 
                                             ZONE_GRID,
                                             ZONE_RADIUS);
    server.getServices().addService(ethereal);

For a complete example of server setup, see: GameServer.java

  1. Forward game object updates the the ZoneManager retrieved by calling EtherealHost.getZones(). This is a game-specific hook-up so it's difficult to show a specific example but the code would look something like:
    ZoneManager zones = ethereal.getZones();
    
    ...   
    
    // On new frame
    zones.beginUpdate(System.nanoTime());
    
    // On each object update
    zones.updateEntity(objectId, true, objectPosition, objectOrientation, objectWorldBounds); 
    
    // At the end of the update frame
    zones.endUpdate();

For a complete example of a physics listener that forwards to the ZoneManager, see: ZoneNetworkSystem.PhysicsObserver

  1. Finally, at some point during HostedConnection setup, the game code must tell the EtherealHost to start hosting the shared space for that client and to tell EtherealHost which object ID represents the client.
    // Time to startup the client on HostedConnection conn
    getService(EtherealHost.class).startHostingOnConnection(conn);
    getService(EtherealHost.class).setConnectionObject(conn, playerObjectId, playerStartingPosition);    

For an in-context example, see the startHostingOnConnection() method of GameSessionHostedService

Setup Client

  1. Add the EtherealClient to SpiderMonkey's Client services using the same constants defined for the server:
    EtherealClient ethereal = new EtherealClient(OBJECT_PROTOCOL,
                                                 ZONE_GRID,
                                                 ZONE_RADIUS); 
    client.getServices().addService(ethereal);

For a complete example of client setup, see: GameClient

  1. Add a SharedObjectListener to get notified about object updates:
    SharedObjectListener gameObjectUpdater = new SharedObjectListener() {
            public void beginFrame( long time ) {}
    
            public void objectUpdated( SharedObject obj ) {
                Spatial spatial = lookUpSpatialFor(obj.getEntityId());
                spatial.setLocalTranslation(obj.getWorldPosition().toVector3f());
                spatial.setLocalRotaiton(obj.getWorldRotation().toQuaternion());
            }
    
            public void objectRemoved( SharedObject obj ) {
               Spatial spatial = removeSpatialFor(obj.getEntityId());
               spatial.removeFromParent();
            }
    
            public void endFrame() {}            
        };
    client.getServices().getService(EtherealClient.class).addObjectListener(gameObjectUpdater);

Optionally

For interpolation of the object updates, you can use some existing classes already provided.

  1. For each object, setup a transition buffer that will record some back-log of object state history:
    // A history of 12 should give us about 200 ms of history at 60 FPS
    // through which to interpolate.  12 * 1/60 = 12/60 = 1/5 = 200 ms.
    TransitionBuffer<PositionTransition> buffer = new TransitionBuffer<>(12); 
  1. In the shared object listener, add the new object state to the transition buffer:
    ....

    @Override
    public void beginFrame( long frameTime ) {
        this.frameTime = frameTime; // for pushing time-based updates into the buffers
    }

    @Override
    public void objectUpdated( SharedObject obj ) {

        TransitionBuffer<Position> buffer = ...retrieve the buffer for obj.getEntityId()
        Vector3f pos = obj.getWorldPosition().toVector3f();
        Quaternion quat = obj.getWorldRotation().toQuaternion();                
        PositionTransition trans = new PositionTransition(frameTime, pos, quat, visible);
        buffer.addTransition(trans);
    }
    
    ....    
  1. On the render thread, pull interpolated results based on the current time from EtherealClient.getTimeSource() and apply them to the spatials.
    long time = client.getServices().getService(EtherealClient.class).getTimeSource().getTime();
    for( Spatial spatial : trackedSpatials ) {
        TransitionBuffer buffer = get buffer for spatial
        PositionTransition trans = buffer.getTransition(time);
        if( trans != null ) {
            spatial.setLocalTranslation(trans.getPosition(time, true));
            spatial.setLocalRotation(trans.getRotation(time, true));
            boolean isVisible = trans.getVisibility(time); 
            spatial.setCullHint(isVisible ? CullHint.Inherit : CullHintAlways);
        }                   
    }

For an in-context example of updating spatials through a TransitionBuffer, see the various subclasses of ModelViewState.java

What Next?

A good place to start would be the more extensive Documentation page.

Also be sure to check out the case study example at Example.

And check the background reading at Resources.