Hello im making a game where there will be 200 - 300 fishes in the screen at the same time.
Game doesnt have any physics and fishes has no component attached, simply all fishes are controlled by a manager script.
Its pretty optimized and working good now but i need a way of doing distance checks for each fish.
All fishes are checking distance to know if they reached given position like this
using _Project.Scripts.Character;
using System;
using UnityEngine;
using iCare.Core;
namespace _Project.Scripts.FishAi
{
internal sealed class AiInput : IMovementInput, ITick
{
internal Action OnPositionReachEvent;
Vector2 _destination;
bool _isMoving;
readonly Transform _fishTransform;
public Vector2 Direction { get; private set; }
const float DistanceThreshold = 0.5f;
const float CheckInterval = 0.2f;
float _timeSinceLastCheck;
public AiInput(Transform fishTransform)
{
_fishTransform = fishTransform;
}
public void SetDestination(Vector2 destination)
{
if (_destination == destination) return;
_destination = destination;
_isMoving = true;
_timeSinceLastCheck = 0.3f;
}
public void Tick()
{
if (!_isMoving) return;
_timeSinceLastCheck += Time.deltaTime;
if (!(_timeSinceLastCheck >= CheckInterval)) return;
_timeSinceLastCheck = 0f;
var difference = _destination - (Vector2)_fishTransform.position;
if (difference.sqrMagnitude < DistanceThreshold * DistanceThreshold)
{
_isMoving = false;
OnPositionReachEvent?.Invoke();
}
else
{
Direction = difference.normalized;
}
}
}
}
There will be one player that kills nearby fishes automatcly. Since i dont use physics i cant just do physics.spherecast etc and get nearby fishes then loop them. So only solution comes to mind is loop through all fishes and check distance to all if its in given range kill.
But this means a lot of distance checks per frame so i wonder if there is a better way to achieve this?
NOT: My target platform is webgl so i cant use multithread options i believe (i dont know how to use dots anyway)
Another small improvement is instead of having a Tick method for each fish you place all the fish in an array and then you can process a batch of fish from within a single loop.
If i understand correctly yes im doing like that already but instead of array im using list because they will be added/removed so frequently so i thought avoiding resizing array and using list will be more performant.
using System.Collections.Generic;
using UnityEngine;
namespace iCare.Core
{
public class Updater : MonoBehaviour
{
readonly List<ITick> _tickables = new();
public void Subscribe(ITick tickable)
{
#if DEBUG
if (_tickables.Contains(tickable))
throw new System.Exception("Trying to register a tickable that was already registered.");
#endif
_tickables.Add(tickable);
}
public void Unsubscribe(ITick tickable)
{
#if DEBUG
if (!_tickables.Contains(tickable))
throw new System.Exception("Trying to deregister a tickable that was never registered.");
#endif
_tickables.Remove(tickable);
}
void Update()
{
for (var i = 0; i < _tickables.Count; i++)
{
_tickables[i].Tick();
}
}
}
}
using iCare.Core;
using UnityEngine;
using UnityEngine.Pool;
namespace _Project.Scripts.FishAi
{
internal sealed class FishPool
{
readonly ObjectPool<Fish> _pool;
readonly FishDataAsset _fishData;
readonly Updater _updater;
internal FishPool(FishDataAsset fishData, Updater updater)
{
_pool = new ObjectPool<Fish>(CreateFish, GetFish, ReleaseFish, DestroyFish);
_fishData = fishData;
_updater = updater;
}
internal Fish Get(Vector3 position, Quaternion rotation)
{
var fish = _pool.Get();
fish.GameObject.transform.position = position;
fish.GameObject.transform.rotation = rotation;
return fish;
}
internal void Release(Fish fish)
{
_pool.Release(fish);
}
internal void Dispose()
{
_pool.Dispose();
}
Fish CreateFish()
{
var fishObject = Object.Instantiate(_fishData.VisualPrefab);
var fishInput = new AiInput(fishObject.transform);
var fish = new Fish(fishObject, _fishData, fishInput);
return fish;
}
void GetFish(Fish fish)
{
_updater.Subscribe(fish);
_updater.Subscribe(fish.AiInput);
}
void ReleaseFish(Fish fish)
{
fish.GameObject.SetActive(false);
_updater.Unsubscribe(fish.AiInput);
_updater.Unsubscribe(fish);
}
static void DestroyFish(Fish fish)
{
Object.Destroy(fish.GameObject);
}
}
}
I’ve not tested but I would do it something like this:
Transform[] fish;
FishInfo[] fishInfo;
int batchStart;
void Update()
{
for (int i = batchStart; i < 50; i++)
{
var difference = _destination - fish[i].position;
if (difference.sqrMagnitude < distanceThreshold) // square the DistanceThreshold in Start
fish[i].position=Random.insideUnitCircle.normalized*100; // Reincarnate the fish
else
fishInfo[i].direction = difference.normalized;
}
if (batchStart<fish.Length)
batchStart+=50;
else
batchStart=0;
}
If performance still isn’t where you need it to be then you could consider using a particle system and particle system force field on the player to attract the fish.
Yes, the IList interface is still very valuable, but from a “more performant” standpoint, this process almost certainly isn’t gonna be a choker for your game.
If you know the max speed of the fish you can defer the check for each fish by currentDistance / maxSpeed since that’s the sooner that the fish could possibly be in range. That way you won’t need to do these checks very frequently.
Why don’t you put a collider on the fish so you can do physics queries? They should be cheaper. Especially if there is a single destination for all fish, then you’d just do one overlap sphere/circle per frame and be done.
Actually i knew lists are aray but i thought they are doing more magic then just resizing array in behinde with lists. Thats why i thought its better but apperantly im wrong
Oh currenDistance/maxspeed also makes a lot of sense i didnt thing about that, thanks.
I didnt use collider because i will only need them when player trying to find closest beside that they will still need distance check to determine if they reached target position. Only for player i will need them i should propoly test if 300 distance check or 300 collider + physics.overlapsphere check is more performant
If you needed the fish to be able to collide with each other then the physics engine with its spatial partitioning would be great. But when you only need to detect collisions between the fish and the player then I doubt that the physics engine will improve performance as moving a rigidbody around has a cost because the physics engine has to update its octree or whatever its using. So the collision check will be quicker but moving the fish will be slower.
I dont need any of that so i think distance is best option
There is a player (diver in sea) shooting fishes automatcly and collecting points, since there is no aim or anything its just automatcly shooting to closest all i need is finding closest and killing it.
Maybe just adding collider but not rigidbody to use physics.spherecast and instead of looping all 300 fish simply looping only in given range would be better performance but im not sure about cost of 300 colliders without rigidbody, so i should test first
Moving a fish with just a collider on it will still require that the physics engine updates its octree. But still, give it a test. It’ll be interesting!