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);
}
}