unity3d

Unity – Local Data Base – SQLite – JavaScript

Unity – Local Data Base – SQLite – JavaScript

What is SQLite?

SQLite is a database system that requires no administration, it is a single file .db that can be written to a USB memory stick.

It is not directly comparable to client/server SQL database engines such as MySQL, Oracle, PostgreSQL, or SQL Server since SQLite is trying to solve a different problem.

SQLite Works Well:

– cellphones, set-top boxes, televisions, game consoles

– media cataloging and editing suites, CAD packages, record keeping programs

– low to medium traffic websites (a conservative estimate of fewer than 100K hits/day)

Creating and Exporting:

– the resulting database is a single file that can be written to a USB memory stick or emailed to a colleague.
A DB file looks like this: PlayersSQLite.db

– it provides access to that file via standard SQL commands.

– a lot af free reader for developers and end-users
I like SQlite Browser (http://sqlitebrowser.org/)

Controls and wizards are available for users to:

Create and compact database files
Create, define, modify and delete tables
Create, define and delete indexes
Browse, edit, add and delete records
Search records
Import and export records as text
Import and export tables from/to CSV files
Import and export databases from/to SQL dump files
Issue SQL queries and inspect the results
Examine a log of all SQL commands issued by the application

Available for Windows, MacOSX, Linux

Official website at: http://www.sqlite.org/whentouse.html

Another good software to create SQLite DB is SQLite Manager, a it is a plug-in for Firefox, free and crossplatform (https://addons.mozilla.org/ru/firefox/addon/sqlite-manager/)

SQLite Browser

1. Open SQLite Browser> File> New Database> create players.db

2. Table: players

3. Add fields:
– ID – INTEGER the ptimary key – check AI and PK (AUTOINCREMENT PRIMARY KEY)
– Name – TEXT to store var:String (Pietro)
– Scores – INTEGER to store var:int (124)
– Time – REAL to store var:float (20.7)

Move field up or down if necessary

or

CREATE TABLE `players` (
	`ID`	INTEGER PRIMARY KEY AUTOINCREMENT,
	`Name`	TEXT,
	`Score`	INTEGER,
	`Time`	REAL
);

4. click ‘OK’

5. Populate the database:
Tab ‘Browse Data’
Table: Players

click ‘New Record’

the id field will auto increment

input Name – Score – Time

Andrea – 100 – 10
Antonio – 50 – 5
Serafina – 70 – 10
Erica – 20 – 5
Alice – 122 – 50

click ‘Write Changes’

SQLite and Unity

Unity 5.x comes with all SQLite Library included, you can see that in your:
C:/Programmi/Unity/Editor/Data/Mono/lib/mono/2.0/
“Mono.Data.dll”, “Mono.Data.Sqlite.dll” and “Mono.Data.SqliteClient.dll”

0. File> Build Settings…> PC MAC LINUX STAND ALONE
NOTICE: if you set WEB Player the console send you the error: Namespace ‘Mono.Data.Sqlite’ not found…
because of SQlite DOES NOT WORK with WEB PLAYER!

1. Download Precompiled Binaries for Windows 32> sqlite-dll-win32-x86-3081101.zip here:
https://www.sqlite.org/download.html
Inside the zip there are: sqlite3.dll and sqlite3.def

2. Download Precompiled Binaries for Windows 64> SQLite3-64.7z here:
http://blog.synopse.info/post/2013/03/23/Latest-version-of-sqlite3.dll-for-Windows-64-bit
There is sqlite3-64.dll, renaming to sqlite3.dll

Download library for Android> sqlite.so here:
https://github.com/ORuban/SQLite4Unity3d/tree/554b7ec0bea8fa17e5c5a11fd37b8f615dc549bc/Plugins/Android/libs/x86

2. Create

– Assets/Plugins/sqlite3.dll -> the sqlite3-64.dll renamed + sqlite3.def (Win MAC IOS)

– Assets/Plugins/Android/sqlite.so (Android)

– Unity/Scenes/MyScene.unity

– Unity/Scripts/dbAccess.js

– Unity Project Root/players.db (the database)

NOTICE: Project> Plugins> sqlite3.dll> Inspector, here you can setup the target platform, do not change anything, the recognization is automatic.

3. GameObject> Create Empty> name it GameController

DB Connection and Delete Table Contents

4. attach to GameController the script dbAccess.js:


#pragma strict

// IMPORT NAMESPACE CLASSES START ----------------------
// Mono non includone SQlite, devo importarlo a parte
// le classi le troviamo qui: C:/Programmi/Unity/Editor/Data/Mono/lib7mono/2.0/
import System.Data;  // we import our  data class
import Mono.Data.Sqlite; // we import sqlite
import System.Collections.Generic;
// IMPORT NAMESPACE CLASSES END ------------------------

// importo il namespace per verificare se un file esiste
import System.IO;

// variables for basic query access
var connection : String;
var dbcon : IDbConnection;
var dbcmd : IDbCommand;
var reader : IDataReader;

function Start () {
	OpenDB ();
}// END Start()

function Update () {
}// END Update()

// ####################################################################
// OPEN DB ############################################################
// ####################################################################
function OpenDB () {
	// Open Connection START ----------------------------
	
	// Windows MAC OS IOS +++++++++++++++++++++++++
	// è la posizione che assegnerà nella build finale
	// SE LO TROVA APRE LA CONNESSIONE
	// SE NON LO TROVA LO CREA IN AUTOMATICO NELLA ROOT DEL PROGETTO UNITY
	connection = "URI=file:players.db"; 
	// Android ++++++++++++++++++++++++++++++++++++
	// connection = "URI=file:" + p; // like this will NOT work on android
    // connection = "URI=file:" + Application.persistentDataPath + "/" + "players.db";  ---> PER ANDROID TOGLIERE QUESTO COMMENTO
    
    // DEBUG: check if connection exists START --------------------
    dbcon = new SqliteConnection(connection);
    if(dbcon != null) {
    	print ("MY COMMENT: connection to players.db OK");
    }
    else{
    	print ("MY COMMENT: connection to players.db FAIL");
    }
  	// DEBUG: check if connection exists END ----------------------
    
    dbcon.Open(); // open connection
    
	// Open Connection END --------------------------------
	
	// DEBUG: check if DB exists START --------------------
 	var fileName = "players.db";
    var path = Directory.GetCurrentDirectory() + "\\" + fileName;
    if (File.Exists(path))
    {
         print ("MY COMMENT: players.db file exists");
    }
    	else
    {
         print ("MY COMMENT: players.db file NOT exists");
    }
	// DEBUG: check if DB exists END ----------------------
}// END OpenDB

// ####################################################################
// DELETE TABLE #######################################################
// ####################################################################
// This function deletes all the data in the given table.  Forever.  WATCH OUT! Use sparingly, if at all
    function DeleteTableContents() {
    var query : String; // la definisco all'interno della funzione perchè resti limitata alla funzione corrente
    query = "DELETE FROM players" ;
    dbcmd = dbcon.CreateCommand();
    dbcmd.CommandText = query; 
    reader = dbcmd.ExecuteReader();
    Debug.Log("You have just deleted players tab");
    }

5. GameObject> UI> Button> name it Button-DeleteTableContents

Button-DeleteTableContents> Inspector> OnClick()> + > DRAG GameController and take function DeleteTableContents()

6. Play and press the button, try reload players.db with SQLite Browser, the table players will be empty.

img-001

img-002

We can analize the code:

1. import namespaces to integrate functions for ‘SQLite’ and ‘if file exist’


// IMPORT NAMESPACE CLASSES START ----------------------
// Mono non includone SQlite, devo importarlo a parte
// le classi le troviamo qui: C:/Programmi/Unity/Editor/Data/Mono/lib7mono/2.0/
import System.Data;  // we import our  data class
import Mono.Data.Sqlite; // we import sqlite
import System.Collections.Generic;
// IMPORT NAMESPACE CLASSES END ------------------------

// importo il namespace per verificare se un file esiste
import System.IO;

2. Set variables for basic query access


// variables for basic query access
var connection : String;
var dbcon : IDbConnection;
var dbcmd : IDbCommand;
var reader : IDataReader;

3. Open into Start() the DB connection


connection = "URI=file:players.db"; 
dbcon = new SqliteConnection(connection);
dbcon.Open(); // open connection

4. Delete Tab function using SQL syntax


function DeleteTableContents() {
    var query : String;
    query = "DELETE FROM players" ;
    dbcmd = dbcon.CreateCommand();
    dbcmd.CommandText = query; 
    reader = dbcmd.ExecuteReader();
    Debug.Log("You have just deleted players tab");
    }

Create Table


// ####################################################################
// CREATE TABLE #######################################################
// ####################################################################
// This function create tables
function CreateTable() { 
        // Create a table, name, column array, column type array
        var name : String = "friends";
        var col = ["Name", "Surname"];
        var colType = ["TEXT", "TEXT"];
        var query : String;
        query  = "CREATE TABLE " + name + "(" + col[0] + " " + colType[0];
        for(var i=1; i<col.length; i++) {
            query += ", " + col[i] + " " + colType[i];
        }
        query += ")";
        dbcmd = dbcon.CreateCommand(); // create empty command
        dbcmd.CommandText = query; // fill the command
        reader = dbcmd.ExecuteReader(); // execute command which returns a reader
        Debug.Log("You have just created friends tab");
    }// END CreateTable()

img-003

I can explain the code easily:

1. Set the name of the table, the name of the columns, the type of the columns
NOTICE: types can be NULL, INT, TEXT, REAL, BLOG
For more info about datatypes see: https://www.sqlite.org/datatype3.html


var name : String = "friends";
var col = ["Name", "Surname"];
var colType = ["TEXT", "TEXT"];

2. user ‘for’ to create all queries
NOTICE THE ROW: query += “)”;


for(var i=1; i<col.length; i++) {
            query += ", " + col[i] + " " + colType[i];
        }
query += ")";

Populate the DB


// #####################################################################
// INSERT INTO #########################################################
// #####################################################################
// This function insert values inside a table
function InsertInto() { // basic Insert with just values
        // our data
        var query : String;
        query = "INSERT INTO friends VALUES ('Jimi','Hendrix')";
              // INSERT INTO TABLE_NAME VALUES ('Name','Surname');
        dbcmd = dbcon.CreateCommand();
        dbcmd.CommandText = query; 
        reader = dbcmd.ExecuteReader(); 
        Debug.Log("You have just added Jimi Hendrix to friends tab");
    }// END InsertInto()

NOTICE THE APOSTROPHE: (‘Jimi’,’Hendrix’), NOT (Jimi,Hendrix)

If you insert the same data twice into the DB you will see:

img-004

Read DB content

Using the DB below:

img-005


...
// variables of ShowDatabase ()
var databaseData = new Array();
var textDbContent : UI.Text; // Assign in Inspector

function Start () {
...

// #########################################################################
// READ FULL TABLE #########################################################
// #########################################################################
    // This returns a simple JS Array
    function ReadFullTable(tableName : String) {
        var query : String;
        query = "SELECT * FROM " + tableName;
        dbcmd = dbcon.CreateCommand();
        dbcmd.CommandText = query; 
        reader = dbcmd.ExecuteReader();
        var readArray = new Array();
        while(reader.Read()) { 
            var lineArray = new Array();
            for (var i:int = 0; i < reader.FieldCount; i++)
                lineArray.Add(reader.GetValue(i)); // This reads the entries in a row
            readArray.Add(lineArray); // This makes an array of all the rows
        }
        return readArray; // return matches
    }// END ReadFullTable()
    
	function ShowDatabase (){
		databaseData = ReadFullTable("friends"); // invia i dati alla funzione per la lettura del DB
		Debug.Log ("This is the array content index 0: " + databaseData[0]); // Numa Pompilio
		Debug.Log ("This is the array content index 1: " + databaseData[1]); // Tullo Ostilio
		Debug.Log ("This is the array content index 2: " + databaseData[2]); // Anco Marzio
		Debug.Log ("This is the array content index 3: " + databaseData[3]); // Tarquinio Prisco
		Debug.Log ("This is the array content index 4: " + databaseData[4]); // Servio Tullio
		Debug.Log ("This is the array content index 5: " + databaseData[5]); // Tarquinio il Superbo
		textDbContent.text = Array(databaseData).ToString(); // Numa,Pompilio,Tullo,Ostilio,Anco,Marzio etc...
}// END ShowDatabase ()

How does it work?

1. Create a UIText> Assign to dbAccess.jstextDbContent> var textDbContent
2. Create UI. Button> Inspector OnClic()> GameController> dbAccess.ShowDatabase
3. Play
4. OnClick() -> ShowDatabase () send the table name ‘friends’ to ReadFullTable(tableName : String)
5. ReadFullTable(tableName : String) return an array
6. ShowDatabase () read the array databaseData

Select DB content WHERE


...
// variables of ShowDatabase ()
var databaseData = new Array();
var textDbContent : UI.Text; // Assign in Inspector

function Start () {
...

// #########################################################################
// READ TABLE WHERE ########################################################
// #########################################################################
    // This returns a simple JS Array
    function ReadTableWhere(tableName : String) {
        var query : String;
        query = "SELECT Surname FROM " + tableName + " WHERE Name='Servio'";
        dbcmd = dbcon.CreateCommand();
        dbcmd.CommandText = query; 
        reader = dbcmd.ExecuteReader();
        var readArray = new Array();
        while(reader.Read()) { 
            var lineArray = new Array();
            for (var i:int = 0; i < reader.FieldCount; i++)
                lineArray.Add(reader.GetValue(i)); // This reads the entries in a row
            readArray.Add(lineArray); // This makes an array of all the rows
        }
        return readArray; // return matches
    }// END ReadFullTable()
    
	function ShowDatabaseWhere (){
		databaseData = ReadTableWhere("friends"); // invia i dati alla funzione per la lettura del DB
		Debug.Log ("This is the array content index 0: " + databaseData[0]); // Tullio
		textDbContent.text = Array(databaseData).ToString(); // Tullio
}// END ShowDatabase ()

NOTICE:


query = "SELECT Surname FROM " + tableName + " WHERE Name='Servio'";
// SELECT Surname FROM friends WHERE Name='Servio'

a common mistake is omit the spaces after FROM or before WHARE as:


query = "SELECT Surname FROM" + tableName + "WHERE Name='Servio'";
// SELECT Surname FROMfriendsWHERE Name='Servio' ---> BAAAAAADDDDDDD!!!!!!

Select DB content ORDER BY



...
// variables of ShowDatabase ()
var databaseData = new Array();
var textDbContent : UI.Text; // Assign in Inspector

function Start () {
...

// #########################################################################
// ORDER BY ################################################################
// #########################################################################
    // This returns a simple JS Array
    function ReadTableOrderBy(tableName : String) {
        var query : String;
        query = "SELECT Surname FROM " + tableName + " ORDER BY Name ASC";
        dbcmd = dbcon.CreateCommand();
        dbcmd.CommandText = query; 
        reader = dbcmd.ExecuteReader();
        var readArray = new Array();
        while(reader.Read()) { 
            var lineArray = new Array();
            for (var i:int = 0; i < reader.FieldCount; i++)
                lineArray.Add(reader.GetValue(i)); // This reads the entries in a row
            readArray.Add(lineArray); // This makes an array of all the rows
        }
        return readArray; // return matches
    }// END ReadFullTable()
    
	function ShowDatabaseOrderBy (){
		databaseData = ReadTableOrderBy("friends"); // invia i dati alla funzione per la lettura del DB
		Debug.Log ("This is the array content index 0: " + databaseData[0]); // Marzio     -> Anco
		Debug.Log ("This is the array content index 1: " + databaseData[1]); // Pompilio   -> Numa
		Debug.Log ("This is the array content index 2: " + databaseData[2]); // Tullio     -> Servio
		Debug.Log ("This is the array content index 3: " + databaseData[3]); // Prisco     -> Tarquinio -> id 4 in DB
		Debug.Log ("This is the array content index 4: " + databaseData[4]); // il superbo -> Tarquinio -> id 6 in DB
		Debug.Log ("This is the array content index 5: " + databaseData[5]); // Ostilio    -> Tullo

		textDbContent.text = Array(databaseData).ToString(); // Tarquinio,Servio,Tarquinio,Numa,Tullo,Anco
}// END ShowDatabase ()

NOTICE:

	
		databaseData[3]); // Prisco     -> Tarquinio -> id 4 in DB
		databaseData[4]); // il superbo -> Tarquinio -> id 6 in DB	

Same Name but different id inside DB.

Close DB



...
// variables of ShowDatabase ()
var databaseData = new Array();
var textDbContent : UI.Text; // Assign in Inspector

function Start () {
...

// #########################################################################
// ORDER BY ################################################################
// #########################################################################
	// This returns a simple JS Array
    function ReadTableOrderBy(tableName : String) {
        var query : String;
        query = "SELECT Surname FROM " + tableName + " ORDER BY Name ASC";
        dbcmd = dbcon.CreateCommand();
        dbcmd.CommandText = query; 
        reader = dbcmd.ExecuteReader();
        var readArray = new Array();
        while(reader.Read()) { 
            var lineArray = new Array();
            for (var i:int = 0; i < reader.FieldCount; i++)
                lineArray.Add(reader.GetValue(i)); // This reads the entries in a row
            readArray.Add(lineArray); // This makes an array of all the rows
        }
        return readArray; // return matches
    }// END ReadFullTable()
    
	function ShowDatabaseOrderBy (){
		databaseData = ReadTableOrderBy("friends"); // invia i dati alla funzione per la lettura del DB
		Debug.Log ("This is the array content index 0: " + databaseData[0]); // Marzio     -> Anco
		Debug.Log ("This is the array content index 1: " + databaseData[1]); // Pompilio   -> Numa
		Debug.Log ("This is the array content index 2: " + databaseData[2]); // Tullio     -> Servio
		Debug.Log ("This is the array content index 3: " + databaseData[3]); // Prisco     -> Tarquinio -> id 4 in DB
		Debug.Log ("This is the array content index 4: " + databaseData[4]); // il superbo -> Tarquinio -> id 6 in DB
		Debug.Log ("This is the array content index 5: " + databaseData[5]); // Ostilio    -> Tullo

		textDbContent.text = Array(databaseData).ToString(); // Tarquinio,Servio,Tarquinio,Numa,Tullo,Anco
		CloseDB(); 
}// END ShowDatabase ()

// #########################################################################
// CLOSE DB ################################################################
// #########################################################################
function CloseDB() {
        reader.Close(); // clean everything up
        reader = null; 
        dbcmd.Dispose(); 
        dbcmd = null; 
        dbcon.Close(); 
        dbcon = null; 
        Debug.Log("DB Closed");
    }// END CloseDB()

References:
http://forum.unity3d.com/threads/unity-3d-android-sqlite-examples.114660/ -> very easy to understand C#
http://wiki.unity3d.com/index.php/SQLite -> complete JS classes
http://sysmagazine.com/posts/181239/
http://answers.unity3d.com/questions/188334/unity-sql-database.html

By |Unity3D, Video Games Development|Commenti disabilitati su Unity – Local Data Base – SQLite – JavaScript

MMORPG Anatomy – Basic Concepts

Today I will write about creating a MMORPG, basic concepts and variables only.

First, what is a MMORPG?

Massively multiplayer online role-playing games (MMORPGs) blend the genres of role-playing video games and massively multiplayer online games, potentially in the form of web browser-based games, in which a very large number of players interact with one another within a world.

As in all RPGs, the player assume the role of a character (often in a fantasy world or science-fiction world) and takes control over many of that character’s actions. MMORPGs are distinguished from single-player or small multi-player online RPGs by the number of players able to interact together, and by the game’s persistent world (usually hosted by the game’s publisher), which continues to exist and evolve while the player is offline and away from the game.

Main concepts

The idea is:
1. You, player have to fight monsters and/or other players to improve your skills.
2. Over time the strong players/monsters are going to beat you because they have more skills and power…
3. It’s time to improve your skill to take your revenge… again and again…

We have 2 protagonist: the player and the monster, this is the ultra basic concept, but it is not simple as it looks.

Click over the flow chart below:

mmorpg-anatomy-by-andrea-tonin-001

Classes

In a classic MMORPG there are 4 classes, every class has different skills and features.
This step is really important to understand because if you fail the character setup the game will be trash.
The right setup  is important to improve team play, strategies and general game play.

See the table below:

mmorpg-anatomy-by-andrea-tonin-002

I try to explain briefly:

1. DPS with short-range weapon, he deals great damage in a very short time. He has light leather armor, good endurance and good speed.

2. TANK. He wears heavy metal armor, he is slow while in combat but has great endurance. His main task is take aggro from monster and resist to frontal attacks to protect other party members.

3. DPS with long-range weapon, he deals good damage in a very short time. He has light leather armor, less endurance than DPS with melee weapon but he moves faster.

4. (Cute!) HEALER. She wears a cloth tunic, she has low endurance but can heal herself and, very important, others party members. Healer usually has buff and debuff ability.

Ok, these are main classes, after that you can imagine intermediate classes as Bersek, Assasin, Mistic, Gunner, Thief with unique abilities; the only limit is your fantasy! Remember that players like fine tuning and choose between a lot of characters and features!

Cast Time and Cool Down

MMORPG is not a Shoot’em Up, inside the game the player can’t spell 100 fire balls per second! He is not a heavy machine gun :/
We need setup inside our code a Cast Time, a Cool Down and a Skill Energy.
– Every magic/moves use an amount of Skill Energy per second.
– Cast Time is the time you need to spell a magic, or the time you need to lift your heavy sword to blow your foe. Light damage move needs low cast time, hight damage move needs long cast time.
– Cool Down is the wait time you need to repeat a move.

Press the key -> Cast Time counter -> Play Animation / Skill energy consumption -> Cool Down counter -> Press the key … and so on …

When player make a combo of moves he has to discover the better combination considering Damage/Cast Time/Cool Down/Skill Energy needed, it is very funny.
This gameplay rewads at the same time good reflex and tactics, it is like a chess game, but with more action :)

Combat Tactics

Understand the combact tactics is essential to create the AI for monsters in a MMORPG, you can see a simple example below:

mmorpg-anatomy-by-andrea-tonin-003

1. TANK takes Aggro to protect other party members and deals damage.
2. DPS with short-range weapon deals damage rear of the monster, to avoid frontal attacks.
3. DPS with long-range weapon deals damage far away of the monster, and support HEALER.
4. HEALER run a lot near and far away the monster to support all party members, also in her spare time, deals damage.

It is clear that our monster will need:
– long range attacks
– short range attacks
– global attacks to damage every party member at the same time.
– attacks to damage
– attacks to stun
– attacks to throwdown
– attacks to debuff
– special features

Next Time I will talk about AI for monsters.

Have a nice day

Andrea

Thanks to:
Roberto ‘Drago’ for the useful informations about general mmorpg gameplay
Chicca ‘Coccinella’, my favorite Priest/Healer 😛

By |Video Games Development|Commenti disabilitati su MMORPG Anatomy – Basic Concepts

Unity3D – Tutorials – UI – Input Field – JS

Unity3D – Tutorials – UI – Input Field – JS

The new UI system give us a new Input Field, in this tutorials we are going to create a responsive ‘Input Field’ in the middle of the screen, get the user input and render it over a label.

Create a scene with:

– Main Camera

– Canvas
-> InputField
->> Placeholder
->> Text (the text of InputField)
-> Text (a label)

– EventSystem

– GameControl (Empty)

Hierarchy> Canvas> Inspector> Canvas Scaler (Script):
– Reference Resolution: X=800 Y=600
– Screen Marìtch Mode: Match Width Or Height

Hierarchy> InputField> Inspector
> Rect Transform
– Pos X=0 Y=0 Z=0
– Width= 200 Height=30
> Input Field (Script)
– Character Limit = 10 (max 10 characters allowed)

Hierarchy> InputField> Placeholder> Inspector
> Text (Script)
– Text= ‘Enter your name…, max 10 characters’

Hierchy> Text (a label)> Inspector
> Rect Transform>
– Pos X=0 Y=0 Z=0
– Width= 100 Height=100

> Text (Script)
– Text= ‘Your name here’

Now we will see in the middle of the screen:

Your name here -> (label)
|Enter your name…, max 10 characters | -> (Input Field)

Attach to GameController gameobject GameControllerScript.js:


#pragma strict
 
var MyInputField : UI.InputField; // ASSIGN IN INSPECTOR the InputField 
var TextGetInputField : UI.Text; // ASSIGN IN INSPECTOR the text of InputField
var TextLabel: UI.Text; // ASSIGN IN INSPECTOR the text of the label
 
function Awake () {
}// END Awake()
 
function Start () {
        // Focus on InputField if doesn't make sense to force the user to click on it.
	MyInputField.ActivateInputField();
	MyInputField.Select();
}// END Start()
 
function Update () {
	TextLabel.text = 'Your name is: ' + TextGetInputField.text;
}// END Update()

Hierarchy> Canvas/InputField> DRAG AND DROP over var MyInputField
Hierarchy> Canvas/InputField/Text> DRAG AND DROP over var TextGetInputField
Hierarchy> Canvas/Text> DRAG AND DROP over var TextLabel

Play and enjoy!

I like group all UI game objects in a single script, getting they from Inspector, in my opinion it is the easy way to manage a lot of text.

Reference: http://docs.unity3d.com/460/Documentation/ScriptReference/UI.InputField.html

By |Unity3D, Video Games Development|Commenti disabilitati su Unity3D – Tutorials – UI – Input Field – JS

Create OnLine Multiplayer Games – Unity3D – Photon PUN – C#

Create OnLine Multiplayer Games – Unity3D – Photon PUN – C#

How specialized services impacted my game development?

To create an online multiplayer game you need a cloud service specialized in gaming.

Manage a multiplayer game is very different than create a web site o simple saving scores inside a MySQL database.

Obstacles to overcome are many:

a. LAG: it is a noticeable delay between the action of players and the reaction of the server.

– It may occur due to insufficient processing power in the server.

– It may occur because of the network latency between a player’s computer (client), and the game server. To improve ping you have to consider the physical location of the server. Clients which are located a continent away from the server may experience a great deal of lag.

b. Cross-Platform: will your game run over different platform? PC, XBox, PS3…

c. Scalability: today you have 100 players, if you are luck tomorrow they will be 100.000 :)

d. Integration with your game framework; if it is possible we would like to develop without pain 😛

e. Integration with the management of player accounts, data storage, social tools, push notification.

f. Integration with the management of virtual goods as vanity items, power enhancements, boosts, consumables.

Ok, then now we have realized that with a specialized service will be much easier develop our games!

Photon PUN Plans

The service Photon PUN is available at: https://www.exitgames.com/en/PUN

Photon have 80.000 developers and top developers also, as Square Enix, Warner Bros Games and others.

The PUN API is very similar to Unity’s networking solution.

Photon Cloud’s hosting centers in US, Europe, Asia (Singapore, Japan) & Australia provide low latency for your games all over the world.

At the moment we have 2 PUN plans:

a. PUN FREE (https://www.assetstore.unity3d.com/en/#!/content/1786)

– Unity free license: no Android and IOS export
– Unity Pro: yes Android and IOS

– included FREE Photon Cloud plan

– included 20 CCU Subscription life-time – 8k;
CCU is concurrent users worldwide for that game id/account.
‘Concurrent users’ refers to the total number of people using the resource within predefined period of time.
In plain words you can develop an RPG and run 1 dungeon with 20 heroes or 2 dungeon with 10 heroes at the same time. Others players stand in queue.

b. PUN PLUS (https://www.assetstore.unity3d.com/en/#!/content/12080 – 95 USD)

– Unity free license: yes Android and IOS export
– Unity Pro: yes Android and IOS

– included 100 CCU Subscription life-time – 40k;

– ‘Ultimate FPS’ integrated (First person shooter framework)

– ‘PlayMaker’ integrated (Quickly make gameplay prototype)

Other plans:

You can upgrade from PUN FREE and PUN PLUS

– 95 USD for 100 CCU life-time – 40k; for example 10 rooms with 10 players in every room at the same time.

– 89 USD/monthly for 500 CCU – 200k;

– 169 USD/monthly for 1000 CCU;

– Overage, CCU Burst: you can exceed your CCU limit and optionally upgrade, so you don’t need to worry about your users losing out on service. Apps and licenses exceeding limits longer than 48 hours will be capped at their plan’s CCU.

Photon Server Side Options

Exit Games Cloud

– It provides hosted Photon Servers for you, fully managed by Exit Games.
– The server can not be authoritative.
– The clients need to be authoritative.
– Clients are identified by “application id”, with ‘game title’ and ‘version’ included.

Photon Server SDK

– You can run your own server and develop server side logic
– The server can be authoritative
– The clients can be authoritative.
– Full control of the server logic.

PUN Network Logic

PUN network logic is well organized, see the scheme below:

Client -> Master Server -> send to address
-> Game Server, game Rooms here
-> Game Server, game Rooms here
-> Game Server, game Rooms here

The individual matches are known as Rooms and are grouped into one or multiple lobbies.

My Game
-> | Lobby1 -> Room1, Room2, Room3
-> | Lobby2 -> Room4, Room5, Room6
-> | Lobby3 -> Room7, Room8, Room9

Multiple lobbies mean the clients get shorter rooms lists. There is no limit to the rooms lists.
If you don’t use custom lobbies explicitly, PUN will use a single lobby for all rooms.

Random matchmaking: it will try to join any room and fail if none has room for another player.
If fails it will create a room without name and wait until other players join it randomly.

Player resquest a matchmaking -> if Room exists -> join it
-> if Room not exist -> create a Room and wait another player

Room properties are synced to all players in the room (current map, round, starttime…).

Offline mode: it is a feature to be able to re-use your multiplayer code in singleplayer game modes as well.
PhotonNetwork needs to be able to distinguish erroneous from intended behaviour.
Using this feature you can reuse certain multiplayer methods without generating any connections and errors.

The cure is simple:

PhotonNetwork.offlineMode = true;

Photon Unity Networking (PUN) VS Unity Networking (UN)

a. Host model: using standard UN, if the player who creates the room (host) leave, the room will crash with all clients. Photon is server-client based as well, but has a dedicated server; No more dropped connections due to hosts leaving.

b. Connectivity: Unity networking works with NAT punch-through to try to improve connectivity: since players host the network servers, the connection often fails due to firewalls/routers etc…
Photon has a dedicated server, there is no need for NAT punch-through or other concepts. Connectivity is a guaranteed 100%.

c. Photon is specialized to provide rooms, matchmaking and in-room communication between players.

NOTE: the matchmaking is a system that finds 2 or more players to play a match. An example, in a MMORPG there are doungeons you can play only with a party. In front of the entrance you will press the button ‘Matchmaking’, the message will be send over the server and others players will join your team. If there is no players after 5 minutes your request will be erased.

To know more about UN see me article about using UN at:
http://www.lucedigitale.com/blog/unity3d-tutorials-multiplayer-introduction/

PUN Installation

1. Download fron Asset Store ‘Photon Cloud Free’
You can find it C:\Utenti\mycomputername\AppData\Roaming\Unity\Asset Store\Exit Games\ScriptingNetwok\Photon Unity Networking Free.unitypackage

2. Go to https://www.exitgames.com/en/PUN and register with your email

3. You will receive a first email, follow the link and create a password

4. You will receive a second email, follow the link ‘Your first Photon Realtime application is already setup in your account.’, get your Application Id

5. Unity3D> MAIN TOP MENU> Window> Photon Unity Networking> PUN Wizard> Settings ‘Setup’> I am already signed up. Let me enter my AppId ‘Setup’> Your AppId>
a. copy here your AppId
b. Cloud Region: eu (example: Europe)
c. Save

Project> Photon Unity Networking> Resources> PhotonServerSettings> Inspector, here your setup.

I found the next code in Paladin Studio’s website, and it was very useful for me, I ‘ll try to explain it in a different way.

I choose C# language istead of JS because Photon PUN is written in C#. You can use JS, if you like, but you will lose some features.
If you can not live without JS :) please read PUN manuals, the installation is slightly different.
To use JS, you’ll need to move the Photon Unity Networking \Plugins folder to the root of your project.

NetworkManger.cs

Create a scene with:
– Main Camera
– Directional Light
– Plane (as ground)
– NetworkController (Empty Object), attach NetworkManager.cs

NetworkManager.cs


using UnityEngine;
using System.Collections;

public class NetworkManager : MonoBehaviour {
	
	// Use this for initialization
	void Start () {
		// Connect to Photon (game version)
		PhotonNetwork.ConnectUsingSettings("0.1");
	}// END Start

	void OnGUI()
	{
		// Message: ConnectingMasterServer -> Authentication -> JoinedLobby
		GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString());
	}
	
	// Update is called once per frame
	void Update () {
		
	}// END Update
}

After the connection has been established, we need to create and connect to a Room.


using UnityEngine;
using System.Collections;

public class NetworkManager : MonoBehaviour {

	private const string roomName = "RoomName"; // Create a Room
	private RoomInfo[] roomsList; // Array of Rooms

	// Use this for initialization
	void Start () {
		// Connect to Photon (game version)
		PhotonNetwork.ConnectUsingSettings("0.1");
	}// END Start


	void OnGUI()
	{
		if (!PhotonNetwork.connected) // se la connessione è andata a buon fine
		{
			// Message: ConnectingMasterServer -> Authentication -> JoinedLobby
			GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString());
		}
		else if (PhotonNetwork.room == null) // se non esiste una Room
		{
			// Create Room
			if (GUI.Button(new Rect(100, 100, 250, 100), "Start Server"))
                                // CreateRoom (string roomName, bool isVisible, bool isOpen, int maxPlayers)
                                // Creates a room with given name but fails if this room is existing already. 
				PhotonNetwork.CreateRoom(roomName, true, true, 5);
			
			// Join Room
			if (roomsList != null) // se esiste una Room
			{
				for (int i = 0; i < roomsList.Length; i++)
				{
					if (GUI.Button(new Rect(100, 250 + (110 * i), 250, 100), "Join " + roomsList[i].name))
					// JoinRoom (string roomName, bool createIfNotExists)	
					PhotonNetwork.JoinRoom(roomsList[i].name);
				}
			}
		}
	}// END OnGUI()
	
	void OnReceivedRoomListUpdate()
	{
		// Get Room list
		roomsList = PhotonNetwork.GetRoomList();
	}// END OnReceivedRoomListUpdate()

	void OnJoinedRoom()
	{
		// Connected!
		Debug.Log("Connected to Room");
	}// END OnJoinedRoom()
	
	// Update is called once per frame
	void Update () {
		
	}// END Update
}

