added gun models and enemy spawner
This commit is contained in:
@@ -5,81 +5,158 @@ public class SimpleGun : MonoBehaviour
|
||||
[Header("Gun Settings")]
|
||||
public float damage = 25f;
|
||||
public float range = 100f;
|
||||
public float fireRate = 0.5f;
|
||||
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;
|
||||
public ParticleSystem muzzleFlash;
|
||||
|
||||
// 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;
|
||||
|
||||
// Find camera if not assigned
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Create simple gun visuals
|
||||
CreateSimpleGunModel();
|
||||
// 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()
|
||||
{
|
||||
// Shoot on left mouse button
|
||||
if (Input.GetButton("Fire1") && Time.time >= nextTimeToFire)
|
||||
// Shooting
|
||||
if (isAutomatic ? Input.GetButton("Fire1") : Input.GetButtonDown("Fire1"))
|
||||
{
|
||||
nextTimeToFire = Time.time + 1f / fireRate;
|
||||
Shoot();
|
||||
if (Time.time >= nextTimeToFire)
|
||||
{
|
||||
nextTimeToFire = Time.time + 1f / fireRate;
|
||||
Shoot();
|
||||
}
|
||||
}
|
||||
|
||||
// Reload on R key
|
||||
// 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 CreateSimpleGunModel()
|
||||
void UpdateBob()
|
||||
{
|
||||
// Main body
|
||||
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>()); // Remove collider
|
||||
|
||||
// Set color
|
||||
Renderer bodyRenderer = body.GetComponent<Renderer>();
|
||||
bodyRenderer.material.color = new Color(0.2f, 0.2f, 0.2f); // Dark gray
|
||||
if (playerController == null)
|
||||
{
|
||||
bobOffset = Vector3.zero;
|
||||
return;
|
||||
}
|
||||
|
||||
// Barrel
|
||||
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>());
|
||||
|
||||
Renderer barrelRenderer = barrel.GetComponent<Renderer>();
|
||||
barrelRenderer.material.color = new Color(0.1f, 0.1f, 0.1f); // Even darker
|
||||
Vector3 horizontalVelocity = new Vector3(playerController.velocity.x, 0f, playerController.velocity.z);
|
||||
bool isMoving = horizontalVelocity.magnitude > 0.5f && playerController.isGrounded;
|
||||
|
||||
// Handle
|
||||
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>());
|
||||
|
||||
Renderer handleRenderer = handle.GetComponent<Renderer>();
|
||||
handleRenderer.material.color = new Color(0.3f, 0.2f, 0.1f); // Brown
|
||||
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()
|
||||
@@ -91,25 +168,239 @@ public class SimpleGun : MonoBehaviour
|
||||
}
|
||||
|
||||
currentAmmo--;
|
||||
Debug.Log($"Shot fired! Ammo: {currentAmmo}/{maxAmmo}");
|
||||
|
||||
// Play muzzle flash if assigned
|
||||
if (muzzleFlash != null)
|
||||
{
|
||||
muzzleFlash.Play();
|
||||
}
|
||||
// Muzzle flash
|
||||
ShowMuzzleFlash();
|
||||
|
||||
// Recoil kick
|
||||
ApplyRecoil();
|
||||
|
||||
// Screen shake
|
||||
if (CameraShake.Instance != null)
|
||||
CameraShake.Instance.Shake(0.06f, 0.08f);
|
||||
|
||||
// Raycast
|
||||
if (fpsCam == null) return;
|
||||
|
||||
// Raycast from camera center
|
||||
RaycastHit hit;
|
||||
if (Physics.Raycast(fpsCam.transform.position, fpsCam.transform.forward, out hit, range))
|
||||
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);
|
||||
|
||||
// You can add hit detection for enemies here later
|
||||
// Example: hit.transform.GetComponent<Enemy>()?.TakeDamage(damage);
|
||||
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...");
|
||||
@@ -118,10 +409,12 @@ public class SimpleGun : MonoBehaviour
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
// Simple ammo counter in bottom-right
|
||||
GUI.color = Color.white;
|
||||
GUI.Label(new Rect(Screen.width - 120, Screen.height - 40, 100, 30),
|
||||
$"Ammo: {currentAmmo}/{maxAmmo}",
|
||||
new GUIStyle() { fontSize = 20, normal = new GUIStyleState() { textColor = Color.white } });
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user