added gun models and enemy spawner

This commit is contained in:
2026-02-11 16:21:45 +00:00
parent 2d70da4827
commit 50968843e5
16 changed files with 1372 additions and 189 deletions

View File

@@ -281,7 +281,7 @@ Transform:
m_GameObject: {fileID: 153407402}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalPosition: {x: 11.9, y: -9.03, z: 3.11}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -300,13 +300,13 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 6b87887b68361bb4dbb1327a9bb7aa82, type: 3}
m_Name:
m_EditorClassIdentifier:
walkSpeed: 5
walkSpeed: 8
runSpeed: 10
jumpHeight: 2
gravity: -9.81
mouseSensitivity: 2
maxLookAngle: 90
playerCamera: {fileID: 0}
playerCamera: {fileID: 2083332238}
--- !u!114 &153407406
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -349,6 +349,52 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: -4926770370004109585, guid: 1b4afb54fbf7b8c46908373caec5cfcf, type: 3}
--- !u!1 &431786309
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 431786311}
- component: {fileID: 431786310}
m_Layer: 0
m_Name: GameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &431786310
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 431786309}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner
enemyType: 0
healthOverride: 0
--- !u!4 &431786311
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 431786309}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -6.61223, y: -16.71, z: 5.89}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &705507993
GameObject:
m_ObjectHideFlags: 0
@@ -676,6 +722,52 @@ MeshCollider:
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 6626584385738647817, guid: 1b4afb54fbf7b8c46908373caec5cfcf, type: 3}
--- !u!1 &1167301493
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1167301495}
- component: {fileID: 1167301494}
m_Layer: 0
m_Name: GameObject (2)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1167301494
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1167301493}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner
enemyType: 2
healthOverride: 0
--- !u!4 &1167301495
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1167301493}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 14.85, y: -0, z: -2.86}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &1188990073
PrefabInstance:
m_ObjectHideFlags: 0
@@ -860,6 +952,52 @@ MeshCollider:
m_Convex: 1
m_CookingOptions: 30
m_Mesh: {fileID: 0}
--- !u!1 &1908691865
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1908691867}
- component: {fileID: 1908691866}
m_Layer: 0
m_Name: GameObject (1)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1908691866
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1908691865}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner
enemyType: 1
healthOverride: 0
--- !u!4 &1908691867
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1908691865}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 19.42, y: -0, z: 10.85739}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1914211071
GameObject:
m_ObjectHideFlags: 0
@@ -869,6 +1007,8 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 1914211072}
- component: {fileID: 1914211073}
- component: {fileID: 1914211074}
m_Layer: 0
m_Name: Gun
m_TagString: Untagged
@@ -891,6 +1031,45 @@ Transform:
m_Children: []
m_Father: {fileID: 2083332236}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1914211073
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1914211071}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e348b4e172ba23d45960c0071cc09b1f, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::SimpleGun
damage: 25
range: 100
fireRate: 0.5
maxAmmo: 30
currentAmmo: 0
isAutomatic: 1
recoilKickback: 0.08
recoilKickUp: 0.04
recoilRecoverySpeed: 12
bobFrequency: 10
bobHorizontalAmplitude: 0.05
bobVerticalAmplitude: 0.03
sprintBobMultiplier: 1.5
bobReturnSpeed: 6
fpsCam: {fileID: 0}
--- !u!114 &1914211074
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1914211071}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9ad5c2eb6d6ecad47af3a531cd239ec5, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::CameraShake
--- !u!1 &1929672446 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: -3210541856918053980, guid: 1b4afb54fbf7b8c46908373caec5cfcf, type: 3}
@@ -1121,3 +1300,6 @@ SceneRoots:
- {fileID: 2129963323}
- {fileID: 754218171}
- {fileID: 58167676}
- {fileID: 431786311}
- {fileID: 1908691867}
- {fileID: 1167301495}

View File

