using UnityEngine;
public class MainScript : MonoBehaviour
{
[Header("Simulation settings")] // creates header in Unity Inspector
public GameObject prefabParticle; // drag particle prefab on this field in Inspector
// and all other game settings
// PRIVARTE FIELDS
private float xMin = -9;
private float xMax = 9;
private float yMin = -5;
private float yMax = 5;
private float vCompMax = 5;
private float particleDiameter = 0.5f;
private int particleCount = 10; // make public later s.t. can adjust in Inspector
private ParticleModel[] particleModels;
private ParticleView[] particleViews;
private SpriteRenderer[] particleRenderers;
float minDistanceSquared;
private float particleRadius;
private Color colorNoCollision = Color.green;
private Color colorCollision = Color.red;
void Start()
{
UnityEngine.Random.InitState(42); // with seed
InitParticles(); // create particles
}
void InitParticles()
{
Debug.Log("Instantiate Particles");
minDistanceSquared = particleDiameter * particleDiameter;
particleRadius = particleDiameter / 2;
// Check if prefab is assigned
if (prefabParticle == null)
{
Debug.LogError("prefabParticle is not assigned! Please assign a prefab in the Inspector.");
return;
}
// Create new arrays for particle models and views
particleModels = new ParticleModel[particleCount];
particleViews = new ParticleView[particleCount];
particleRenderers = new SpriteRenderer[particleCount];
// CREATE A PARTICLE AND ITS VIEW, THEN ASSIGN TO ARRAY
for (int i = 0; i < particleCount; i++)
{
// create model for particle
Vector2 position = new Vector2(UnityEngine.Random.Range(xMin + particleDiameter, xMax - particleDiameter), UnityEngine.Random.Range(yMin + particleDiameter, yMax - particleDiameter));
Vector2 velocity = new Vector2(UnityEngine.Random.Range(-vCompMax, vCompMax), UnityEngine.Random.Range(-vCompMax, vCompMax));
ParticleModel particleModel = new ParticleModel(position, velocity, 1.0f);
// create game object for particle with prefab as image, will be added to Unity Hierarchy:
GameObject particleGameObject = Instantiate(prefabParticle, particleModel.position, Quaternion.identity);
particleGameObject.transform.localScale = Vector3.one * particleDiameter;
if (particleGameObject != null)
{
// Try to get ParticleView component, if not found, add it
ParticleView particleView = particleGameObject.GetComponent<ParticleView>();
if (particleView == null)
{
particleView = particleGameObject.AddComponent<ParticleView>();
Debug.LogWarning("ParticleView component was missing on prefab, automatically added for particle");
}
particleView.Bind(particleModel);
// assign particle model, view and renderer to array
particleModels[i] = particleModel;
particleViews[i] = particleView;
particleRenderers[i] = particleView.GetComponent<SpriteRenderer>();
}
else
{
Debug.LogError("Failed to instantiate prefab for particle! Make sure prefabParticle is assigned in Inspector.");
}
}
// Set correct color
for (int i = 0; i < particleCount; i++)
{
particleRenderers[i].color = colorNoCollision;
}
}
void Update()
{
Step(); // one simulation step
}
void Step()
{
// UPDATE POSITION
float dt = Time.deltaTime; // ensures that velocity is independent of frame-rate
foreach (var particleModel in particleModels)
{
particleModel.position += particleModel.velocity * dt;
}
for (int i = 0; i < particleCount; i++)
{
particleModels[i].wasColliding = particleModels[i].isColliding;
particleModels[i].isColliding = false;
}
// COLLISION DETECTION
for (int i = 0; i < particleCount; i++)
{
ParticleModel particleModel1 = particleModels[i];
for (int j = i + 1; j < particleCount; j++)
{
if (i == j) continue;
ParticleModel particleModel2 = particleModels[j];
Vector2 delta = particleModel1.position - particleModel2.position;
float distSq = delta.sqrMagnitude;
if (distSq < minDistanceSquared)
{
particleModel1.isColliding = true;
particleModel2.isColliding = true;
}
}
}
// CHANGE COLOR IF COLLIDE
for (int i = 0; i < particleCount; i++)
{
if (particleModels[i].isColliding && !particleModels[i].wasColliding)
{
particleRenderers[i].color = colorCollision;
}
else if (!particleModels[i].isColliding && particleModels[i].wasColliding)
{
particleRenderers[i].color = colorNoCollision;
}
}
// COLLISION HANDLING: WALLS ONLY
for (int i = 0; i < particleCount; i++)
{
ParticleModel particle = particleModels[i];
if (particle.position.x < xMin + particleRadius)
{
particle.position.x = xMin + particleRadius;
particle.velocity.x = -particle.velocity.x;
}
else if (particle.position.x > xMax - particleRadius)
{
particle.position.x = xMax - particleRadius;
particle.velocity.x = -particle.velocity.x;
}
if (particle.position.y < yMin + particleRadius)
{
particle.position.y = yMin + particleRadius;
particle.velocity.y = -particle.velocity.y;
}
else if (particle.position.y > yMax - particleRadius)
{
particle.position.y = yMax - particleRadius;
particle.velocity.y = -particle.velocity.y;
}
}
}
}
using UnityEngine;
public class ParticleModel
{
public Vector2 position;
public Vector2 velocity;
public float mass;
public bool isColliding = false; // is colliding in current frame
public bool wasColliding = false; // was colliding in last frame
public ParticleModel(Vector2 position, Vector2 velocity, float mass)
{
this.position = position;
this.velocity = velocity;
this.mass = mass;
}
}
using UnityEngine;
public class ParticleView : MonoBehaviour
{
ParticleModel particleModel;
void Start() {}
public void Bind(ParticleModel p)
{
particleModel = p;
transform.position = particleModel.position;
}
void Update()
{
// position of view = position of model (of ParticleModel)
if (particleModel != null)
{
transform.position = particleModel.position;
}
}
}