Stay focus on ‘PhotonNetwork.CreateRoom(roomName, true, true, 5);’:
– roomName Unique name of the room to create. Pass null or “” to make the server generate a name.
– isVisible Shows (or hides) this room from the lobby’s listing of rooms.
– isOpen Allows (or disallows) others to join this room.
– maxPlayers Max number of players that can join the room.
Creates a room with given name but fails if this room is existing already.

After that PhotonNetwork.GetRoomList();:
gets currently known rooms as RoomInfo array.

After that PhotonNetwork.JoinRoom(roomsList[i].name);:
JoinRoom() -> success calls OnJoinedRoom().
JoinRoom() -> fails if the room is either full or no longer available.

Ok, build it and let’s try!
1. Run the first executable on your 1st PC, press “Start Server”, this creates the server RoomName.
2. Run a second executable on your 2nd PC, press “RoomName”, this joins as client into RoomName

1rs .exe -> create the room
2nd .exe -> join the room

For italian people: come funziona?

1. Stabilisco una connessione con i server di Photon – PhotonNetwork.ConnectUsingSettings(“0.1”); –

2. Se è riuscita invio dei messaggi di connessione – GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString()); –

3. Ricevo in continuazione la lista delle Room disponibili – OnReceivedRoomListUpdate() –

3a. Se non esiste una Room – if (PhotonNetwork.room == null) – visualizza il bottone per avviare il Server – “Start Server” –
3b. Se esiste una una Room – (roomsList != null) – visualizza il bottone “Start Server” e il bottone con il nome dalla Room