@@ -0,0 +1,53 @@
using UnityEngine;
public class CameraShake : MonoBehaviour
{
public static CameraShake Instance { get; private set; }
private float shakeDuration = 0f;
private float shakeIntensity = 0f;
private float shakeFalloff = 1f;
private Vector3 originalLocalPos;
private bool shaking = false;
void Awake()
{
Instance = this;
originalLocalPos = transform.localPosition;
}
void LateUpdate()
{
if (!shaking) return;
if (shakeDuration > 0f)
{
Vector3 offset = Random.insideUnitSphere * shakeIntensity;
offset.z = 0f; // Keep shake on X/Y only
transform.localPosition = originalLocalPos + offset;
shakeDuration -= Time.deltaTime;
shakeIntensity = Mathf.Lerp(shakeIntensity, 0f, Time.deltaTime * shakeFalloff);
}
else
{
shaking = false;
shakeDuration = 0f;
transform.localPosition = originalLocalPos;
}
}
/// <summary>
/// Trigger a camera shake.
/// </summary>
/// <param name="duration">How long to shake (seconds)</param>
/// <param name="intensity">How far to displace the camera</param>
public void Shake(float duration, float intensity)
{
shakeDuration = duration;
shakeIntensity = intensity;
shakeFalloff = intensity / duration;
shaking = true;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9ad5c2eb6d6ecad47af3a531cd239ec5

View File

@@ -0,0 +1,93 @@
using UnityEngine;
public class EnemyHealth : MonoBehaviour
{
public float maxHealth = 100f;
public float currentHealth;
[Header("Death Settings")]
public bool destroyOnDeath = true;
public float deathDelay = 0.1f;
private bool isDead = false;
void Start()
{
currentHealth = maxHealth;
}
public void TakeDamage(float amount)
{
if (isDead) return;
currentHealth -= amount;
Debug.Log($"{gameObject.name} took {amount} damage! HP: {currentHealth}/{maxHealth}");
// Flash red on hit
StartCoroutine(HitFlash());
if (currentHealth <= 0f)
{
Die();
}
}
System.Collections.IEnumerator HitFlash()
{
Renderer[] renderers = GetComponentsInChildren<Renderer>();
if (renderers.Length == 0) yield break;
// Store original colors
Color[] originalColors = new Color[renderers.Length];
for (int i = 0; i < renderers.Length; i++)
originalColors[i] = renderers[i].material.color;
// Flash all parts white-red
for (int i = 0; i < renderers.Length; i++)
renderers[i].material.color = new Color(1f, 0.3f, 0.3f);
yield return new WaitForSeconds(0.08f);
// Restore
if (!isDead)
{
for (int i = 0; i < renderers.Length; i++)
{
if (renderers[i] != null)
renderers[i].material.color = originalColors[i];
}
}
}
void Die()
{
isDead = true;
Debug.Log($"{gameObject.name} KILLED!");
if (destroyOnDeath)
{
// Quick death: disable collider immediately, destroy after delay
Collider col = GetComponent<Collider>();
if (col != null) col.enabled = false;
// Optional: add a little "pop" by scaling down
StartCoroutine(DeathEffect());
}
}
System.Collections.IEnumerator DeathEffect()
{
float timer = 0f;
Vector3 startScale = transform.localScale;
while (timer < deathDelay)
{
timer += Time.deltaTime;
float t = timer / deathDelay;
transform.localScale = Vector3.Lerp(startScale, Vector3.zero, t);
yield return null;
}
Destroy(gameObject);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 13343cfbf2e477741a831a3db3c9cf80

View File

@@ -0,0 +1,81 @@
using UnityEngine;
/// <summary>
/// Drop this anywhere in your scene to spawn a humanoid enemy at that position.
/// Configure the enemy type in the Inspector then hit Play.
/// Delete this script after you've set up your enemies if you prefer manual placement.
/// </summary>
public class EnemySpawner : MonoBehaviour
{
public HumanoidEnemy.EnemyType enemyType = HumanoidEnemy.EnemyType.Grunt;
[Header("Health Override (0 = use defaults)")]
public float healthOverride = 0f;
void Start()
{
SpawnEnemy();
}
void SpawnEnemy()
{
GameObject enemyObj = new GameObject($"Enemy_{enemyType}");
enemyObj.transform.position = transform.position;
enemyObj.transform.rotation = transform.rotation;
// Add CharacterController (required by HumanoidEnemy)
CharacterController cc = enemyObj.AddComponent<CharacterController>();
cc.slopeLimit = 45f;
cc.stepOffset = 0.5f;
// Add health
EnemyHealth health = enemyObj.AddComponent<EnemyHealth>();
// Add enemy AI + model
HumanoidEnemy enemy = enemyObj.AddComponent<HumanoidEnemy>();
enemy.enemyType = enemyType;
if (healthOverride > 0f)
{
health.maxHealth = healthOverride;
health.currentHealth = healthOverride;
}
}
void OnDrawGizmos()
{
// Show enemy position in editor
switch (enemyType)
{
case HumanoidEnemy.EnemyType.Grunt:
Gizmos.color = new Color(0.8f, 0.5f, 0.2f, 0.8f);
DrawEnemyGizmo(1f);
break;
case HumanoidEnemy.EnemyType.Brute:
Gizmos.color = new Color(0.8f, 0.1f, 0.1f, 0.8f);
DrawEnemyGizmo(1.3f);
break;
case HumanoidEnemy.EnemyType.Runner:
Gizmos.color = new Color(0.2f, 0.7f, 0.2f, 0.8f);
DrawEnemyGizmo(0.9f);
break;
}
}
void DrawEnemyGizmo(float scale)
{
Vector3 pos = transform.position;
// Body
Gizmos.DrawCube(pos + Vector3.up * 1.15f * scale, new Vector3(0.45f, 0.6f, 0.25f) * scale);
// Head
Gizmos.DrawSphere(pos + Vector3.up * 1.65f * scale, 0.15f * scale);
// Legs
Gizmos.DrawCube(pos + Vector3.up * 0.4f * scale + Vector3.left * 0.12f, new Vector3(0.18f, 0.6f, 0.18f) * scale);
Gizmos.DrawCube(pos + Vector3.up * 0.4f * scale + Vector3.right * 0.12f, new Vector3(0.18f, 0.6f, 0.18f) * scale);
// Direction arrow
Gizmos.color = Color.yellow;
Gizmos.DrawRay(pos + Vector3.up * scale, transform.forward * 1.5f);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 180c6606991a4b64f812f9713907fd0c

View File

@@ -3,8 +3,8 @@ using UnityEngine;
public class FirstPersonController : MonoBehaviour
{
[Header("Movement Settings")]
public float walkSpeed = 8f;
public float runSpeed = 14f;
public float walkSpeed = 50f; // Boosted from 8f to overcome collision issues
public float runSpeed = 80f; // Boosted from 14f
public float jumpHeight = 2.5f;
public float gravity = -20f;
@@ -23,66 +23,96 @@ public class FirstPersonController : MonoBehaviour
void Start()
{
Debug.Log( "Starting game" );
// Get the CharacterController component
controller = GetComponent<CharacterController>();
Debug.Log("Starting game");
// If no camera is assigned, try to find one
if (playerCamera == null)
// FORCE NORMAL TIME (in case something external changed it)
Time.timeScale = 1f;
controller = GetComponent<CharacterController>();
if (controller == null)
{
playerCamera = GetComponentInChildren<Camera>();
Debug.LogError("FirstPersonController: No CharacterController found!");
return;
}
// Lock and hide the cursor
if (playerCamera == null)
playerCamera = GetComponentInChildren<Camera>();
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
void Update()
{
// Check if player is on the ground
if (controller == null) return;
isGrounded = controller.isGrounded;
// Reset vertical velocity when grounded
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
// ─── Movement via direct KeyCode ───
float moveX = 0f;
float moveZ = 0f;
if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) moveZ += 1f;
if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) moveZ -= 1f;
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) moveX -= 1f;
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) moveX += 1f;
// DEBUG: Log once when W is first pressed
if (Input.GetKeyDown(KeyCode.W))
{
Debug.Log($"[FPC DEBUG] W pressed | controller.enabled={controller.enabled} | position={transform.position} | isGrounded={isGrounded}");
Debug.Log($"[FPC DEBUG] Time.timeScale={Time.timeScale} | Time.deltaTime={Time.deltaTime} | walkSpeed={walkSpeed}");
}
// Get movement input
float moveX = Input.GetAxis("Horizontal"); // A/D or Left/Right arrows
float moveZ = Input.GetAxis("Vertical"); // W/S or Up/Down arrows
// Calculate movement direction
Vector3 move = transform.right * moveX + transform.forward * moveZ;
// Determine current speed (run if Shift is held)
if (move.magnitude > 1f)
move.Normalize();
float currentSpeed = Input.GetKey(KeyCode.LeftShift) ? runSpeed : walkSpeed;
// Move the character
Vector3 posBefore = transform.position;
controller.Move(move * currentSpeed * Time.deltaTime);
// Jumping
if (Input.GetButtonDown("Jump") && isGrounded)
// DEBUG: Log if we tried to move but didn't
if (move.magnitude > 0f && Input.GetKeyDown(KeyCode.W))
{
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
Vector3 posAfter = transform.position;
Debug.Log($"[FPC DEBUG] Move attempt: delta={move * currentSpeed * Time.deltaTime} | actualDelta={(posAfter - posBefore)} | controller.height={controller.height} | controller.radius={controller.radius}");
// Check what we're colliding with
Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, controller.radius + 0.5f);
Debug.Log($"[FPC DEBUG] Found {nearbyColliders.Length} colliders near player");
foreach (Collider col in nearbyColliders)
{
if (col != controller && !(col is CharacterController))
Debug.LogWarning($"[FPC DEBUG] Nearby collider: {col.gameObject.name} on layer {LayerMask.LayerToName(col.gameObject.layer)}");
}
}
// Apply gravity
// Jumping
if (Input.GetKey(KeyCode.Space) && isGrounded)
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
// Gravity
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
// Mouse look
HandleMouseLook();
// Press Escape to unlock cursor
// Escape to unlock cursor
if (Input.GetKeyDown(KeyCode.Escape))
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
// Click to lock cursor again
// Click to re-lock cursor
if (Input.GetMouseButtonDown(0) && Cursor.lockState == CursorLockMode.None)
{
Cursor.lockState = CursorLockMode.Locked;
@@ -92,16 +122,13 @@ public class FirstPersonController : MonoBehaviour
void HandleMouseLook()
{
// Get mouse input
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
// Rotate camera up/down (pitch)
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, -maxLookAngle, maxLookAngle);
playerCamera.transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);
// Rotate player left/right (yaw)
transform.Rotate(Vector3.up * mouseX);
}
}

View File

@@ -0,0 +1,394 @@
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(CharacterController))]
public class HumanoidEnemy : MonoBehaviour
{
public enum EnemyType { Grunt, Brute, Runner }
[Header("Enemy Type")]
public EnemyType enemyType = EnemyType.Grunt;
[Header("Movement")]
public float moveSpeed = 5f;
public float chaseRange = 25f;
public float attackRange = 2f;
public float gravity = -20f;
[Header("Combat")]
public float attackDamage = 10f;
public float attackCooldown = 1f;
[Header("Patrol (optional)")]
public float patrolRadius = 8f;
public float patrolWaitTime = 2f;
// Internal
private Transform player;
private CharacterController controller;
private EnemyHealth health;
private Vector3 spawnPoint;
private Vector3 patrolTarget;
private float nextAttackTime;
private float verticalVelocity;
private float patrolWaitTimer;
private bool isAggro = false;
// Animation state
private Transform bodyRoot;
private Transform headTransform;
private Transform torsoTransform;
private Transform leftArm;
private Transform rightArm;
private Transform leftLeg;
private Transform rightLeg;
private float animTimer = 0f;
void Start()
{
controller = GetComponent<CharacterController>();
health = GetComponent<EnemyHealth>();
// Find player
GameObject playerObj = GameObject.Find("Player");
if (playerObj != null)
player = playerObj.transform;
spawnPoint = transform.position;
patrolTarget = GetRandomPatrolPoint();
ApplyTypeStats();
BuildHumanoidModel();
}
void ApplyTypeStats()
{
switch (enemyType)
{
case EnemyType.Grunt:
moveSpeed = 4f;
attackDamage = 10f;
attackCooldown = 1.2f;
chaseRange = 20f;
if (health != null) health.maxHealth = 80f;
break;
case EnemyType.Brute:
moveSpeed = 2.5f;
attackDamage = 25f;
attackCooldown = 2f;
chaseRange = 18f;
if (health != null) health.maxHealth = 200f;
break;
case EnemyType.Runner:
moveSpeed = 8f;
attackDamage = 8f;
attackCooldown = 0.6f;
chaseRange = 30f;
if (health != null) health.maxHealth = 50f;
break;
}
if (health != null)
health.currentHealth = health.maxHealth;
}
void Update()
{
if (health != null && health.currentHealth <= 0f) return;
if (player == null) return;
float distToPlayer = Vector3.Distance(transform.position, player.position);
// Aggro check
if (distToPlayer <= chaseRange)
isAggro = true;
if (isAggro)
{
if (distToPlayer <= attackRange)
Attack();
else
ChasePlayer();
}
else
{
Patrol();
}
// Gravity
if (controller.isGrounded && verticalVelocity < 0f)
verticalVelocity = -2f;
verticalVelocity += gravity * Time.deltaTime;
controller.Move(Vector3.up * verticalVelocity * Time.deltaTime);
// Animate
AnimateModel(isAggro && distToPlayer > attackRange);
}
void ChasePlayer()
{
Vector3 direction = (player.position - transform.position);
direction.y = 0f;
direction.Normalize();
controller.Move(direction * moveSpeed * Time.deltaTime);
// Face the player
if (direction != Vector3.zero)
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 8f);
}
void Patrol()
{
float distToTarget = Vector3.Distance(transform.position, patrolTarget);
if (distToTarget < 1.5f)
{
patrolWaitTimer += Time.deltaTime;
if (patrolWaitTimer >= patrolWaitTime)
{
patrolTarget = GetRandomPatrolPoint();
patrolWaitTimer = 0f;
}
return;
}
Vector3 direction = (patrolTarget - transform.position);
direction.y = 0f;
direction.Normalize();
controller.Move(direction * (moveSpeed * 0.4f) * Time.deltaTime);
if (direction != Vector3.zero)
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 4f);
}
Vector3 GetRandomPatrolPoint()
{
Vector2 randomCircle = Random.insideUnitCircle * patrolRadius;
return spawnPoint + new Vector3(randomCircle.x, 0f, randomCircle.y);
}
void Attack()
{
// Face the player
Vector3 direction = (player.position - transform.position);
direction.y = 0f;
if (direction != Vector3.zero)
transform.rotation = Quaternion.LookRotation(direction);
if (Time.time >= nextAttackTime)
{
nextAttackTime = Time.time + attackCooldown;
Debug.Log($"{gameObject.name} attacks for {attackDamage} damage!");
// Punch animation
StartCoroutine(PunchAnimation());
// TODO: Hook into player health system when you have one
// player.GetComponent<PlayerHealth>()?.TakeDamage(attackDamage);
}
}
// ─── Procedural Humanoid Model ───
void BuildHumanoidModel()
{
// Sizing based on type
float scale = 1f;
Color skinColor = new Color(0.6f, 0.45f, 0.35f);
Color shirtColor;
Color pantsColor = new Color(0.2f, 0.2f, 0.25f);
switch (enemyType)
{
case EnemyType.Grunt:
scale = 1f;
shirtColor = new Color(0.4f, 0.25f, 0.2f); // brown shirt
break;
case EnemyType.Brute:
scale = 1.3f;
shirtColor = new Color(0.5f, 0.1f, 0.1f); // red shirt
skinColor = new Color(0.5f, 0.35f, 0.3f);
break;
case EnemyType.Runner:
scale = 0.9f;
shirtColor = new Color(0.2f, 0.35f, 0.2f); // green shirt
break;
default:
shirtColor = Color.gray;
break;
}
bodyRoot = new GameObject("BodyRoot").transform;
bodyRoot.SetParent(transform);
bodyRoot.localPosition = Vector3.zero;
bodyRoot.localRotation = Quaternion.identity;
// Head
headTransform = CreatePart("Head", bodyRoot,
new Vector3(0f, 1.65f, 0f) * scale,
new Vector3(0.3f, 0.3f, 0.3f) * scale,
skinColor, PrimitiveType.Sphere);
// Eyes (two small dark spheres)
CreatePart("LeftEye", headTransform,
new Vector3(-0.08f, 0.03f, 0.12f),
new Vector3(0.08f, 0.08f, 0.05f),
new Color(0.9f, 0.1f, 0.1f), PrimitiveType.Sphere);
CreatePart("RightEye", headTransform,
new Vector3(0.08f, 0.03f, 0.12f),
new Vector3(0.08f, 0.08f, 0.05f),
new Color(0.9f, 0.1f, 0.1f), PrimitiveType.Sphere);
// Torso
torsoTransform = CreatePart("Torso", bodyRoot,
new Vector3(0f, 1.15f, 0f) * scale,
new Vector3(0.45f, 0.6f, 0.25f) * scale,
shirtColor, PrimitiveType.Cube);
// Left Arm
leftArm = CreatePart("LeftArm", bodyRoot,
new Vector3(-0.35f, 1.15f, 0f) * scale,
new Vector3(0.15f, 0.55f, 0.15f) * scale,
skinColor, PrimitiveType.Cube);
// Right Arm
rightArm = CreatePart("RightArm", bodyRoot,
new Vector3(0.35f, 1.15f, 0f) * scale,
new Vector3(0.15f, 0.55f, 0.15f) * scale,
skinColor, PrimitiveType.Cube);
// Left Leg
leftLeg = CreatePart("LeftLeg", bodyRoot,
new Vector3(-0.12f, 0.4f, 0f) * scale,
new Vector3(0.18f, 0.6f, 0.18f) * scale,
pantsColor, PrimitiveType.Cube);
// Right Leg
rightLeg = CreatePart("RightLeg", bodyRoot,
new Vector3(0.12f, 0.4f, 0f) * scale,
new Vector3(0.18f, 0.6f, 0.18f) * scale,
pantsColor, PrimitiveType.Cube);
// Adjust CharacterController to fit
controller.height = 1.9f * scale;
controller.radius = 0.3f * scale;
controller.center = new Vector3(0f, 0.95f * scale, 0f);
}
Transform CreatePart(string name, Transform parent, Vector3 localPos, Vector3 localScale, Color color, PrimitiveType shape = PrimitiveType.Cube)
{
GameObject part = GameObject.CreatePrimitive(shape);
part.name = name;
part.transform.SetParent(parent);
part.transform.localPosition = localPos;
part.transform.localScale = localScale;
part.transform.localRotation = Quaternion.identity;
// Remove collider from body parts (CharacterController handles collision)
Collider col = part.GetComponent<Collider>();
if (col != null) Destroy(col);
Renderer rend = part.GetComponent<Renderer>();
rend.material = new Material(Shader.Find("Standard"));
rend.material.color = color;
return part.transform;
}
// ─── Simple Limb Animation ───
void AnimateModel(bool isWalking)
{
if (bodyRoot == null) return;
if (isWalking)
{
animTimer += Time.deltaTime * moveSpeed * 1.5f;
float limbSwing = Mathf.Sin(animTimer) * 30f;
// Arms swing opposite to legs
if (leftArm != null)
leftArm.localRotation = Quaternion.Euler(limbSwing, 0f, 0f);
if (rightArm != null)
rightArm.localRotation = Quaternion.Euler(-limbSwing, 0f, 0f);
if (leftLeg != null)
leftLeg.localRotation = Quaternion.Euler(-limbSwing, 0f, 0f);
if (rightLeg != null)
rightLeg.localRotation = Quaternion.Euler(limbSwing, 0f, 0f);
// Slight torso bob
if (torsoTransform != null)
{
Vector3 torsoPos = torsoTransform.localPosition;
float scale = enemyType == EnemyType.Brute ? 1.3f : (enemyType == EnemyType.Runner ? 0.9f : 1f);
torsoPos.y = 1.15f * scale + Mathf.Abs(Mathf.Sin(animTimer * 2f)) * 0.03f;
torsoTransform.localPosition = torsoPos;
}
}
else
{
// Return to idle pose
animTimer = 0f;
if (leftArm != null)
leftArm.localRotation = Quaternion.Slerp(leftArm.localRotation, Quaternion.identity, Time.deltaTime * 5f);
if (rightArm != null)
rightArm.localRotation = Quaternion.Slerp(rightArm.localRotation, Quaternion.identity, Time.deltaTime * 5f);
if (leftLeg != null)
leftLeg.localRotation = Quaternion.Slerp(leftLeg.localRotation, Quaternion.identity, Time.deltaTime * 5f);
if (rightLeg != null)
rightLeg.localRotation = Quaternion.Slerp(rightLeg.localRotation, Quaternion.identity, Time.deltaTime * 5f);
}
}
IEnumerator PunchAnimation()
{
if (rightArm == null) yield break;
// Wind up
float timer = 0f;
float punchDuration = 0.15f;
while (timer < punchDuration)
{
timer += Time.deltaTime;
float t = timer / punchDuration;
rightArm.localRotation = Quaternion.Euler(Mathf.Lerp(0f, -90f, t), 0f, 0f);
yield return null;
}
// Snap back
timer = 0f;
while (timer < punchDuration)
{
timer += Time.deltaTime;
float t = timer / punchDuration;
rightArm.localRotation = Quaternion.Euler(Mathf.Lerp(-90f, 0f, t), 0f, 0f);
yield return null;
}
rightArm.localRotation = Quaternion.identity;
}
// ─── Gizmos ───
void OnDrawGizmosSelected()
{
// Chase range
Gizmos.color = new Color(1f, 0f, 0f, 0.15f);
Gizmos.DrawWireSphere(transform.position, chaseRange);
// Attack range
Gizmos.color = new Color(1f, 0.5f, 0f, 0.3f);
Gizmos.DrawWireSphere(transform.position, attackRange);
// Patrol radius
Gizmos.color = new Color(0f, 0.5f, 1f, 0.15f);
Gizmos.DrawWireSphere(Application.isPlaying ? spawnPoint : transform.position, patrolRadius);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 363b918c62c4e944faca34713da9120e

View File

@@ -0,0 +1,53 @@
using UnityEngine;
/// <summary>
/// Attach this to your Player object and hit Play once to remove duplicate guns.
/// Delete this script after running it.
/// </summary>
public class RemoveDuplicateGuns : MonoBehaviour
{
void Start()
{
Debug.Log("=== SEARCHING FOR DUPLICATE GUNS ===");
// Find all SimpleGun components in children
SimpleGun[] guns = GetComponentsInChildren<SimpleGun>();
Debug.Log($"Found {guns.Length} gun(s)");
if (guns.Length <= 1)
{
Debug.Log("Only one gun found - no duplicates to remove");
return;
}
// Keep the gun that's furthest from the player's feet (highest Y position)
SimpleGun highestGun = guns[0];
float highestY = guns[0].transform.position.y;
foreach (SimpleGun gun in guns)
{
Vector3 worldPos = gun.transform.position;
Debug.Log($"Gun at {gun.gameObject.name}: World Position = {worldPos}");
if (worldPos.y > highestY)
{
highestGun = gun;
highestY = worldPos.y;
}
}
// Remove all guns except the highest one
foreach (SimpleGun gun in guns)
{
if (gun != highestGun)
{
Debug.LogWarning($"REMOVING duplicate gun: {gun.gameObject.name} at {gun.transform.position}");
Destroy(gun.gameObject);
}
}
Debug.Log($"Kept gun: {highestGun.gameObject.name} at {highestGun.transform.position}");
Debug.Log("=== DUPLICATE REMOVAL COMPLETE ===");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ed4b2209a2bb458458ceb5e8a3ba5950

View File

@@ -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);
}
}

