Unity Code Showcase

posted in: Non classé, Realisations, Unity | 0

ParticlesSystem_ShowPlayerData

Use Particles system & delegate system to efficiently shows player’s data in a fill up bar with thousounds of particles, because why not !

using System.Collections;
using UnityEngine;

/// <summary>
/// Linked to player Data to show his current status.
/// Fill a bar with thousands of particles of differents sprites.
/// Particles are shown by an Orthographic camera with a UI culling mask as well as the particles are in an UI Layer
/// </summary>
public class ParticlesSystem_ShowPlayerData : MonoBehaviour
{
    // Each particle system is a view container that show one type of sprite.
    [SerializeField]
    ParticleSystem _particleSystemGreen;
    [SerializeField]
    ParticleSystem _particleSystemRed;

    // spawn this particle system one time to use it on the differents sprite containers
    ParticleSystem.Particle[] particles;

    Bounds _areaToSpawnParticles;

    // Spawn partcile in a bound, using margin 
    const float ui_marginSpawnSize = 1.5f;
    // How many particles is needed to fill the bar. When the bar is filled, more particles are spawn randomly
    const int maxAmountTopSpawn = 10000;
    // Spawn/remove particles in a durey so it is pretty ;)
    const float timeBetweenPop = 0.02f;

    enum ShowInfo
    {
        Red,
        Green,
        AvailableChips
    }

    // https://answers.unity.com/questions/1013011/convert-recttransform-rect-to-screen-space.html
    // Form a 3D bound to have particle position from UI description
    public Bounds GetRectTransformBounds(RectTransform transform)
    {
        Vector3[] WorldCorners = new Vector3[4];
        transform.GetWorldCorners(WorldCorners);
        Bounds bounds = new Bounds(WorldCorners[0], Vector3.zero);
        for (int i = 1; i < 4; ++i)
        {
            bounds.Encapsulate(WorldCorners[i]);
        }
        return bounds;
    }

    // Start is called before the first frame update
    void Start()
    {
        // This UI_ShowPlayerData rect transform is the show area
        _areaToSpawnParticles = GetRectTransformBounds(GetComponent<RectTransform>());

        particles = new ParticleSystem.Particle[_particleSystemGreen.main.maxParticles];

        // Linked to the data delegate to show any data changes efficiently
        Player_Manager.Instance.onCurrentAccountChange += OnCurrentAccountChange;
        OnCurrentAccountChange(0);
    }

    void OnCurrentAccountChange(int a)
    {
        // Eventually do some stuff here

        UpdateVisual(a);
    }

    void UpdateVisual(int newCurrentChips)
    {
        // We use coroutine to fill up the bar instead of instantly spawn it
        StopAllCoroutines();
        StartCoroutine(DoUpdateVisual(newCurrentChips));
    }

    IEnumerator DoUpdateVisual(int newCurrentChips)
    {
        int AlreadySpawnParticleCount = 0;

        AlreadySpawnParticleCount = _particleSystemRed.particleCount + _particleSystemGreen.particleCount;

        int dif = newCurrentChips - AlreadySpawnParticleCount;

        if (dif > 0)
        {
            // Emit new particles
            var emitParams = new ParticleSystem.EmitParams();

            for (int i = 0; i < dif; i++)
            {
                float deltaYPost = Mathf.InverseLerp(0, maxAmountTopSpawn, newCurrentChips - dif + i);

                // Emit particle from bottom to top
                emitParams.position = new Vector3(
                    Random.Range(-_areaToSpawnParticles.size.x / 2, _areaToSpawnParticles.size.x / 2),
                    (
                    deltaYPost < 1 ?
                    -(_areaToSpawnParticles.size.y / 2) + ui_marginSpawnSize + (_areaToSpawnParticles.size.y - ui_marginSpawnSize / 2) * deltaYPost :
                    Random.Range(-_areaToSpawnParticles.size.y / 2 + ui_marginSpawnSize, _areaToSpawnParticles.size.y / 2 + ui_marginSpawnSize / 2)
                    ),
                    0);

                // Eventual errors are thrown via the system by default if we need to improve code
                ParticleSystem emitter = emitter = Random.value > 0.5f ? _particleSystemGreen : _particleSystemRed;
                emitter.Emit(emitParams, 1);
                yield return new WaitForSeconds(timeBetweenPop);
            }
        }
        else if (dif < 0)
        {
            // Remove particles
            int removeRed = 0;
            int removeGreen = 0;

            // Select emmitter

            if (_particleSystemRed.particleCount + _particleSystemGreen.particleCount <= -dif)
            {
                removeRed = _particleSystemRed.particleCount;
                removeGreen = _particleSystemGreen.particleCount;

            }
            else
            {
                removeRed = Mathf.FloorToInt(-dif / 2f);
                removeGreen = Mathf.CeilToInt(-dif / 2f);

                if (_particleSystemRed.particleCount > _particleSystemGreen.particleCount)
                {
                    int overCharge = removeGreen - _particleSystemGreen.particleCount;
                    if (overCharge > 0)
                    {
                        removeGreen -= overCharge;
                        removeRed += overCharge;
                    }

                }
                else if (_particleSystemRed.particleCount < _particleSystemGreen.particleCount)
                {
                    int overCharge = removeRed - _particleSystemRed.particleCount;
                    if (overCharge > 0)
                    {
                        removeRed -= overCharge;
                        removeGreen += overCharge;
                    }
                }

                do
                {
                    // Remove particles
                    if (removeRed > 0)
                    {
                        int particleAlive = _particleSystemRed.GetParticles(particles);

                        _particleSystemRed.SetParticles(particles, particleAlive - 1);
                        removeRed--;
                        continue;
                    }

                    if (removeGreen > 0)
                    {
                        int particleAlive = _particleSystemGreen.GetParticles(particles);

                        _particleSystemGreen.SetParticles(particles, particleAlive - 1);
                        removeGreen--;
                    }

                    yield return new WaitForSeconds(timeBetweenPop);
                } while (removeRed + removeGreen > 0);
            }
        }
    }
}