4. Se premo il bottone “Start Server” creo la Room che farà da hosting

5. Se premo il bottone “RoomName” entro come client nella Room creata nel punto 4.

6. Controllo se sono riuscito ad entrare nella Room – OnJoinedRoom() – se ci riesco scrivo in console – Debug.Log(“Connected to Room”); –

Player

1. MAIN TOP MENU> GameObject> 3D Object> Cube> Inspector> Transform X=0 Y=0.5 Z=0, attach Player.cs
2. ProjecT> Cube> Inspector> ‘Add Component’> Physic> RigidBody

PlayerScript.cs


using UnityEngine;
using System.Collections;

public class PlayerScript : MonoBehaviour {

	public float speed = 10f;
	
	void Update()
	{
		InputMovement();
	}
	
	void InputMovement()
	{
		if (Input.GetKey(KeyCode.W))
			rigidbody.MovePosition(rigidbody.position + Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.S))
			rigidbody.MovePosition(rigidbody.position - Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.D))
			rigidbody.MovePosition(rigidbody.position + Vector3.right * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.A))
			rigidbody.MovePosition(rigidbody.position - Vector3.right * speed * Time.deltaTime);
	}
}


The player will use classic ‘WASD’ keyboard key controls to move the Cube.

a. Execute the 1st game, move the Cube using ‘WASD’ and press “Start Server”
b. Execute the 2nd game, move the Cube using ‘WASD’ and press “RoomName”
1st and 2nd join in the same room but they can’t see each other.

