Série jeu multiplayer avec Unity 2017 et Unet (Agar.io clone)

agar.io unity clone multi
agar.io unity clone multi

 

Dans ce première épisode d’une série qui s’annonce longue sur le développement de jeux en Multiplayer en utilisant la partie networking d’ Unity (Unet). je vous propose de réaliser un clone modeste du célèbre jeu en ligne Agar.io.

 

Agar.io est un jeu vidéo multijoueur free-to-play développé par Matheus Valadares (Zeach). Le joueur contrôle un cercle coloré représentant une cellule, dont le but est de grossir le plus possible en absorbant des cellules plus petites sans être absorbée par des cellules plus grosses. Le nom Agar.io vient de l’agar-agar, un produit gélifiant utilisé pour cultiver des bactéries.

Le jeu s’est fait connaître à sa sortie sur le site 4chan, en étant d’abord uniquement disponible sur navigateur web. Il est devenu populaire grâce au bouche à oreille sur les médias sociaux, dont YouTube (principalement avec Squeezie en France) et Twitch4, et a ensuite été publié sur Steam. Le 8 juillet 2015, le jeu est sorti sur iOS et Android, édité par Miniclip.

Agar.io a été acclamé par les critiques, qui ont loué sa simplicité, son aspect compétitif et ses mécanismes de jeu. Certaines publications ont regretté la répétitivité du jeu et la lourdeur des commandes sur mobile. Le jeu a été comparé à Osmos et au stage cellule de Spore. Agar.io a connu un succès très rapide à sa sortie. Le site web agar.io  est devenu, selon Alexa, l’un des mille sites les plus visités au monde, et les versions mobiles du jeu ont été téléchargées plus de dix millions de fois lors de la semaine de leur sortie. Agar.io est aussi un jeu d’équipe où l’on peut s’allier avec d’autres personnes dans le mode Team, ou dans le mode FFA (Free For All, ou « Chacun pour soi »). Sur Play Store, Le jeu a connu plus de 50 millions de téléchargements.

 

Alors je propose de réaliser pas à pas ce projet avec Unity et je met a disposition les ressources de la vidéo.

 

Episode 1 : Mise en place de la grille, du Player et de ses déplacements.

Ressources :grille.png player.png

Script :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class controller : MonoBehaviour {

    public float speed = 4f;
    
    void FixedUpdate () {
        transform.position = Vector2.MoveTowards(
            transform.position,
            GetComponentInChildren<Camera>().ScreenToWorldPoint(Input.mousePosition),
            speed * Time.deltaTime);
    }
}

 

Episode 2 : Générer les pastilles (food)

SpritePlayer