PoolManagerNetwork

Both Combined to handle death & respawn of objects that dies/respawn a lot.

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

/// <summary>
/// Manage a pool of object to be spawn/reuse effitiently
/// We don't call Destroy, but instead use a Destructible component to handle death/life
/// </summary>
public class PoolManagerNetwork : NetworkBehaviour
{
    protected Stack<GameObject> pool = new Stack<GameObject>();
    public static Dictionary<string, GameObject> AllObjectWithId = new Dictionary<string, GameObject>();

    [HideInInspector]
    public Transform root;

    public GameObject objectToInstantiate;

    private static int nextNameId = 0;

    protected virtual void Start()
    {
        root = transform;
    }

    protected virtual GameObject Generate(Vector3 position, int playerIndex)
    {
        GameObject toPop = Instantiate(objectToInstantiate, position, Quaternion.identity) as GameObject;

        toPop.name = nextNameId.ToString();
        nextNameId++;

        toPop.transform.parent = root;

        toPop.GetComponent<Destructible>().HandleDestroyed += OnDeath;
        toPop.GetComponent<Unit_ID>().SetMyUniqueID(toPop.name);
        toPop.GetComponent<Unit_ID>().SetPlayerIndex(playerIndex);

        UpdatePosition(toPop, position);
        AllObjectWithId.Add(toPop.name, toPop);

        NetworkServer.Spawn(toPop);

        return toPop;
    }

    public GameObject Pop(Vector3 position, int playerIndex)
    {
        if (!hasAuthority)
        {
            Debug.LogError("Only Server should pop unit !");
        }

        GameObject toPop;

        if (pool.Count > 0)
        {
            toPop = pool.Pop();
            toPop.GetComponent<Unit_ID>().SetPlayerIndex(playerIndex);
            UpdatePosition(toPop, position);
        }
        else
        {
            toPop = Generate(position, playerIndex);
        }
        
        toPop.GetComponent<Destructible>().GoAlive();

        return toPop;
    }

    private void UpdatePosition(GameObject go, Vector3 position)
    {
        go.GetComponent<SyncMovement>().SetStartingPosition(position);
    }

    public void OnDeath(GameObject deadObject, Destructible attaking)
    {
        if (deadObject.GetComponent<Planet>() != null)
        {
            pool.Push(deadObject);
        }
        else
        {
            NetworkServer.Destroy(deadObject);
        }

    }
}

Online_Destructible

Component to manage life & death using delegate and online sync

#define DIE_ACTIVATION
//#define DIE_MOVE

//#define FADEINOUT

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

public class Online_Destructible : NetworkBehaviour
{
    [SyncVar]
    int life;
    [SerializeField]
    [SyncVar]
    protected int maxLife;

    public Action<GameObject, Destructible> HandleDestroyed = delegate { };
    public Action<GameObject> HandleAlive = delegate { };

    [SerializeField]
    AudioClip[] _deadSound;
    [SerializeField]
    GameObject _deadSprite;

    public int GetLife()
    {
        return life;
    }
    public int GetMaxLife()
    {
        return maxLife;
    }
    public void SetMaxLife(int newMaxLife)
    {
        int dif = maxLife - life;

        maxLife = newMaxLife;
        life = maxLife - dif;
    }

    [Server]
    void Start()
    {
        if (hasAuthority)
        {
            life = maxLife;
        }
    }

    [Server]
    public virtual void TakeDamage(int damage, Destructible attaking)
    {
        if (!hasAuthority)
        {
            Debug.LogError("Only server should do that");
            return;
        }

        life = Mathf.Clamp(life - damage, 0, maxLife);
        if (life <= 0)
        {
            GoDead(attaking);
        }
    }

    [Server]
    public void GoDead(Destructible attaking)
    {
        if (!hasAuthority)
        {
            Debug.LogError("Only server should do that");
            return;
        }

     //   RpcGoDeath();
       
        GoDeadNoBroadcast();
        HandleDestroyed(gameObject, attaking);
    }
    [Server]
    public void GoDeadNoBroadcast()
    {
        if (!hasAuthority)
        {
            Debug.LogError("Only server should do that");
            return;
        }

        // TODO put it back when pool is ready again
        //RpcGoDeath();

    }
    [Server]
    public void GoAlive()
    {
        if (!hasAuthority)
        {
            Debug.LogError("Only server should do that");
            return;
        }
        life = maxLife;

        RpcGoAlive();
        HandleAlive(gameObject);
    }

    [ClientRpc]
    private void RpcGoAlive()
    {
        gameObject.SetActive(true);
    }

    [ClientRpc]
    private void RpcGoDeath()
    {
        if (_deadSound.Length > 0)
            AudioManager.Instance.PlaySoundAtPoint(_deadSound[UnityEngine.Random.Range(0, _deadSound.Length)], transform.position);
        if (_deadSprite)
        {
            GameObject d = Instantiate(_deadSprite, transform.position, Quaternion.identity) as GameObject;
            d.transform.localScale = transform.localScale;
        }

        //gameObject.SetActive(false);
        // transform.position = UnityEngine.Random.onUnitSphere * 1000;
    }


    private void Alive(bool a)
    {
        gameObject.SetActive(a);
    }
}