Welcome to my second tutorial. I will show you how you can use more advanced (also easy to use) techniques to avoid messy networking solutions.

Propagating properties to clients

In the first tutorial QuickStart I wrote about disributing properties and creating distributed objects.

A quick summary:

1. Derive your class from DisributableObject
2. Write your property and flag it with a Distribute custom attribute. This will tell the DistributionSession that this property will participate in distribution process.
Without this attribute, the property will be ignored.
(3. Optional) Register the property with DistributionProperty.Register in the class constructor

There are two ways a property will participate in distribution process.

A. The property will only be flaged with the Distribute custom attribute. You don't register it.
For example:

    [Distribute]
    public int TimeToLive {get;set;}


B. The property will be flaged with the Distribute custom attribute. Furthermore you use inner private fields which wraps the distribution and register it:

   [Distribute]
    public int TimeToLive 
    {
          get { return _timeToLive.GetValue(); }
          set { _timeToLive.SetValue(value); }
    }
    DistributableProperty<int> _timeToLive;

    //class constructor:
    public MyDistrClass()
    {
          _timeToLive = DistributableProperty<int>.Register("TimeToLive", this);
    }

So what is the difference between these two approaches?

In A. you tell the DistributionSession that this property will be distributed only once: on creating of the instance.
More specifically you don't use DistributionSession.CreateObject() method, instead you use DistributionSession.DistributeObject().
For example
         //A. approach:
         MyDistrClass instance = new MyDistrClass(); //create normally an instance
         instance.TimeToLive = 100;  //set the distributed property using the ordinary .NET setter

         DistributionSession.DistributeObject(instance); //this will register this object to the distribution process and also the 'TimeToLive' property will be set to 100, e.g. all clients will have the same 100 value.
//The important thing is that if you set the property after DistributeObject() method, the value will never be delivered to other clients
//So use the A approach to initialize same values for all clients (i.e. start position of a tank)

In B. you tell the DistributionSession that this property will be distributed allways, e.g. all clients will have allways the same value.

         //B. approach:
         MyDistrClass instance = new MyDistrClass(); //create normally an instance
         
         DistributionSession.DistributeObject(instance);  //lets distribute this object

         instance.TimeToLive = 100;  //set the distributed property using the ordinary .NET setter. Set it *after* DistributeObject() method!

Why did we set the TimeToLive after DistributeObject() method? Because the instance must be first registred as distributed (DistributeObject()).
If we set this property prior of DistributeObject(), you get a DistributableObjectException.

We can of cource create multiple properties and combine them like this:

public class MyDistrClass: DistributableObject
{
    [Distribute]
    public int Velocity    //distribute allways
    {
          get { return _velocity.GetValue(); }
          set { _velocity.SetValue(value); }
    }
    DistributableProperty<int> _velocity;

    [Distribute]
    public int Position {get;set;}   //distribute only on creation 

    //class constructor:
    public MyDistrClass()
    {
          _velocity= DistributableProperty<int>.Register("Velocity", this);
    }

    public void Update()
    {
          this.Position += this.Velocity;
    }
}

//use:
MyDistrClass instance = new MyDistrClass();
instance.Position = 80;   //all clients will recieve this value +only-once+  e.g. on creation
DistributionSession.DistributeObject(instance);  //register and set the Position property to 80 for all clients

//from now on, all clients will individually handle theirs Position property, e.g. change won't propagate to other clients

instance.Velocity = 2;  //this will propagate the change to all clients +allways+


It is good to have properties which will never be distributed (private).
Then it is good to have properties, which will be distributed only once. And the lastly, you can have properties, which will propage their changes to all clients allways.

Setting UDP properties

It is possible to change the way UDP sends packets. There are four ways how to deliver a packet in general:

1. Reliable
2. Unreliable

A. Ordered
B. Unordered

You can combine 1,2 with A,B, so you get lastly 4 options.
The DistributableProperty lets set this behavior in the Register method:

DistributableProperty<T>.Register(string propName, object owner, NetChannel channel)

where the channel parameter is an enum which let you choose from many options (ReliableInOrder, Unreliable, ..).

Note, that the registering (eighter using CreateObject<T>() or DistributeObject()) of an instance will allways be reliable and in order.

Disposing of distributed objects

You can register the objects as distributable. So you can unregister them (make dispose of them) in the distribution process.

The best way to show you is an easy example:


MyDistrClass instance = DistributionSession.CreateObject<MyDistrClass>();
// or
MyDistrClass instance = new MyDistrClass();
DistributionSession.DistributeObject(instance);

//now dispose of it (unregister)
DistributionSession.Dispose(instance);

That's it!
This will notify all clients about disposing of this object, e.g. it will remove it from the DistributionSession.DistributableObjects dictionary in every client.

Handling updates from server

Sometimes you want handle special situations when a change in your property occurs.
You already know that if you change the value of your distributed property, the new value will be set from the server-update. Until then the property Getter will return the old value.

For example you created a simple multiplayer tank game. Now you want to handle the 'death' situation of the tank.
Some code:

public class Tank: DistributableObject
{
    [Distribute]
    public int Live    //distribute allways
    {
          get { return _live.GetValue(); }
          set { _live.SetValue(value); }
    }
    DistributableProperty<int> _live;

    [Distribute]
    public int Position {get;set;}   //distribute only on creation 

    //class constructor, we add the callback ('onLiveChanged') if the value gets updated from server:
    public Tank()
    {
          _live= DistributableProperty<int>.Register("Live", this, onLiveChanged);
    }

    private void onLiveChanged(object sender, PropertyChangedEventArgs<int> args)
    {
         if ( args.NewValue == 0 ) DoExplosion();
    }

}

Lets assume that the tank was hit by a huge projectille shot from other tank. This would mean that the Host lowered our tanks Live property to zero. And you want to handle an explosion on this special occasion.
We can look at it as a server update, e.g. the server propagates new values to all clients, so we have to handle this property changed event:
Easy to do!



We just added the callback onLiveChanged, which is called whenever the new value is updated from server.

In the explosion method we could add also the disposal of this Tank (so it won't participate in any Update() or Draw() method):

void DoExplosion()
{
      //start some cool explosion effect

     //dispose of the Tank:
     DistributionSession.Dispose(this);
}

Now if the tank live goes to zero, every player on its machine will invoke the DoExplosion() method.

Ok, I hope this will help you to manage your network related issues.


Cheers!

Last edited Oct 15, 2009 at 10:03 AM by Kaleta, version 19

Comments

No comments yet.