Phoni System

From Unity3D

Jump to: navigation, search

Contents

What is Phoni System?

Phoni System is an open source network framework to make standalone device into game controllers for PC. It is not a framework designed for MMO, but for all needs that multiple controllers may have, auto sending sensor data from controllers to games, and sending commands back and forth. It is up to you to define what device can be controllers, for now it supports PC/Mac, Android devices, and potentially PS Vita.

Phoni System is designed to support multi-player(multi-controller) cross-platform play. You will be able create a Wii U like experience with PC and Android device or PS Vita.

On Android platform for example, your game on PC can easily access to touches, acceleration, gyroscope, and any other custom defined data from Android with Phoni System just as from keyboard and mouse.

Requirements:

Wifi environment for Andoird device, and internet for PC.
Unity Basic for PC.
Unity Android Pro for Android.

Demo Video

Why Phoni System?

PhoniSystem
  • Fully support Unity.
    You can use Unity to build both Android side and PC side, fully customize your display UI as all you can do with Unity.
  • Independent of Unity.
    You can use the core part of Phoni System on any other .Net platform. Actually I'm going to port Phoni to PS Vita as the next step, which will involve PSM SDK instead of Unity for Vita side.
  • Easy customizable data.
    You only need to define your data/class in 2 or 3 steps, and Phoni will do anything else for you just as using built-in Phoni data and command.
  • Multi-player cross-platform play.
    You can play the same game on multiple android phones/tablets, PCs and, in the future, PS Vita or any other device running Phoni. Phoni is designed to support cross platform.

Get Started with Phoni

In the section we will give you a quick start of using Phoni System. Go to section Package and Example Project download either package or example project, import/open it in Unity. There are 3 folders in the package:

  • Phoni folder contains the core part of Phoni System and a Unity prefab you may want to use in your game. If you want to start from a blank scene/project, this folder has everything you need.
  • PCDemo is an example for game side, where you access to data from players and implement gameplay. We will use PhoniGameSample.cs to get you started to use Phoni on game side.
  • AndroidDemo is an example for player side, where you send data to game. We will use PhoniPlayerSample.cs to get you started to use Phoni on player side. You need Unity Android Pro License to build this scene.

Basic Setup

It is recommended to start with example scene provided in PCDemo or AnroidDemo to make your game or player client. If you start with a blank scene, you can simple drag PhoniController.prefab into the scene and have your controller script holds a reference of the PhoniControllerForUnity script attached to it. The script will do common operation you'll need for Phoni, which we will further discuss in latter sections. The prefab is already marked DontDestroyOnLoad, so put it in your initial scene.

Game Side

We use PhoniGameSample.cs in PCDemo folder as an example.

Start Game

You just need one call:

PhoniGameController.GameClientStart(int listenPort)

, where you put in a port you want to open for listening. Note that you are not guaranteed to get the port; if the port is already in use, system will assign a random port for you. Or if you don't care which port to open, use the variation

PhoniGameController.GameClientStart().

You can only start the game on one port. If you want to start on another port, you need to shut down game first.

Access Connected Players

After open up your game client, Phoni System will listen to connection command and connect players automatically. You can access your players by

foreach(PhoniDataPort player in PhoniInput.Player)

, where each player is a PhoniDataPort. You can also access single player by

PhoniInput.Player[NetworkInfo info] or PhoniInput.Player[int playerID].

NetworkInfo represents your players' IP and port, and playerID is assigned by Phoni to keep track of player while they are connected. playerID is not consistent for reconnection.

For example, player A, B, C connected and get assigned as playerID 0, 1 and 2; Then A and C disconnect, player B will keep its ID 1; If player C reconnect, he will get playerID 0 instead of his old ID 2, but B will still keep its ID 1.

Finally you can get a list of all players by

PhoniInput.Player.GetPortList()

, or get a list of active/inactive players by

PhoniInput.Player.GetPortListByActivity(bool isActive).

By default, a player is inactive means the player client app is suspended/paused on Android device (but still connected).

Get Data from Players

You should always try to access your player before you try to get data from it. Use

PhoniInput.Player.Contains()

function to check if a player exists (networkInfo or playerID).

Once you can access your player, say a player with player ID 0 PhoniDataPort player = PhoniInput.Player[0], receiving data is easy. Take touch data for example, just access

