added gun models and enemy spawner
This commit is contained in:
@@ -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}
|
||||
|
||||
53
Assets/Scripts/CameraShake.cs
Normal file
53
Assets/Scripts/CameraShake.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/CameraShake.cs.meta
Normal file
2
Assets/Scripts/CameraShake.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ad5c2eb6d6ecad47af3a531cd239ec5
|
||||
93
Assets/Scripts/EnemyHealth.cs
Normal file
93
Assets/Scripts/EnemyHealth.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/EnemyHealth.cs.meta
Normal file
2
Assets/Scripts/EnemyHealth.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13343cfbf2e477741a831a3db3c9cf80
|
||||
81
Assets/Scripts/EnemySpawner.cs
Normal file
81
Assets/Scripts/EnemySpawner.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/EnemySpawner.cs.meta
Normal file
2
Assets/Scripts/EnemySpawner.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 180c6606991a4b64f812f9713907fd0c
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
394
Assets/Scripts/HumanoidEnemy.cs
Normal file
394
Assets/Scripts/HumanoidEnemy.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/HumanoidEnemy.cs.meta
Normal file
2
Assets/Scripts/HumanoidEnemy.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 363b918c62c4e944faca34713da9120e
|
||||
53
Assets/Scripts/RemoveDuplicateGuns.cs
Normal file
53
Assets/Scripts/RemoveDuplicateGuns.cs
Normal 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 ===");
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/RemoveDuplicateGuns.cs.meta
Normal file
2
Assets/Scripts/RemoveDuplicateGuns.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed4b2209a2bb458458ceb5e8a3ba5950
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
54
Assets/Scripts/WeaponBob.cs
Normal file
54
Assets/Scripts/WeaponBob.cs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/WeaponBob.cs.meta
Normal file
2
Assets/Scripts/WeaponBob.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b2ef1ad29b012344bd26f70130d3c2e
|
||||
133
SETUP_GUIDE.md
133
SETUP_GUIDE.md
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user