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")] [Tooltip("Set false if WeaponBob component is on the Camera — they'll double-stack otherwise.")] public bool enableInternalBob = false; 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; // Cached refs for inventory check private Inventory _inventory; private WeaponManager _weaponManager; void Start() { currentAmmo = maxAmmo; if (fpsCam == null) fpsCam = Camera.main; if (fpsCam == null) fpsCam = GetComponentInParent(); playerController = GetComponentInParent(); _inventory = GetComponentInParent(); _weaponManager = GetComponentInParent(); // originalLocalPos is driven by WeaponManager's offset — don't cache here originalLocalPos = _weaponManager != null ? _weaponManager.weaponPositionOffset : transform.localPosition; // NUCLEAR OPTION: Remove ANY collider on this object or children Collider[] allColliders = GetComponentsInChildren(); foreach (Collider col in allColliders) { if (!(col is CharacterController)) { Debug.LogWarning($"Found collider on {col.gameObject.name} - REMOVING IT!"); Destroy(col); } } // Strip colliders from all children so they don't block the CharacterController StripChildColliders(); 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(); if (col != null && !(col is CharacterController)) { Destroy(col); } } } void Update() { // Don't shoot while the inventory is open bool inventoryOpen = _inventory != null && _inventory.IsOpen; if (inventoryOpen) return; // Shooting if (isAutomatic ? InputData.leftMouseButton.down : InputData.leftMouseButton.pressed) { if (Time.time >= nextTimeToFire) { nextTimeToFire = Time.time + 1f / fireRate; Shoot(); } } // Reload if (InputData.reload.pressed) Reload(); // Keep base position in sync with WeaponManager's live offset if (_weaponManager != null) originalLocalPos = _weaponManager.weaponPositionOffset; // Recover from recoil recoilOffset = Vector3.Lerp(recoilOffset, Vector3.zero, Time.deltaTime * recoilRecoverySpeed); // Weapon bob (only if internal bob is enabled — disable if WeaponBob is on the Camera) if (enableInternalBob) UpdateBob(); else bobOffset = Vector3.zero; // 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); // Head hitbox = one shot kill EnemyHeadHitbox head = hit.transform.GetComponent(); if (head != null && head.enemyHealth != null) { Debug.Log("HEADSHOT! Instakill."); head.enemyHealth.TakeDamage(head.enemyHealth.maxHealth * 10f); } else { // Body shot — check the hit object AND walk up to parent for EnemyHealth EnemyHealth enemy = hit.transform.GetComponentInParent(); 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()); Renderer r = muzzleFlashObj.GetComponent(); 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(); 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()); Renderer dr = decal.GetComponent(); 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(); 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(); 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()); body.GetComponent().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()); barrel.GetComponent().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()); handle.GetComponent().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); } }