Sunday, January 18, 2015

Step by Step how to create multiplayer games on scratch in Unity3D

So my previous post is for you to run the demo, but if you want to build it from scratch, here is below step by step

Credit to quill18creates ( I learned from his video)
https://www.youtube.com/user/quill18creates/videos

Step1: Open unity3D

Step2: Open asset store

Step3: IMPORT PACKAGE ->search for "Photon unity networking" in asset store, and import the package

Step4: SERVER SETTING -> Under Assets-> Photon Unity Networking -> Resources -> PhotonServerSetting
/////////////////////////
Choose:

Hosting: Photon Cloud

Region: USA

(make sure you created account at)
https://www.exitgames.com/en/Account/SignIn

App ID: (copy from your exitgames dashboard)

Protocol: UDP
/////////////////////////

Step5: Create empty object in your scene
-> name it "networking"

Step6: Create a C# script and attach it to your empty object "networking"

So let us put in the code, function by function,

Step7: CONNECT TO SERVER ->the first function we put in is the void Connect()


//////////////////////////////////////////////////////////  inside networking.cs /////////////////////////////
using UnityEngine;
using System.Collections;

public class networking : MonoBehaviour {

    // Use this for initialization
    void Start () 
    {
        Connect();
    }
    
    // Update is called once per frame
    void Update () 
    {
    
    }

    void Connect()
    {
        PhotonNetwork.ConnectUsingSettings("1.0.0");
    }
}


/////////////////////////
You must use C# script, if you use javascript, it will say "Unknown Identifier PhotonNetwork"

So when your game start, the first thing you want to do is to connect to the PhotonNetwork using the setting you put on step 4

Step8: DISPLAY STATUS -> Once that is done, you want to put in void OnGUI()

/////////////////////////inside networking.cs /////////////////////////////
void OnGUI()
{
   GUILayout.Label (PhotonNetwork.connectionStateDetailed.ToString ());
}


///////////////////////////////////////
OnGUI() will show your label on top left, telling you what happen inside PhotonNetwork,
if you save the script, attach it to your empty gameobject and click run, you will see on top left display
-> Connecting to master server
-> Connected
-> Authenticating
-> Authenticated
-> Joined lobby

By default, once you connected to the PhotonNetwork server, they will automatically put you inside the lobby, so the next step you want to do, is to create a room and joining a room

Step 9: JOIN ROOM -> So our next function is to create room inside the function OnJoinedLobby()

//////////////////////////inside networking.cs /////////////////////////////
void OnJoinedLobby()
    {
        PhotonNetwork.JoinRandomRoom ();
    }

    void OnPhotonRandomJoinFailed()
    {
        Debug.Log ("Fail to join");
    }


///////////////////////////////////////

So after you going into the lobby, you want to join a random room, so you need to put PhotonNetwork.JoinRandomRoom()

and also you need the system to tell you "fail to join" if you really fail to join

Step 10: CREATE ROOM -> because you have not create any room, so if you try to run the program now it will display "fail to join", so you need to add another line such as below, PhotonNetwork.CreateRoom(null)



//////////////////////////inside networking.cs /////////////////////////////
    void OnPhotonRandomJoinFailed()
    {
        Debug.Log ("Fail to join");
        PhotonNetwork.CreateRoom (null);
    }

    void OnJoinedRoom()
    {
        Debug.Log ("Successful Join Room");
    }


///////////////////////////////////////

Meaning, if fail to join any room, you create a room, after create a new room, it will automatically join the room, and you want to display a message tell you "successful join room"

you can save the scene and run the game, you will now see the debug message "Succesful Join Room"

Step 11: INSTANTIATE PLAYER -> Next, we want to instantiate your player into the scene, we will use the PhotonNetwork.instantiate() function, 
->this will instantiate your player into every person scenes, if you have 10 player on the same room, everyone game will instantiate that player

Note: your prefab MUST inside "Resources" folder or it will return error, you can however put your "Resources" folder under any sub directory

Note2: your prefab MUST have a "photon View" script attached to it or it will return error

Note3: after you drag the "Photon View" script into your prefab player, you MUST drag the "transform" of your prefab player into the "Photon View" -> "Observed Component", if you dont do this step, it will not sync the game

////////////////////////inside networking.cs /////////////////////////////

    void OnJoinedRoom()
    {
        Debug.Log ("Successful Join Room");
        PhotonNetwork.Instantiate ("spaceship",new Vector3(0.0f,2.0f,-2.0f),Quaternion.identity,0);
    }


///////////////////////////////////////

