Advanced Use of Phoni

From ETC Public Wiki
Jump to: navigation, search

Phoni System

This page is associated with Phoni System, a network framework to make standalone device into game controllers for PC. Please visit Phoni System for other information.

Advanced Use of Phoni

In this section we will discuss more stuff you can do with Phoni System, which requires a little bit more understanding of the system itself.

Send Data from Game to Player

While it is common that player (controller) keep sending its data (like touch, motion, buttons) to the game, there might be a situation that you want your game to keep sharing a game state or anything with all your players. Surely you can use command, but for data needs continuously update, a UDP data sending is a better solution. In this case we need the game be able to send data to player.

As described in PhoniTerms, both game and player are of form PhoniDataPort, so naturally it allows us to do this reverse sending. But there is one more modification you need to do. Take "TouchData" for example, go to PhoniDataPort and you'll find the declaration as following:

[PhoniData(PhoniDataCode.DATA_PHONI_TOUCH)]
public PhoniDataWrapper<PhoniTouchData, PhoniTouchDataSerializer> TouchData {get; private set;}

The PhoniDataAttribute allows Phoni System to recognize this data is one of the data you want to send and receive via network. The attribute has an alternative constructor:

[PhoniData(PhoniDataCode.DATA_PHONI_TOUCH, SendingCase.SendToGame)]

where the 2nd parameter has the following definition:

public enum SendingCase {
SendToNone, // does not add to sendingCodes
SendToGame, // add to sendingCodes if the PhoniDataPort is of a player and connected to a game
SendToPlayer, // add to sendingCodes if the PhoniDataPort is of a game and connected to a player
SendToBoth, // add to sendingCodes anyway
}

The SendingCase determines whether the data will be initially considered for sending, and the default value is SendToGame, which means only PhoniDataPort in PhoniInput.Game will send out the data. If you want to send data to player, explicitly define

[PhoniData(PhoniDataCode.DATA_PHONI_TOUCH, SendingCase.SendToPlayer)]

Then you can simply modify, say PhoniInput.Player[0].GameState.SendingData, and the data will get sent out. Try to avoid SendToBoth unless you really need, just to reduce unnecessary network transaction.

Control What Data You Want to Send at Runtime

From the above we know we can change SendingCase in PhoniDataAttribute to decide whether a data should be sent out when Phoni starts up. We can actually change this at runtime. Instead of change SendingCase, which make no sense at runtime, we directly access phoniDataPort.sendingCodes by adding/removing PhoniDataCode to enable/disable specific data from sending.

For example, after the following operation, the touch data will not be updated to the game:

PhoniInput.Game[0].sendingCodes.Remove(PhoniDataCode.DATA_PHONI_TOUCH)

Game and Player on the same device

Though it might be a rare case, but Phoni System allow you to start a game and connected to another game as a player on the same device at the same time. You simply need to follow the instructions in section Get Started with Phoni, and start game and player. Make use of PhoniInput.Game and PhoniInput.Player for data sending/retrieving and command sending. For command process, if you just want a single command process callback:

phoniController.CommandEventFromPlayers += ProcessCommand
phoniController.CommandEventFromGames += ProcessCommand

Then in the "ProcessCommand", check the PhoniDataPort get passed in:

phoniDataPort.IsPlayerPort or phoniDataPort.IsGamePort

to determine whether you should to go PhoniInput.Player and PhoniInput.Game.

Create Custom Data and Command

After you start playing around Phoni, you will reach a stage that you want to send over a instance of your own class with a command, or you want to add a new data to be continuously sent/received via UDP. This section is for the need. There are several cases listed as following from the most simple one to fully customization.

For customization, you only need to modify at most 3 scripts (All in "PhoniSystem/PhoniData" folder) in Phoni System even for the most complex case. Here is the general step:

  1. If using your own class, define your class according to the following cases. You can define it anywhere, but it is recommended to put it in PhoniStructurePackage.cs.
  2. Define your code. All the code is in PhoniStructureBase.cs. If making a new command, define your command code in PhoniCommandCode; if making a new UDP-handled data, define your data code in PhoniDataCode; if making a new PhoniData class, define your package code in PhoniPackageCode and add your class in PhoniPackageCenter.
  3. If you are making a new UDP-handled data, go to PhoniDataPort.cs and declare the property associated with your class according to the following cases.

Case 1 : Use primitive or serializable class