‘Photon View’ will send data to the server to synchronize the player.

3. ProjecT> Cube> Inspector> ‘Add Component’> Photon Networking> Photon View (C#)

– Observe option: ‘Reliable Delta Compressed’, data will be sent automatically, but only if its value changed, it is ‘State Synchronization’ method.
– Observed Components: DRAG AND DROP here Cube> Transform component
– Transform Serialization: Only Position

4. Project> create a folder ‘Resources’> DRAG AND DROP here from Hierarchy> Cube
Cube will turn in prefab, after that delete Hierarchy> Cube to remove it from the scene.
Instantiating on a Photon network requires the prefab to be placed in the Resources folder.

6. Improving NetworkManager.cs


using UnityEngine;
using System.Collections;

public class NetworkManager : MonoBehaviour {

	private const string roomName = "RoomName"; // Create a Room
	private RoomInfo[] roomsList; // Array of Rooms

	public GameObject playerPrefab; // Assign in Inspector

	// Use this for initialization
	void Start () {
		// Connect to Photon (game version)
		PhotonNetwork.ConnectUsingSettings("0.1");
	}// END Start


	void OnGUI()
	{
		if (!PhotonNetwork.connected) // se la connessione è andata a buon fine
		{
			// Message: ConnectingMasterServer -> Authentication -> JoinedLobby
			GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString());
		}
		else if (PhotonNetwork.room == null) // se non esiste una Room
		{
			// Create Room
			if (GUI.Button(new Rect(100, 100, 250, 100), "Start Server"))
				PhotonNetwork.CreateRoom(roomName, true, true, 5);
			
			// Join Room
			if (roomsList != null) // se esiste una Room
			{
				for (int i = 0; i < roomsList.Length; i++)
				{
					if (GUI.Button(new Rect(100, 250 + (110 * i), 250, 100), "Join " + roomsList[i].name))
						PhotonNetwork.JoinRoom(roomsList[i].name);
				}
			}
		}
	}// END OnGUI()
	
	void OnReceivedRoomListUpdate()
	{
		// Get Room list
		roomsList = PhotonNetwork.GetRoomList();
	}// END OnReceivedRoomListUpdate()

	void OnJoinedRoom()
	{
		// Connected!
		Debug.Log("Connected to Room");

		// Spawn player
		PhotonNetwork.Instantiate(playerPrefab.name, Vector3.up * 5, Quaternion.identity, 0);
	}// END OnJoinedRoom()
	
	// Update is called once per frame
	void Update () {
		
	}// END Update
}