so you can see, I instantiate a spaceship that I can control using "WASD" and to test for 2 player, 
-> on top, File -> Build Setting -> build your project into EXE
-> run the EXE
-> now you can see you have 2 spaceship, one for each player, but when you move the "WASD", you will notice 2 spaceship moving together, you only want to have 1 spaceship moving

step 12: DISABLE CONTROLLER -> You have to disable the "mouse view" , "mouse controller", or "keyboard controller" on your prefab(you might only have 1,2 or 3)
-> this is because if 10 player spawn on the same room, everyone also have "mouse view" , "keyboard controller" enable and you can control everyone, so we need to disable it first, and then we enable our controller

step 13: ENABLE CONTROLLER -> Then you enable it back immediately after you instantiate

 ///////////////////////inside networking.cs /////////////////////////////
void OnJoinedRoom()
    {
        Debug.Log ("Successful Join Room");

        GameObject myplayer= (GameObject)PhotonNetwork.Instantiate ("spaceship",new Vector3(0.0f,2.0f,-2.0f),Quaternion.identity,0);

        ((MonoBehaviour)myplayer.GetComponent ("shipmain")).enabled=true;


    }

//////////////////////////////////////////

So what we do here is immediately after we instantiate our spaceship, we enable the main coding , in this example, i put my code inside "shipmain" (the one tell it to WASD for control)

Your game will work fine until this stage but you will notice it seem like very lag, only your main character is non lag but the other player seem like super lag, this is because

-> the server try to update the other player coordinate every 1/100 of the seconds, but the network is not fast enough 
-> what you want to do is to get the other player coordinate, then you slowly push the other player to the new coordinate 

STEP 14: SMOOTH OTHER PLAYER -> by creating a new C# script and attach it to the "PHOTON VIEW"-> "Observe Component"

-> create a new C# script, name it "networkcontrol"

///////////////////////inside networkcontrol.cs ////////////////////////////
using UnityEngine;
using System.Collections;

public class networkcontrol : Photon.MonoBehaviour 
{

    Vector3 realPosition = Vector3.zero;
    Quaternion realRotation = Quaternion.identity;

    // Use this for initialization
    void Start () {
    
    }
    
    // Update is called once per frame
    void Update () 
    {
        if(photonView.isMine)
        {
            //do nothing
        }
        else
        {
            transform.position = Vector3.Lerp (transform.position,realPosition,0.1f);
            transform.rotation = Quaternion.Lerp (transform.rotationrealRotation,0.1f);
        }
    
    }

    void OnPhotonSerializeView(PhotonStream streamPhotonMessageInfo info)
    {
        //This is our player
        if(stream.isWriting)
        {
            stream.SendNext (transform.position);
            stream.SendNext (transform.rotation);
        }
        //this is other player
        else
        {
            realPosition=(Vector3)stream.ReceiveNext ();
            realRotation=(Quaternion)stream.ReceiveNext ();
        }
    }

}

////////////////////////////////////////////////////////

the function OnPhotonSerializeView() is where your player sending position signal to the server, and receive position signal for other player from the server

-> so when you receive other player position, you record them inside realPosition and realRotation first
-> then, on the update() function, you slowly move the other player to their real position, slowly incrementing by 0.1f so that it look smooth on your game

STEP15: Preparing Bullet
-> First we create a new sphere, rename as bullet
-> Add rigid body to the bullet
-> Attach the bullet.cs script into it
-> Drag the bullet object you created into the prefab folder, and delete it from the scene

/////////////////inside bullet.cs////////////////
using UnityEngine;
using System.Collections;

public class bullet : MonoBehaviour {


    public float speed;
    public Transform explosionPrefab;


    // Use this for initialization
    void Start () 
    {
        rigidbody.velocity = transform.forward * speed;    
    }
    
    // Update is called once per frame
    void Update () 
    {
    
    }

    void OnCollisionEnter(Collision collision
    {
        
        //debug
        Debug.Log("we hit "+collision.collider.gameObject.name);


        if(collision.collider.gameObject.GetComponent<enemy>())
        {
            collision.collider.gameObject.GetComponent<enemy>().gethit();
        }

        
        
        // Rotate the object so that the y-axis faces along the normal of the surface
        ContactPoint contact = collision.contacts[0];
        Quaternion rot = Quaternion.FromToRotation(Vector3.upcontact.normal);
        Vector3 pos = contact.point;
        
        
        Instantiate(explosionPrefabposrot);
        
        Destroy (gameObject);
        
    }
}

///////////////////////////////////////////////