This is the most simple case that you only want to use a primitive (ie. float) or a serializable class (ie. string).

Setup
Well you don't really need to setup anything :)
Use with command
You need to define your command in PhoniCommandCode and use PhoniData<T> with that command:
phoniDataPort.SendCommand(PhoniCommandCode.MY_FLOAT_COMMAND, new PhoniData<float>(1.0f))
phoniDataPort.SendCommand(PhoniCommandCode.MY_STRING_COMMAND, new PhoniData<string>("hello"))
Use with data
You need to define your data in PhoniDataCode, and use PhoniDataWrapper<T> to define the following in PhoniDataPort:
[PhoniData(PhoniDataCode.MY_FLOAT_DATA)]
public PhoniDataWrapper<float> MyFloat{get; private set;}
[PhoniData(PhoniDataCode.MY_STRING_DATA)]
public PhoniDataWrapper<string> MyString{get; private set;}
Now Phoni will handle everything else for you.

IMPORTANT: Some Unity structures including Vector3, Vector2 and Quaternion is NOT serializable. Please refer to Case 3 for these structures.

Case 2 : Make your serializable class

If you need to use your own class, it is the simple case if all your class member is serialzable, then you can define your class and mark it serializable.

Setup
Define a serializable class and make a default/parameterless constructor:
[System.Serializable]
public class MySerializableClass {
// members
public MySerializableClass() {
// do initialization
}
}
You need to make sure your members are actually serializable, otherwise even if you mark it with [System.Serializable], it won't get serialized.
Keep in mind that if you are a game and have not received this data from player yet, when you access this data via PhoniDataPort, you'll get the instance generated from default constructor. The same, if you are a player and do not make any modification to this sending data, the data get sent to game will be the instance generated from default constructor. So make sure you initialize all your members appropriately.
Use with Command
The same as Case 1:
phoniDataPort.SendCommand(PhoniCommandCode.MY_SERIALIZABLE_CLASS_COMMAND, new PhoniData<MySerializableClass>(new MySerializableClass()))
Use with Data
The same as Case 1:
[PhoniData(PhoniDataCode.MY_SERIALIZABLE_CLASS_DATA)]
public PhoniDataWrapper<MySerializableClass> MySerializableClassData{get; private set;}

In PhoniStructurePackage.cs, you can use PhoniConnectData or PhoniButtonData as an example.

Case 3 : Make your class and your serializer class

A more complicated cases comes when your class has a un-seializable member, a Vector3 for example. In this case, besides defining your class, you need to also make a serializer for your class (aka. a serializable version of your class).

Setup
First define your class and make a default/parameterless constructor:
public class MyClass {
Vector3 vector;
// members
public MyClass() {
// do initialization
}
}
Then define your serializer that implements interface IPhoniPackage<MyClass>, your serializer must be serializable:
[System.Serializable]
public class MySerializer : IPhoniPackage<MyClass> {
Vector3Serializer vector;
// members
public MySerializer () {
// do initialization
}
public object Unpack () {
MyClass myClass = new MyClass()
// fill in data to myClass
return myClass
}
public void Pack(MyClass myClass) {
// fill in data from myClass
}
}
Use with Command
You need to define your command in PhoniCommandCode and use PhoniData<T, U> with that command:
phoniDataPort.SendCommand(PhoniCommandCode.MY_CLASS_COMMAND, new PhoniData<MyClass, MySerializer>(new MyClass()))
Use with Data
You need to define your data in PhoniDataCode, and use PhoniDataWrapper<T, U> to define the following in PhoniDataPort:
[PhoniData(PhoniDataCode.MY_CLASS_DATA)]
public PhoniDataWrapper<MyClass, MySerializer> MyClassData{get; private set;}

Phoni System provides serializers for Unity structures that are not serializable, including Vector2Serializer for Vector2, Vector3Serializer for Vector3 and QuaternionSerializer for Quaternion. You can now use these Unity structures as command and data as following:

phoniDataPort.SendCommand(PhoniCommandCode.MY_VECTOR3, new PhoniData<Vector3, Vector3Serializer>(Vector3.zero))

and

[PhoniData(PhoniDataCode.MY_VECTOR3_DATA)]
public PhoniDataWrapper<Vector3, Vector3Serializer> MyVector3{get; private set;}

In PhoniStructurePackage.cs, you can use PhoniAnalogData as an example.

Case 4 : Make your class and fully customize serialization