View File

@@ -0,0 +1,54 @@
using UnityEngine;
public class WeaponBob : MonoBehaviour
{
[Header("Bob Settings")]
public float bobFrequency = 10f;
public float bobHorizontalAmplitude = 0.05f;
public float bobVerticalAmplitude = 0.03f;
[Header("Sprint Multiplier")]
public float sprintBobMultiplier = 1.5f;
[Header("Smoothing")]
public float returnSpeed = 6f;
private Vector3 originalLocalPos;
private float bobTimer = 0f;
private CharacterController controller;
void Start()
{
originalLocalPos = transform.localPosition;
controller = GetComponentInParent<CharacterController>();
}
void Update()
{
if (controller == null) return;
// Check if the player is moving on the ground
Vector3 horizontalVelocity = new Vector3(controller.velocity.x, 0f, controller.velocity.z);
bool isMoving = horizontalVelocity.magnitude > 0.5f && controller.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;
transform.localPosition = originalLocalPos + new Vector3(bobX, bobY, 0f);
}
else
{
bobTimer = 0f;
transform.localPosition = Vector3.Lerp(
transform.localPosition,
originalLocalPos,
Time.deltaTime * returnSpeed
);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4b2ef1ad29b012344bd26f70130d3c2e

View File

@@ -1,105 +1,46 @@
# LiDAR FPS Game - Setup Guide
# OGG Shooting System - Setup Guide
## Project Structure Created:
- **Assets/Scripts/** - Your C# scripts (including FirstPersonController.cs)
- **Assets/Models/LidarScans/** - Where you'll import your GLB files
- **Assets/Prefabs/** - For reusable game objects
- **Assets/Scenes/** - Your game levels
## Scene Hierarchy (how it should look)
## Step 1: Import Your GLB Files
```
Player [CharacterController + FirstPersonController + SimpleCrosshair]
└── Camera [Camera + AudioListener + CameraShake + WeaponBob]
└── Gun [SimpleGun]
```
1. Open Unity and your OGG project
2. Copy some GLB files from `K:\OGG\lidar\` to `K:\OGG\OGG\Assets\Models\LidarScans\`
- Start with one or two to test (e.g., "my bedroom.glb" or "studio old.glb")
3. Unity will automatically import them
## Step-by-step Setup
## Step 2: Install glTFast Package (for better GLB support)
### 1. Player object (already done)
- Has: CharacterController, FirstPersonController, SimpleCrosshair ✔
- **Fix**: Drag the **Camera** child into the `Player Camera` field on FirstPersonController
1. In Unity, go to **Window → Package Manager**
2. Click the **"+"** button in top-left
3. Select **"Add package by name"**
4. Enter: `com.atteneder.gltfast`
5. Click **Add**
### 2. Camera object (child of Player)
- Has: Camera, AudioListener ✔
- **Add Component**: `CameraShake`
- **Add Component**: `WeaponBob`
## Step 3: Create Your Player
### 3. Gun object (child of Camera)
- Currently empty!
- **Add Component**: `SimpleGun`
- The gun auto-creates its own primitive model and muzzle flash
1. In the Hierarchy, **Right-click → Create Empty**
2. Rename it to "Player"
3. With Player selected, click **Add Component**
4. Search for and add: **Character Controller**
5. Add Component again, search for: **First Person Controller** (our script)
### 4. Testing enemies
- Create any GameObject (e.g. a Cube) in the scene
- Add a **Collider** (Box Collider, etc.)
- **Add Component**: `EnemyHealth`
- Shoot it and watch it flash red and pop!
## Step 4: Set Up the Camera
## Controls
| Key | Action |
|-----|--------|
| Left Click (hold) | Shoot (automatic fire) |
| R | Reload |
| W/A/S/D | Move |
| Shift | Sprint |
| Space | Jump |
1. In Hierarchy, **Right-click on Player → Camera**
2. Position the camera:
- Set Position Y to about 1.6 (eye height)
- Reset X and Z to 0
3. The FirstPersonController script should automatically find this camera
## Step 5: Import a LiDAR Scan into Your Scene
1. From Project window, navigate to **Assets/Models/LidarScans/**
2. Drag one of your GLB files into the Scene (Hierarchy)
3. Select the imported model in Hierarchy
4. In Inspector, click **Add Component → Mesh Collider**
- This allows the player to walk on it
5. You might need to adjust the scale:
- Try Scale: X=1, Y=1, Z=1 first
- If too big/small, adjust all values equally
## Step 6: Position Your Player
1. Select the Player in Hierarchy
2. In the Inspector, set Transform Position:
- Move the player above your scan (Y=2 or Y=3)
- Adjust X and Z to be inside the scan area
3. Make sure Character Controller settings are reasonable:
- Radius: 0.5
- Height: 2
- Center: Y=1
## Step 7: Test Your Game!
1. Click the **Play** button at the top
2. **Controls:**
- **WASD** or **Arrow Keys** - Move
- **Mouse** - Look around
- **Left Shift** - Run
- **Space** - Jump
- **Escape** - Unlock cursor
## Troubleshooting:
### Player falls through the floor:
- Make sure your LiDAR model has a **Mesh Collider** component
- Check that the Mesh Collider is enabled (checkbox ticked)
### Can't see anything:
- The camera might be inside geometry - move the Player position up (increase Y value)
- Check that the Camera is a child of Player and positioned at Y=1.6
### Movement feels weird:
- Adjust the Character Controller's Radius and Height
- Try different Walk/Run speeds in the FirstPersonController settings
### GLB files won't import:
- Install the glTFast package (see Step 2)
- Alternatively, you can convert GLB to OBJ using Blender if needed
## Next Steps:
Once you have basic movement working, we can add:
- Crosshair UI
- Health system
- Shooting mechanics
- Enemies/targets
- Multiple level loading
- Lighting improvements for your LiDAR scans
## Tips for LiDAR Scans:
- Your scans might be very detailed - this can slow down the game
- You may need to optimize them later (reduce polygon count)
- Start with smaller scans (single rooms) before loading larger areas
- You can have multiple scans in one scene to create larger levels
## Tuning Tips
- **Fire rate**: `SimpleGun.fireRate` (8 = fast, 2 = slow)
- **Shake intensity**: `CameraShake.Shake()` is called with (duration, intensity) - tweak in SimpleGun
- **Bob feel**: Adjust `WeaponBob.bobFrequency` and amplitude values
- **Recoil punch**: `SimpleGun.recoilKickback` and `recoilKickUp`