 rigidbody.velocity = transform.forward * speed;
-> what we doing here is once the bullet is instantiate, we want to move the bullet forward

function OnCollisionEnter
-> this is what we want to do if the bullet hit something, we want to instantiate a particle system that mimic the explosion
-> To import the explosion, go to top-> Asset -> Import Packages -> particles
-> then you can drag the explosion you like in the folder Asset-> Standard Asset -> Legacy Particle

-> example you can drag the "small explosion" into the bullet.js "explosionPrefab"

OPTIONAL: if the particle you instantiate keep looping forever you can add a script to your particle to make it destroy themself after 0.5 seconds


//////////////inside destroy.js////////////////////////////

#pragma strict
var seconds:float;

function Start () {

}

function Update () 
{

    seconds+=Time.deltaTime;
    
    if(seconds>0.5)
    {
        Destroy(gameObject);
        
    }
    
}



/////////////////////////////////////////////

STEP 16: Shooting Bullet
-> the way I want to do it is to shoot all the time every second
-> so first I am going to create a new script shootonly.js and attach it to my main character prefab
-> then on the outside, you drag the "bullet" prefab you did in step 15 to the "shot" under the script as your bullet
-> you create an empty object "shotspawn" put it under your main character, and drag the "showspawn" into shotspawn under the script to act as the position to spawn the bullet
-> finally you give a value to fireRate, 1.0 mean every 1 second shoot 1 bullet, 0.5 mean every 0.5 seconds shoot 1 bullet

/////////////////inside shootonly.js////////////////
#pragma strict


var seconds:float;
var fireRate : float;
var shot : GameObject;
var shotSpawn : Transform;

function Start () {

}



function Update () 
{

    seconds+=Time.deltaTime;
    
    if(seconds>fireRate)
    {
        seconds=0;
        Instantiate(shotshotSpawn.positionshotSpawn.rotation);
    }
    
}


////////////////////////////////////////////////////////
as you can see, the seconds will adding 0.01 seconds and once it reach the firerate you set, example 0.5, then it will instantiate a new bullet spawning on the "shotspawn" you specify

/////////////////////////////////////////////////////////

STEP 17: adding enemy
-> create a new cube rename it as enemy
-> add rigid body to the enemy
-> drag the enemy into prefab-> resource folder
-> attach "PhotonView" script to the enemy
-> drag the "network control" script to the enemy
-> drag the "network control" script to the "photonview" script -> Observed Component
-> add a script enemy.cs
-> after the enemy script saved and attached, key in the value for the
--> life: 100
--> speed: 3
--> tilt:1
-->zmin, zmax,xmin,xmax any value you like


/////////////////inside enemy.cs////////////////

using UnityEngine;
using System.Collections;

public class enemy : MonoBehaviour {

    public float life;
    public float speed;
    public float tilt;
    float randomx;
    float randomy;
    float timer;
    public float xMin;
    public float xMax;
    public float zMin;
    public float zMax;
    
    // Use this for initialization
    void Start () 
    {
        if(life<20.0f)
        {
            life =20.0f;
        }
    
    }
    
    // Update is called once per frame
    void Update () 
    {
        timer+=Time.deltaTime;



        if(timer>1.0f)
        {
            timer=0.0f;
        


            if(PhotonNetwork.isMasterClient==true)
            {


                randomx=Random.Range (-1.0f,1.0f);
                randomyRandom.Range (-1.0f,1.0f);

                Vector3 movementnew Vector3 (randomx0.0frandomy);
                rigidbody.velocity = movement * speed;
                rigidbody.position = new Vector3 
                    (
                        Mathf.Clamp (rigidbody.position.xxMinxMax), 
                        0.0f
                        Mathf.Clamp (rigidbody.position.zzMinzMax)
                        );
                
                rigidbody.rotation = Quaternion.Euler (0.0f0.0frigidbody.velocity.x * -tilt);

                Debug.Log ("randomx: "+randomx);
                Debug.Log ("randomy: "+randomy);

            }

        }
    }

    void OnGUI()
    {
        GUI.Label(new Rect(10,10,1000,30),"randomx: " + randomx);
    }

    public void gethit()
    {
        life-=10.0f;
        
        if(life<=0.0f)
        {
            Destroy(gameObject);
        }
    }
}

/////////////////////////////////////////////////////////

Basically the enemy script have 2 component
1. update() function
-> first we increment a timer, and we want to move the enemy every 1 seconds
-> second we want to make sure this is masterclient, only masterclient decide the enemy movement, other people only get the enemy position
-> then we move the enemy

2. gethit() function
-> so when the bullet hit the enemy, this function will be called and the enemy life will be deducted

No comments:

Post a Comment