Notice:

...
public GameObject playerPrefab; // Assign in Inspector
...
void OnJoinedRoom()
	{
		// Connected!
		Debug.Log("Connected to Room");

		// Spawn player
		PhotonNetwork.Instantiate(playerPrefab.name, Vector3.up * 5, Quaternion.identity, 0);
	}/
...

Projects> Resources> Cube DRAG AND DROP NetworkController> Network Manager (Script)> Player Prefab var

Create 2 builds and play them:

1rs .exe -> create the room
2nd .exe -> join the room
1rs .exe -> WASD move all cubes
2nd .exe -> WASD move all cubes

Ops! There is an error, but we can fix it easy :)

We are going to check Cube, it has to receive WASD input only from the .exe that instantiated the object.

7. Improving PlayerScript.cs


using UnityEngine;
using System.Collections;

public class PlayerScript : Photon.MonoBehaviour {

	public float speed = 10f;
	
	void Update()
	{
		if (photonView.isMine)
		InputMovement();
	}
	
	void InputMovement()
	{
		if (Input.GetKey(KeyCode.W))
			rigidbody.MovePosition(rigidbody.position + Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.S))
			rigidbody.MovePosition(rigidbody.position - Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.D))
			rigidbody.MovePosition(rigidbody.position + Vector3.right * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.A))
			rigidbody.MovePosition(rigidbody.position - Vector3.right * speed * Time.deltaTime);
	}
}


Notice:

...
public class PlayerScript : Photon.MonoBehaviour {
...
if (photonView.isMine)
InputMovement();
...

a. We load – Photon.MonoBehaviour not MonoBehaviour –
b. Move the player only if is mine!

Create 2 builds and play them:

1rs .exe -> create the room
2nd .exe -> join the room
1rs .exe -> WASD move 1st cube
2nd .exe -> WASD move 2nd cube

We have got it!

Personalized State Synchronization

1. Project> Resources> Cube> Photon View (Script)> Observed Components> DRAG AND DROP here Inspector> Cube> Player Script.cs
Transform components will be overwritten.

2. We can write our own synchronization method in PlayerScript.cs:


using UnityEngine;
using System.Collections;

public class PlayerScript : Photon.MonoBehaviour {

	public float speed = 10f;

	// It is automatically called every time it either sends or receives data
	void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
	{
		// If the user is the one updating the object, he writes to the stream.
		// This occurs automatically based on the sendrate
		if (stream.isWriting)
		{
			// It sends the rigidbody’s position with stream.SendNext()
			stream.SendNext(rigidbody.position);
		}
		else
		{
			// this is received by the clients with stream.ReceiveNext()
			rigidbody.position = (Vector3)stream.ReceiveNext();
		}
	}

	void Update()
	{
		if (photonView.isMine)
		InputMovement();
	}
	
	void InputMovement()
	{
		if (Input.GetKey(KeyCode.W))
			rigidbody.MovePosition(rigidbody.position + Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.S))
			rigidbody.MovePosition(rigidbody.position - Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.D))
			rigidbody.MovePosition(rigidbody.position + Vector3.right * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.A))
			rigidbody.MovePosition(rigidbody.position - Vector3.right * speed * Time.deltaTime);
	}
}


Notice:


// It is automatically called every time it either sends or receives data
	void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
	{
		// If the user is the one updating the object, he writes to the stream.
		// This occurs automatically based on the sendrate
		if (stream.isWriting)
		{
			// It sends the rigidbody’s position with stream.SendNext()
			stream.SendNext(rigidbody.position);
		}
		else
		{
			// this is received by the clients with stream.ReceiveNext()
			rigidbody.position = (Vector3)stream.ReceiveNext();
		}
	}

For italian people: come funziona?
Scrivere uno ‘State Synchronization’ personalizzato ci permette di avere maggior controllo sui parametri che vogliamo inviare in sincronizzazione, slegandoci dall’utilizzo dei soli componenti già pronti. Nel caso sopra visivamente il risultato sarà lo stesso ma invece di inviare il componente ‘Transform’ invio i dati di posizione tramite un componente ‘Script’, specificando via codice esattamente cosa inviare. Con questo sistema posso inviare qualsiasi dato che io possa codificare in uno script, ad esempio una funzione personalizzata per gestire un inventario o lo sblocco di una porta o le caratteristiche di un armamento in un gioco sparatutto.

Interpolation

Have you noticed the LAG? There is a delay between the action of players and the reaction of the server, this happens because the position is updated after the new data is received.