Now we comes to a most complicated case, if you want to go that deep. The default serialization is using standard xml serialization. In some case you want to use other serialization method, for example Unity iOS does not support xml serialzation.

Phoni System has a JSON option, just go to PhoniSystem/PhoniCore/PhoniUtil.cs and uncomment #define JSON in the first line, and it will use JSON instead.

Moreover, you may modify PhoniUtil.Serialize(object obj) and PhoniUtil.Deserialize(byte[] data) function to change the serialization implementation for all data in Phoni System.

The following steps are for those who want to keep the way of serialization for other PhoniData, but just create a class that handles serialization itself.

Setup
First you need to define your package code in PhoniPackageCode
Then define your class inheriting PhoniDataBase and make a default/parameterless constructor and override the following:
  1. override GetCode() : return YourCode
  2. override Serialize () : handle your serialization
  3. override Deserialize() : handle your deserialization
Go to PhoniStructureBase.cs->PhoniPackageCenter and add your class in its static constructor by
Add<MyCustomClass>();
Now you can use your class directly. You don't need to call GetData<T>() when you receive a PhoniDataBase in command process callback, just cast it to your class and access your members.
Use with Command
You need to define your command in PhoniCommandCode and use MyCustomClass directly with that command:
phoniDataPort.SendCommand(PhoniCommandCode.MY_CUSTOM_CLASS_COMMAND, new MyCustomClass())
Use with Data
You need to define your data in PhoniDataCode, and use PhoniDataWrapperCustom<T> to define the following in PhoniDataPort:
[PhoniData(PhoniDataCode.MY_CUSTOM_CLASS_DATA)]
public PhoniDataWrapperCustom<MyCustomClass> MyCustomClassData{get; private set;}

We don't provide the definition of class, but you in PhoniStructurePackage.cs, you can use PhoniCustomDataSample as an example.

Cross-platform Play & Use Phoni for a New Platform

Phoni support cross-platform play, which means you may use Android tablet to play against your friend on PS Vita in the same game on PC. To determine which platform, simply call:

PhoniInput.Player[0].RemotePlatform

and you can retrieve input data correspondingly.

Phoni can be easily port to other platforms. For a new platform you will need a new set of date and command, refer to section Create Custom Data and Command to see what you need to do. Other than that, go to PhoniStructureBase.cs and add your platform code in PhoniPlatformCode. If you are still using Unity for the new platform, go in "PhoniControllerForUnity.cs" and do local platform initialization in the Awake() function if the script is running on the new platform:

PhoniGameController.Init(PhoniPlatformCode.MY_PLATFORM);
PhoniPlayerController.Init(PhoniPlatformCode.MY_PLATFORM);

If you are NOT using Unity, you need another script to do the work that PhoniControllerForUnity did for you. Please refer to the next section What does PhoniControllerForUnity do

What does PhoniControllerForUnity do

In this section we will discuss why Phoni System need PhoniControllerForUnity as a monobehavior script attached to a game object in your game or player scene.

PhoniControllerForUnity has 3 missions:

  1. Initialize local platform, this value is used for connection.
    Set appropriate platform at when your application starts as discussed in the section above.
  2. Make sure the data and command you get/send in Unity is synchronized with all underlying UDP and TCP threads.
    Since Phoni is a multi-thread framework, the data received by UDP read thread will not be directly assigned to the "ReceivedData", instead it is saved in a backlog until you call "UpdateReceivedData", where we swap backlog data into "ReceivedData" in a thread safe way.
    Similarly for player, the "SendingData" you modified will not reflect directly on the data you sent out until you call "FlushSendingData" to swap the modified data to where UDP write thread actually read from.
    The command you received is handled in a different way. It is stored in command queues, and you need to safely retrieve the queues and clean it up if you consume the command.
    There are functions already defined to do these work as you can see in 'PhoniControllerForUnity. You should call these functions, most efficiently, once per update. Note we call UpdateData() and FlushData() in LateUpdate() to make sure every script will get the same data in the next Update().
  3. Default operations such as handling disconnection, lost connection, application pause and application quit.
    Basically you need to process DISCONNECT and LOST_CONNECTION command (remove and disconnect PhoniDataPort), and process SUSPEND command (Set PhoniDataPort's activity).
    Also send out SUSPEND command when your application is paused/resumed, and do clean up when your application gets shut down.

Contact

If you have any question or if you are interested in further development, please contact [Xun Zhang].