player.TouchData.ReceivedData

as you are using a local device.

Send Command to Players

Assume we have PhoniDataPort player = PhoniInput.Player[0].

If your command does not need a parameter, like the rumble command, you may call

player.SendCommand(PhoniCommandCode.COMMAND_RUMBLE).

If your command need a parameter, like the switch UI command with a bool value, you may call

player.SendCommand(PhoniCommandCode.COMMAND_UI_SWITCH, new PhoniData<bool>(true))

PhoniData<T> is one way to wrap up you data, so it will be packed, send over network and be able to be unpacked by the player. We just briefly introduce sending command here, more information about how to define you command and data structure you want to send over, please refer to section Advanced Use of Phoni -> Create Custom Data.

Process Command from Players

Players will also send command to the game. To process the command, you need to define your command process function as PhoniCommandCallback. For example you may define void ProcessCommand(PhoniDataPort player, PhoniCommandInfo info)

Recall that in the Basic Setup step we recommend you to hold a reference to PhoniControllerForUnity, assume you have PhoniControllerForUnity phoniController. You need to add your process function to PhoniControllerForUnity's event when the script starts:

phoniController.CommandEventFromPlayers += ProcessCommand.

Your function will get called when a command arrives. Now you can go on implement your process function. Parameter "player" will give you which player send you the command, while "info" provides command contents. info.command is the command code, info.data is the parameter that player send along.

Note info.data is of type PhoniDataBase, which is the base class of PhoniData<T>. You may get your data by info.data.GetData<T>(). For example when you receive command code COMMAND_UI_SWITCH, you know the data will be a bool, so retrieve you data by

info.data.GetData<bool>().

You may want to take a look at section Built-in Data and Commands. Again more information about how to define you command and data structure you want to send over, please refer to section Advanced Use of Phoni -> Create Custom Data.

Disconnect Player

Assume we have PhoniDataPort player = PhoniInput.Player[0]. You may disconnect a single player by

PhoniInput.RemovePhoniDataPort(player).

Or if you know the player's ID or networkInfo, you can use

PhoniInput.Player.Remove(ID) or PhoniInput.Player.Remove(networkInfo).

To disconnect all players, use

PhoniInput.Player.Clear().

After this operation, the game are still open to receive new connections from players.

Shutdown Game

Simply call

PhoniGameController.GameClientShutdown().

This will shut down the game client, and no connection will be processed.

Player Side

We use PhoniPlayerSample.cs in AndroidDemo folder as an example.

Start Player

PhoniPlayerController.PlayerClientConnect(ipAddress, port)

Player can actually connect to multiple games by call the above method on different ip and port.

Access Connected Games

It is exactly the same structure as we discussed in Access Connected Players section in Game Side. You just need to use PhoniInput.Game instead of PhoniInput.Player. For example,

PhoniInput.Game.GetPortList()[0]'

will get the first connected game.

Notice that PhoniDataPort does not only represent a connected player, but can also represent a connected game. In Phoni System, player and game are of the same structure in terms of data.

Send Data to the Game

Assume we have PhoniDataPort game = PhoniInput.Game[0].

Since Phoni System has already handled all the work for actual sending, all you need to do is to modify data values according to local input you get from Unity input. We take touch data for example.

PhoniTouchData touchData = game.TouchData.SendingData;
touchData.touches = PhoniTouch.ConvertTouchArray(Input.touches, new Rect(0, 0, Screen.width, Screen.height));
touchData.multiTouchEnabled = Input.multiTouchEnabled;

PhoniTouch is basically the same structure as the Touch structure provided by Unity, with more handy member and methods. We wrapped it up because the member of Touch is readonly.

Send/Process Command

Exactly the same method as we discussed in Game Side. Here is the only difference:

Assume you have PhoniControllerForUnity phoniController. You need to add your process function to PhoniControllerForUnity's event when the script starts:

phoniController.CommandEventFromGames += ProcessCommand.

Disconnect Player

We don't have a "shutdown" method for player, it will only disconnect from connected games. So two methods are provided:

PhoniPlayerController.PlayerClientDisconnect(ipAddress, port)
PhoniPlayerController.PlayerClientDisconnectAll()

Phoni Terms

Before we go deep into advanced usage, we discuss some phoni terms here to get you familiar with the system.

