Skip to content

Instantly share code, notes, and snippets.

@andrew-raphael-lukasik
Last active March 26, 2024 21:51
Show Gist options
  • Save andrew-raphael-lukasik/d65b2d97d88767b938cf226c15216480 to your computer and use it in GitHub Desktop.
Save andrew-raphael-lukasik/d65b2d97d88767b938cf226c15216480 to your computer and use it in GitHub Desktop.
job system for basic steering behavior at scale
// src*: https://gist.github.com/andrew-raphael-lukasik/d65b2d97d88767b938cf226c15216480
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Jobs;
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Collections;
using Unity.Transforms;
public class CarsAISystem : MonoBehaviour
{
[SerializeField] GameObject _carPrefab = null;
[SerializeField] float _topSpeed = 4;
[SerializeField] float _acceleration = 2;
[SerializeField] float _turningRate = 1;
[SerializeField][Range(0,1)] float _performanceVariation = 0.2f;
[SerializeField] int _numberOfCarsToSpawn = 10;
Transform[] _cars = null;
TransformAccessArray _carsTransformAccess;
NativeArray<int> _carWaypointData;
NativeArray<float> _carSpeedData;
NativeArray<float> _carPerformanceData;
[SerializeField] Transform[] _waypoints = new Transform[2];
NativeArray<float3> _pathVertices;
JobHandle Dependency = default(JobHandle);
void Awake ()
{
_cars = new Transform[ _numberOfCarsToSpawn ];
for( int i=0 ; i<_numberOfCarsToSpawn ; i++ )
{
_cars[i] = GameObject.Instantiate(
original: _carPrefab ,
position: (float3)UnityEngine.Random.insideUnitSphere * new float3{ x=30 , y=0 , z=30 } ,
rotation: Quaternion.identity
).transform;
}
_carsTransformAccess = new TransformAccessArray( _cars );
_carSpeedData = new NativeArray<float>( _cars.Length , Allocator.Persistent );
_carPerformanceData = new NativeArray<float>( _cars.Length , Allocator.Persistent );
for( int i=0 ; i<_carPerformanceData.Length ; i++ )
_carPerformanceData[i] = UnityEngine.Random.Range( 1f-_performanceVariation , 1f+_performanceVariation );
_pathVertices = new NativeArray<float3>( _waypoints.Length , Allocator.Persistent );
for( int i=0 ; i<_pathVertices.Length ; i++ )
_pathVertices[i] = _waypoints[i].position;
_carWaypointData = new NativeArray<int>( _cars.Length , Allocator.Persistent );
for( int i=0 ; i<_carWaypointData.Length ; i++ )
_carWaypointData[i] = UnityEngine.Random.Range( 0 , _pathVertices.Length );
}
void OnDestroy ()
{
Dependency.Complete();
if( _pathVertices.IsCreated ) _pathVertices.Dispose();
if( _carsTransformAccess.isCreated ) _carsTransformAccess.Dispose();
if( _carWaypointData.IsCreated ) _carWaypointData.Dispose();
if( _carSpeedData.IsCreated ) _carSpeedData.Dispose();
if( _carPerformanceData.IsCreated ) _carPerformanceData.Dispose();
}
void Update ()
{
Dependency.Complete();
var job = new FollowWaypoints{
DeltaTime = Time.deltaTime ,
TopSpeed = _topSpeed ,
Acceleration = _acceleration ,
TurningRate = _turningRate ,
Path = _pathVertices ,
Waypoint = _carWaypointData ,
Speed = _carSpeedData ,
Performance = _carPerformanceData
};
Dependency = job.Schedule( _carsTransformAccess , Dependency );
}
#if UNITY_EDITOR
void OnDrawGizmos ()
{
int length = _waypoints.Length;
for( int i=0 ; i<length ; i++ )
{
var a = _waypoints[i];
var b = _waypoints[(i+1)%length];
if( a && b ) Gizmos.DrawLine( a.position , b.position );
}
}
#endif
[Unity.Burst.BurstCompile]
public struct FollowWaypoints : IJobParallelForTransform
{
public float DeltaTime, TopSpeed, Acceleration, TurningRate;
[NativeDisableParallelForRestriction] public NativeArray<float3> Path;
public NativeArray<int> Waypoint;
public NativeArray<float> Speed;
public NativeArray<float> Performance;
void IJobParallelForTransform.Execute ( int index , TransformAccess transform )
{
float3 position = transform.position;
float3 forward = math.mul( transform.rotation , new float3{ z=1 } );
float performance = Performance[index];
float3 waypointPos = Path[ Waypoint[index] ];
// switch waypoints:
if( math.lengthsq(waypointPos-position)<math.pow(Speed[index],2f) )
{
// will be there in about a second, lets select next waypoint
int next = ( Waypoint[index] +1 )%Path.Length;
Waypoint[index] = next;
waypointPos = Path[next];
}
// steering control:
float3 desiredForward = math.normalize( waypointPos - position );
float makeSteeringDecreseWithSpeed = math.lerp( 2f , 1f , math.saturate(math.pow(Speed[index]/TopSpeed,2f)) );
transform.rotation = math.slerp(
transform.rotation , quaternion.LookRotation( desiredForward , new float3{y=1} ) ,
math.saturate( makeSteeringDecreseWithSpeed * TurningRate*performance * DeltaTime )
);
// speed control:
float makeDesiredSpeedDependOnAngleToTarget = math.dot( forward , desiredForward );
float desiredSpeed = TopSpeed*performance * makeDesiredSpeedDependOnAngleToTarget;
float desiredSpeedDiff = desiredSpeed - Speed[index];
Speed[index] += math.min( math.abs(desiredSpeedDiff) , Acceleration*performance ) * math.sign(desiredSpeedDiff) * DeltaTime;
transform.position += (Vector3)( forward * Speed[index] * DeltaTime );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment