355 lines
9.4 KiB
C#
355 lines
9.4 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
#if ENABLE_INPUT_SYSTEM
|
|
using UnityEngine.InputSystem;
|
|
#endif
|
|
|
|
namespace Hovl
|
|
{
|
|
public class HS_DemoShooting : MonoBehaviour
|
|
{
|
|
[Header("Fire rate")]
|
|
[Range(0.01f, 1f)]
|
|
public float fireRate = 0.1f;
|
|
float fireCountdown;
|
|
|
|
[Header("References")]
|
|
[SerializeField] Transform firePoint;
|
|
[SerializeField] Camera cam;
|
|
[SerializeField] Animation camAnim;
|
|
|
|
[Header("Projectile settings")]
|
|
[SerializeField] float maxLength = 100f;
|
|
[SerializeField] GameObject[] prefabs;
|
|
|
|
[Header("Pooling")]
|
|
[SerializeField] int maxPoolSizePerPrefab = 40;
|
|
|
|
[Header("Projectile switching")]
|
|
[SerializeField] float switchDelay = 0.4f;
|
|
|
|
int currentPrefabIndex;
|
|
float buttonSaver;
|
|
|
|
static Transform globalPoolRoot;
|
|
|
|
// key = prefab instance id, value = pool list
|
|
static readonly Dictionary<int, List<GameObject>> pools = new Dictionary<int, List<GameObject>>();
|
|
static readonly Dictionary<int, Transform> poolParents = new Dictionary<int, Transform>();
|
|
|
|
void Awake()
|
|
{
|
|
EnsureGlobalPool();
|
|
|
|
if (cam == null)
|
|
cam = Camera.main;
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
Counter(0);
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
HandleShooting();
|
|
HandleProjectileSwitch();
|
|
HandleRotation();
|
|
|
|
if (fireCountdown > 0f)
|
|
fireCountdown -= Time.deltaTime;
|
|
|
|
buttonSaver += Time.deltaTime;
|
|
}
|
|
|
|
void HandleShooting()
|
|
{
|
|
if (IsFirePressedThisFrame())
|
|
{
|
|
Shoot();
|
|
}
|
|
|
|
if (IsFastFireHeld() && fireCountdown <= 0f)
|
|
{
|
|
Shoot();
|
|
fireCountdown = fireRate;
|
|
}
|
|
}
|
|
|
|
void HandleProjectileSwitch()
|
|
{
|
|
float horizontal = GetHorizontalInput();
|
|
|
|
if (horizontal < 0f && buttonSaver >= switchDelay)
|
|
{
|
|
buttonSaver = 0f;
|
|
Counter(-1);
|
|
}
|
|
else if (horizontal > 0f && buttonSaver >= switchDelay)
|
|
{
|
|
buttonSaver = 0f;
|
|
Counter(1);
|
|
}
|
|
}
|
|
|
|
void HandleRotation()
|
|
{
|
|
if (cam == null)
|
|
return;
|
|
|
|
Vector2 pointerPosition = GetPointerScreenPosition();
|
|
Ray ray = cam.ScreenPointToRay(pointerPosition);
|
|
|
|
if (Physics.Raycast(ray, out RaycastHit hit, maxLength))
|
|
{
|
|
RotateToMouseDirection(hit.point);
|
|
}
|
|
}
|
|
|
|
void Shoot()
|
|
{
|
|
if (prefabs == null || prefabs.Length == 0)
|
|
return;
|
|
|
|
if (firePoint == null)
|
|
{
|
|
Debug.LogWarning("HS_DemoShooting: FirePoint is not assigned.");
|
|
return;
|
|
}
|
|
|
|
GameObject prefab = prefabs[currentPrefabIndex];
|
|
if (prefab == null)
|
|
return;
|
|
|
|
GameObject projectile = GetProjectile(prefab);
|
|
if (projectile == null)
|
|
return;
|
|
|
|
if (camAnim != null && camAnim.clip != null)
|
|
camAnim.Play(camAnim.clip.name);
|
|
|
|
Transform projectileTransform = projectile.transform;
|
|
projectileTransform.SetParent(null, false);
|
|
projectileTransform.SetPositionAndRotation(firePoint.position, firePoint.rotation);
|
|
|
|
Rigidbody rb = projectile.GetComponent<Rigidbody>();
|
|
if (rb != null)
|
|
{
|
|
#if UNITY_6000_0_OR_NEWER
|
|
rb.linearVelocity = Vector3.zero;
|
|
#else
|
|
rb.velocity = Vector3.zero;
|
|
#endif
|
|
rb.angularVelocity = Vector3.zero;
|
|
}
|
|
|
|
projectile.SetActive(true);
|
|
|
|
IPooledProjectile pooledProjectile = projectile.GetComponent<IPooledProjectile>();
|
|
if (pooledProjectile != null)
|
|
pooledProjectile.OnSpawnedFromPool();
|
|
}
|
|
|
|
GameObject GetProjectile(GameObject prefab)
|
|
{
|
|
if (prefab == null)
|
|
return null;
|
|
|
|
int prefabId = prefab.GetInstanceID();
|
|
|
|
if (!pools.TryGetValue(prefabId, out List<GameObject> pool))
|
|
{
|
|
pool = new List<GameObject>();
|
|
pools[prefabId] = pool;
|
|
}
|
|
|
|
CleanupDestroyedObjects(pool);
|
|
|
|
for (int i = 0; i < pool.Count; i++)
|
|
{
|
|
GameObject pooledObject = pool[i];
|
|
|
|
if (pooledObject == null)
|
|
continue;
|
|
|
|
if (!pooledObject.activeInHierarchy)
|
|
return pooledObject;
|
|
}
|
|
|
|
if (GetValidObjectCount(pool) >= maxPoolSizePerPrefab)
|
|
return null;
|
|
|
|
GameObject newProjectile = CreateProjectile(prefab, prefabId);
|
|
if (newProjectile != null)
|
|
pool.Add(newProjectile);
|
|
|
|
return newProjectile;
|
|
}
|
|
|
|
void CleanupDestroyedObjects(List<GameObject> pool)
|
|
{
|
|
for (int i = pool.Count - 1; i >= 0; i--)
|
|
{
|
|
if (pool[i] == null)
|
|
pool.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
int GetValidObjectCount(List<GameObject> pool)
|
|
{
|
|
int count = 0;
|
|
|
|
for (int i = 0; i < pool.Count; i++)
|
|
{
|
|
if (pool[i] != null)
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
GameObject CreateProjectile(GameObject prefab, int prefabId)
|
|
{
|
|
Transform parent = GetOrCreatePoolParent(prefab, prefabId);
|
|
|
|
GameObject newProjectile = Instantiate(prefab, parent);
|
|
newProjectile.SetActive(false);
|
|
|
|
return newProjectile;
|
|
}
|
|
|
|
Transform GetOrCreatePoolParent(GameObject prefab, int prefabId)
|
|
{
|
|
if (poolParents.TryGetValue(prefabId, out Transform existingParent) && existingParent != null)
|
|
return existingParent;
|
|
|
|
GameObject parentObject = new GameObject(prefab.name + "_Pool");
|
|
parentObject.transform.SetParent(globalPoolRoot);
|
|
poolParents[prefabId] = parentObject.transform;
|
|
|
|
return parentObject.transform;
|
|
}
|
|
|
|
static void EnsureGlobalPool()
|
|
{
|
|
if (globalPoolRoot != null)
|
|
return;
|
|
|
|
GameObject existing = GameObject.Find("Hovl_GlobalProjectilePool");
|
|
if (existing != null)
|
|
{
|
|
globalPoolRoot = existing.transform;
|
|
DontDestroyOnLoad(existing);
|
|
return;
|
|
}
|
|
|
|
GameObject poolObject = new GameObject("Hovl_GlobalProjectilePool");
|
|
DontDestroyOnLoad(poolObject);
|
|
globalPoolRoot = poolObject.transform;
|
|
}
|
|
|
|
void Counter(int count)
|
|
{
|
|
if (prefabs == null || prefabs.Length == 0)
|
|
return;
|
|
|
|
currentPrefabIndex += count;
|
|
|
|
if (currentPrefabIndex >= prefabs.Length)
|
|
currentPrefabIndex = 0;
|
|
else if (currentPrefabIndex < 0)
|
|
currentPrefabIndex = prefabs.Length - 1;
|
|
}
|
|
|
|
void RotateToMouseDirection(Vector3 destination)
|
|
{
|
|
Vector3 direction = destination - transform.position;
|
|
|
|
if (direction.sqrMagnitude <= 0.0001f)
|
|
return;
|
|
|
|
transform.rotation = Quaternion.LookRotation(direction);
|
|
}
|
|
|
|
bool IsFirePressedThisFrame()
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)
|
|
return true;
|
|
#endif
|
|
|
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
|
if (Input.GetButtonDown("Fire1"))
|
|
return true;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsFastFireHeld()
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
if (Mouse.current != null && Mouse.current.rightButton.isPressed)
|
|
return true;
|
|
#endif
|
|
|
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
|
if (Input.GetMouseButton(1))
|
|
return true;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
float GetHorizontalInput()
|
|
{
|
|
float horizontal = 0f;
|
|
|
|
#if ENABLE_INPUT_SYSTEM
|
|
if (Keyboard.current != null)
|
|
{
|
|
if (Keyboard.current.aKey.isPressed)
|
|
horizontal -= 1f;
|
|
|
|
if (Keyboard.current.dKey.isPressed)
|
|
horizontal += 1f;
|
|
}
|
|
#endif
|
|
|
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
|
if (Mathf.Approximately(horizontal, 0f))
|
|
{
|
|
if (Input.GetKey(KeyCode.A))
|
|
horizontal -= 1f;
|
|
|
|
if (Input.GetKey(KeyCode.D))
|
|
horizontal += 1f;
|
|
|
|
if (Mathf.Approximately(horizontal, 0f))
|
|
horizontal = Input.GetAxisRaw("Horizontal");
|
|
}
|
|
#endif
|
|
|
|
return Mathf.Clamp(horizontal, -1f, 1f);
|
|
}
|
|
|
|
Vector2 GetPointerScreenPosition()
|
|
{
|
|
#if ENABLE_INPUT_SYSTEM
|
|
if (Mouse.current != null)
|
|
return Mouse.current.position.ReadValue();
|
|
#endif
|
|
|
|
#if ENABLE_LEGACY_INPUT_MANAGER
|
|
return Input.mousePosition;
|
|
#else
|
|
return Vector2.zero;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public interface IPooledProjectile
|
|
{
|
|
void OnSpawnedFromPool();
|
|
}
|
|
} |