Script :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Controller : MonoBehaviour {

    [SerializeField]
    float speed = 4f;
    [SerializeField]
    float variationFood = 0.01f;
    private float sizeStart, grilleMinX, grilleMaxX, grilleMinY, grilleMaxY;
    private SpriteRenderer srGrille;
    public GameObject playerFood;

    private void Awake()
    {
        sizeStart = transform.localScale.x;
        srGrille = GameObject.Find("grille").GetComponent<SpriteRenderer>();
        grilleMaxX = srGrille.bounds.max.x;
        grilleMaxY = srGrille.bounds.max.y;
        grilleMinX = srGrille.bounds.min.x;
        grilleMinY = srGrille.bounds.min.y;
    }

    void FixedUpdate () {

        Vector2 newPos= Vector2.MoveTowards(
            transform.position,
            GetComponentInChildren<Camera>().ScreenToWorldPoint(Input.mousePosition),
            speed * Time.fixedDeltaTime);
        transform.position = Clamp(newPos);

    }

    Vector2 Clamp (Vector2 value)
    {
        value.x = Mathf.Clamp(value.x, grilleMinX, grilleMaxX);
        value.y= Mathf.Clamp(value.y, grilleMinY, grilleMaxY);
        return value;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag=="food")
        {
            Destroy(collision.gameObject);
            transform.localScale += new Vector3(variationFood, variationFood, variationFood);
            speed -= variationFood;
        }
    }

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.W))
        {
            if( (sizeStart*2)<=transform.localScale.x)
            {
                transform.localScale -= new Vector3(sizeStart, sizeStart, sizeStart);
                speed += sizeStart;

                GameObject Go = Instantiate(playerFood, transform.position, Quaternion.identity);
                Go.transform.localScale = new Vector3(sizeStart, sizeStart, sizeStart);
                StartCoroutine(ActivateTrigger(Go));
            }
        }
    }

    IEnumerator ActivateTrigger(GameObject Go)
    {
        yield return new WaitForSeconds(0.5f);
        Go.GetComponent<CircleCollider2D>().enabled = true;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FoodManager : MonoBehaviour {

    public GameObject food, grille;
    Color[] colors = new Color[4];
    [SerializeField]
    int numberOfFood = 100;
    private float posMinX, posMinY, posMaxX, posMaxY;

    void Awake () {

        colors[0] = Color.blue;
        colors[1] = Color.red;
        colors[2] = Color.green;
        colors[3] = Color.yellow;

        SpriteRenderer gr = grille.GetComponent<SpriteRenderer>();

        posMinX = gr.bounds.min.x;
        posMinY = gr.bounds.min.y;
        posMaxX = gr.bounds.max.x;
        posMaxY = gr.bounds.max.y;

        Initialize();
    }

    private void Initialize()
    {
        for(int i=0; i<numberOfFood;i++)
        {
            GenerateFood();
        }

        InvokeRepeating("GenerateFood", 3f, 0.5f);
    }

    void GenerateFood()
    {       
        Vector2 pos = new Vector2(Random.Range(posMinX, posMaxX), Random.Range(posMinY, posMaxY));
        GameObject Go = Instantiate(food, pos, Quaternion.identity);
        Go.GetComponent<Renderer>().material.color = colors[Random.Range(0, 3)];
    }
}
Episode 3 : Mise en place de la partie Networking et Synchronisation du déplacement des players

Script :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class Controller : NetworkBehaviour {

    [SerializeField]
    float speed = 4f;

    [SerializeField]
    float variationFood = 0.01f;

    private float sizeStart, grilleMinX, grilleMaxX, grilleMinY, grilleMaxY;

    private SpriteRenderer srGrille;

    public GameObject playerFood;

    [SyncVar]
    private Vector2 syncPosition;

    private Transform myTransform;

    private void Awake()
    {
        sizeStart = transform.localScale.x;
        srGrille = GameObject.Find("grille").GetComponent<SpriteRenderer>();
        grilleMaxX = srGrille.bounds.max.x;
        grilleMaxY = srGrille.bounds.max.y;
        grilleMinX = srGrille.bounds.min.x;
        grilleMinY = srGrille.bounds.min.y;
    }

    private void Start()
    {
        myTransform = GetComponent<Transform>();

        if(isLocalPlayer)
        {
            GetComponentInChildren<Camera>().enabled = true;
        }
    }
    void FixedUpdate () {

        if(isLocalPlayer)
        {
            Vector2 newPos = Vector2.MoveTowards(
           transform.position,
           GetComponentInChildren<Camera>().ScreenToWorldPoint(Input.mousePosition),
           speed * Time.fixedDeltaTime);

            transform.position = Clamp(newPos);

            TransmitPosition();
        }
        else
        {
            myTransform.position = Vector2.Lerp(myTransform.position, syncPosition, 9 * Time.fixedDeltaTime);
        }

    }

    Vector2 Clamp (Vector2 value)
    {
        value.x = Mathf.Clamp(value.x, grilleMinX, grilleMaxX);
        value.y= Mathf.Clamp(value.y, grilleMinY, grilleMaxY);
        return value;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag=="food")
        {
            Destroy(collision.gameObject);
            transform.localScale += new Vector3(variationFood, variationFood, variationFood);
            speed -= variationFood;
        }
    }

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.W))
        {
            if( (sizeStart*2)<=transform.localScale.x)
            {
                transform.localScale -= new Vector3(sizeStart, sizeStart, sizeStart);
                speed += sizeStart;

                GameObject Go = Instantiate(playerFood, transform.position, Quaternion.identity);
                Go.transform.localScale = new Vector3(sizeStart, sizeStart, sizeStart);
                StartCoroutine(ActivateTrigger(Go));
            }
        }
    }

    IEnumerator ActivateTrigger(GameObject Go)
    {
        yield return new WaitForSeconds(0.5f);
        Go.GetComponent<CircleCollider2D>().enabled = true;
    }

    [Command]
    void CmdSendMyPositionToServer(Vector2 positionReceived)
    {
        syncPosition = positionReceived;
    }

    void TransmitPosition()
    {
        CmdSendMyPositionToServer(myTransform.position);
    }
}

 