MAIN TOP MENU> Edit> Project Settings > Network> Send Rate: 15
The standard settings in Unity3D is that a package is being tried to send 15 times per second, but our eyes are faster than 15 fps and we will see the LAG! It is not a good practice use a greater value than 15, because we have to optimized the data will send thought the network.

To smooth the transition from the old to the new data values and fix these latency issues, interpolation can be used.

In computer animation, interpolation is inbetweening, or filling in frames between the key frames. It typically calculates the in between frames through use of (usually) piecewise polynomial interpolation to draw images semi-automatically.

We will interpolate between the current position and the new position received after synchronization.

Improving PlayerScript.cs


using UnityEngine;
using System.Collections;

public class PlayerScript : Photon.MonoBehaviour {

	public float speed = 10f;

	private float lastSynchronizationTime = 0f;
	private float syncDelay = 0f;
	private float syncTime = 0f;
	private Vector3 syncStartPosition = Vector3.zero;
	private Vector3 syncEndPosition = Vector3.zero;

	// It is automatically called every time it either sends or receives data
	void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
	{
		// If the user is the one updating the object, he writes to the stream.
		// This occurs automatically based on the sendrate
		if (stream.isWriting)
		{
			// It sends the rigidbody’s position with stream.SendNext()
			stream.SendNext(rigidbody.position);
		}
		else
		{
			// this is received by the clients with stream.ReceiveNext()
			syncEndPosition = (Vector3)stream.ReceiveNext();
			syncStartPosition = rigidbody.position;
			
			syncTime = 0f;
			syncDelay = Time.time - lastSynchronizationTime;
			lastSynchronizationTime = Time.time;
		}
	}

	void Update()
	{
		if (photonView.isMine)
		{
			InputMovement();
		}
		else
		{
			SyncedMovement();
		}
	}

	private void SyncedMovement()
	{
		syncTime += Time.deltaTime;
		rigidbody.position = Vector3.Lerp(syncStartPosition, syncEndPosition, syncTime / syncDelay);
	}
	
	void InputMovement()
	{
		if (Input.GetKey(KeyCode.W))
			rigidbody.MovePosition(rigidbody.position + Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.S))
			rigidbody.MovePosition(rigidbody.position - Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.D))
			rigidbody.MovePosition(rigidbody.position + Vector3.right * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.A))
			rigidbody.MovePosition(rigidbody.position - Vector3.right * speed * Time.deltaTime);
	}
}


Notice:


...

private float lastSynchronizationTime = 0f;
	private float syncDelay = 0f;
	private float syncTime = 0f;
	private Vector3 syncStartPosition = Vector3.zero;
	private Vector3 syncEndPosition = Vector3.zero;
...

else
			// this is received by the clients with stream.ReceiveNext()
			syncEndPosition = (Vector3)stream.ReceiveNext();
			syncStartPosition = rigidbody.position;
			
			syncTime = 0f;
			syncDelay = Time.time - lastSynchronizationTime;
			lastSynchronizationTime = Time.time;
...

else
		{
			SyncedMovement();
		}
	}

	// Movimento sincronizzato
	private void SyncedMovement()
	{
		syncTime += Time.deltaTime;
		// calculate the interpolation - Lerp interpolates between from and to by the fraction t
		rigidbody.position = Vector3.Lerp(syncStartPosition, syncEndPosition, syncTime / syncDelay);
	}
...

Prediction

You will notice a small delay between the input and the actual movement. This is because the position is updated after the new data is received.
All we can do is predict what is going to happen based on the old data.

One method to predict the next position is by taking the velocity into account. A more accurate end position can be calculated by adding the velocity multiplied by the delay.

Improving PlayerScript.cs


using UnityEngine;
using System.Collections;

public class PlayerScript : Photon.MonoBehaviour {

	public float speed = 10f;

	private float lastSynchronizationTime = 0f;
	private float syncDelay = 0f;
	private float syncTime = 0f;
	private Vector3 syncStartPosition = Vector3.zero;
	private Vector3 syncEndPosition = Vector3.zero;

	// It is automatically called every time it either sends or receives data
	void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
	{
		// If the user is the one updating the object, he writes to the stream.
		// This occurs automatically based on the sendrate
		if (stream.isWriting)
		{
			// It sends the rigidbody’s position with stream.SendNext()
			stream.SendNext(rigidbody.position);
			stream.SendNext(rigidbody.velocity);
		}
		else
		{
		Vector3 syncPosition = (Vector3)stream.ReceiveNext();
		Vector3 syncVelocity = (Vector3)stream.ReceiveNext();
		
		syncTime = 0f;
		syncDelay = Time.time - lastSynchronizationTime;
		lastSynchronizationTime = Time.time;
		
		syncEndPosition = syncPosition + syncVelocity * syncDelay;
		syncStartPosition = rigidbody.position;
		}
	}

	void Update()
	{
		if (photonView.isMine)
		{
			InputMovement();
		}
		else
		{
			SyncedMovement();
		}
	}

	// Movimento sincronizzato
	private void SyncedMovement()
	{
		syncTime += Time.deltaTime;
		// calculate the interpolation - Lerp interpolates between from and to by the fraction t
		rigidbody.position = Vector3.Lerp(syncStartPosition, syncEndPosition, syncTime / syncDelay);
	}
	
	void InputMovement()
	{
		if (Input.GetKey(KeyCode.W))
			rigidbody.MovePosition(rigidbody.position + Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.S))
			rigidbody.MovePosition(rigidbody.position - Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.D))
			rigidbody.MovePosition(rigidbody.position + Vector3.right * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.A))
			rigidbody.MovePosition(rigidbody.position - Vector3.right * speed * Time.deltaTime);
	}
}

Notice:


void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
	{
		// If the user is the one updating the object, he writes to the stream.
		// This occurs automatically based on the sendrate
		if (stream.isWriting)
		{
			// It sends the rigidbody’s position with stream.SendNext()
			stream.SendNext(rigidbody.position);
			stream.SendNext(rigidbody.velocity);
		}
		else
		{
		Vector3 syncPosition = (Vector3)stream.ReceiveNext();
		Vector3 syncVelocity = (Vector3)stream.ReceiveNext();
		
		syncTime = 0f;
		syncDelay = Time.time - lastSynchronizationTime;
		lastSynchronizationTime = Time.time;
		
		syncEndPosition = syncPosition + syncVelocity * syncDelay;
		syncStartPosition = rigidbody.position;
		}
	}

Remote Procedure Calls (RPCs) – photonView.RPC()

RPCs is the best choice when data does not constantly change.
For example you will use RPC when an enemy explode or you get the flag of opponents.

To better understand:

Player’s position -> change constantly -> State Synchronization
Enemy’s explosion -> change only one time -> RPCs

RPCs sends thought the network less data than State Synchronization and we like that!

RPCs is only able to send integers, floats, strings, vectors and quaternions.
To send other data you can convert it before sending, for example a color can be send by converting it to a vector or quaternion.

photonView.RPC() can send to:
– Server: it sends data to Server only. (no buffered)
– Others: it sends data to everyone on the server except yourself. (yes buffered)
– All: it sends data to everyone. (yes buffered)

If buffered the data will be storaged in RAM and newly connected players receiving all these buffered values.

Improving PlayerScript.cs


using UnityEngine;
using System.Collections;

public class PlayerScript : Photon.MonoBehaviour {

	public float speed = 10f;

	private float lastSynchronizationTime = 0f;
	private float syncDelay = 0f;
	private float syncTime = 0f;
	private Vector3 syncStartPosition = Vector3.zero;
	private Vector3 syncEndPosition = Vector3.zero;

	// It is automatically called every time it either sends or receives data
	void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
	{
		// If the user is the one updating the object, he writes to the stream.
		// This occurs automatically based on the sendrate
		if (stream.isWriting)
		{
			// It sends the rigidbody’s position with stream.SendNext()
			stream.SendNext(rigidbody.position);
			stream.SendNext(rigidbody.velocity);
		}
		else
		{
		Vector3 syncPosition = (Vector3)stream.ReceiveNext();
		Vector3 syncVelocity = (Vector3)stream.ReceiveNext();
		
		syncTime = 0f;
		syncDelay = Time.time - lastSynchronizationTime;
		lastSynchronizationTime = Time.time;
		
		syncEndPosition = syncPosition + syncVelocity * syncDelay;
		syncStartPosition = rigidbody.position;
		}
	}

	void Update()
	{
		if (photonView.isMine)
		{
			// If I have spawned the player
			// move it
			InputMovement();
			// change its color
			InputColorChange();
		}
		else
		{
			SyncedMovement();
		}
	}// END Update()

	// Change the player's color pressing R on the keyboard
	private void InputColorChange()
	{
		if (Input.GetKeyDown(KeyCode.R))
			// convert color to Vector3 because of RPCs' limits
			// RPCs is only able to send integers, floats, strings, vectors and quaternions 
			ChangeColorTo(new Vector3(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)));
	}// END InputColorChange()
	
	// if called RPC sends data though the network
	[RPC] void ChangeColorTo(Vector3 color)
	{
		renderer.material.color = new Color(color.x, color.y, color.z, 1f);
		
		        if (photonView.isMine)
			// - OthersBuffered - it sends data to everyone on the server except yourself
			photonView.RPC("ChangeColorTo", PhotonTargets.OthersBuffered, color);
	}// END ChangeColorTo()

	// Movimento sincronizzato
	private void SyncedMovement()
	{
		syncTime += Time.deltaTime;
		// calculate the interpolation - Lerp interpolates between from and to by the fraction t
		rigidbody.position = Vector3.Lerp(syncStartPosition, syncEndPosition, syncTime / syncDelay);
	}
	
	void InputMovement()
	{
		if (Input.GetKey(KeyCode.W))
			rigidbody.MovePosition(rigidbody.position + Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.S))
			rigidbody.MovePosition(rigidbody.position - Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.D))
			rigidbody.MovePosition(rigidbody.position + Vector3.right * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.A))
			rigidbody.MovePosition(rigidbody.position - Vector3.right * speed * Time.deltaTime);
	}
}


Notice:


...

if (photonView.isMine)
		{
			// If I have spawned the player
			// move it
			InputMovement();
			// change its color
			InputColorChange();
...

private void InputColorChange()
	{
		if (Input.GetKeyDown(KeyCode.R))
			// convert color to Vector3 because of RPCs' limits
			// RPCs is only able to send integers, floats, strings, vectors and quaternions 
			ChangeColorTo(new Vector3(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)));
	}// END InputColorChange()
	
	// if called RPC sends data though the network
	[RPC] void ChangeColorTo(Vector3 color)
	{
		renderer.material.color = new Color(color.x, color.y, color.z, 1f);
		
		if (photonView.isMine)
			// - OthersBuffered - it sends data to everyone on the server except yourself
			photonView.RPC("ChangeColorTo", PhotonTargets.OthersBuffered, color);
	}// END ChangeColorTo()
...

Resume

Now it is time for a final resume.

Install Photon PUN Free, activate your free account, get an AppID.

Create a scene with:

– Main Camera
– Directional Light
– Plane (as ground)
– NetworkController (Empty Object), attach NetworkManager.cs (Script)
– Project/Resources/Cube (prefab as player), attach a Rigidbody, PhotonView (Script), PlayerScript.cs (Script)

PhotonView (Script)

Observed Components -> Cube (PlayerScript.cs)

NetworkManager.cs (Script)

Inspector> Player Prefar var> Cube (prefab)


using UnityEngine;
using System.Collections;

public class NetworkManager : MonoBehaviour {

	private const string roomName = "RoomName"; // Create a Room
	private RoomInfo[] roomsList; // Array of Rooms

	public GameObject playerPrefab; // Assign in Inspector

	// Use this for initialization
	void Start () {
		// Connect to Photon (game version)
		PhotonNetwork.ConnectUsingSettings("0.1");
	}// END Start


	void OnGUI()
	{
		if (!PhotonNetwork.connected) // se la connessione è andata a buon fine
		{
			// Message: ConnectingMasterServer -> Authentication -> JoinedLobby
			GUILayout.Label(PhotonNetwork.connectionStateDetailed.ToString());
		}
		else if (PhotonNetwork.room == null) // se non esiste una Room
		{
			// Create Room
			if (GUI.Button(new Rect(100, 100, 250, 100), "Start Server"))
				PhotonNetwork.CreateRoom(roomName, true, true, 5);
			
			// Join Room
			if (roomsList != null) // se esiste una Room
			{
				for (int i = 0; i < roomsList.Length; i++)
				{
					if (GUI.Button(new Rect(100, 250 + (110 * i), 250, 100), "Join " + roomsList[i].name))
						PhotonNetwork.JoinRoom(roomsList[i].name);
				}
			}
		}
	}// END OnGUI()
	
	void OnReceivedRoomListUpdate()
	{
		// Get Room list
		roomsList = PhotonNetwork.GetRoomList();
	}// END OnReceivedRoomListUpdate()

	void OnJoinedRoom()
	{
		// Connected!
		Debug.Log("Connected to Room");

		// Spawn player
		PhotonNetwork.Instantiate(playerPrefab.name, Vector3.up * 5, Quaternion.identity, 0);
	}// END OnJoinedRoom()
	
	// Update is called once per frame
	void Update () {
		
	}// END Update
}

PlayerScript.cs (Script)


using UnityEngine;
using System.Collections;

public class PlayerScript : Photon.MonoBehaviour {

	public float speed = 10f;

	private float lastSynchronizationTime = 0f;
	private float syncDelay = 0f;
	private float syncTime = 0f;
	private Vector3 syncStartPosition = Vector3.zero;
	private Vector3 syncEndPosition = Vector3.zero;

	// It is automatically called every time it either sends or receives data
	void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
	{
		// If the user is the one updating the object, he writes to the stream.
		// This occurs automatically based on the sendrate
		if (stream.isWriting)
		{
			// It sends the rigidbody’s position with stream.SendNext()
			stream.SendNext(rigidbody.position);
			stream.SendNext(rigidbody.velocity);
		}
		else
		{
		Vector3 syncPosition = (Vector3)stream.ReceiveNext();
		Vector3 syncVelocity = (Vector3)stream.ReceiveNext();
		
		syncTime = 0f;
		syncDelay = Time.time - lastSynchronizationTime;
		lastSynchronizationTime = Time.time;
		
		syncEndPosition = syncPosition + syncVelocity * syncDelay;
		syncStartPosition = rigidbody.position;
		}
	}

	void Update()
	{
		if (photonView.isMine)
		{
			// If I have spawned the player
			// move it
			InputMovement();
			// change its color
			InputColorChange();
		}
		else
		{
			SyncedMovement();
		}
	}// END Update()

	// Change the player's color pressing R on the keyboard
	private void InputColorChange()
	{
		if (Input.GetKeyDown(KeyCode.R))
			// convert color to Vector3 because of RPCs' limits
			// RPCs is only able to send integers, floats, strings, vectors and quaternions 
			ChangeColorTo(new Vector3(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f)));
	}// END InputColorChange()
	
	// if called RPC sends data though the network
	[RPC] void ChangeColorTo(Vector3 color)
	{
		renderer.material.color = new Color(color.x, color.y, color.z, 1f);
		
		if (photonView.isMine)
			// - OthersBuffered - it sends data to everyone on the server except yourself
			photonView.RPC("ChangeColorTo", PhotonTargets.OthersBuffered, color);
	}// END ChangeColorTo()

	// Movimento sincronizzato
	private void SyncedMovement()
	{
		syncTime += Time.deltaTime;
		// calculate the interpolation - Lerp interpolates between from and to by the fraction t
		rigidbody.position = Vector3.Lerp(syncStartPosition, syncEndPosition, syncTime / syncDelay);
	}
	
	void InputMovement()
	{
		if (Input.GetKey(KeyCode.W))
			rigidbody.MovePosition(rigidbody.position + Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.S))
			rigidbody.MovePosition(rigidbody.position - Vector3.forward * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.D))
			rigidbody.MovePosition(rigidbody.position + Vector3.right * speed * Time.deltaTime);
		
		if (Input.GetKey(KeyCode.A))
			rigidbody.MovePosition(rigidbody.position - Vector3.right * speed * Time.deltaTime);
	}
}

Have fun!

My official website: http://www.lucedigitale.com

Reference:
– http://doc-api.exitgames.com/en/pun/current/pun/doc/class_photon_network.html
– http://doc.exitgames.com/en/pun/current/tutorials/tutorial-marco-polo
– http://www.paladinstudios.com/2014/05/08/how-to-create-an-online-multiplayer-game-with-photon-unity-networking/

By |Unity3D, Video Games Development|Commenti disabilitati su Create OnLine Multiplayer Games – Unity3D – Photon PUN – C#

Unity3D Tutorials – Multiplayer – Latency Compensating Methods

Unity3D Tutorials – Multiplayer – Latency Compensating Methods

First see my previous tutorial about multiplayer basics at:
http://www.lucedigitale.com/blog/unity3d-tutorials-multiplayer-introduction/

What is LAG?

In online gaming, LAG is a noticeable delay between the action of players and the reaction of the server. Although LAG may be caused by high latency, it may also occur due to insufficient processing power in the client and/or server.

The tolerance for lag depends heavily on the type of game. For instance, a strategy game or a turn-based game with a low pace may have a high threshold or even be mostly unaffected by high delays, whereas a twitch gameplay game such as a first-person shooter with a considerably higher pace may require significantly lower delay to be able to provide satisfying gameplay.

LAG Compensation

First, to follow this lesson you need to see my tutorial about Multiplayer Introduction here:

http://www.lucedigitale.com/blog/unity3d-tutorials-multiplayer-introduction/

In the previous lesson you will notice a small delay between the input and the actual movement, this is because the position is updated after the new data is received. All we can do is predict what is going to happen based on the old data.

One method to predict the next position is by taking the velocity into account. A more accurate end position can be calculated by adding the velocity multiplied by the delay.