Because of the length limit, please refer to Phoni Terms for details.

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.

The topics includes:

  • Send Data from Game to Player
  • Control What Data You Want to Send at Runtime
  • Game and Player on the same device
  • Create Custom Data and Command
  • Cross-platform Play & Use Phoni for a New Platform
  • What does PhoniControllerForUnity do

Because of the length limit, please refer to Advanced Use of Phoni for details

Built-in Data and Commands

Data

TouchData

int TouchCount
represents current available touches.
PhoniTouch[] touches
an array of PhoniTouch to provide touch information.
PhoniTouch basically have the same member as Touch data structure in Unity. Please refer to Unity Document for details.
PhoniTouch do have some extra contents:
Vector2 positionNormalized
A Vector2 with range of (0,0) to (1,1) represents a relative position on your touch area. By default the touch area is the whole screen, and it can be assigned when you create a PhoniTouch instance from a Touch object.
bool IsTouchOn
Represents whether this touch is on the screen. (The same as phase != TouchPhase.Ended && phase != TouchPhase.Canceled)
  • On PS Vita, it only supports fingerId, position, positionNormalized and phase.
bool multiTouchEnabaled
represents whether the device support multi-touch.
PhoniTouch GetTouch(int fingerId)
Get a PhoniTouch' from a specific finger ID. It will return null if the finger ID is not found.
The finger ID is assigned by device, it might have different meanings for different devices.
bool HasTouch(int fingerId)
Check if a touch with specific finger ID exist.

MotionData

Vector3 acceleration
represents current acceleration (including gravity acceleration).
PhoniGyroscope gyro
provides touch gyroscope information.
PhoniGyroscope basically have the same member as Gyroscope data structure in Unity. Please refer to Unity Document for details.
  • On PS Vita, it only supports rotationRate.

AnalogData

Vector2 left
Left analog data, both x and y range from -1 to 1. This value is normalized.
Vector2 right
Right analog data, both x and y range from -1 to 1. This value is normalized.
  • On Android, the analog data can be filled in by using virtual analog UI.
For the default Phoni Player Sample, when alternative UI is switched on, the left analog data is available.

ButtonData

PhoniButton buttons
an enum mask of PhoniButton. For example, you may check if CIRCLE button is pressed down by:
(buttons & PhoniButton.Circle) == PhoniButton.Circle
or use one of the following functions.
bool GetButton(PhoniButton requestButtons)
check if the buttons are pressed down. It supports button combination. For example,
GetButton(PhoniButton.Circle | PhoniButton.Cross)
checks if Circle button AND Cross button are both pressed down.
bool GetButtonAny(PhoniButton requestButtons)
check if any of the buttons are pressed down. It supports button combination. For example,
GetButtonAny(PhoniButton.Circle | PhoniButton.Cross)
checks if either Circle button OR Cross button is pressed down.
bool GetButtonAny()
check if any button is pressed down. The same as GetButtonAny(PhoniButton.All)
  • On Android, the button data can be filled in by using virtual button UI.
For the default Phoni Player Sample, when alternative UI is switched on, the CIRCLE, CROSS, TRIANGLE and SQUARE buttons are available.

Command

Phoni System Use Command

COMMAND_CONNECT
command for connection, send with PhoniConnectData.
This command is internal use only, you don't need to send or process this command.
COMMAND_DISCONNECT
this command is sent out when a device actively request for disconnection, no data required.
Process this command when you need extra clean up for specific player/game.
Send out this command will result in remote device close their PhoniDataPort used to talk to your local device.
COMMAND_LOST_CONNECTION
this command is sent out internally when local device lose connection with remote device, no data required.
Process this command when you need extra clean up for specific player/game.
You should not actively send out this command.
COMMAND_SUSPEND
this command is sent out when the device is suspended or paused, with a bool value (TRUE for resume, FALSE for pause).
Process this command when you need extra action for pause and resume.
Send out this command if you need logical pause in your game/player client besides physical pause.
Also you should not trust your data when your remote device is paused, because the data might not be updated.

Custom Use Command

COMMAND_RUMBLE
command for rumble, no data required.
For now only Android platform is able to process this command.
COMMAND_UI_SWITCH
command use for the default Phoni Player Sample to turn on/off the alternative UI, a bool value is required.

Some Notes