Episode 4 : Spawn des « Food » sur le reseau et Synchronisation du « Scale » des Players

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FoodColor : MonoBehaviour {

    Color[] colors = new Color[4];
   
    void Awake () {
        colors[0] = Color.blue;
        colors[1] = Color.red;
        colors[2] = Color.green;
        colors[3] = Color.yellow;
    }
        
    void Start () {
        GetComponent<Renderer>().material.color = colors[Random.Range(0, 3)];
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class FoodManager : NetworkBehaviour {

    public GameObject food, grille;    

    [SerializeField]
    int maxFood = 10; 

    private float posMinX, posMinY, posMaxX, posMaxY;

    public override void OnStartServer() {       

        SpriteRenderer gr = grille.GetComponent<SpriteRenderer>();

        posMinX = gr.bounds.min.x;
        posMinY = gr.bounds.min.y;
        posMaxX = gr.bounds.max.x;
        posMaxY = gr.bounds.max.y;
       
    }

    private void Start()
    {
        CmdGenerateFood();
    }

    [Command]
    void CmdGenerateFood()
    {
        InvokeRepeating("CmdGenerateOneFood", 0f, 0.1f);
    }

    [Command]
    void CmdGenerateOneFood()
    {       
        Vector2 pos = new Vector2(Random.Range(posMinX, posMaxX), Random.Range(posMinY, posMaxY));
        GameObject Go = Instantiate(food, pos, Quaternion.identity);
        NetworkServer.Spawn(Go);
    }

    private void Update()
    {
        if(isServer)
        {
            if(!CanGenerateFood(maxFood))
            {
                CancelInvoke();
            }
            else
            {
                InvokeRepeating("CmdGenerateOneFood", 0f, 0.1f);
            }
        }
    }

    bool CanGenerateFood (int value)
    {
        GameObject[] nbFood = GameObject.FindGameObjectsWithTag("food");

        if(nbFood.Length<value)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class Controller : NetworkBehaviour {

    [SerializeField]
    float speed = 4f;

    [SerializeField]
    float variationFood = 0.01f;

    private float sizeStart, grilleMinX, grilleMaxX, grilleMinY, grilleMaxY;

    private SpriteRenderer srGrille;

    public GameObject playerFood;

    [SyncVar]
    private Vector2 syncPosition;
    [SyncVar]
    private Vector3 syncScale;

    private Transform myTransform;
    private Vector3 myScale;

    private void Awake()
    {
        sizeStart = transform.localScale.x;
        srGrille = GameObject.Find("grille").GetComponent<SpriteRenderer>();
        grilleMaxX = srGrille.bounds.max.x;
        grilleMaxY = srGrille.bounds.max.y;
        grilleMinX = srGrille.bounds.min.x;
        grilleMinY = srGrille.bounds.min.y;
    }

    private void Start()
    {
        myTransform = GetComponent<Transform>();

        if(isLocalPlayer)
        {
            GetComponentInChildren<Camera>().enabled = true;
        }
    }
    void FixedUpdate () {

        if(isLocalPlayer)
        {
            Vector2 newPos = Vector2.MoveTowards(
               transform.position,
               GetComponentInChildren<Camera>().ScreenToWorldPoint(Input.mousePosition),
               speed * Time.fixedDeltaTime);

            transform.position = Clamp(newPos);

            TransmitPosition();
            TransmitScale();
        }
        else
        {
            myTransform.position = Vector2.Lerp(myTransform.position, syncPosition, 9 * Time.fixedDeltaTime);
            transform.localScale = syncScale;
        }

    }

    Vector2 Clamp (Vector2 value)
    {
        value.x = Mathf.Clamp(value.x, grilleMinX, grilleMaxX);
        value.y= Mathf.Clamp(value.y, grilleMinY, grilleMaxY);
        return value;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag=="food")
        {
            Destroy(collision.gameObject);
            transform.localScale += new Vector3(variationFood, variationFood, variationFood);
            speed -= variationFood;
        }
    }

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.W))
        {
            if( (sizeStart*2)<=transform.localScale.x)
            {
                transform.localScale -= new Vector3(sizeStart, sizeStart, sizeStart);
                speed += sizeStart;

                GameObject Go = Instantiate(playerFood, transform.position, Quaternion.identity);
                Go.transform.localScale = new Vector3(sizeStart, sizeStart, sizeStart);
                StartCoroutine(ActivateTrigger(Go));
            }
        }
    }

    IEnumerator ActivateTrigger(GameObject Go)
    {
        yield return new WaitForSeconds(0.5f);
        Go.GetComponent<CircleCollider2D>().enabled = true;
    }

    [Command]
    void CmdSendMyPositionToServer(Vector2 positionReceived)
    {
        syncPosition = positionReceived;
    }

    [Command]
    void CmdSendMyScaleToServer(Vector3 scaleReceived)
    {
        syncScale = scaleReceived;
    }

    void TransmitPosition()
    {
        CmdSendMyPositionToServer(myTransform.position);
    }

    void TransmitScale()
    {
        myScale = myTransform.localScale;
        CmdSendMyScaleToServer(myScale);
    }
}

 

