lucid-super-dream/Assets/Scripts/Weapon.cs

237 lines
7.4 KiB
C#
Raw Normal View History

2021-01-05 13:10:20 +01:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.Jobs;
using Utils;
using Weapons.Scripts;
using Weapons.Spawning;
using Random = UnityEngine.Random;
[CreateAssetMenu]
public class Weapon : ScriptableObject
{
[BurstCompile]
private struct BulletMoveJob : IJobParallelForTransform
{
public float DeltaTime;
public NativeArray<Bullet> Bullets;
public void Execute(int index, TransformAccess transform)
{
if (Bullets[index].IsAlive)
transform.position += Bullets[index].Direction * Bullets[index].Speed * DeltaTime;
}
}
private struct Bullet
{
public Vector3 Direction;
public float Speed;
public float Lifetime;
public bool IsAlive;
public int Idx;
}
public event Action<Vector3> BulletCollision;
[SerializeField] private float fireRate;
[SerializeField] private ParticleSystem.MinMaxCurve bulletSpeed;
[SerializeField] private ParticleSystem.MinMaxCurve bulletLifetime;
[SerializeField] private ParticleSystem.MinMaxCurve bulletSize;
[SerializeField] private float damage;
[SerializeField] private LayerMask collidesWith;
[SerializeField] private float accuracy;
[SerializeField] private SpawnZone zone;
private List<Bullet> _bullets;
private List<Transform> _bulletTransforms;
private static Collider[] _results = new Collider[32];
private ObjectPool<Transform> _pool;
private BulletMoveJob _job;
private JobHandle _jobHandle;
private Bullet _currentBullet;
private Transform _currentTransform;
private TransformAccessArray _accessArray;
private float _currentCooldown;
public void Init()
{
_job = new BulletMoveJob();
_bullets = new List<Bullet>();
_bulletTransforms = new List<Transform>();
}
public void Update()
{
_currentCooldown += Time.deltaTime;
_job.DeltaTime = Time.deltaTime;
_job.Bullets = _bullets.ToNativeArray(Allocator.Persistent);
_accessArray = new TransformAccessArray(_bulletTransforms.ToArray());
_jobHandle = _job.Schedule(_accessArray);
}
public void LateUpdate()
{
_jobHandle.Complete();
_job.Bullets.Dispose();
_accessArray.Dispose();
// remove all the inactive bullets from our internal list so we don't accidentally update them multiple times
for (var i = _bullets.Count - 1; i >= 0; i--)
{
_currentBullet = _bullets[i];
_currentBullet.Lifetime -= Time.deltaTime;
if (_currentBullet.Lifetime <= 0)
_currentBullet.IsAlive = false;
_bullets[i] = _currentBullet;
if (_bullets[i].IsAlive) continue;
_bulletTransforms[i].gameObject.SetActive(false);
_pool.ReturnObject(_bulletTransforms[i], _bullets[i].Idx);
_bullets.RemoveAt(i);
_bulletTransforms.RemoveAt(i);
}
}
public void FixedUpdate()
{
// loop through the bullets. Backwards, because that is marginally quicker
for (var i = _bullets.Count - 1; i >= 0; i--)
{
_currentBullet = _bullets[i];
_currentTransform = _bulletTransforms[i];
// if the bullet is inactive, continue. We don't care about it
if (!_currentTransform.gameObject.activeSelf || !_currentBullet.IsAlive) continue;
// if it has hit something
if (CheckCollision(_currentTransform, _currentTransform.localScale.x, out var numHits))
{
// send the event
BulletCollision?.Invoke(_currentTransform.position);
// damage any damageable entities it has hit
for (int j = 0; j < numHits; j++)
{
var healthObject = _results[j].GetComponent<IDamageable>();
healthObject?.Hit(damage);
}
// deactivate the bullet
_currentBullet.IsAlive = false;
}
// apply the changes we made
_bullets[i] = _currentBullet;
}
}
private bool CheckCollision(Transform instance, float size, out int numHits)
{
numHits = 0;
// if it's inactive, we haven't hit anything
if (!instance.gameObject.activeSelf) return false;
// use the non allocating version so we don't have to allocate memory for every bullet
numHits = Physics.OverlapSphereNonAlloc(instance.position, size/2f, _results, collidesWith, QueryTriggerInteraction.Collide);
return numHits > 0;
}
public bool Fire(ObjectPool<Transform> pool, Transform position)
{
if (_pool == null)
_pool = pool;
if (_currentCooldown < fireRate) return false;
_currentCooldown = 0;
SpawnBullets(position);
return true;
}
/// <summary>
/// Spawn the bullets.
/// </summary>
private void SpawnBullets(Transform transform)
{
// alter the direction based on accuracy
var direction = Quaternion.Euler(0, Random.Range(-accuracy, accuracy), 0) * transform.forward;
// for every bullet
zone.GetPoint(transform, (point, dir) =>
{
// get the object in the pool
var (bullet, idx) = _pool.GetObject();
// if the pool returns no bullet, continue (it probably hasn't initialised yet)
if (bullet == null) return;
// enable the bullet
bullet.gameObject.SetActive(true);
var newPos = new Vector3(point.x, point.y);
newPos = Vector3.Lerp(newPos, Vector3.zero, Random.Range(0, 1f));
bullet.position = transform.position + newPos;
// point the bullet in the right direction
bullet.forward = new Vector3(dir.x, dir.y);
bullet.transform.localScale = Vector3.one * bulletSize.EvaluateMinMaxCurve();
if (zone.SpawnDir != SpawnDir.Spherised)
{
var y = bullet.eulerAngles.y;
bullet.forward = Quaternion.Euler(0, y, 0) * direction;
}
if (zone.SpawnDir == SpawnDir.None)
bullet.forward = direction;
// add the bullet to the list we're returning
_bulletTransforms.Add(bullet);
_bullets.Add(new Bullet
{
Direction = bullet.forward,
IsAlive = true,
Speed = bulletSpeed.EvaluateMinMaxCurve(),
Lifetime = bulletLifetime.EvaluateMinMaxCurve(),
Idx = idx
});
});
}
/// <summary>
/// Draw gizmos for the weapon
/// </summary>
public void DrawGizmos(Transform transform)
{
#if UNITY_EDITOR
var color = Color.cyan;
color.a = Selection.activeObject == this ? 1 : 0.05f;
zone.DrawGizmos(color, transform);
if (_bullets == null) return;
foreach (var b in _bulletTransforms.Where(b => b != null))
{
Gizmos.DrawWireSphere(b.position, b.localScale.x/2f * bulletSize.EvaluateMinMaxCurve());
}
#endif
}
}