When you access PhoniInput.Player[0], you get player with ID 0, NOT the first player in the array.
I just want to bring up this issue again. A common mistake will be checking if PhoniInput.Player.Count > 0, then "safely" access PhoniInput.Player[0]. This is totally wrong.
Surely the first player connected will be assigned ID 0. But if another player get connected and your first player left, the first connected player now will be of ID 1.
The use of ID is make sure you can always find the consistent player while they are connecting. Once connected, a player's ID will remain the same during connection. However the ID MAY change if the player disconnects and reconnects again. I'll copy the example here again:
Assume player A, B, C connected and get assigned as playerID 0, 1 and 2; Then A and C disconnect, player B will keep its ID 1; If player C reconnect, he will get playerID 0 instead of his old ID 2, but B will still keep its ID 1.
A good practice to access player with ID 0 will be:
if(PhoniInput.Player.Contains(0)) { Debug.Log(PhoniInput.Player[0].ID); }
Or if you just need the first connected player, do the following:
if(PhoniInput.Player.Count > 0) { Debug.Log(PhoniInput.Player.GetPortList()[0].ID); }
Use PhoniData or PhoniDataWrapper with List<T> instead of array
There might be some cases you want to send out an array with command or use array as UDP data in PhoniDataPort.
PhoniData doesn't support array, but you can easily use generic List instead:
PhoniInput.Player[0].SendCommand(PhoniCommandCode.MY_COMMAND, new PhoniData<List<string>>(new List<string>(new string[]{"Hello ", "World!"})))
Similarly use generic list for UDP data:
[PhoniData(PhoniDataCode.MY_DATA)]
public PhoniDataWrapper<List<string>> MyString{get; private set;}
Note the class T you put in List<T> must be serializable, otherwise you need create a new class with the List or array as a member and write a serializer for that class. Refer to section Advanced Use of Phoni -> Create Custom Data and Command -> Case 3 for details.
Enable gyroscope on Android device
If you are working on player client on android device, sometimes you need to manually enable its gyroscope sensor.
If you are using Unity, execute the following code on start:
if(!Input.gyro.enabled) { Input.gyro.enabled = true;}
Be careful with finger ID in TouchData
Finger ID has the similar issue as ID for player and game. Besides finger ID largely depends on the actual device, there is situation that the device just skip some numbers and finger ID starts from, say 4, from now on.
Like ID for player and game, only trust finger ID when the touch is on screen so you can keep track of it.
Be careful with touch phase in TouchData
It's a hard decision for me to keep touch phase in PhoniTouch. For an application running on Android, touch phase provides information about touch down, touch up, and whether it is moving or stationary.
However in a network framework, it is not reliable at all. A touch end phase might not be able to send out, or it is received but quickly be flushed by new comers so you never get that phase in your update.
I keep the touch phase in the philosophy that providing everything and let users decide what to use, but it is really recommended to use only IsTouchOn property (that's the reason why this property exists) to determine whether the touch is on the screen, and record touch status of the previous update to determine TouchUp and TouchDown yourself.
Why not iOS?
Well, mainly because I don't have a device to test on. Unity iOS doesn't support standard xml serialization provided by .net, so you need to switch to JSON option by going to PhoniSystem/PhoniCore/PhoniUtil.cs and uncomment #define JSON in the first line, or use other serialization options.

Update Log

2013.3.14

Finally Phoni System is online.

I want to give special thanks to Sun Moon Hwang, Karthik Krishnamurthy and Rohan for their initial support.

When I start Phoni around 10 month ago, it about 6 month before the release of Wii U. And in the past year, many systems with similar features has come out, like PlayStation Remote Play, Microsoft Smartglass and Apple AirPlay. I did not expect the development of Phoni will take that long, but it did; mainly because of my limited spare time after school/work, plus learning network and multi-thread programming on the fly and lots of architecture re-design to make it efficient and easy enough to use.

The feature of Phoni might not be as new and exciting as I started, however I still think the architecture and approaches to achieve easy customizable data is interesting. Besides it's an open source project you can jump in and hack, and a chance for people to easily prototype some experimental gameplay that you want try on Wii U or similar devices.

Contact

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

Package and Example Project

Phoni System Package for Unity3D
Phoni System Project for Unity3D
Phoni System game sample EXE for PC
Phoni System player sample APK for Android

Personal tools