Episode 5 : Split et attaque des joueurs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class Controller : NetworkBehaviour
{

    [SerializeField]
    float speed = 4f;

    [SerializeField]
    float variationFood = 0.01f;

    private float sizeStart, grilleMinX, grilleMaxX, grilleMinY, grilleMaxY;

    private SpriteRenderer srGrille;

    public GameObject playerFood;

    [SyncVar]
    private Vector2 syncPosition;
    [SyncVar]
    private Vector3 syncScale;

    private Transform myTransform;
    private Vector3 myScale;
    private float newSize;


    private void Awake()
    {
        sizeStart = transform.localScale.x;
        srGrille = GameObject.Find("grille").GetComponent<SpriteRenderer>();
        grilleMaxX = srGrille.bounds.max.x;
        grilleMaxY = srGrille.bounds.max.y;
        grilleMinX = srGrille.bounds.min.x;
        grilleMinY = srGrille.bounds.min.y;
    }

    private void Start()
    {
        myTransform = GetComponent<Transform>();

        if (isLocalPlayer)
        {
            GetComponentInChildren<Camera>().enabled = true;
        }
    }
    void FixedUpdate()
    {

        if (isLocalPlayer)
        {
            Vector2 newPos = Vector2.MoveTowards(
               transform.position,
               GetComponentInChildren<Camera>().ScreenToWorldPoint(Input.mousePosition),
               speed * Time.fixedDeltaTime);

            transform.position = Clamp(newPos);

            TransmitPosition();
            TransmitScale();
        }
        else
        {
            myTransform.position = Vector2.Lerp(myTransform.position, syncPosition, 9 * Time.fixedDeltaTime);
            transform.localScale = syncScale;
        }

    }

    Vector2 Clamp(Vector2 value)
    {
        value.x = Mathf.Clamp(value.x, grilleMinX, grilleMaxX);
        value.y = Mathf.Clamp(value.y, grilleMinY, grilleMaxY);
        return value;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "food")
        {
            CmdDestroy(collision.gameObject);
            transform.localScale += new Vector3(variationFood, variationFood, variationFood);
            speed = Mathf.Clamp((4f - transform.localScale.x), 0.2f, 4);
        }

        if(collision.tag=="Player" || collision.tag=="PlayerFood")
        {
            if(myTransform.localScale.x > collision.transform.localScale.x)
            {
                CmdDestroy(collision.gameObject);
                transform.localScale += collision.transform.localScale;
                speed = Mathf.Clamp((4f - transform.localScale.x), 0.2f, 4);
            }
        }
    }

    [Command]
    void CmdDestroy(GameObject Go)
    {
        NetworkServer.Destroy(Go);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.W) && isLocalPlayer)
        {
            if ((sizeStart * 2) <= transform.localScale.x)
            {
                newSize = transform.localScale.x - 0.5f;
                transform.localScale = new Vector3(newSize, newSize, newSize);
                speed = Mathf.Clamp((4f - transform.localScale.x), 0.2f, 4);
                CmdServerSpawnPrefab();

            }
        }
    }

    

    [Command]
    void CmdSendMyPositionToServer(Vector2 positionReceived)
    {
        syncPosition = positionReceived;
    }

    [Command]
    void CmdSendMyScaleToServer(Vector3 scaleReceived)
    {
        syncScale = scaleReceived;
    }

    void TransmitPosition()
    {
        CmdSendMyPositionToServer(myTransform.position);
    }

    void TransmitScale()
    {
        myScale = myTransform.localScale;
        CmdSendMyScaleToServer(myScale);
    }

    [Command]
    void CmdServerSpawnPrefab()
    {
        GameObject Obj = Instantiate(playerFood, transform.position, Quaternion.identity);
        NetworkServer.Spawn(Obj);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerFoodScript : MonoBehaviour {

    private void OnTriggerExit2D(Collider2D collision)
    {
        gameObject.tag = "PlayerFood";
    }
}

 

 

Episode 6 : Très Prochainement ….
Like, partage, et commente pour m’encourager !!!
A propos de upln 225 Articles
En informatique le problème se situe souvent entre la chaise et le clavier !

Soyez le premier à commenter

Poster un Commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.