Files
OGG/Assets/Scripts/SimpleGun.cs

421 lines
13 KiB
C#

using UnityEngine;
public class SimpleGun : MonoBehaviour
{
[Header("Gun Settings")]
public float damage = 25f;
public float range = 100f;
public float fireRate = 8f;
public int maxAmmo = 30;
public int currentAmmo;
public bool isAutomatic = true;
[Header("Recoil Settings")]
public float recoilKickback = 0.08f;
public float recoilKickUp = 0.04f;
public float recoilRecoverySpeed = 12f;
[Header("Weapon Bob Settings")]
public float bobFrequency = 10f;
public float bobHorizontalAmplitude = 0.05f;
public float bobVerticalAmplitude = 0.03f;
public float sprintBobMultiplier = 1.5f;
public float bobReturnSpeed = 6f;
[Header("References")]
public Camera fpsCam;
// Muzzle flash
private GameObject muzzleFlashObj;
private Light muzzleLight;
private float muzzleFlashTimer = 0f;
private float muzzleFlashDuration = 0.05f;
// Impact
private static int maxDecals = 30;
private static GameObject[] decalPool;
private static int decalIndex = 0;
// Recoil
private Vector3 originalLocalPos;
private Vector3 recoilOffset;
// Bob
private float bobTimer = 0f;
private Vector3 bobOffset;
private CharacterController playerController;
// Fire timing
private float nextTimeToFire = 0f;
void Start()
{
currentAmmo = maxAmmo;
if (fpsCam == null)
fpsCam = Camera.main;
if (fpsCam == null)
fpsCam = GetComponentInParent<Camera>();
playerController = GetComponentInParent<CharacterController>();
originalLocalPos = transform.localPosition;
// Strip colliders from all children so they don't block the CharacterController
StripChildColliders();
// NUCLEAR OPTION: Remove ANY collider on this object or children
Collider[] allColliders = GetComponentsInChildren<Collider>();
foreach (Collider col in allColliders)
{
if (!(col is CharacterController))
{
Debug.LogWarning($"Found collider on {col.gameObject.name} - REMOVING IT!");
Destroy(col);
}
}
// Only create a procedural gun if there's no existing model geometry
if (GetComponentsInChildren<MeshRenderer>().Length == 0)
CreateSimpleGunModel();
CreateMuzzleFlash();
InitDecalPool();
}
void StripChildColliders()
{
// Only strip colliders from direct children of the gun - never destroy CharacterControllers
foreach (Transform child in transform)
{
Collider col = child.GetComponent<Collider>();
if (col != null && !(col is CharacterController))
{
Destroy(col);
}
}
}
void Update()
{
// Shooting
if (isAutomatic ? Input.GetButton("Fire1") : Input.GetButtonDown("Fire1"))
{
if (Time.time >= nextTimeToFire)
{
nextTimeToFire = Time.time + 1f / fireRate;
Shoot();
}
}
// Reload
if (Input.GetKeyDown(KeyCode.R))
Reload();
// Recover from recoil
recoilOffset = Vector3.Lerp(recoilOffset, Vector3.zero, Time.deltaTime * recoilRecoverySpeed);
// Weapon bob
UpdateBob();
// Apply combined position: original + recoil + bob
transform.localPosition = originalLocalPos + recoilOffset + bobOffset;
// Muzzle flash timer
if (muzzleFlashTimer > 0f)
{
muzzleFlashTimer -= Time.deltaTime;
if (muzzleFlashTimer <= 0f)
{
if (muzzleFlashObj != null) muzzleFlashObj.SetActive(false);
if (muzzleLight != null) muzzleLight.enabled = false;
}
}
}
void UpdateBob()
{
if (playerController == null)
{
bobOffset = Vector3.zero;
return;
}
Vector3 horizontalVelocity = new Vector3(playerController.velocity.x, 0f, playerController.velocity.z);
bool isMoving = horizontalVelocity.magnitude > 0.5f && playerController.isGrounded;
if (isMoving)
{
float multiplier = Input.GetKey(KeyCode.LeftShift) ? sprintBobMultiplier : 1f;
bobTimer += Time.deltaTime * bobFrequency * multiplier;
float bobX = Mathf.Sin(bobTimer) * bobHorizontalAmplitude * multiplier;
float bobY = Mathf.Sin(bobTimer * 2f) * bobVerticalAmplitude * multiplier;
bobOffset = new Vector3(bobX, bobY, 0f);
}
else
{
bobTimer = 0f;
bobOffset = Vector3.Lerp(bobOffset, Vector3.zero, Time.deltaTime * bobReturnSpeed);
}
}
void Shoot()
{
if (currentAmmo <= 0)
{
Debug.Log("Out of ammo! Press R to reload.");
return;
}
currentAmmo--;
// Muzzle flash
ShowMuzzleFlash();
// Recoil kick
ApplyRecoil();
// Screen shake
if (CameraShake.Instance != null)
CameraShake.Instance.Shake(0.06f, 0.08f);
// Raycast
if (fpsCam == null) return;
RaycastHit hit;
Vector3 origin = fpsCam.transform.position;
Vector3 direction = fpsCam.transform.forward;
if (Physics.Raycast(origin, direction, out hit, range))
{
Debug.Log($"Hit: {hit.transform.name}");
SpawnImpactEffect(hit.point, hit.normal);
EnemyHealth enemy = hit.transform.GetComponent<EnemyHealth>();
if (enemy != null)
enemy.TakeDamage(damage);
}
}
void ApplyRecoil()
{
recoilOffset += new Vector3(
Random.Range(-0.005f, 0.005f),
Random.Range(0f, recoilKickUp),
-recoilKickback
);
}
// ─── Shader Helper ───
Material CreateUnlitMaterial(Color color)
{
string[] shaderNames = {
"Unlit/Color",
"Universal Render Pipeline/Unlit",
"Hidden/InternalErrorShader"
};
Shader shader = null;
foreach (string name in shaderNames)
{
shader = Shader.Find(name);
if (shader != null) break;
}
if (shader == null)
return new Material(Shader.Find("Standard")) { color = color };
Material mat = new Material(shader);
mat.color = color;
return mat;
}
Material CreateParticleMaterial(Color color)
{
string[] shaderNames = {
"Particles/Standard Unlit",
"Universal Render Pipeline/Particles/Unlit",
"Unlit/Color",
"Hidden/InternalErrorShader"
};
Shader shader = null;
foreach (string name in shaderNames)
{
shader = Shader.Find(name);
if (shader != null) break;
}
Material mat = new Material(shader);
mat.color = color;
return mat;
}
// ─── Muzzle Flash ───
void CreateMuzzleFlash()
{
muzzleFlashObj = GameObject.CreatePrimitive(PrimitiveType.Quad);
muzzleFlashObj.name = "MuzzleFlash";
muzzleFlashObj.transform.SetParent(transform);
muzzleFlashObj.transform.localPosition = new Vector3(0f, -0.05f, 0.75f);
muzzleFlashObj.transform.localScale = new Vector3(0.15f, 0.15f, 0.15f);
Destroy(muzzleFlashObj.GetComponent<Collider>());
Renderer r = muzzleFlashObj.GetComponent<Renderer>();
r.material = CreateUnlitMaterial(new Color(1f, 0.85f, 0.3f));
GameObject lightObj = new GameObject("MuzzleLight");
lightObj.transform.SetParent(muzzleFlashObj.transform);
lightObj.transform.localPosition = Vector3.zero;
muzzleLight = lightObj.AddComponent<Light>();
muzzleLight.type = LightType.Point;
muzzleLight.color = new Color(1f, 0.8f, 0.3f);
muzzleLight.range = 8f;
muzzleLight.intensity = 3f;
muzzleLight.enabled = false;
muzzleFlashObj.SetActive(false);
}
void ShowMuzzleFlash()
{
if (muzzleFlashObj == null) return;
muzzleFlashObj.SetActive(true);
if (muzzleLight != null) muzzleLight.enabled = true;
muzzleFlashTimer = muzzleFlashDuration;
float angle = Random.Range(0f, 360f);
muzzleFlashObj.transform.localRotation = Quaternion.Euler(0f, 0f, angle);
float scale = Random.Range(0.1f, 0.2f);
muzzleFlashObj.transform.localScale = new Vector3(scale, scale, scale);
if (muzzleLight != null) muzzleLight.intensity = Random.Range(2f, 4f);
}
// ─── Impact Effects ───
void InitDecalPool()
{
if (decalPool != null) return;
decalPool = new GameObject[maxDecals];
for (int i = 0; i < maxDecals; i++)
{
GameObject decal = GameObject.CreatePrimitive(PrimitiveType.Quad);
decal.name = "BulletDecal";
decal.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
Destroy(decal.GetComponent<Collider>());
Renderer dr = decal.GetComponent<Renderer>();
dr.material = CreateUnlitMaterial(new Color(0.05f, 0.05f, 0.05f));
decal.SetActive(false);
decalPool[i] = decal;
}
}
void SpawnImpactEffect(Vector3 position, Vector3 normal)
{
if (decalPool == null) return;
GameObject decal = decalPool[decalIndex % maxDecals];
decalIndex++;
decal.SetActive(true);
decal.transform.position = position + normal * 0.005f;
decal.transform.rotation = Quaternion.LookRotation(-normal);
float s = Random.Range(0.06f, 0.12f);
decal.transform.localScale = new Vector3(s, s, s);
SpawnSparks(position, normal);
}
void SpawnSparks(Vector3 position, Vector3 normal)
{
GameObject sparksObj = new GameObject("ImpactSparks");
sparksObj.transform.position = position;
sparksObj.transform.rotation = Quaternion.LookRotation(normal);
ParticleSystem ps = sparksObj.AddComponent<ParticleSystem>();
ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
var main = ps.main;
main.duration = 0.1f;
main.loop = false;
main.playOnAwake = false;
main.startLifetime = new ParticleSystem.MinMaxCurve(0.1f, 0.3f);
main.startSpeed = new ParticleSystem.MinMaxCurve(3f, 8f);
main.startSize = new ParticleSystem.MinMaxCurve(0.015f, 0.04f);
main.startColor = new Color(1f, 0.8f, 0.3f);
main.maxParticles = 12;
main.gravityModifier = 2f;
main.simulationSpace = ParticleSystemSimulationSpace.World;
var emission = ps.emission;
emission.enabled = true;
emission.SetBursts(new ParticleSystem.Burst[] {
new ParticleSystem.Burst(0f, 6, 12)
});
emission.rateOverTime = 0;
var shape = ps.shape;
shape.shapeType = ParticleSystemShapeType.Cone;
shape.angle = 35f;
shape.radius = 0.01f;
ParticleSystemRenderer psr = sparksObj.GetComponent<ParticleSystemRenderer>();
psr.material = CreateParticleMaterial(new Color(1f, 0.9f, 0.4f));
ps.Play();
Destroy(sparksObj, 0.5f);
}
// ─── Procedural Gun Model (only if no children exist) ───
void CreateSimpleGunModel()
{
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.transform.SetParent(transform);
body.transform.localPosition = new Vector3(0, -0.1f, 0.3f);
body.transform.localScale = new Vector3(0.1f, 0.15f, 0.4f);
body.transform.localRotation = Quaternion.identity;
Destroy(body.GetComponent<Collider>());
body.GetComponent<Renderer>().material.color = new Color(0.2f, 0.2f, 0.2f);
GameObject barrel = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
barrel.transform.SetParent(transform);
barrel.transform.localPosition = new Vector3(0, -0.05f, 0.6f);
barrel.transform.localScale = new Vector3(0.03f, 0.15f, 0.03f);
barrel.transform.localRotation = Quaternion.Euler(90, 0, 0);
Destroy(barrel.GetComponent<Collider>());
barrel.GetComponent<Renderer>().material.color = new Color(0.1f, 0.1f, 0.1f);
GameObject handle = GameObject.CreatePrimitive(PrimitiveType.Cube);
handle.transform.SetParent(transform);
handle.transform.localPosition = new Vector3(0, -0.25f, 0.15f);
handle.transform.localScale = new Vector3(0.08f, 0.2f, 0.1f);
handle.transform.localRotation = Quaternion.Euler(15, 0, 0);
Destroy(handle.GetComponent<Collider>());
handle.GetComponent<Renderer>().material.color = new Color(0.3f, 0.2f, 0.1f);
}
void Reload()
{
Debug.Log("Reloading...");
currentAmmo = maxAmmo;
}
void OnGUI()
{
GUIStyle ammoStyle = new GUIStyle();
ammoStyle.fontSize = 28;
ammoStyle.fontStyle = FontStyle.Bold;
ammoStyle.normal.textColor = currentAmmo > 5 ? Color.white : Color.red;
string ammoText = $"{currentAmmo} / {maxAmmo}";
GUI.Label(new Rect(Screen.width - 180, Screen.height - 55, 160, 40), ammoText, ammoStyle);
}
}