Unity3D – Our own synchronization method

MAIN TOP MENU> Edit> Project Settings > Network> Send Rate: 15
The standard settings in Unity3D is that a package is being tried to send 15 times per second, but our eyes are faster than 15 fps and we will see the LAG!
It is not a good practice use a greater value than 15, because we have to optimized the data will send thought the network.

The previous setup was:
Project> Cube (our player prefab)> Inspector> Network View> Observed: Transform

The new setup is:
Project> Cube (our player prefab)> Inspector> Network View> Observed: DRAG AND DROP here ‘Movement (Script)’ component
We will send thought the network our Movement.js to write our own synchronization method.

Movement.js


#pragma strict
 
function Start () {
 
}// END Start()
 
function Update () {
 
 var horiz : float = Input.GetAxis("Horizontal"); // get AD buttons input
 var vert : float = Input.GetAxis("Vertical");    // get WS buttons input
 
 // Solo il server, cioè quello che parte per primo potrà spostare il cubo
 // Object instanted by me
 if (networkView.isMine){
 		transform.Translate(Vector3(horiz,0,vert));      // move along X -> AD and Z -> WS
	} else {
	enabled = false;
	}
}// END Update()

// We can write our own synchronization method START ############################
// This function is automatically called every time it either sends or receives data.
// By using stream.Serialize() the variable will be serialized and received by other clients.
function OnSerializeNetworkView(stream: BitStream, info: NetworkMessageInfo)
{
    var syncPosition : Vector3 = Vector3.zero; // management of position of rigid body
    if (stream.isWriting)
    {
        syncPosition = rigidbody.position; 
        stream.Serialize(syncPosition);
    }
    else
    {
        stream.Serialize(syncPosition);
        rigidbody.position = syncPosition;
    }
}
// We can write our own synchronization method END ###############################

Unity3D – Interpolation

To smooth the transition from the old to the new data values and fix these latency issues, interpolation can be used.

In computer animation, interpolation is inbetweening, or filling in frames between the key frames. It typically calculates the in between frames through use of (usually) piecewise polynomial interpolation to draw images semi-automatically.

We will interpolate between the current position and the new position received after synchronization.

Movement.js


#pragma strict
 
function Start () {
 
}// END Start()
 
function Update () {
 
 // User Input Control START ---------------------------------------------------
 var horiz : float = Input.GetAxis("Horizontal"); // get AD buttons input
 var vert : float = Input.GetAxis("Vertical");    // get WS buttons input
 
 // Solo il server, cioè quello che parte per primo potrà spostare il cubo
 // Object instanted by me
 if (networkView.isMine){
 		transform.Translate(Vector3(horiz,0,vert));      // move along X -> AD and Z -> WS
	} else {
		SyncedMovement();
	}	
 // User Input Control END -----------------------------------------------------	
	
}// END Update()

// We can write our own synchronization method START ############################

private var lastSynchronizationTime: float = 0f;
private var syncDelay: float = 0f; // Delay between Updates
private var syncTime: float = 0f;
private var syncStartPosition: Vector3 = Vector3.zero; // Current Position
private var syncEndPosition: Vector3 = Vector3.zero; // New Position
 
function SyncedMovement ()
{
    syncTime += Time.deltaTime;
    rigidbody.position = Vector3.Lerp(syncStartPosition, syncEndPosition, syncTime / syncDelay);
}// END SyncedMovement()
   
// This function is automatically called every time it either sends or receives data.
// By using stream.Serialize() the variable will be serialized and received by other clients. 
function OnSerializeNetworkView(stream: BitStream , info: NetworkMessageInfo )
{
    var syncPosition: Vector3 = Vector3.zero;
    // The BitStream class represents seralized variables, packed into a stream.
    // if the BitStream is currently being read= TRUE
    // se il flusso di dati sta per essere scritto la posizione è quella ricevuta
    if (stream.isWriting) 
    {
        syncPosition = rigidbody.position;
        stream.Serialize(syncPosition);
    }
    // se il flusso NON stà per essere scritto, interpola i dati INIZIO ---------
    else
    {
        stream.Serialize(syncPosition);
 
        syncTime = 0f;
        syncDelay = Time.time - lastSynchronizationTime;
        lastSynchronizationTime = Time.time;
 
        syncStartPosition = rigidbody.position;
        syncEndPosition = syncPosition;
    }
    // se il flusso NON stà per essere scritto, interpola i dati FINE -----------
}

// We can write our own synchronization method END ###############################

Play and try, now the transitions look smooth! YEAH!

For italian people: come funziona?

1. Se il player è stato generato da me muovilo con GetAxis, altrimenti non posso muoverlo

...

 if (networkView.isMine){
 		transform.Translate(Vector3(horiz,0,vert));
...

2. altrimenti muovilo utilizzando l’interpolazione

...
function SyncedMovement ()
{
    syncTime += Time.deltaTime;
    rigidbody.position = Vector3.Lerp(syncStartPosition, syncEndPosition, syncTime / syncDelay);
}// END SyncedMovement()
...

3. L’interpolazione viene attivata solamente nel momento in cui il flusso di dati non è in scrittura

...
else
    {
        stream.Serialize(syncPosition);
 
        syncTime = 0f;
        syncDelay = Time.time - lastSynchronizationTime;
        lastSynchronizationTime = Time.time;
 
        syncStartPosition = rigidbody.position;
        syncEndPosition = syncPosition;
    }
...

Unity3D – Prediction

You will notice a small delay between the input and the actual movement. This is because the position is updated after the new data is received.
All we can do is predict what is going to happen based on the old data.

One method to predict the next position is by taking the velocity into account. A more accurate end position can be calculated by adding the velocity multiplied by the delay.

Movement.js


#pragma strict
 
function Start () {
 
}// END Start()
 
function Update () {
 
 // User Input Control START ---------------------------------------------------
 var horiz : float = Input.GetAxis("Horizontal"); // get AD buttons input
 var vert : float = Input.GetAxis("Vertical");    // get WS buttons input
 
 // Solo il server, cioè quello che parte per primo potrà spostare il cubo
 // Object instanted by me
 if (networkView.isMine){
 		transform.Translate(Vector3(horiz,0,vert));      // move along X -> AD and Z -> WS
	} else {
		SyncedMovement();
	}	
 // User Input Control END -----------------------------------------------------	
	
}// END Update()

// We can write our own synchronization method START ############################

private var lastSynchronizationTime: float = 0f;
private var syncDelay: float = 0f; // Delay between Updates
private var syncTime: float = 0f;
private var syncStartPosition: Vector3 = Vector3.zero; // Current Position
private var syncEndPosition: Vector3 = Vector3.zero; // New Position
 
function SyncedMovement ()
{
    syncTime += Time.deltaTime;
    rigidbody.position = Vector3.Lerp(syncStartPosition, syncEndPosition, syncTime / syncDelay);
}// END SyncedMovement()
   
// This function is automatically called every time it either sends or receives data.
// By using stream.Serialize() the variable will be serialized and received by other clients. 
function OnSerializeNetworkView(stream: BitStream , info: NetworkMessageInfo )
{
    var syncPosition: Vector3 = Vector3.zero;
    var syncVelocity: Vector3 = Vector3.zero;
    // The BitStream class represents seralized variables, packed into a stream.
    // if the BitStream is currently being read= TRUE
    // se il flusso di dati sta per essere scritto la posizione è quella ricevuta
    if (stream.isWriting) 
    {
        syncPosition = rigidbody.position;
        stream.Serialize(syncPosition);
        
        syncVelocity = rigidbody.velocity;
        stream.Serialize(syncVelocity);
    }
    // se il flusso NON stà per essere scritto, interpola i dati INIZIO ---------
    else
    {
        stream.Serialize(syncPosition);
        stream.Serialize(syncVelocity);
 
        syncTime = 0f;
        syncDelay = Time.time - lastSynchronizationTime;
        lastSynchronizationTime = Time.time;
 
        syncEndPosition = syncPosition + syncVelocity * syncDelay;
        syncStartPosition = rigidbody.position;
    }
    // se il flusso NON stà per essere scritto, interpola i dati FINE -----------
}

// We can write our own synchronization method END ###############################

You will notice the transitions are still smooth and the latency between your input and the actual movement seem less.

My official website: http://www.lucedigitale.com

References:

– http://docs.unity3d.com/Manual/net-HighLevelOverview.html
– http://www.inetdaemon.com/tutorials/internet/ip/whatis_ip_network.shtml
– http://whatismyipaddress.com/nat
– http://vimeo.com/33996023#
– http://www.paladinstudios.com/2013/07/10/how-to-create-an-online-multiplayer-game-with-unity/

Extra resources:

– http://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
– http://developer.valvesoftware.com/wiki/Lag_Compensation
– http://developer.valvesoftware.com/wiki/Working_With_Prediction
– http://www.gamasutra.com/resource_guide/20020916/lambright_01.htm

By |Unity3D, Video Games Development|Commenti disabilitati su Unity3D Tutorials – Multiplayer – Latency Compensating Methods