From 27f2931ce1fc35b3eaaad1b5a904006bd3291631 Mon Sep 17 00:00:00 2001 From: rapid Date: Tue, 17 Feb 2026 15:38:46 +0000 Subject: [PATCH] added inventory and stamina system --- Assets/Items.meta | 8 + Assets/Items/BhopBoots_Item.asset | 20 + Assets/Items/BhopBoots_Item.asset.meta | 8 + Assets/Items/GunSplat_Item.asset | 19 + Assets/Items/GunSplat_Item.asset.meta | 8 + Assets/Models/LidarScans/material_0 8.mat | 40 ++ Assets/Prefabs/Pickups.meta | 8 + .../Prefabs/Pickups/BhopBoots_Pickup.prefab | 161 +++++ .../Pickups/BhopBoots_Pickup.prefab.meta | 7 + Assets/Prefabs/Weapons.meta | 8 + Assets/Prefabs/Weapons/GunSplat.prefab | 141 ++++ Assets/Prefabs/Weapons/GunSplat.prefab.meta | 7 + Assets/Scenes/Testing Scenes/Movement.unity | 469 ++++++++----- Assets/Scripts/BootsEffect.cs | 46 ++ Assets/Scripts/BootsEffect.cs.meta | 2 + Assets/Scripts/Editor.meta | 8 + Assets/Scripts/Editor/BhopBootsSetup.cs | 100 +++ Assets/Scripts/Editor/BhopBootsSetup.cs.meta | 2 + Assets/Scripts/Editor/GunSplatSetup.cs | 151 +++++ Assets/Scripts/Editor/GunSplatSetup.cs.meta | 2 + Assets/Scripts/Editor/WeaponManagerEditor.cs | 92 +++ .../Editor/WeaponManagerEditor.cs.meta | 2 + Assets/Scripts/FirstPersonController.cs | 94 +-- Assets/Scripts/Game/Input/InputData.cs | 2 +- Assets/Scripts/HumanoidEnemy.cs | 9 +- Assets/Scripts/Inventory.cs | 632 ++++++++++++++++++ Assets/Scripts/Inventory.cs.meta | 2 + Assets/Scripts/ItemDefinition.cs | 28 + Assets/Scripts/ItemDefinition.cs.meta | 2 + Assets/Scripts/PickupItem.cs | 156 +++++ Assets/Scripts/PickupItem.cs.meta | 2 + Assets/Scripts/Player.cs | 61 +- Assets/Scripts/PlayerHUD.cs | 281 ++++++++ Assets/Scripts/PlayerHUD.cs.meta | 2 + Assets/Scripts/SimpleGun.cs | 28 +- Assets/Scripts/StaminaBoostPickup.cs | 36 + Assets/Scripts/StaminaBoostPickup.cs.meta | 2 + Assets/Scripts/WeaponManager.cs | 197 ++++++ Assets/Scripts/WeaponManager.cs.meta | 2 + Assets/Scripts/WeaponViewmodel.cs | 29 + Assets/Scripts/WeaponViewmodel.cs.meta | 2 + DEV_NOTES.md | 353 ++++++++++ OGG.slnx | 1 + 43 files changed, 2980 insertions(+), 250 deletions(-) create mode 100644 Assets/Items.meta create mode 100644 Assets/Items/BhopBoots_Item.asset create mode 100644 Assets/Items/BhopBoots_Item.asset.meta create mode 100644 Assets/Items/GunSplat_Item.asset create mode 100644 Assets/Items/GunSplat_Item.asset.meta create mode 100644 Assets/Prefabs/Pickups.meta create mode 100644 Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab create mode 100644 Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab.meta create mode 100644 Assets/Prefabs/Weapons.meta create mode 100644 Assets/Prefabs/Weapons/GunSplat.prefab create mode 100644 Assets/Prefabs/Weapons/GunSplat.prefab.meta create mode 100644 Assets/Scripts/BootsEffect.cs create mode 100644 Assets/Scripts/BootsEffect.cs.meta create mode 100644 Assets/Scripts/Editor.meta create mode 100644 Assets/Scripts/Editor/BhopBootsSetup.cs create mode 100644 Assets/Scripts/Editor/BhopBootsSetup.cs.meta create mode 100644 Assets/Scripts/Editor/GunSplatSetup.cs create mode 100644 Assets/Scripts/Editor/GunSplatSetup.cs.meta create mode 100644 Assets/Scripts/Editor/WeaponManagerEditor.cs create mode 100644 Assets/Scripts/Editor/WeaponManagerEditor.cs.meta create mode 100644 Assets/Scripts/Inventory.cs create mode 100644 Assets/Scripts/Inventory.cs.meta create mode 100644 Assets/Scripts/ItemDefinition.cs create mode 100644 Assets/Scripts/ItemDefinition.cs.meta create mode 100644 Assets/Scripts/PickupItem.cs create mode 100644 Assets/Scripts/PickupItem.cs.meta create mode 100644 Assets/Scripts/PlayerHUD.cs create mode 100644 Assets/Scripts/PlayerHUD.cs.meta create mode 100644 Assets/Scripts/StaminaBoostPickup.cs create mode 100644 Assets/Scripts/StaminaBoostPickup.cs.meta create mode 100644 Assets/Scripts/WeaponManager.cs create mode 100644 Assets/Scripts/WeaponManager.cs.meta create mode 100644 Assets/Scripts/WeaponViewmodel.cs create mode 100644 Assets/Scripts/WeaponViewmodel.cs.meta create mode 100644 DEV_NOTES.md diff --git a/Assets/Items.meta b/Assets/Items.meta new file mode 100644 index 0000000..9cef3be --- /dev/null +++ b/Assets/Items.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 94c8f7f884ccd0e47a66f83dc8fe8b0e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Items/BhopBoots_Item.asset b/Assets/Items/BhopBoots_Item.asset new file mode 100644 index 0000000..9ff50fd --- /dev/null +++ b/Assets/Items/BhopBoots_Item.asset @@ -0,0 +1,20 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b549159f64e1b164082e2e4ae4d28ac1, type: 3} + m_Name: BhopBoots_Item + m_EditorClassIdentifier: Assembly-CSharp::ItemDefinition + itemName: Bunny Hop Boots + icon: {fileID: 0} + type: 0 + weaponPrefab: {fileID: 0} + isEquippable: 1 + description: Illegal footwear. Gives 200 stamina while worn. Do not wear near cliffs. diff --git a/Assets/Items/BhopBoots_Item.asset.meta b/Assets/Items/BhopBoots_Item.asset.meta new file mode 100644 index 0000000..1825a76 --- /dev/null +++ b/Assets/Items/BhopBoots_Item.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e09f7b05a155ba8449a0f0d8412ebe9d +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Items/GunSplat_Item.asset b/Assets/Items/GunSplat_Item.asset new file mode 100644 index 0000000..93d3e28 --- /dev/null +++ b/Assets/Items/GunSplat_Item.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b549159f64e1b164082e2e4ae4d28ac1, type: 3} + m_Name: GunSplat_Item + m_EditorClassIdentifier: Assembly-CSharp::ItemDefinition + itemName: Gun Splat + icon: {fileID: 0} + type: 2 + weaponPrefab: {fileID: 8985654986098955209, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + description: A janky lidar-scanned sidearm. Shoots first, looks weird always. diff --git a/Assets/Items/GunSplat_Item.asset.meta b/Assets/Items/GunSplat_Item.asset.meta new file mode 100644 index 0000000..398a208 --- /dev/null +++ b/Assets/Items/GunSplat_Item.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2b5a641a42945604c9ffa88754a9f6d1 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Models/LidarScans/material_0 8.mat b/Assets/Models/LidarScans/material_0 8.mat index 5c96f19..2be6572 100644 --- a/Assets/Models/LidarScans/material_0 8.mat +++ b/Assets/Models/LidarScans/material_0 8.mat @@ -1,5 +1,18 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!114 &-809172725653918854 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 --- !u!21 &2100000 Material: serializedVersion: 8 @@ -30,8 +43,22 @@ Material: m_Texture: {fileID: -5390149515647955136, guid: 24bb6e32322f940e59304941b7849582, type: 3} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} m_Ints: [] m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 - _BUILTIN_AlphaClip: 0 - _BUILTIN_Blend: 0 - _BUILTIN_CullMode: 2 @@ -43,6 +70,19 @@ Material: - _BUILTIN_ZTest: 4 - _BUILTIN_ZWrite: 1 - _BUILTIN_ZWriteControl: 0 + - _Blend: 0 + - _CastShadows: 1 + - _Cull: 2 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _QueueControl: 1 + - _QueueOffset: 0 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _ZTest: 4 + - _ZWrite: 1 + - _ZWriteControl: 0 - alphaCutoff: 0 - baseColorTexture_texCoord: 0 m_Colors: diff --git a/Assets/Prefabs/Pickups.meta b/Assets/Prefabs/Pickups.meta new file mode 100644 index 0000000..82c0cda --- /dev/null +++ b/Assets/Prefabs/Pickups.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c47b29375f392f14385951a3f2e89a56 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab b/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab new file mode 100644 index 0000000..ff678b2 --- /dev/null +++ b/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab @@ -0,0 +1,161 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &976112319745431805 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4275644691617312389} + - component: {fileID: 5061255202814967808} + - component: {fileID: 1990460956270860801} + m_Layer: 0 + m_Name: Visual + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4275644691617312389 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 976112319745431805} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0.4, y: 0.2, z: 0.7} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 9221872812818225285} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &5061255202814967808 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 976112319745431805} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &1990460956270860801 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 976112319745431805} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_MaskInteraction: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!1 &5967924902892500278 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 9221872812818225285} + - component: {fileID: 8317664859568879488} + - component: {fileID: 6478899211359510473} + m_Layer: 0 + m_Name: BhopBoots_Pickup + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &9221872812818225285 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5967924902892500278} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4275644691617312389} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8317664859568879488 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5967924902892500278} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PickupItem + definition: {fileID: 11400000, guid: e09f7b05a155ba8449a0f0d8412ebe9d, type: 2} + itemName: Item + itemIcon: {fileID: 0} + spinSpeed: 140 + bobHeight: 0.2 + bobSpeed: 2.8 + pickupRadius: 2.2 + pickupKey: 101 + pickupParticlesPrefab: {fileID: 0} + pickupSound: {fileID: 0} +--- !u!114 &6478899211359510473 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5967924902892500278} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1ce88c8ee6fd8a0469215a541f72f835, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::StaminaBoostPickup + newMaxStamina: 200 diff --git a/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab.meta b/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab.meta new file mode 100644 index 0000000..54fc949 --- /dev/null +++ b/Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9ead08e094f7bc74c86e58083dfcdf75 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Weapons.meta b/Assets/Prefabs/Weapons.meta new file mode 100644 index 0000000..532f1a1 --- /dev/null +++ b/Assets/Prefabs/Weapons.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ef5d1bcacc1d9e346b6e53fbcec62ad8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Weapons/GunSplat.prefab b/Assets/Prefabs/Weapons/GunSplat.prefab new file mode 100644 index 0000000..1cd8dd4 --- /dev/null +++ b/Assets/Prefabs/Weapons/GunSplat.prefab @@ -0,0 +1,141 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &8985654986098955209 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6346070617587720957} + - component: {fileID: 8585139455581857007} + - component: {fileID: 5387598106901245531} + m_Layer: 0 + m_Name: GunSplat + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6346070617587720957 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8985654986098955209} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6241336930636794420} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &8585139455581857007 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8985654986098955209} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e348b4e172ba23d45960c0071cc09b1f, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::SimpleGun + damage: 30 + range: 120 + fireRate: 6 + maxAmmo: 24 + currentAmmo: 0 + isAutomatic: 0 + recoilKickback: 0.08 + recoilKickUp: 0.04 + recoilRecoverySpeed: 12 + enableInternalBob: 0 + bobFrequency: 10 + bobHorizontalAmplitude: 0.05 + bobVerticalAmplitude: 0.03 + sprintBobMultiplier: 1.5 + bobReturnSpeed: 6 + fpsCam: {fileID: 0} +--- !u!114 &5387598106901245531 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8985654986098955209} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59433c687536a60418803799027ddcc3, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WeaponViewmodel + positionOffset: {x: 0.13, y: -0.25, z: 0.45} + rotationOffset: {x: 0.03, y: 0, z: 0} + scale: {x: 1, y: 1, z: 1} +--- !u!1001 &8738524764220850214 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 6346070617587720957} + m_Modifications: + - target: {fileID: -8098169881513260187, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_Name + value: Visual + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 3150474306388093854, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} +--- !u!4 &6241336930636794420 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} + m_PrefabInstance: {fileID: 8738524764220850214} + m_PrefabAsset: {fileID: 0} diff --git a/Assets/Prefabs/Weapons/GunSplat.prefab.meta b/Assets/Prefabs/Weapons/GunSplat.prefab.meta new file mode 100644 index 0000000..b171bfd --- /dev/null +++ b/Assets/Prefabs/Weapons/GunSplat.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: aa9cce2c129fa1a41bf5a539b136f7a6 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/Testing Scenes/Movement.unity b/Assets/Scenes/Testing Scenes/Movement.unity index 9330936..db3c93a 100644 --- a/Assets/Scenes/Testing Scenes/Movement.unity +++ b/Assets/Scenes/Testing Scenes/Movement.unity @@ -340,9 +340,22 @@ PrefabInstance: value: 0 objectReference: {fileID: 0} m_RemovedComponents: [] - m_RemovedGameObjects: [] + m_RemovedGameObjects: + - {fileID: 2029899304289424255, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3} m_AddedGameObjects: [] - m_AddedComponents: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3} + insertIndex: -1 + addedObject: {fileID: 2039163533} + - targetCorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3} + insertIndex: -1 + addedObject: {fileID: 2039163534} + - targetCorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3} + insertIndex: -1 + addedObject: {fileID: 2039163535} + - targetCorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3} + insertIndex: -1 + addedObject: {fileID: 2039163536} m_SourcePrefab: {fileID: 100100000, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3} --- !u!1001 &197010634 PrefabInstance: @@ -6459,52 +6472,6 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 0} ---- !u!1 &487503082 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 487503084} - - component: {fileID: 487503083} - 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 &487503083 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 487503082} - 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 &487503084 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 487503082} - serializedVersion: 2 - m_LocalRotation: {x: 0.07095997, y: 0.5789249, z: -0.050682172, w: 0.8107047} - m_LocalPosition: {x: 243.82, y: -35.34, z: 91.07} - 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 &571790233 stripped GameObject: m_CorrespondingSourceObject: {fileID: -8098169881513260187, guid: 3df8bb31de452e64fa85a1265898d698, type: 3} @@ -6532,52 +6499,6 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 1050908021570271116, guid: 3df8bb31de452e64fa85a1265898d698, type: 3} ---- !u!1 &606146705 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 606146707} - - component: {fileID: 606146706} - 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 &606146706 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 606146705} - 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 &606146707 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 606146705} - serializedVersion: 2 - m_LocalRotation: {x: 0.07095997, y: 0.5789249, z: -0.050682172, w: 0.8107047} - m_LocalPosition: {x: 244.40219, y: -35.34, z: 89.64255} - 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 &624019478 stripped GameObject: m_CorrespondingSourceObject: {fileID: -8220620780705863596, guid: 9e848a421785b4212abcb7cf4ae082f3, type: 3} @@ -6732,6 +6653,33 @@ MonoBehaviour: m_ShadowLayerMask: 1 m_RenderingLayers: 1 m_ShadowRenderingLayers: 1 +--- !u!1 &715417260 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + m_PrefabInstance: {fileID: 791320992} + m_PrefabAsset: {fileID: 0} +--- !u!114 &715417264 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 715417260} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PickupItem + definition: {fileID: 11400000, guid: e09f7b05a155ba8449a0f0d8412ebe9d, type: 2} + itemName: Item + itemIcon: {fileID: 0} + spinSpeed: 120 + bobHeight: 0.18 + bobSpeed: 2.2 + pickupRadius: 2 + pickupKey: 101 + pickupParticlesPrefab: {fileID: 0} + pickupSound: {fileID: 0} --- !u!1001 &791320992 PrefabInstance: m_ObjectHideFlags: 0 @@ -6758,7 +6706,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} propertyPath: m_LocalPosition.x - value: 365.609 + value: 363.888 objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} propertyPath: m_LocalPosition.y @@ -6766,7 +6714,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} propertyPath: m_LocalPosition.z - value: 156.609 + value: 157.267 objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} propertyPath: m_LocalRotation.w @@ -6799,7 +6747,10 @@ PrefabInstance: m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] - m_AddedComponents: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: -8098169881513260187, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} + insertIndex: -1 + addedObject: {fileID: 715417264} m_SourcePrefab: {fileID: 3150474306388093854, guid: 681011e79237b4409ad76bc628bdf5b3, type: 3} --- !u!1001 &806276174 PrefabInstance: @@ -6919,6 +6870,66 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 1050908021570271116, guid: 1e096925873fc492b854ee214231ab86, type: 3} +--- !u!1001 &1007367441 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalPosition.x + value: 355.01 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalPosition.y + value: -18.848 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalPosition.z + value: 167.46 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6346070617587720957, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8985654986098955209, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + propertyPath: m_Name + value: GunSplat + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 8985654986098955209, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + insertIndex: -1 + addedObject: {fileID: 2128421432} + m_SourcePrefab: {fileID: 100100000, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} --- !u!1001 &1022368674 PrefabInstance: m_ObjectHideFlags: 0 @@ -7125,6 +7136,60 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 1050908021570271116, guid: d26387bf4a33f4f03b916907f6b50828, type: 3} +--- !u!1 &1114521747 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1114521749} + - component: {fileID: 1114521748} + m_Layer: 0 + m_Name: gunsplat + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1114521748 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1114521747} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PickupItem + definition: {fileID: 11400000, guid: 2b5a641a42945604c9ffa88754a9f6d1, type: 2} + itemName: Weapon + itemIcon: {fileID: 0} + spinSpeed: 120 + bobHeight: 0.18 + bobSpeed: 2.2 + pickupRadius: 2 + pickupKey: 101 + pickupParticlesPrefab: {fileID: 0} + pickupSound: {fileID: 0} +--- !u!4 &1114521749 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1114521747} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 355.188, y: -16.728, z: 155.372} + 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 &1121149997 stripped GameObject: m_CorrespondingSourceObject: {fileID: -8098169881513260187, guid: 3c8a5d44028bd40d3abe5e22cafcb901, type: 3} @@ -7569,11 +7634,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} propertyPath: m_LocalPosition.x - value: 304.18 + value: -186547 objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} propertyPath: m_LocalPosition.y - value: -146.63 + value: -74119 objectReference: {fileID: 0} - target: {fileID: 3447742079981357586, guid: 4f15ea45bfb5e4f11bc2bce50e39fc10, type: 3} propertyPath: m_LocalPosition.z @@ -8155,52 +8220,6 @@ PrefabInstance: insertIndex: -1 addedObject: {fileID: 277384082} m_SourcePrefab: {fileID: 3150474306388093854, guid: 4e219defa86804242a4efe4306a4b26b, type: 3} ---- !u!1 &1882479419 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1882479421} - - component: {fileID: 1882479420} - m_Layer: 0 - m_Name: GameObject (3) - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1882479420 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1882479419} - 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 &1882479421 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1882479419} - serializedVersion: 2 - m_LocalRotation: {x: 0.07095997, y: 0.5789249, z: -0.050682172, w: 0.8107047} - m_LocalPosition: {x: 243.82, y: -35.34, z: 91.07} - 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 &2026185005 PrefabInstance: m_ObjectHideFlags: 0 @@ -8277,52 +8296,115 @@ PrefabInstance: insertIndex: -1 addedObject: {fileID: 990201665} m_SourcePrefab: {fileID: 3150474306388093854, guid: 1e096925873fc492b854ee214231ab86, type: 3} ---- !u!1 &2073272903 +--- !u!1 &2039163524 stripped GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} + m_CorrespondingSourceObject: {fileID: 6882903375964528458, guid: 33de4b621d70a49aab5df775f2b826ef, type: 3} + m_PrefabInstance: {fileID: 110786869} m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 2073272905} - - component: {fileID: 2073272904} - m_Layer: 0 - m_Name: GameObject - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &2073272904 +--- !u!114 &2039163533 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2073272903} + m_GameObject: {fileID: 2039163524} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 180c6606991a4b64f812f9713907fd0c, type: 3} + m_Script: {fileID: 11500000, guid: 514ba78e0d4cf8b4787471c2db0c6976, type: 3} m_Name: - m_EditorClassIdentifier: Assembly-CSharp::EnemySpawner - enemyType: 1 - healthOverride: 0 ---- !u!4 &2073272905 -Transform: + m_EditorClassIdentifier: Assembly-CSharp::Inventory + items: [] + toggleKey: 105 + equipKey: 102 + columns: 4 + maxSlots: 20 + colBackground: {r: 0.05, g: 0.05, b: 0.05, a: 0.92} + colPanel: {r: 0.1, g: 0.1, b: 0.1, a: 0.95} + colSlotEmpty: {r: 0.18, g: 0.18, b: 0.18, a: 1} + colSlotFilled: {r: 0.22, g: 0.28, b: 0.22, a: 1} + colSlotHover: {r: 0.35, g: 0.45, b: 0.35, a: 1} + colSlotSelected: {r: 0.45, g: 0.75, b: 0.45, a: 1} + colSlotEquipped: {r: 0.6, g: 0.4, b: 0.1, a: 1} + colBorder: {r: 0.4, g: 0.65, b: 0.4, a: 1} + colText: {r: 0.85, g: 0.95, b: 0.85, a: 1} + colDim: {r: 0.5, g: 0.6, b: 0.5, a: 1} + colAccent: {r: 0.5, g: 0.9, b: 0.5, a: 1} + colWeaponAccent: {r: 0.95, g: 0.75, b: 0.2, a: 1} + colCtxBg: {r: 0.08, g: 0.1, b: 0.08, a: 0.98} + colCtxBorder: {r: 0.4, g: 0.65, b: 0.4, a: 1} + colCtxHover: {r: 0.2, g: 0.35, b: 0.2, a: 1} + colCtxText: {r: 0.85, g: 0.95, b: 0.85, a: 1} + colCtxDestructive: {r: 0.85, g: 0.25, b: 0.2, a: 1} +--- !u!114 &2039163534 +MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2073272903} - serializedVersion: 2 - m_LocalRotation: {x: 0.07095997, y: 0.5789249, z: -0.050682172, w: 0.8107047} - m_LocalPosition: {x: 243.82, y: -35.34, z: 91.07} - 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} + m_GameObject: {fileID: 2039163524} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 18715d388d5da7544b10c5d7a385cbee, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WeaponManager + weaponHolder: {fileID: 0} + weaponPositionOffset: {x: 0.2, y: -0.25, z: 0.45} + weaponRotationOffset: {x: 0, y: 0, z: 0} + weaponScale: {x: 1, y: 1, z: 1} + allowScrollSwitch: 1 + allowNumberKeys: 1 +--- !u!114 &2039163535 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2039163524} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1d84d3e6c1cf6794da6ff84203edb291, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PlayerHUD + barWidth: 220 + barHeight: 18 + barSpacing: 10 + edgePadX: 18 + edgePadY: 18 + anchor: 0 + colHealthFull: {r: 0.15, g: 0.8, b: 0.25, a: 1} + colHealthMid: {r: 0.9, g: 0.75, b: 0.1, a: 1} + colHealthLow: {r: 0.9, g: 0.15, b: 0.1, a: 1} + healthMidThreshold: 0.5 + healthLowThreshold: 0.25 + colStaminaNormal: {r: 0.2, g: 0.55, b: 0.95, a: 1} + colStaminaRegen: {r: 0.2, g: 0.55, b: 0.95, a: 0.45} + colStaminaExhaust: {r: 0.6, g: 0.18, b: 0.18, a: 1} + colBarBackground: {r: 0.06, g: 0.06, b: 0.06, a: 0.9} + colBarBorder: {r: 0.28, g: 0.28, b: 0.28, a: 1} + colLabel: {r: 0.8, g: 0.8, b: 0.8, a: 1} + colLabelCritical: {r: 1, g: 0.25, b: 0.25, a: 1} + borderThickness: 1.5 + pulseSpeed: 4 + showSpeedometer: 1 + colSpeedo: {r: 0.2, g: 0.95, b: 0.4, a: 1} + colSpeedoFast: {r: 0.95, g: 0.8, b: 0.1, a: 1} + colSpeedoCritical: {r: 0.95, g: 0.2, b: 0.2, a: 1} + speedoFastThreshold: 60 + speedoCritThreshold: 75 +--- !u!114 &2039163536 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2039163524} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5680222822977cd4a988c93d7b8881bc, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::BootsEffect + bootsItemName: Bunny Hop Boots + boostedMaxStamina: 200 --- !u!1001 &2100225160 PrefabInstance: m_ObjectHideFlags: 0 @@ -8445,6 +8527,33 @@ MeshCollider: m_Convex: 0 m_CookingOptions: 30 m_Mesh: {fileID: 0} +--- !u!1 &2128421431 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 8985654986098955209, guid: aa9cce2c129fa1a41bf5a539b136f7a6, type: 3} + m_PrefabInstance: {fileID: 1007367441} + m_PrefabAsset: {fileID: 0} +--- !u!114 &2128421432 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2128421431} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 95709fa8fd6c75045b1ef8e4f505c152, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::PickupItem + definition: {fileID: 11400000, guid: 2b5a641a42945604c9ffa88754a9f6d1, type: 2} + itemName: Item + itemIcon: {fileID: 0} + spinSpeed: 120 + bobHeight: 0.18 + bobSpeed: 2.2 + pickupRadius: 2 + pickupKey: 101 + pickupParticlesPrefab: {fileID: 0} + pickupSound: {fileID: 0} --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -8454,10 +8563,6 @@ SceneRoots: - {fileID: 1719075356} - {fileID: 1410308539} - {fileID: 343869061} - - {fileID: 2073272905} - - {fileID: 487503084} - - {fileID: 606146707} - - {fileID: 1882479421} - {fileID: 1360218911} - {fileID: 271705868} - {fileID: 1022368674} @@ -8478,3 +8583,5 @@ SceneRoots: - {fileID: 1716730849} - {fileID: 2026185005} - {fileID: 65142585} + - {fileID: 1114521749} + - {fileID: 1007367441} diff --git a/Assets/Scripts/BootsEffect.cs b/Assets/Scripts/BootsEffect.cs new file mode 100644 index 0000000..f90b13e --- /dev/null +++ b/Assets/Scripts/BootsEffect.cs @@ -0,0 +1,46 @@ +using UnityEngine; + +/// +/// Attach to the Player. Watches the inventory for the Bunny Hop Boots being +/// equipped/unequipped and adjusts max stamina accordingly. +/// +public class BootsEffect : MonoBehaviour +{ + [Tooltip("Must match the ItemDefinition itemName exactly.")] + public string bootsItemName = "Bunny Hop Boots"; + public float boostedMaxStamina = 200f; + + private Inventory _inventory; + private Player _player; + private bool _boosted = false; + + void Start() + { + _inventory = GetComponent(); + _player = GetComponent(); + } + + void Update() + { + if (_inventory == null || _player == null) return; + + // Find the boots entry in inventory + var entry = _inventory.items.Find(e => e.DisplayName == bootsItemName); + bool shouldBoost = entry != null && entry.isEquipped; + + if (shouldBoost && !_boosted) + { + _player.maxStamina = boostedMaxStamina; + _player.stamina = Mathf.Min(_player.stamina + (boostedMaxStamina - 100f), boostedMaxStamina); + _boosted = true; + Debug.Log("[BootsEffect] Boots equipped — stamina boosted to " + boostedMaxStamina); + } + else if (!shouldBoost && _boosted) + { + _player.maxStamina = 100f; + _player.stamina = Mathf.Min(_player.stamina, 100f); + _boosted = false; + Debug.Log("[BootsEffect] Boots unequipped — stamina restored to 100"); + } + } +} diff --git a/Assets/Scripts/BootsEffect.cs.meta b/Assets/Scripts/BootsEffect.cs.meta new file mode 100644 index 0000000..c0df0e9 --- /dev/null +++ b/Assets/Scripts/BootsEffect.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5680222822977cd4a988c93d7b8881bc \ No newline at end of file diff --git a/Assets/Scripts/Editor.meta b/Assets/Scripts/Editor.meta new file mode 100644 index 0000000..3f89efa --- /dev/null +++ b/Assets/Scripts/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 384e9957bd5199549870be11d0838d80 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/BhopBootsSetup.cs b/Assets/Scripts/Editor/BhopBootsSetup.cs new file mode 100644 index 0000000..9b2f89a --- /dev/null +++ b/Assets/Scripts/Editor/BhopBootsSetup.cs @@ -0,0 +1,100 @@ +using UnityEngine; +using UnityEditor; +using System.IO; + +/// +/// OGG → Setup → Create Bunny Hop Boots +/// Creates the ItemDefinition and a world pickup for the bhop boots. +/// +public static class BhopBootsSetup +{ + private const string ITEM_DEF_PATH = "Assets/Items/BhopBoots_Item.asset"; + private const string PICKUP_PREFAB = "Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab"; + + [MenuItem("OGG/Setup/Create Bunny Hop Boots")] + public static void Create() + { + EnsureFolder("Assets/Items"); + EnsureFolder("Assets/Prefabs/Pickups"); + + // ── ItemDefinition ──────────────────────────────────────────── + ItemDefinition item = AssetDatabase.LoadAssetAtPath(ITEM_DEF_PATH); + bool isNew = item == null; + if (isNew) item = ScriptableObject.CreateInstance(); + + item.itemName = "Bunny Hop Boots"; + item.type = ItemDefinition.ItemType.Misc; + item.isEquippable = true; + item.description = "Illegal footwear. Gives 200 stamina while worn. Do not wear near cliffs."; + + if (isNew) + AssetDatabase.CreateAsset(item, ITEM_DEF_PATH); + else + EditorUtility.SetDirty(item); + + AssetDatabase.SaveAssets(); + + // ── Pickup prefab — procedural boot shape ───────────────────── + GameObject root = new GameObject("BhopBoots_Pickup"); + + // Simple visual: a squashed cube (boot-ish) + GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube); + body.name = "Visual"; + body.transform.SetParent(root.transform, false); + body.transform.localPosition = new Vector3(0f, 0f, 0f); + body.transform.localScale = new Vector3(0.4f, 0.2f, 0.7f); + Object.DestroyImmediate(body.GetComponent()); + + // Bright accent colour so it stands out + var rend = body.GetComponent(); + if (rend != null) + { + rend.material = new Material(Shader.Find("Universal Render Pipeline/Lit")); + rend.material.color = new Color(0.1f, 0.9f, 0.4f); + } + + PickupItem pickup = root.AddComponent(); + pickup.definition = item; + pickup.spinSpeed = 140f; + pickup.bobHeight = 0.2f; + pickup.bobSpeed = 2.8f; + pickup.pickupRadius = 2.2f; + pickup.pickupKey = KeyCode.E; + + StaminaBoostPickup boost = root.AddComponent(); + boost.newMaxStamina = 200f; + + if (File.Exists(Application.dataPath + "/../" + PICKUP_PREFAB)) + AssetDatabase.DeleteAsset(PICKUP_PREFAB); + + GameObject saved = PrefabUtility.SaveAsPrefabAsset(root, PICKUP_PREFAB); + Object.DestroyImmediate(root); + + AssetDatabase.Refresh(); + + EditorUtility.DisplayDialog( + "Bunny Hop Boots ✓", + $"Created:\n• {ITEM_DEF_PATH}\n• {PICKUP_PREFAB}\n\n" + + "Drag BhopBoots_Pickup into the scene.\n" + + "Pick it up with [E] to unlock bunny hopping.\n\n" + + "Hold SPACE while landing to chain hops and build speed.\n" + + "Strafe left/right mid-air to steer.", + "Let's go"); + + EditorGUIUtility.PingObject(saved); + Selection.activeObject = saved; + } + + static void EnsureFolder(string path) + { + string[] parts = path.Split('/'); + string current = parts[0]; + for (int i = 1; i < parts.Length; i++) + { + string next = current + "/" + parts[i]; + if (!AssetDatabase.IsValidFolder(next)) + AssetDatabase.CreateFolder(current, parts[i]); + current = next; + } + } +} diff --git a/Assets/Scripts/Editor/BhopBootsSetup.cs.meta b/Assets/Scripts/Editor/BhopBootsSetup.cs.meta new file mode 100644 index 0000000..bd5124d --- /dev/null +++ b/Assets/Scripts/Editor/BhopBootsSetup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 92ff81f98e809934bab9c5b8f160276a \ No newline at end of file diff --git a/Assets/Scripts/Editor/GunSplatSetup.cs b/Assets/Scripts/Editor/GunSplatSetup.cs new file mode 100644 index 0000000..9e0eb9c --- /dev/null +++ b/Assets/Scripts/Editor/GunSplatSetup.cs @@ -0,0 +1,151 @@ +using UnityEngine; +using UnityEditor; +using System.IO; + +/// +/// OGG → Setup → Create Gun Splat Weapon +/// Builds: +/// • Assets/Prefabs/Weapons/GunSplat.prefab — the held weapon (SimpleGun) +/// • Assets/Prefabs/Pickups/GunSplat_Pickup.prefab — world pickup (spins/bobs, press E) +/// • Assets/Items/GunSplat_Item.asset — ItemDefinition (type = Weapon) +/// +public static class GunSplatSetup +{ + private const string GLB_PATH = "Assets/Models/LidarScans/Gun Splat.glb"; + private const string WEAPON_PREFAB = "Assets/Prefabs/Weapons/GunSplat.prefab"; + private const string PICKUP_PREFAB = "Assets/Prefabs/Pickups/GunSplat_Pickup.prefab"; + private const string ITEM_DEF_PATH = "Assets/Items/GunSplat_Item.asset"; + + [MenuItem("OGG/Setup/Create Gun Splat Weapon")] + public static void CreateGunSplat() + { + // ── 1. Ensure output folders exist ────────────────────────── + EnsureFolder("Assets/Prefabs/Weapons"); + EnsureFolder("Assets/Prefabs/Pickups"); + EnsureFolder("Assets/Items"); + + // ── 2. Load the GLB model ──────────────────────────────────── + GameObject modelAsset = AssetDatabase.LoadAssetAtPath(GLB_PATH); + if (modelAsset == null) + { + Debug.LogError($"[GunSplatSetup] Could not find GLB at: {GLB_PATH}"); + EditorUtility.DisplayDialog("Gun Splat Setup", $"Could not find model at:\n{GLB_PATH}", "OK"); + return; + } + + // ── 3. Build the held-weapon prefab ────────────────────────── + GameObject weaponRoot = new GameObject("GunSplat"); + AttachVisual(weaponRoot, modelAsset); + + SimpleGun gun = weaponRoot.AddComponent(); + gun.damage = 30f; + gun.range = 120f; + gun.fireRate = 6f; + gun.maxAmmo = 24; + gun.isAutomatic = false; + + if (File.Exists(DataRelative(WEAPON_PREFAB))) + AssetDatabase.DeleteAsset(WEAPON_PREFAB); + + GameObject savedWeapon = PrefabUtility.SaveAsPrefabAsset(weaponRoot, WEAPON_PREFAB); + Object.DestroyImmediate(weaponRoot); + + if (savedWeapon == null) + { + Debug.LogError("[GunSplatSetup] Failed to save weapon prefab."); + return; + } + Debug.Log($"[GunSplatSetup] Weapon prefab → {WEAPON_PREFAB}"); + + // ── 4. Create / update ItemDefinition ──────────────────────── + ItemDefinition item = AssetDatabase.LoadAssetAtPath(ITEM_DEF_PATH); + bool isNew = item == null; + if (isNew) item = ScriptableObject.CreateInstance(); + + item.itemName = "Gun Splat"; + item.type = ItemDefinition.ItemType.Weapon; + item.weaponPrefab = savedWeapon; + item.description = "A janky lidar-scanned sidearm. Shoots first, looks weird always."; + + if (isNew) + AssetDatabase.CreateAsset(item, ITEM_DEF_PATH); + else + EditorUtility.SetDirty(item); + + AssetDatabase.SaveAssets(); + Debug.Log($"[GunSplatSetup] ItemDefinition → {ITEM_DEF_PATH}"); + + // ── 5. Build the world pickup prefab ───────────────────────── + GameObject pickupRoot = new GameObject("GunSplat_Pickup"); + + // Visual child — the GLB model + AttachVisual(pickupRoot, modelAsset); + + // PickupItem component + PickupItem pickup = pickupRoot.AddComponent(); + pickup.definition = item; + pickup.spinSpeed = 90f; + pickup.bobHeight = 0.15f; + pickup.bobSpeed = 2.0f; + pickup.pickupRadius = 2.2f; + pickup.pickupKey = KeyCode.E; + + if (File.Exists(DataRelative(PICKUP_PREFAB))) + AssetDatabase.DeleteAsset(PICKUP_PREFAB); + + GameObject savedPickup = PrefabUtility.SaveAsPrefabAsset(pickupRoot, PICKUP_PREFAB); + Object.DestroyImmediate(pickupRoot); + + if (savedPickup == null) + { + Debug.LogError("[GunSplatSetup] Failed to save pickup prefab."); + return; + } + Debug.Log($"[GunSplatSetup] Pickup prefab → {PICKUP_PREFAB}"); + + AssetDatabase.Refresh(); + + // ── 6. Done ────────────────────────────────────────────────── + EditorUtility.DisplayDialog( + "Gun Splat Setup ✓", + "Created:\n" + + $"• {WEAPON_PREFAB}\n" + + $"• {PICKUP_PREFAB}\n" + + $"• {ITEM_DEF_PATH}\n\n" + + "Drag GunSplat_Pickup into the scene wherever you want it to spawn.\n" + + "It will spin, bob, and show an [E] prompt when the player gets close.", + "Sick"); + + EditorGUIUtility.PingObject(savedPickup); + Selection.activeObject = savedPickup; + } + + // ─── Helpers ───────────────────────────────────────────────────── + + static void AttachVisual(GameObject parent, GameObject modelAsset) + { + GameObject visual = (GameObject)PrefabUtility.InstantiatePrefab(modelAsset); + if (visual == null) visual = Object.Instantiate(modelAsset); + visual.name = "Visual"; + visual.transform.SetParent(parent.transform, false); + visual.transform.localPosition = Vector3.zero; + visual.transform.localRotation = Quaternion.identity; + visual.transform.localScale = Vector3.one; + } + + static string DataRelative(string assetPath) => + Application.dataPath + "/../" + assetPath; + + static void EnsureFolder(string path) + { + string[] parts = path.Split('/'); + string current = parts[0]; + for (int i = 1; i < parts.Length; i++) + { + string next = current + "/" + parts[i]; + if (!AssetDatabase.IsValidFolder(next)) + AssetDatabase.CreateFolder(current, parts[i]); + current = next; + } + } +} diff --git a/Assets/Scripts/Editor/GunSplatSetup.cs.meta b/Assets/Scripts/Editor/GunSplatSetup.cs.meta new file mode 100644 index 0000000..5c0aac5 --- /dev/null +++ b/Assets/Scripts/Editor/GunSplatSetup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a0ceea0a64e3fa344b83004be51fe457 \ No newline at end of file diff --git a/Assets/Scripts/Editor/WeaponManagerEditor.cs b/Assets/Scripts/Editor/WeaponManagerEditor.cs new file mode 100644 index 0000000..09bc96f --- /dev/null +++ b/Assets/Scripts/Editor/WeaponManagerEditor.cs @@ -0,0 +1,92 @@ +using UnityEngine; +using UnityEditor; + +[CustomEditor(typeof(WeaponManager))] +public class WeaponManagerEditor : Editor +{ + public override void OnInspectorGUI() + { + DrawDefaultInspector(); + + WeaponManager wm = (WeaponManager)target; + + if (!Application.isPlaying) return; + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("── Live Positioning ──", EditorStyles.boldLabel); + + // Show what's active + string activeName = wm.ActiveWeaponName; + if (string.IsNullOrEmpty(activeName)) + { + EditorGUILayout.HelpBox("No weapon currently equipped.", MessageType.Info); + return; + } + + EditorGUILayout.HelpBox($"Active: {activeName}", MessageType.None); + + // Select the live GO so you can drag gizmos in Scene view + if (GUILayout.Button("🎯 Select Active Weapon in Scene")) + { + var slot = wm.slots.Find(s => s.itemName == activeName); + if (slot != null) + { + Selection.activeGameObject = slot.instance; + SceneView.lastActiveSceneView?.Focus(); + } + } + + EditorGUILayout.Space(); + + // Sync the current transform back into the WeaponViewmodel (or fallback global offset) + if (GUILayout.Button("⬆ Sync Transform → Offset Fields")) + { + var slot = wm.slots.Find(s => s.itemName == activeName); + if (slot != null) + { + var vm = slot.instance.GetComponent(); + if (vm != null) + { + Undo.RecordObject(vm, "Sync Weapon Viewmodel"); + vm.SyncFromTransform(); + EditorUtility.SetDirty(vm); + + // Also save back to the source prefab asset + var prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(slot.instance); + if (prefabAsset != null) + { + var prefabVm = prefabAsset.GetComponent(); + if (prefabVm != null) + { + Undo.RecordObject(prefabVm, "Sync Weapon Viewmodel Prefab"); + prefabVm.positionOffset = vm.positionOffset; + prefabVm.rotationOffset = vm.rotationOffset; + prefabVm.scale = vm.scale; + EditorUtility.SetDirty(prefabVm); + AssetDatabase.SaveAssets(); + } + } + Debug.Log($"[WeaponManager] Synced to WeaponViewmodel: pos={vm.positionOffset} rot={vm.rotationOffset} scale={vm.scale}"); + } + else + { + // Fallback: no viewmodel, write to global offsets + Undo.RecordObject(wm, "Sync Weapon Offset"); + wm.weaponPositionOffset = slot.instance.transform.localPosition; + wm.weaponRotationOffset = slot.instance.transform.localRotation.eulerAngles; + wm.weaponScale = slot.instance.transform.localScale; + EditorUtility.SetDirty(wm); + Debug.Log($"[WeaponManager] Synced to global offset: pos={wm.weaponPositionOffset}"); + } + } + } + + EditorGUILayout.HelpBox( + "1. Hit Play & equip the gun\n" + + "2. Click 'Select Active Weapon'\n" + + "3. Use Move/Rotate gizmos in Scene view\n" + + "4. Click 'Sync Transform → Offset Fields'\n" + + "5. Stop Play — values are saved", + MessageType.Info); + } +} diff --git a/Assets/Scripts/Editor/WeaponManagerEditor.cs.meta b/Assets/Scripts/Editor/WeaponManagerEditor.cs.meta new file mode 100644 index 0000000..cd3d70d --- /dev/null +++ b/Assets/Scripts/Editor/WeaponManagerEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fe67de8a32d4bf5449ee8def34a1bb8a \ No newline at end of file diff --git a/Assets/Scripts/FirstPersonController.cs b/Assets/Scripts/FirstPersonController.cs index d6a0f32..c71b5d4 100644 --- a/Assets/Scripts/FirstPersonController.cs +++ b/Assets/Scripts/FirstPersonController.cs @@ -4,33 +4,31 @@ using UnityEngine; public class FirstPersonController : MonoBehaviour { [Header("Movement Settings")] - public float walkSpeed = 50f; // Boosted from 8f to overcome collision issues - public float runSpeed = 80f; // Boosted from 14f + public float walkSpeed = 50f; + public float runSpeed = 80f; public float jumpHeight = 2.5f; - public float gravity = -20f; + public float gravity = -20f; [Header("Mouse Look Settings")] public float mouseSensitivity = 3f; - public float maxLookAngle = 90f; + public float maxLookAngle = 90f; [Header("References")] public Camera playerCamera; // Private variables private CharacterController controller; - private Vector3 velocity; - private bool isGrounded; - private float xRotation = 0f; + private Inventory inventory; + private Player player; + private Vector3 velocity; + private bool isGrounded; + private float xRotation = 0f; void Start() { - Debug.Log("Starting game"); - - // FORCE NORMAL TIME (in case something external changed it) Time.timeScale = 1f; controller = GetComponent(); - if (controller == null) { Debug.LogError("FirstPersonController: No CharacterController found!"); @@ -40,85 +38,58 @@ public class FirstPersonController : MonoBehaviour if (playerCamera == null) playerCamera = GetComponentInChildren(); + inventory = GetComponent(); + player = GetComponent(); + Cursor.lockState = CursorLockMode.Locked; - Cursor.visible = false; + Cursor.visible = false; } void Update() { - if( controller == null ) - return; + if (controller == null) return; isGrounded = controller.isGrounded; 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}"); - } + float moveX = 0f, 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; Vector3 move = transform.right * moveX + transform.forward * moveZ; + if (move.magnitude > 1f) move.Normalize(); - if (move.magnitude > 1f) - move.Normalize(); + bool wantSprint = Input.GetKey(KeyCode.LeftShift) && move.magnitude > 0f; + bool isSprinting = wantSprint && (player == null || player.CanSprint()); + if (player != null) player.isSprinting = isSprinting; - float currentSpeed = Input.GetKey(KeyCode.LeftShift) ? runSpeed : walkSpeed; - - Vector3 posBefore = transform.position; + float currentSpeed = isSprinting ? runSpeed : walkSpeed; controller.Move(move * currentSpeed * Time.deltaTime); - // DEBUG: Log if we tried to move but didn't - if (move.magnitude > 0f && Input.GetKeyDown(KeyCode.W)) - { - 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)}"); - } - } - - // 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); + controller.Move(new Vector3(0f, velocity.y, 0f) * Time.deltaTime); - // Mouse look - HandleMouseLook(); + bool inventoryOpen = inventory != null && inventory.IsOpen; + if (!inventoryOpen) + HandleMouseLook(); - // Escape to unlock cursor if (Input.GetKeyDown(KeyCode.Escape)) { Cursor.lockState = CursorLockMode.None; - Cursor.visible = true; + Cursor.visible = true; } - // Click to re-lock cursor - if (Input.GetMouseButtonDown(0) && Cursor.lockState == CursorLockMode.None) + if (Input.GetMouseButtonDown(0) && Cursor.lockState == CursorLockMode.None && !inventoryOpen) { Cursor.lockState = CursorLockMode.Locked; - Cursor.visible = false; + Cursor.visible = false; } } @@ -128,9 +99,8 @@ public class FirstPersonController : MonoBehaviour float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity; xRotation -= mouseY; - xRotation = Mathf.Clamp(xRotation, -maxLookAngle, maxLookAngle); + xRotation = Mathf.Clamp(xRotation, -maxLookAngle, maxLookAngle); playerCamera.transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f); - transform.Rotate(Vector3.up * mouseX); } } diff --git a/Assets/Scripts/Game/Input/InputData.cs b/Assets/Scripts/Game/Input/InputData.cs index 5492156..99d727b 100644 --- a/Assets/Scripts/Game/Input/InputData.cs +++ b/Assets/Scripts/Game/Input/InputData.cs @@ -45,7 +45,7 @@ public static class InputData new MouseButton( Defines.Input.kLeftMouseButton), new KeyboardButton( KeyCode.LeftControl ), } ) ); - reload = AddButton( new KeyboardButton( KeyCode.Space ) ); + reload = AddButton( new KeyboardButton( KeyCode.R ) ); jump = AddButton( new KeyboardButton( KeyCode.Space ) ); horizontalMovement = AddAxis( new MultiAxis( diff --git a/Assets/Scripts/HumanoidEnemy.cs b/Assets/Scripts/HumanoidEnemy.cs index 759c383..b3db006 100644 --- a/Assets/Scripts/HumanoidEnemy.cs +++ b/Assets/Scripts/HumanoidEnemy.cs @@ -187,8 +187,13 @@ public class HumanoidEnemy : MonoBehaviour // Punch animation StartCoroutine(PunchAnimation()); - // TODO: Hook into player health system when you have one - // player.GetComponent()?.TakeDamage(attackDamage); + // Damage the player via Player.health + Player playerHealth = player.GetComponent(); + if (playerHealth != null) + { + playerHealth.health -= attackDamage; + Debug.Log($"[Enemy] Player health: {playerHealth.health}"); + } } } diff --git a/Assets/Scripts/Inventory.cs b/Assets/Scripts/Inventory.cs new file mode 100644 index 0000000..dafbef9 --- /dev/null +++ b/Assets/Scripts/Inventory.cs @@ -0,0 +1,632 @@ +using System.Collections.Generic; +using UnityEngine; + +/// +/// Inventory system with a toggleable HUD (press I) and right-click context menu. +/// Attach to the Player GameObject alongside WeaponManager. +/// +public class Inventory : MonoBehaviour +{ + // ─── Item data ──────────────────────────────────────────────────── + [System.Serializable] + public class InventoryEntry + { + public ItemDefinition definition; + public string itemName; + public Sprite icon; + public int count; + public bool isEquipped; + + public InventoryEntry(string name, Sprite icon) + { + this.itemName = name; + this.icon = icon; + this.count = 1; + this.isEquipped = false; + } + + public InventoryEntry(ItemDefinition def) + { + this.definition = def; + this.itemName = def.itemName; + this.icon = def.icon; + this.count = 1; + this.isEquipped = false; + } + + public bool _isWeaponSlot; + + public bool IsWeapon => + (definition != null && definition.type == ItemDefinition.ItemType.Weapon) + || _isWeaponSlot; + + // True for weapons AND equippable misc items + public bool IsEquippable => + IsWeapon || (definition != null && definition.isEquippable); + + public string DisplayName => definition != null ? definition.itemName : itemName; + } + + public List items = new List(); + + // ─── HUD config ─────────────────────────────────────────────────── + [Header("HUD")] + public KeyCode toggleKey = KeyCode.I; + public KeyCode equipKey = KeyCode.F; + public int columns = 4; + public int maxSlots = 20; + + [Header("HUD Colours")] + public Color colBackground = new Color(0.05f, 0.05f, 0.05f, 0.92f); + public Color colPanel = new Color(0.10f, 0.10f, 0.10f, 0.95f); + public Color colSlotEmpty = new Color(0.18f, 0.18f, 0.18f, 1.00f); + public Color colSlotFilled = new Color(0.22f, 0.28f, 0.22f, 1.00f); + public Color colSlotHover = new Color(0.35f, 0.45f, 0.35f, 1.00f); + public Color colSlotSelected = new Color(0.45f, 0.75f, 0.45f, 1.00f); + public Color colSlotEquipped = new Color(0.60f, 0.40f, 0.10f, 1.00f); + public Color colBorder = new Color(0.40f, 0.65f, 0.40f, 1.00f); + public Color colText = new Color(0.85f, 0.95f, 0.85f, 1.00f); + public Color colDim = new Color(0.50f, 0.60f, 0.50f, 1.00f); + public Color colAccent = new Color(0.50f, 0.90f, 0.50f, 1.00f); + public Color colWeaponAccent = new Color(0.95f, 0.75f, 0.20f, 1.00f); + + [Header("Context Menu Colours")] + public Color colCtxBg = new Color(0.08f, 0.10f, 0.08f, 0.98f); + public Color colCtxBorder = new Color(0.40f, 0.65f, 0.40f, 1.00f); + public Color colCtxHover = new Color(0.20f, 0.35f, 0.20f, 1.00f); + public Color colCtxText = new Color(0.85f, 0.95f, 0.85f, 1.00f); + public Color colCtxDestructive = new Color(0.85f, 0.25f, 0.20f, 1.00f); + + // ─── Private state ──────────────────────────────────────────────── + private bool _open = false; + private int _selectedIndex = 0; + private int _hoveredIndex = -1; + private WeaponManager _weaponManager; + + // ─── Context menu state ─────────────────────────────────────────── + private bool _ctxOpen = false; + private int _ctxItemIndex = -1; + private Vector2 _ctxPos; + + private const float kCtxItemH = 28f; + private const float kCtxWidth = 140f; + private const float kCtxPadX = 10f; + + // Context menu action labels — built per item + private struct CtxAction + { + public string label; + public bool isDestructive; + public System.Action callback; + } + private List _ctxActions = new List(); + + // ─── Layout constants ───────────────────────────────────────────── + private const float kSlotSize = 80f; + private const float kSlotPad = 8f; + private const float kPanelPad = 20f; + private const float kHeaderH = 40f; + private const float kDetailPanelH = 120f; + + // ───────────────────────────────────────────────────────────────── + void Start() + { + _weaponManager = GetComponent(); + if (_weaponManager != null) + Invoke(nameof(SyncStartingWeapons), 0.1f); + } + + void SyncStartingWeapons() + { + if (_weaponManager == null) return; + foreach (var slot in _weaponManager.slots) + { + if (slot.definition != null) + AddItem(slot.definition, autoEquip: false); + else + AddItemRaw(slot.itemName, null, isWeaponSlot: true); + } + SyncEquippedState(); + } + + // ─── Public API ─────────────────────────────────────────────────── + public void AddItem(ItemDefinition def, bool autoEquip = false) + { + if (def == null) return; + InventoryEntry existing = items.Find(e => e.definition == def); + if (existing != null) + { + if (def.type != ItemDefinition.ItemType.Weapon) existing.count++; + Debug.Log($"[Inventory] Already have: {def.itemName}"); + } + else + { + var entry = new InventoryEntry(def); + items.Add(entry); + Debug.Log($"[Inventory] Picked up: {def.itemName}"); + if (def.type == ItemDefinition.ItemType.Weapon) + { + _weaponManager?.AddWeapon(def); + if (autoEquip) EquipItem(items.Count - 1); + } + } + } + + public void AddItem(string itemName, Sprite icon = null) => AddItemRaw(itemName, icon, false); + + private void AddItemRaw(string itemName, Sprite icon, bool isWeaponSlot) + { + InventoryEntry existing = items.Find(e => e.itemName == itemName && e.definition == null); + if (existing != null) { existing.count++; return; } + var entry = new InventoryEntry(itemName, icon); + entry._isWeaponSlot = isWeaponSlot; + items.Add(entry); + } + + public bool RemoveItem(string itemName, int amount = 1) + { + InventoryEntry entry = items.Find(e => e.DisplayName == itemName); + if (entry == null) return false; + entry.count -= amount; + if (entry.count <= 0) items.Remove(entry); + return true; + } + + public bool HasItem(string itemName) => items.Exists(e => e.DisplayName == itemName); + public int CountOf(string itemName) + { + var e = items.Find(x => x.DisplayName == itemName); + return e != null ? e.count : 0; + } + + public bool IsOpen => _open; + + // ─── Equip / Unequip ────────────────────────────────────────────── + void EquipItem(int index) + { + if (index < 0 || index >= items.Count) return; + InventoryEntry entry = items[index]; + if (!entry.IsEquippable) return; + + if (entry.IsWeapon) + { + // Unequip other weapons + foreach (var e in items) { if (e.IsWeapon) e.isEquipped = false; } + entry.isEquipped = true; + _weaponManager?.EquipByName(entry.DisplayName); + } + else + { + // Toggle equip for misc equippables (boots, etc.) + entry.isEquipped = true; + } + Debug.Log($"[Inventory] Equipped: {entry.DisplayName}"); + } + + void UnequipItem(int index) + { + if (index < 0 || index >= items.Count) return; + InventoryEntry entry = items[index]; + if (!entry.IsEquippable || !entry.isEquipped) return; + entry.isEquipped = false; + + if (entry.IsWeapon && _weaponManager != null) + { + var slot = _weaponManager.slots.Find(s => s.itemName == entry.DisplayName); + if (slot != null) slot.instance.SetActive(false); + _weaponManager.Unequip(); + } + Debug.Log($"[Inventory] Unequipped: {entry.DisplayName}"); + } + + void DropItem(int index) + { + if (index < 0 || index >= items.Count) return; + InventoryEntry entry = items[index]; + if (entry.isEquipped) UnequipItem(index); + items.RemoveAt(index); + if (_selectedIndex >= items.Count) _selectedIndex = items.Count - 1; + Debug.Log($"[Inventory] Dropped: {entry.DisplayName}"); + // TODO: optionally spawn the pickup back in the world here + } + + void SyncEquippedState() + { + if (_weaponManager == null) return; + string active = _weaponManager.ActiveWeaponName; + foreach (var e in items) + { + // Only sync weapon equipped state — leave misc equippables alone + if (e.IsWeapon) + e.isEquipped = (e.DisplayName == active); + } + } + + // ─── Context menu builder ───────────────────────────────────────── + void OpenContextMenu(int itemIndex, Vector2 screenPos) + { + if (itemIndex < 0 || itemIndex >= items.Count) return; + + _ctxItemIndex = itemIndex; + _ctxActions.Clear(); + + InventoryEntry entry = items[itemIndex]; + + if (entry.IsEquippable) + { + if (entry.isEquipped) + { + int captured = itemIndex; + _ctxActions.Add(new CtxAction + { + label = "Unequip", + isDestructive = false, + callback = () => UnequipItem(captured) + }); + } + else + { + int captured = itemIndex; + _ctxActions.Add(new CtxAction + { + label = "Equip", + isDestructive = false, + callback = () => EquipItem(captured) + }); + } + } + + // Consumables / misc could have a "Use" action here in future + // For now everyone gets Inspect (just selects and focuses the detail panel) + { + int captured = itemIndex; + _ctxActions.Add(new CtxAction + { + label = "Inspect", + isDestructive = false, + callback = () => _selectedIndex = captured + }); + } + + { + int captured = itemIndex; + _ctxActions.Add(new CtxAction + { + label = "Drop", + isDestructive = true, + callback = () => DropItem(captured) + }); + } + + // Clamp so the menu doesn't go off screen + float menuH = _ctxActions.Count * kCtxItemH + 8f; + float clampedX = Mathf.Min(screenPos.x, Screen.width - kCtxWidth - 4f); + float clampedY = Mathf.Min(screenPos.y, Screen.height - menuH - 4f); + _ctxPos = new Vector2(clampedX, clampedY); + _ctxOpen = true; + } + + void CloseContextMenu() { _ctxOpen = false; _ctxItemIndex = -1; } + + // ─── Toggle ─────────────────────────────────────────────────────── + void Update() + { + if (Input.GetKeyDown(toggleKey)) + { + CloseContextMenu(); + SetOpen(!_open); + } + + if (_open && Input.GetKeyDown(equipKey)) + { + if (_selectedIndex < items.Count && items[_selectedIndex].IsWeapon) + EquipItem(_selectedIndex); + CloseContextMenu(); + } + + if (!_open) + SyncEquippedState(); + } + + void SetOpen(bool open) + { + _open = open; + SyncEquippedState(); + Cursor.lockState = open ? CursorLockMode.None : CursorLockMode.Locked; + Cursor.visible = open; + } + + // ─── GUI ────────────────────────────────────────────────────────── + void OnGUI() + { + if (!_open) return; + + int rows = Mathf.CeilToInt((float)maxSlots / columns); + float gridW = columns * (kSlotSize + kSlotPad) - kSlotPad; + float gridH = rows * (kSlotSize + kSlotPad) - kSlotPad; + float panelW = gridW + kPanelPad * 2f; + float panelH = kHeaderH + kPanelPad + gridH + kPanelPad + kDetailPanelH + kPanelPad; + float panelX = (Screen.width - panelW) * 0.5f; + float panelY = (Screen.height - panelH) * 0.5f; + + Vector2 mouse = Event.current.mousePosition; + + // ── Click outside context menu to close it ──────────────────── + if (_ctxOpen && Event.current.type == EventType.MouseDown) + { + Rect ctxRect = GetCtxRect(); + if (!ctxRect.Contains(mouse)) + CloseContextMenu(); + } + + // ── Backdrop ────────────────────────────────────────────────── + DrawRect(new Rect(0, 0, Screen.width, Screen.height), new Color(0, 0, 0, 0.55f)); + + // ── Main panel ──────────────────────────────────────────────── + DrawBorderedBox(new Rect(panelX, panelY, panelW, panelH), colPanel, colBorder, 2f); + + GUIStyle headerStyle = MakeStyle(18, FontStyle.Bold, colAccent, TextAnchor.MiddleCenter); + GUI.Label(new Rect(panelX, panelY + 8f, panelW, 28f), "[ INVENTORY ]", headerStyle); + DrawRect(new Rect(panelX + 10f, panelY + kHeaderH - 2f, panelW - 20f, 1f), colBorder); + + if (_weaponManager != null && _weaponManager.ActiveWeaponName != "") + { + GUIStyle activeStyle = MakeStyle(11, FontStyle.Normal, colWeaponAccent, TextAnchor.MiddleRight); + GUI.Label(new Rect(panelX, panelY + 10f, panelW - 10f, 20f), + $"EQUIPPED: {_weaponManager.ActiveWeaponName}", activeStyle); + } + + float gx = panelX + kPanelPad; + float gy = panelY + kHeaderH + kPanelPad; + + _hoveredIndex = -1; + + // ── Slot grid ───────────────────────────────────────────────── + for (int i = 0; i < maxSlots; i++) + { + int col = i % columns; + int row = i / columns; + float sx = gx + col * (kSlotSize + kSlotPad); + float sy = gy + row * (kSlotSize + kSlotPad); + Rect slotRect = new Rect(sx, sy, kSlotSize, kSlotSize); + + bool hasItem = i < items.Count; + bool isHover = slotRect.Contains(mouse) && !_ctxOpen; + bool isSel = (i == _selectedIndex); + bool isEquipped = hasItem && items[i].isEquipped; + bool isWeapon = hasItem && items[i].IsWeapon; + + if (isHover) _hoveredIndex = i; + + // Left-click: select + if (isHover && Event.current.type == EventType.MouseDown + && Event.current.button == 0 && hasItem) + { + _selectedIndex = i; + CloseContextMenu(); + } + + // Left double-click: equip weapon + if (isHover && Event.current.type == EventType.MouseDown + && Event.current.button == 0 && Event.current.clickCount == 2 && isWeapon) + EquipItem(i); + + // Right-click: open context menu + if (isHover && Event.current.type == EventType.MouseDown + && Event.current.button == 1 && hasItem) + { + _selectedIndex = i; + OpenContextMenu(i, mouse); + } + + // Slot colour + Color slotCol; + if (isEquipped) slotCol = colSlotEquipped; + else if (isSel) slotCol = colSlotSelected; + else if (isHover) slotCol = colSlotHover; + else if (hasItem) slotCol = colSlotFilled; + else slotCol = colSlotEmpty; + + Color borderCol = isEquipped ? colWeaponAccent : (isSel ? colAccent : colBorder); + DrawBorderedBox(slotRect, slotCol, borderCol, (isSel || isEquipped) ? 2f : 1f); + + if (!hasItem) continue; + + InventoryEntry entry = items[i]; + + // Icon or letter placeholder + if (entry.icon != null) + { + float ip = 10f; + GUI.DrawTexture(new Rect(sx + ip, sy + ip, kSlotSize - ip * 2f, kSlotSize - 28f), + entry.icon.texture, ScaleMode.ScaleToFit, true); + } + else + { + Color lc = isEquipped ? colWeaponAccent : (isSel ? Color.white : colAccent); + GUI.Label(new Rect(sx, sy + 8f, kSlotSize, kSlotSize - 24f), + entry.DisplayName.Substring(0, 1).ToUpper(), MakeStyle(26, FontStyle.Bold, lc, TextAnchor.MiddleCenter)); + } + + // Weapon tag + if (isWeapon) + { + Color tc = isEquipped ? colWeaponAccent : new Color(0.6f, 0.5f, 0.2f, 1f); + GUI.Label(new Rect(sx + 3f, sy + 2f, 40f, 12f), + isEquipped ? "EQUIP" : "WPN", MakeStyle(8, FontStyle.Bold, tc, TextAnchor.UpperLeft)); + } + + // Right-click hint on hovered slot + if (isHover) + { + GUIStyle hint = MakeStyle(8, FontStyle.Normal, new Color(0.6f, 0.6f, 0.6f, 0.9f), TextAnchor.LowerRight); + GUI.Label(new Rect(sx + 2f, sy + 2f, kSlotSize - 4f, kSlotSize - 4f), "RMB ▾", hint); + } + + // Item name + string dn = entry.DisplayName.Length > 10 ? entry.DisplayName.Substring(0, 9) + "…" : entry.DisplayName; + Color nc = isEquipped ? colWeaponAccent : (isSel ? Color.white : colText); + GUI.Label(new Rect(sx + 2f, sy + kSlotSize - 28f, kSlotSize - 4f, 16f), + dn, MakeStyle(9, FontStyle.Bold, nc, TextAnchor.LowerCenter)); + + // Count badge + if (entry.count > 1) + { + DrawRect(new Rect(sx + kSlotSize - 22f, sy + kSlotSize - 18f, 20f, 16f), new Color(0, 0, 0, 0.7f)); + GUI.Label(new Rect(sx + kSlotSize - 22f, sy + kSlotSize - 18f, 19f, 16f), + $"x{entry.count}", MakeStyle(10, FontStyle.Bold, Color.white, TextAnchor.LowerRight)); + } + } + + // ── Detail panel ────────────────────────────────────────────── + float detailY = gy + gridH + kPanelPad; + Rect detailRect = new Rect(panelX + kPanelPad, detailY, panelW - kPanelPad * 2f, kDetailPanelH - kPanelPad); + DrawBorderedBox(detailRect, new Color(0.08f, 0.08f, 0.08f, 1f), colBorder, 1f); + + if (_selectedIndex < items.Count) + { + InventoryEntry sel = items[_selectedIndex]; + float dy = detailRect.y + 8f; + float dx = detailRect.x + 10f; + float dw = detailRect.width - 20f; + + GUI.Label(new Rect(dx, dy, dw, 16f), "SELECTED", MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.UpperLeft)); + GUI.Label(new Rect(dx, dy + 16f, dw, 22f), sel.DisplayName, MakeStyle(14, FontStyle.Bold, colText, TextAnchor.UpperLeft)); + + string desc = (sel.definition != null && sel.definition.description != "") + ? sel.definition.description : (sel.IsWeapon ? "Weapon" : "Item"); + GUI.Label(new Rect(dx, dy + 38f, dw * 0.65f, 30f), desc, MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.UpperLeft)); + + if (sel.IsEquippable) + { + Color wc = sel.isEquipped ? colWeaponAccent : colDim; + string tag = sel.isEquipped ? "▶ EQUIPPED" : "right-click or [F] to equip"; + GUI.Label(new Rect(dx, dy + 68f, dw, 18f), tag, MakeStyle(11, FontStyle.Bold, wc, TextAnchor.UpperLeft)); + } + else if (sel.count > 1) + { + GUI.Label(new Rect(dx, dy + 68f, dw, 18f), $"Quantity: {sel.count}", + MakeStyle(11, FontStyle.Bold, colAccent, TextAnchor.UpperLeft)); + } + + // Equip / Unequip button bottom-right of detail panel + if (sel.IsEquippable) + { + Rect btnRect = new Rect(detailRect.x + detailRect.width - 110f, + detailRect.y + detailRect.height - 34f, 100f, 26f); + if (sel.isEquipped) + { + DrawBorderedBox(btnRect, new Color(0.4f, 0.15f, 0.05f, 1f), colWeaponAccent, 1f); + GUI.Label(btnRect, "[ F ] UNEQUIP", MakeStyle(11, FontStyle.Bold, Color.white, TextAnchor.MiddleCenter)); + if (GUI.Button(btnRect, "", GUIStyle.none)) UnequipItem(_selectedIndex); + } + else + { + DrawBorderedBox(btnRect, colSlotEquipped, colWeaponAccent, 1f); + GUI.Label(btnRect, "[ F ] EQUIP", MakeStyle(11, FontStyle.Bold, Color.white, TextAnchor.MiddleCenter)); + if (GUI.Button(btnRect, "", GUIStyle.none)) EquipItem(_selectedIndex); + } + } + } + else + { + GUI.Label(detailRect, "No item selected", MakeStyle(11, FontStyle.Normal, colDim, TextAnchor.MiddleCenter)); + } + + // Hints + GUIStyle hintStyle = MakeStyle(10, FontStyle.Normal, colDim, TextAnchor.LowerRight); + GUI.Label(new Rect(panelX, panelY + panelH - 18f, panelW - 8f, 16f), "[ I ] close | RMB slot for options", hintStyle); + + // ── Context menu (drawn last so it's on top) ────────────────── + if (_ctxOpen) + DrawContextMenu(mouse); + } + + // ─── Context menu draw ──────────────────────────────────────────── + Rect GetCtxRect() + { + float menuH = _ctxActions.Count * kCtxItemH + 8f; + return new Rect(_ctxPos.x, _ctxPos.y, kCtxWidth, menuH); + } + + void DrawContextMenu(Vector2 mouse) + { + float menuH = _ctxActions.Count * kCtxItemH + 8f; + Rect bgRect = new Rect(_ctxPos.x, _ctxPos.y, kCtxWidth, menuH); + + // Shadow + DrawRect(new Rect(bgRect.x + 3f, bgRect.y + 3f, bgRect.width, bgRect.height), + new Color(0f, 0f, 0f, 0.45f)); + + // Background + border + DrawBorderedBox(bgRect, colCtxBg, colCtxBorder, 1.5f); + + // Header strip — item name + if (_ctxItemIndex >= 0 && _ctxItemIndex < items.Count) + { + string title = items[_ctxItemIndex].DisplayName; + DrawRect(new Rect(bgRect.x + 1.5f, bgRect.y + 1.5f, bgRect.width - 3f, 20f), + new Color(0.15f, 0.22f, 0.15f, 1f)); + GUI.Label(new Rect(bgRect.x + kCtxPadX, bgRect.y + 3f, kCtxWidth - kCtxPadX * 2f, 16f), + title.ToUpper(), MakeStyle(9, FontStyle.Bold, colWeaponAccent, TextAnchor.MiddleLeft)); + } + + // Action rows + for (int i = 0; i < _ctxActions.Count; i++) + { + float rowY = bgRect.y + 22f + i * kCtxItemH; + Rect rowRect = new Rect(bgRect.x + 1.5f, rowY, bgRect.width - 3f, kCtxItemH); + bool hover = rowRect.Contains(mouse); + + if (hover) + DrawRect(rowRect, colCtxHover); + + Color textColor = _ctxActions[i].isDestructive + ? (hover ? Color.white : colCtxDestructive) + : (hover ? Color.white : colCtxText); + + GUI.Label(new Rect(bgRect.x + kCtxPadX, rowY + 2f, kCtxWidth - kCtxPadX * 2f, kCtxItemH - 4f), + _ctxActions[i].label, MakeStyle(12, FontStyle.Normal, textColor, TextAnchor.MiddleLeft)); + + // Separator line between items + if (i < _ctxActions.Count - 1) + DrawRect(new Rect(bgRect.x + 6f, rowY + kCtxItemH - 1f, bgRect.width - 12f, 1f), + new Color(1f, 1f, 1f, 0.06f)); + + // Click to fire action + if (hover && Event.current.type == EventType.MouseDown && Event.current.button == 0) + { + _ctxActions[i].callback?.Invoke(); + CloseContextMenu(); + Event.current.Use(); + } + } + } + + // ─── Style / Draw helpers ───────────────────────────────────────── + static GUIStyle MakeStyle(int size, FontStyle style, Color color, TextAnchor align) + { + var s = new GUIStyle(); + s.fontSize = size; + s.fontStyle = style; + s.normal.textColor = color; + s.alignment = align; + return s; + } + + static void DrawRect(Rect r, Color c) + { + Color prev = GUI.color; + GUI.color = c; + GUI.DrawTexture(r, Texture2D.whiteTexture); + GUI.color = prev; + } + + static void DrawBorderedBox(Rect r, Color fill, Color border, float thickness) + { + DrawRect(r, border); + DrawRect(new Rect(r.x + thickness, r.y + thickness, + r.width - thickness * 2f, + r.height - thickness * 2f), fill); + } +} diff --git a/Assets/Scripts/Inventory.cs.meta b/Assets/Scripts/Inventory.cs.meta new file mode 100644 index 0000000..57093dd --- /dev/null +++ b/Assets/Scripts/Inventory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 514ba78e0d4cf8b4787471c2db0c6976 \ No newline at end of file diff --git a/Assets/Scripts/ItemDefinition.cs b/Assets/Scripts/ItemDefinition.cs new file mode 100644 index 0000000..4c44ebc --- /dev/null +++ b/Assets/Scripts/ItemDefinition.cs @@ -0,0 +1,28 @@ +using UnityEngine; + +/// +/// Create via right-click → Create → OGG → Item Definition +/// Drag onto PickupItem components in the scene. +/// +[CreateAssetMenu(fileName = "New Item", menuName = "OGG/Item Definition")] +public class ItemDefinition : ScriptableObject +{ + public enum ItemType { Misc, Consumable, Weapon } + + [Header("Identity")] + public string itemName = "Unnamed Item"; + public Sprite icon; + public ItemType type = ItemType.Misc; + + [Header("Weapon (only if type == Weapon)")] + [Tooltip("The prefab that gets spawned in the weapon holder when this is equipped.")] + public GameObject weaponPrefab; + + [Header("Equipment")] + [Tooltip("If true, this item can be equipped/unequipped from the inventory even if it isn't a weapon.")] + public bool isEquippable = false; + + [Header("Flavour")] + [TextArea(2, 4)] + public string description = ""; +} diff --git a/Assets/Scripts/ItemDefinition.cs.meta b/Assets/Scripts/ItemDefinition.cs.meta new file mode 100644 index 0000000..d06fa61 --- /dev/null +++ b/Assets/Scripts/ItemDefinition.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b549159f64e1b164082e2e4ae4d28ac1 \ No newline at end of file diff --git a/Assets/Scripts/PickupItem.cs b/Assets/Scripts/PickupItem.cs new file mode 100644 index 0000000..a07305a --- /dev/null +++ b/Assets/Scripts/PickupItem.cs @@ -0,0 +1,156 @@ +using UnityEngine; + +/// +/// Attach to any pickup object. Spins + bobs like MGS rations. +/// Assign an ItemDefinition asset for full weapon/equip support, +/// or just fill itemName for a simple non-weapon pickup. +/// +public class PickupItem : MonoBehaviour +{ + [Header("Item")] + [Tooltip("Preferred: drag an ItemDefinition ScriptableObject here.")] + public ItemDefinition definition; + + [Tooltip("Fallback name if no ItemDefinition is set.")] + public string itemName = "Item"; + public Sprite itemIcon; + + [Header("Spin & Bob")] + public float spinSpeed = 120f; + public float bobHeight = 0.18f; + public float bobSpeed = 2.2f; + + [Header("Pickup")] + public float pickupRadius = 2.0f; + public KeyCode pickupKey = KeyCode.E; + + [Header("FX")] + public GameObject pickupParticlesPrefab; + public AudioClip pickupSound; + + // ───────────────────────────────────────── + private Vector3 _startPos; + private Transform _playerTransform; + private Inventory _inventory; + private AudioSource _audioSource; + private bool _collected = false; + + // Prompt display + private bool _inRange = false; + + void Start() + { + _startPos = transform.position; + + Player player = FindObjectOfType(); + if (player != null) + { + _playerTransform = player.transform; + _inventory = player.GetComponent(); + } + + _audioSource = gameObject.AddComponent(); + _audioSource.spatialBlend = 1f; + _audioSource.playOnAwake = false; + + // If we have a definition, use its name/icon + if (definition != null) + { + itemName = definition.itemName; + itemIcon = definition.icon; + } + } + + void Update() + { + if (_collected) return; + + // ── Spin ────────────────────────────── + transform.Rotate(Vector3.up, spinSpeed * Time.deltaTime, Space.World); + + // ── Bob ─────────────────────────────── + float newY = _startPos.y + Mathf.Sin(Time.time * bobSpeed * Mathf.PI * 2f) * bobHeight; + transform.position = new Vector3(transform.position.x, newY, transform.position.z); + + // ── Pickup check ───────────────────── + if (_playerTransform == null) return; + + float dist = Vector3.Distance(transform.position, _playerTransform.position); + _inRange = dist <= pickupRadius; + + if (_inRange && Input.GetKeyDown(pickupKey)) + Collect(); + } + + void OnGUI() + { + if (_collected || !_inRange) return; + + // World-to-screen prompt + Vector3 screenPos = Camera.main != null + ? Camera.main.WorldToScreenPoint(_startPos + Vector3.up * (bobHeight + 0.4f)) + : Vector3.zero; + + if (screenPos.z < 0) return; // behind camera + + float sx = screenPos.x; + float sy = Screen.height - screenPos.y; // flip y + + string label = definition != null ? definition.itemName : itemName; + bool isWeapon = definition != null && definition.type == ItemDefinition.ItemType.Weapon; + string prompt = $"[{pickupKey}] Pick up {label}"; + + GUIStyle bg = new GUIStyle(GUI.skin.box); + bg.fontSize = 12; + bg.fontStyle = FontStyle.Bold; + bg.normal.textColor = isWeapon ? new Color(1f, 0.85f, 0.2f) : Color.white; + + Vector2 size = bg.CalcSize(new GUIContent(prompt)); + Rect bgRect = new Rect(sx - size.x * 0.5f - 6f, sy - size.y - 4f, size.x + 12f, size.y + 6f); + + // Dark backing box + Color prev = GUI.color; + GUI.color = new Color(0, 0, 0, 0.65f); + GUI.DrawTexture(bgRect, Texture2D.whiteTexture); + GUI.color = prev; + + GUI.Label(bgRect, " " + prompt + " ", bg); + } + + void OnDrawGizmosSelected() + { + Gizmos.color = Color.yellow; + Gizmos.DrawWireSphere(transform.position, pickupRadius); + } + + void Collect() + { + _collected = true; + _inRange = false; + + if (_inventory != null) + { + if (definition != null) + _inventory.AddItem(definition, autoEquip: true); + else + _inventory.AddItem(itemName, itemIcon); + } + else + { + Debug.LogWarning("PickupItem: No Inventory found on player!"); + } + + if (pickupParticlesPrefab != null) + Instantiate(pickupParticlesPrefab, transform.position, Quaternion.identity); + + if (pickupSound != null) + { + _audioSource.PlayOneShot(pickupSound); + Destroy(gameObject, pickupSound.length); + } + else + { + Destroy(gameObject); + } + } +} diff --git a/Assets/Scripts/PickupItem.cs.meta b/Assets/Scripts/PickupItem.cs.meta new file mode 100644 index 0000000..b907c42 --- /dev/null +++ b/Assets/Scripts/PickupItem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 95709fa8fd6c75045b1ef8e4f505c152 \ No newline at end of file diff --git a/Assets/Scripts/Player.cs b/Assets/Scripts/Player.cs index c16e024..6e6954b 100644 --- a/Assets/Scripts/Player.cs +++ b/Assets/Scripts/Player.cs @@ -1,6 +1,65 @@ using UnityEngine; +/// +/// Core player stats: health and stamina. +/// Stamina drains while sprinting, regens when not. +/// If stamina hits zero the player is exhausted and can't sprint again until it fully refills. +/// public class Player : MonoBehaviour { - public float health; + [Header("Health")] + public float maxHealth = 100f; + public float health = 100f; + + [Header("Stamina")] + public float maxStamina = 100f; + public float stamina = 100f; + public float staminaDrain = 22f; // per second while sprinting + public float staminaRegen = 12f; // per second while not sprinting + public float regenDelay = 1.2f; // seconds after stopping sprint before regen kicks in + + // ─── State ─────────────────────────────────────────────────────── + [HideInInspector] public bool isSprinting = false; + [HideInInspector] public bool isExhausted = false; // true until stamina fully refills + + private float _regenTimer = 0f; + + // ───────────────────────────────────────────────────────────────── + void Update() + { + if (isSprinting && !isExhausted) + { + stamina -= staminaDrain * Time.deltaTime; + _regenTimer = 0f; + + if (stamina <= 0f) + { + stamina = 0f; + isExhausted = true; + } + } + else + { + // Count down regen delay + _regenTimer += Time.deltaTime; + + if (_regenTimer >= regenDelay) + { + stamina = Mathf.MoveTowards(stamina, maxStamina, staminaRegen * Time.deltaTime); + + // Clear exhaustion only once fully refilled + if (isExhausted && stamina >= maxStamina) + isExhausted = false; + } + } + + health = Mathf.Clamp(health, 0f, maxHealth); + stamina = Mathf.Clamp(stamina, 0f, maxStamina); + } + + // ─── Helpers other scripts can call ────────────────────────────── + public bool CanSprint() => !isExhausted && stamina > 0f; + + public float HealthFraction => health / maxHealth; + public float StaminaFraction => stamina / maxStamina; } diff --git a/Assets/Scripts/PlayerHUD.cs b/Assets/Scripts/PlayerHUD.cs new file mode 100644 index 0000000..c020909 --- /dev/null +++ b/Assets/Scripts/PlayerHUD.cs @@ -0,0 +1,281 @@ +using UnityEngine; + +/// +/// Draws the player health and stamina bars. +/// Attach to the Player GameObject alongside Player.cs +/// +/// Deliberately ugly. This is a Cruelty Squad game. +/// +public class PlayerHUD : MonoBehaviour +{ + [Header("Layout")] + public float barWidth = 220f; + public float barHeight = 16f; + public float barSpacing = 10f; // gap between health and stamina bars + public float edgePadX = 18f; + public float edgePadY = 18f; + public AnchorCorner anchor = AnchorCorner.BottomLeft; + + public enum AnchorCorner { BottomLeft, BottomRight, TopLeft, TopRight } + + [Header("Health Bar")] + public Color colHealthFull = new Color(0.15f, 0.80f, 0.25f, 1f); + public Color colHealthMid = new Color(0.85f, 0.75f, 0.05f, 1f); + public Color colHealthLow = new Color(0.90f, 0.12f, 0.12f, 1f); + public float healthMidThreshold = 0.5f; + public float healthLowThreshold = 0.25f; + + [Header("Stamina Bar")] + public Color colStaminaNormal = new Color(0.20f, 0.55f, 0.95f, 1f); + public Color colStaminaRegen = new Color(0.20f, 0.55f, 0.95f, 0.45f); // dimmed while regenerating + public Color colStaminaExhaust = new Color(0.60f, 0.18f, 0.18f, 1f); // red flash when tapped out + + [Header("Shared Style")] + public Color colBarBackground = new Color(0.06f, 0.06f, 0.06f, 0.90f); + public Color colBarBorder = new Color(0.28f, 0.28f, 0.28f, 1.00f); + public Color colLabel = new Color(0.80f, 0.80f, 0.80f, 1.00f); + public Color colLabelCritical = new Color(1.00f, 0.25f, 0.25f, 1.00f); + public float borderThickness = 1.5f; + + [Header("Pulse")] + [Tooltip("The low-health bar pulses opacity when below the low threshold.")] + public float pulseSpeed = 3.5f; + + [Header("Speedometer")] + public bool showSpeedometer = true; + public Color colSpeedo = new Color(0.20f, 0.95f, 0.40f, 1f); + public Color colSpeedoFast = new Color(0.95f, 0.80f, 0.10f, 1f); + public Color colSpeedoCritical = new Color(0.95f, 0.20f, 0.20f, 1f); + public float speedoFastThreshold = 60f; + public float speedoCritThreshold = 75f; + + // ─── Private ───────────────────────────────────────────────────── + private Player _player; + private CharacterController _cc; + private Texture2D _white; + + // Smooth display values so bars glide rather than snap + private float _displayHealth = 1f; + private float _displayStamina = 1f; + private const float kSmoothSpeed = 8f; + + // Speed tracking + private Vector3 _lastPos; + private float _displaySpeed; + + // ───────────────────────────────────────────────────────────────── + void Start() + { + _player = GetComponent(); + _cc = GetComponent(); + _white = Texture2D.whiteTexture; + _lastPos = transform.position; + + if (_player == null) + Debug.LogWarning("[PlayerHUD] No Player component found on this GameObject!"); + } + + void Update() + { + if (_player == null) return; + + // Smooth bar fill values towards actual values + _displayHealth = Mathf.Lerp(_displayHealth, _player.HealthFraction, Time.deltaTime * kSmoothSpeed); + _displayStamina = Mathf.Lerp(_displayStamina, _player.StaminaFraction, Time.deltaTime * kSmoothSpeed); + + // Calculate speed from position delta (horizontal only) + Vector3 pos = transform.position; + float rawSpeed = new Vector3(pos.x - _lastPos.x, 0f, pos.z - _lastPos.z).magnitude / Time.deltaTime; + _displaySpeed = Mathf.Lerp(_displaySpeed, rawSpeed, Time.deltaTime * 10f); + _lastPos = pos; + } + + void OnGUI() + { + if (_player == null) return; + + float totalH = barHeight * 2f + barSpacing + 20f; // label heights included + float totalW = barWidth; + + float ox, oy; + switch (anchor) + { + case AnchorCorner.BottomLeft: + ox = edgePadX; + oy = Screen.height - totalH - edgePadY; + break; + case AnchorCorner.BottomRight: + ox = Screen.width - totalW - edgePadX; + oy = Screen.height - totalH - edgePadY; + break; + case AnchorCorner.TopLeft: + ox = edgePadX; + oy = edgePadY; + break; + default: // TopRight + ox = Screen.width - totalW - edgePadX; + oy = edgePadY; + break; + } + + float cursor = oy; + + // ── Health bar ─────────────────────────────────────────────── + cursor = DrawStatBar( + ox, cursor, + label: "HEALTH", + value: _player.health, + maxValue: _player.maxHealth, + displayFraction: _displayHealth, + fillColor: GetHealthColor(), + pulse: _player.HealthFraction <= healthLowThreshold, + labelCritical: _player.HealthFraction <= healthLowThreshold + ); + + cursor += barSpacing; + + // ── Stamina bar ────────────────────────────────────────────── + Color staminaColor; + if (_player.isExhausted) + staminaColor = colStaminaExhaust; + else if (!_player.isSprinting && _player.StaminaFraction < 1f) + staminaColor = colStaminaRegen; + else + staminaColor = colStaminaNormal; + + string staminaLabel = _player.isExhausted ? "STAMINA [EXHAUSTED]" : "STAMINA"; + + DrawStatBar( + ox, cursor, + label: staminaLabel, + value: _player.stamina, + maxValue: _player.maxStamina, + displayFraction: _displayStamina, + fillColor: staminaColor, + pulse: _player.isExhausted, + labelCritical: _player.isExhausted + ); + + // ── Speedometer ────────────────────────────────────────────── + if (showSpeedometer && _cc != null) + DrawSpeedometer(ox, oy - 90f); + } + + void DrawSpeedometer(float x, float y) + { + float speed = _displaySpeed; + + Color col; + if (speed >= speedoCritThreshold) col = colSpeedoCritical; + else if (speed >= speedoFastThreshold) col = colSpeedoFast; + else col = colSpeedo; + + float w = 130f; + float h = 70f; + + // Dark backing box + DrawTex(new Rect(x - 6f, y - 6f, w + 12f, h + 12f), new Color(0f, 0f, 0f, 0.55f)); + // Coloured left edge accent + DrawTex(new Rect(x - 6f, y - 6f, 3f, h + 12f), col); + + // "SPEED" label + GUIStyle labelStyle = new GUIStyle(); + labelStyle.fontSize = 10; + labelStyle.fontStyle = FontStyle.Bold; + labelStyle.normal.textColor = new Color(col.r, col.g, col.b, 0.75f); + GUI.Label(new Rect(x, y, w, 16f), "SPEED", labelStyle); + + // Big number + GUIStyle numStyle = new GUIStyle(); + numStyle.fontSize = 36; + numStyle.fontStyle = FontStyle.Bold; + numStyle.normal.textColor = col; + GUI.Label(new Rect(x, y + 14f, w, 42f), $"{speed:F1}", numStyle); + + // Unit + GUIStyle unitStyle = new GUIStyle(); + unitStyle.fontSize = 10; + unitStyle.fontStyle = FontStyle.Normal; + unitStyle.normal.textColor = new Color(col.r, col.g, col.b, 0.55f); + GUI.Label(new Rect(x, y + 54f, w, 16f), "units / sec", unitStyle); + } + + // ─── Bar renderer ───────────────────────────────────────────────── + /// Draws one labelled bar. Returns the Y position after the bar. + float DrawStatBar( + float x, float y, + string label, + float value, float maxValue, + float displayFraction, + Color fillColor, + bool pulse, + bool labelCritical) + { + float labelH = 14f; + float innerW = barWidth - borderThickness * 2f; + float innerH = barHeight - borderThickness * 2f; + + // ── Label ──────────────────────────────────────────────────── + GUIStyle labelStyle = new GUIStyle(); + labelStyle.fontSize = 10; + labelStyle.fontStyle = FontStyle.Bold; + labelStyle.normal.textColor = labelCritical ? colLabelCritical : colLabel; + + string valueStr = $"{Mathf.CeilToInt(value)} / {Mathf.CeilToInt(maxValue)}"; + GUI.Label(new Rect(x, y, barWidth * 0.6f, labelH), label, labelStyle); + + GUIStyle valStyle = new GUIStyle(labelStyle); + valStyle.alignment = TextAnchor.UpperRight; + valStyle.normal.textColor = labelCritical ? colLabelCritical : colLabel; + GUI.Label(new Rect(x, y, barWidth, labelH), valueStr, valStyle); + + float barY = y + labelH + 2f; + + // ── Background ─────────────────────────────────────────────── + DrawTex(new Rect(x, barY, barWidth, barHeight), colBarBorder); + DrawTex(new Rect(x + borderThickness, barY + borderThickness, innerW, innerH), colBarBackground); + + // ── Fill ───────────────────────────────────────────────────── + float fillW = Mathf.Max(0f, displayFraction * innerW); + + // Pulse alpha on low health / exhaustion + Color drawColor = fillColor; + if (pulse) + { + float alpha = Mathf.Lerp(0.35f, 1f, (Mathf.Sin(Time.time * pulseSpeed) + 1f) * 0.5f); + drawColor = new Color(fillColor.r, fillColor.g, fillColor.b, fillColor.a * alpha); + } + + if (fillW > 0f) + DrawTex(new Rect(x + borderThickness, barY + borderThickness, fillW, innerH), drawColor); + + // ── Segment tick marks (every 25%) ─────────────────────────── + for (int i = 1; i < 4; i++) + { + float tickX = x + borderThickness + innerW * (i / 4f); + DrawTex(new Rect(tickX - 0.5f, barY + borderThickness, 1f, innerH), + new Color(0f, 0f, 0f, 0.45f)); + } + + return barY + barHeight; // bottom of bar + } + + // ─── Helpers ───────────────────────────────────────────────────── + Color GetHealthColor() + { + float f = _player.HealthFraction; + if (f <= healthLowThreshold) + return colHealthLow; + if (f <= healthMidThreshold) + return Color.Lerp(colHealthLow, colHealthMid, (f - healthLowThreshold) / (healthMidThreshold - healthLowThreshold)); + return Color.Lerp(colHealthMid, colHealthFull, (f - healthMidThreshold) / (1f - healthMidThreshold)); + } + + void DrawTex(Rect r, Color c) + { + Color prev = GUI.color; + GUI.color = c; + GUI.DrawTexture(r, _white); + GUI.color = prev; + } +} diff --git a/Assets/Scripts/PlayerHUD.cs.meta b/Assets/Scripts/PlayerHUD.cs.meta new file mode 100644 index 0000000..e00207e --- /dev/null +++ b/Assets/Scripts/PlayerHUD.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1d84d3e6c1cf6794da6ff84203edb291 \ No newline at end of file diff --git a/Assets/Scripts/SimpleGun.cs b/Assets/Scripts/SimpleGun.cs index 2a6c245..ad100e9 100644 --- a/Assets/Scripts/SimpleGun.cs +++ b/Assets/Scripts/SimpleGun.cs @@ -16,6 +16,8 @@ public class SimpleGun : MonoBehaviour public float recoilRecoverySpeed = 12f; [Header("Weapon Bob Settings")] + [Tooltip("Set false if WeaponBob component is on the Camera — they'll double-stack otherwise.")] + public bool enableInternalBob = false; public float bobFrequency = 10f; public float bobHorizontalAmplitude = 0.05f; public float bobVerticalAmplitude = 0.03f; @@ -48,6 +50,10 @@ public class SimpleGun : MonoBehaviour // Fire timing private float nextTimeToFire = 0f; + // Cached refs for inventory check + private Inventory _inventory; + private WeaponManager _weaponManager; + void Start() { currentAmmo = maxAmmo; @@ -58,7 +64,12 @@ public class SimpleGun : MonoBehaviour fpsCam = GetComponentInParent(); playerController = GetComponentInParent(); - originalLocalPos = transform.localPosition; + _inventory = GetComponentInParent(); + _weaponManager = GetComponentInParent(); + // originalLocalPos is driven by WeaponManager's offset — don't cache here + originalLocalPos = _weaponManager != null + ? _weaponManager.weaponPositionOffset + : transform.localPosition; // NUCLEAR OPTION: Remove ANY collider on this object or children @@ -93,6 +104,10 @@ public class SimpleGun : MonoBehaviour void Update() { + // Don't shoot while the inventory is open + bool inventoryOpen = _inventory != null && _inventory.IsOpen; + if (inventoryOpen) return; + // Shooting if (isAutomatic ? InputData.leftMouseButton.down : InputData.leftMouseButton.pressed) { @@ -107,11 +122,18 @@ public class SimpleGun : MonoBehaviour if (InputData.reload.pressed) Reload(); + // Keep base position in sync with WeaponManager's live offset + if (_weaponManager != null) + originalLocalPos = _weaponManager.weaponPositionOffset; + // Recover from recoil recoilOffset = Vector3.Lerp(recoilOffset, Vector3.zero, Time.deltaTime * recoilRecoverySpeed); - // Weapon bob - UpdateBob(); + // Weapon bob (only if internal bob is enabled — disable if WeaponBob is on the Camera) + if (enableInternalBob) + UpdateBob(); + else + bobOffset = Vector3.zero; // Apply combined position: original + recoil + bob transform.localPosition = originalLocalPos + recoilOffset + bobOffset; diff --git a/Assets/Scripts/StaminaBoostPickup.cs b/Assets/Scripts/StaminaBoostPickup.cs new file mode 100644 index 0000000..04ff082 --- /dev/null +++ b/Assets/Scripts/StaminaBoostPickup.cs @@ -0,0 +1,36 @@ +using UnityEngine; + +/// +/// Add to a pickup GameObject alongside PickupItem. +/// When the item is collected, applies a one-time boost to the player's max stamina. +/// +[RequireComponent(typeof(PickupItem))] +public class StaminaBoostPickup : MonoBehaviour +{ + [Tooltip("Max stamina will be set to this value when picked up.")] + public float newMaxStamina = 200f; + + void Start() + { + // Hook into PickupItem's collect event via a simple poll + var pickup = GetComponent(); + // We override by watching PickupItem's collected state each frame + StartCoroutine(WatchForCollect(pickup)); + } + + System.Collections.IEnumerator WatchForCollect(PickupItem pickup) + { + // Wait until the pickup is destroyed (collected) + while (pickup != null) + yield return null; + + // Find player and apply boost + Player p = FindObjectOfType(); + if (p != null) + { + p.maxStamina = newMaxStamina; + p.stamina = newMaxStamina; + Debug.Log($"[StaminaBoost] Max stamina set to {newMaxStamina}"); + } + } +} diff --git a/Assets/Scripts/StaminaBoostPickup.cs.meta b/Assets/Scripts/StaminaBoostPickup.cs.meta new file mode 100644 index 0000000..ae73f6f --- /dev/null +++ b/Assets/Scripts/StaminaBoostPickup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1ce88c8ee6fd8a0469215a541f72f835 \ No newline at end of file diff --git a/Assets/Scripts/WeaponManager.cs b/Assets/Scripts/WeaponManager.cs new file mode 100644 index 0000000..d5792ba --- /dev/null +++ b/Assets/Scripts/WeaponManager.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; +using UnityEngine; + +/// +/// Attach to the Player GameObject. +/// Manages all instantiated weapon instances and switching between them. +/// Also registers any weapon already in the scene (e.g. the starting gun). +/// +public class WeaponManager : MonoBehaviour +{ + [Header("References")] + [Tooltip("The transform weapons are parented to — usually the Camera or a WeaponHolder child.")] + public Transform weaponHolder; + + [Header("Weapon Position")] + [Tooltip("Local position offset applied to all equipped weapons.")] + public Vector3 weaponPositionOffset = new Vector3(0.2f, -0.25f, 0.45f); + [Tooltip("Local euler rotation offset applied to all equipped weapons.")] + public Vector3 weaponRotationOffset = new Vector3(0f, 0f, 0f); + [Tooltip("Scale applied to all equipped weapons.")] + public Vector3 weaponScale = new Vector3(1f, 1f, 1f); + + [Header("Switching")] + public bool allowScrollSwitch = true; + public bool allowNumberKeys = true; + + // ─── internal slot ─────────────────────────────────────────────── + public class WeaponSlot + { + public string itemName; + public GameObject instance; // the live GO in weaponHolder + public ItemDefinition definition; // may be null for the starting gun + } + + public List slots = new List(); + public int activeIndex { get; private set; } = -1; + + // ───────────────────────────────────────────────────────────────── + void Start() + { + // Auto-find weapon holder if not set + if (weaponHolder == null) + { + Camera cam = GetComponentInChildren(); + weaponHolder = cam != null ? cam.transform : transform; + } + + // Register any SimpleGun already present as children of the holder + SimpleGun[] existing = weaponHolder.GetComponentsInChildren(true); + foreach (SimpleGun gun in existing) + { + RegisterExistingGun(gun.gameObject); + } + } + + private Inventory _inventory; + + void Update() + { + if (slots.Count == 0) return; + + // Live-update active weapon transform so Inspector tweaks take effect immediately + if (activeIndex >= 0 && activeIndex < slots.Count) + { + var vm = slots[activeIndex].instance.GetComponent(); + if (vm != null) + vm.Apply(); + else + { + Transform t = slots[activeIndex].instance.transform; + t.localPosition = weaponPositionOffset; + t.localRotation = Quaternion.Euler(weaponRotationOffset); + t.localScale = weaponScale; + } + } + + // Don't switch weapons while the inventory UI is open + if (_inventory == null) _inventory = GetComponent(); + if (_inventory != null && _inventory.IsOpen) return; + + // Scroll wheel + if (allowScrollSwitch) + { + float scroll = Input.GetAxis("Mouse ScrollWheel"); + if (scroll > 0f) CycleWeapon(1); + if (scroll < 0f) CycleWeapon(-1); + } + + // Number keys 1–9 + if (allowNumberKeys) + { + for (int i = 0; i < Mathf.Min(slots.Count, 9); i++) + { + if (Input.GetKeyDown(KeyCode.Alpha1 + i)) + EquipByIndex(i); + } + } + } + + // ─── Public API ────────────────────────────────────────────────── + + /// Register a weapon that already exists in the scene (starting gun). + public void RegisterExistingGun(GameObject gunGO, ItemDefinition def = null) + { + string name = def != null ? def.itemName : gunGO.name; + + // Don't double-register + if (slots.Exists(s => s.instance == gunGO)) return; + + slots.Add(new WeaponSlot { itemName = name, instance = gunGO, definition = def }); + int idx = slots.Count - 1; + + // Always start inactive — player must pick up / equip from inventory + gunGO.SetActive(false); + + Debug.Log($"[WeaponManager] Registered existing weapon: {name} at slot {idx} (inactive)"); + } + + /// Add a new weapon from a definition (called by Inventory on first equip). + public int AddWeapon(ItemDefinition def) + { + if (def == null || def.weaponPrefab == null) + { + Debug.LogWarning("[WeaponManager] AddWeapon: definition or prefab is null."); + return -1; + } + + // Already registered? + int existing = slots.FindIndex(s => s.definition == def); + if (existing >= 0) return existing; + + GameObject instance = Instantiate(def.weaponPrefab, weaponHolder); + var vm = instance.GetComponent(); + if (vm != null) + vm.Apply(); + else + { + instance.transform.localPosition = weaponPositionOffset; + instance.transform.localRotation = Quaternion.Euler(weaponRotationOffset); + instance.transform.localScale = weaponScale; + } + instance.SetActive(false); + + // Strip colliders so they don't mess with the CharacterController + foreach (Collider col in instance.GetComponentsInChildren()) + if (!(col is CharacterController)) Destroy(col); + + slots.Add(new WeaponSlot { itemName = def.itemName, instance = instance, definition = def }); + int newIdx = slots.Count - 1; + Debug.Log($"[WeaponManager] Added weapon: {def.itemName} at slot {newIdx}"); + return newIdx; + } + + /// Equip weapon by slot index. + public void EquipByIndex(int index) + { + if (index < 0 || index >= slots.Count) return; + + // Deactivate current + if (activeIndex >= 0 && activeIndex < slots.Count) + slots[activeIndex].instance.SetActive(false); + + activeIndex = index; + slots[activeIndex].instance.SetActive(true); + Debug.Log($"[WeaponManager] Equipped: {slots[activeIndex].itemName}"); + } + + /// Equip weapon by item name. + public void EquipByName(string name) + { + int idx = slots.FindIndex(s => s.itemName == name); + if (idx >= 0) + EquipByIndex(idx); + else + Debug.LogWarning($"[WeaponManager] No slot found for: {name}"); + } + + /// Cycle forward (+1) or backward (-1). + public void CycleWeapon(int dir) + { + if (slots.Count == 0) return; + int next = (activeIndex + dir + slots.Count) % slots.Count; + EquipByIndex(next); + } + + /// Deactivate the current weapon without switching to another. + public void Unequip() + { + if (activeIndex >= 0 && activeIndex < slots.Count) + slots[activeIndex].instance.SetActive(false); + activeIndex = -1; + Debug.Log("[WeaponManager] Unequipped all weapons."); + } + + public string ActiveWeaponName => + (activeIndex >= 0 && activeIndex < slots.Count) ? slots[activeIndex].itemName : ""; +} diff --git a/Assets/Scripts/WeaponManager.cs.meta b/Assets/Scripts/WeaponManager.cs.meta new file mode 100644 index 0000000..bf010c4 --- /dev/null +++ b/Assets/Scripts/WeaponManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 18715d388d5da7544b10c5d7a385cbee \ No newline at end of file diff --git a/Assets/Scripts/WeaponViewmodel.cs b/Assets/Scripts/WeaponViewmodel.cs new file mode 100644 index 0000000..96ccd1e --- /dev/null +++ b/Assets/Scripts/WeaponViewmodel.cs @@ -0,0 +1,29 @@ +using UnityEngine; + +/// +/// Add to a weapon prefab to define how it sits in the player's hands. +/// WeaponManager reads these values when the weapon is equipped. +/// +public class WeaponViewmodel : MonoBehaviour +{ + [Header("View Model Transform")] + public Vector3 positionOffset = new Vector3(0.2f, -0.25f, 0.45f); + public Vector3 rotationOffset = new Vector3(0f, 0f, 0f); + public Vector3 scale = new Vector3(1f, 1f, 1f); + + // Called by WeaponManager to apply these values + public void Apply() + { + transform.localPosition = positionOffset; + transform.localRotation = Quaternion.Euler(rotationOffset); + transform.localScale = scale; + } + + // Called by WeaponManager to sync back after gizmo editing + public void SyncFromTransform() + { + positionOffset = transform.localPosition; + rotationOffset = transform.localRotation.eulerAngles; + scale = transform.localScale; + } +} diff --git a/Assets/Scripts/WeaponViewmodel.cs.meta b/Assets/Scripts/WeaponViewmodel.cs.meta new file mode 100644 index 0000000..b805958 --- /dev/null +++ b/Assets/Scripts/WeaponViewmodel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 59433c687536a60418803799027ddcc3 \ No newline at end of file diff --git a/DEV_NOTES.md b/DEV_NOTES.md new file mode 100644 index 0000000..3f26f00 --- /dev/null +++ b/DEV_NOTES.md @@ -0,0 +1,353 @@ +# OGG — Dev Notes & Systems Guide + +> Boomer shooter. Cruelty Squad vibes. Jank is a feature. +> This doc covers all current systems. + +--- + +## Scene Hierarchy + +``` +Player [CharacterController, FirstPersonController, SimpleCrosshair] + │ [Player, PlayerHUD, Inventory, WeaponManager, BootsEffect] + └── Camera [Camera, AudioListener, CameraShake, WeaponBob] +``` + +Pickups are standalone GameObjects placed anywhere in the scene — **not** children of the player. + +--- + +## Scripts Overview + +| Script | Lives On | What it does | +|---|---|---| +| `FirstPersonController` | Player | WASD move, mouse look, jump, sprint, cursor lock | +| `Player` | Player | Health + stamina stats, drain/regen logic, sprint gating | +| `PlayerHUD` | Player | Health bar, stamina bar, speedometer (IMGUI) | +| `SimpleGun` | Weapon prefabs | Shooting, ammo, recoil, muzzle flash, bullet decals | +| `CameraShake` | Camera | Singleton shake impulses, called by SimpleGun on fire | +| `WeaponBob` | Camera | Procedural weapon sway while moving | +| `WeaponViewmodel` | Weapon prefabs | Per-weapon position/rotation/scale in the player's hands | +| `SimpleCrosshair` | Player | Draws the crosshair via OnGUI | +| `EnemyHealth` | Enemy objects | Takes damage from SimpleGun raycasts, flashes red, dies | +| `EnemySpawner` | Any object | Spawns enemies at runtime | +| `HumanoidEnemy` | Enemy objects | Enemy AI / behaviour | +| `ItemDefinition` | ScriptableObject asset | Data definition for any item: name, icon, type, weapon prefab, equippable flag | +| `PickupItem` | Pickup GameObjects | Spin/bob animation, proximity prompt, E-to-collect | +| `Inventory` | Player | Item list, [I] HUD, right-click context menu, equip logic | +| `WeaponManager` | Player | Instantiates weapon prefabs, handles slot switching | +| `BootsEffect` | Player | Watches inventory for Bunny Hop Boots equipped state, applies stamina boost | +| `StaminaBoostPickup` | Pickup GameObjects | Legacy — superseded by BootsEffect. Left in for reference. | + +--- + +## 1. Player Stats — `Player` + `PlayerHUD` + +`Player.cs` is the data container for all player stats. `PlayerHUD.cs` reads from it and draws the HUD. + +**Health fields:** +- `maxHealth` / `health` — enemies subtract from `health` directly, no auto-regen + +**Stamina fields:** +- `maxStamina` / `stamina` — depletes while sprinting +- `staminaDrain` — units lost per second while sprinting (default 22) +- `staminaRegen` — units gained per second when resting (default 12) +- `regenDelay` — seconds of non-sprinting before regen starts (default 1.2) +- When stamina hits 0 the player is **exhausted** — sprint locked out until stamina fully refills + +**HUD Layout:** +``` +[bottom-left corner] +SPEED +42.3 +units / sec + +HEALTH 85 / 100 +[████████████░░░] +STAMINA 62 / 100 +[██████░░░░░░░░░] +``` + +**Speedometer:** +- Sits above the health/stamina bars, bottom-left +- Speed calculated from actual position delta per frame (horizontal only) +- Colour shifts: green → yellow → red as speed increases +- Thresholds configurable in Inspector (`speedoFastThreshold`, `speedoCritThreshold`) +- Can be toggled off via `showSpeedometer` bool + +--- + +## 2. Movement — `FirstPersonController` + +Standard FPS controller. + +**Inspector fields:** +- `walkSpeed` / `runSpeed` — intentionally high (50/80) due to CharacterController collision tuning +- `mouseSensitivity` — linear, no acceleration +- `playerCamera` — auto-found if empty, best to assign explicitly + +**Controls:** +| Key | Action | +|---|---| +| W A S D / Arrows | Move | +| Left Shift | Sprint (drains stamina) | +| Space | Jump | +| Mouse | Look | +| Escape | Unlock cursor | +| Left Click (unlocked) | Re-lock cursor | + +--- + +## 3. Shooting — `SimpleGun` + +Raycast-based hitscan. No projectiles. + +**Key fields:** +- `damage` — per shot +- `range` — raycast distance in units +- `fireRate` — shots per second +- `isAutomatic` — hold vs click +- `recoilKickback` / `recoilKickUp` — feel tuning +- `fpsCam` — auto-found if null + +**Internals:** +- Muzzle flash: child Quad + Point Light, shown ~50ms per shot +- Bullet decals: pooled (30 max), oldest reused when full +- Spark particles: spawned and self-destroyed on impact +- Calls `CameraShake.Instance.Shake()` on every shot +- Shooting blocked when inventory is open +- Position driven by `WeaponManager.weaponPositionOffset` each frame so recoil applies on top correctly + +**Ammo:** `currentAmmo` / `maxAmmo`. Press **R** to reload (instant). + +--- + +## 4. Item Definitions — `ItemDefinition` (ScriptableObject) + +**Create:** Right-click in Project → **Create → OGG → Item Definition** + +| Field | Notes | +|---|---| +| `itemName` | Display name everywhere — must be unique | +| `icon` | Sprite shown in inventory slots | +| `type` | `Misc`, `Consumable`, or `Weapon` | +| `isEquippable` | Tick for non-weapon items that can be equipped (e.g. boots) — shows equip button in inventory | +| `weaponPrefab` | Weapon type only — prefab spawned in weapon holder when equipped | +| `description` | Shown in inventory detail panel | + +--- + +## 5. Pickups — `PickupItem` + +Attach to any GameObject to make it a spinning, bobbing pickup. + +**Setup:** +1. Place a GameObject in the scene with any mesh +2. Add `PickupItem` component +3. Drag an `ItemDefinition` into the `Definition` slot + +**Inspector fields:** +| Field | Notes | +|---|---| +| `definition` | The ItemDefinition asset | +| `spinSpeed` | Degrees/sec Y rotation (default 120) | +| `bobHeight` | Vertical travel in units (default 0.18) | +| `bobSpeed` | Bob cycles per second (default 2.2) | +| `pickupRadius` | Proximity trigger distance | +| `pickupKey` | Default E | +| `pickupParticlesPrefab` | Optional — spawned on collect | +| `pickupSound` | Optional — played on collect | + +**On pickup:** Calls `Inventory.AddItem(definition, autoEquip: true)` — picking up a weapon automatically equips it. + +--- + +## 6. Inventory — `Inventory` + +Lives on the Player. Press **I** to open/close. Cursor unlocks when open. + +**HUD Layout:** +``` +┌──────────────────────────────┐ +│ [ INVENTORY ] │ ← header, shows active weapon top-right +├──────────────────────────────┤ +│ [ ] [ ] [ ] [ ] │ +│ [ ] [ ] [ ] [ ] │ ← item grid (4×5 by default) +├──────────────────────────────┤ +│ SELECTED: Item Name │ ← detail panel +│ Description │ +│ ▶ EQUIPPED [ F ] UNEQUIP│ ← shown for weapons AND equippable items +└──────────────────────────────┘ +``` + +**Right-click context menu** on any slot: +- **Equip** / **Unequip** — shown for weapons and items with `isEquippable = true` +- **Inspect** — selects the item in the detail panel +- **Drop** — removes from inventory + +**Controls:** +| Input | Action | +|---|---| +| I | Toggle inventory open/close | +| Click slot | Select item | +| Double-click weapon slot | Equip it | +| Right-click slot | Open context menu | +| F | Equip selected item | +| Scroll Wheel (closed) | Cycle weapons | +| 1–9 (closed) | Equip weapon by slot number | + +**Slot colours:** +- Dark grey = empty +- Dark green = has item +- Bright green = selected +- Orange = equipped + +**Useful methods:** +```csharp +inventory.HasItem("Medkit") // bool +inventory.CountOf("Grenade") // int +inventory.RemoveItem("Medkit", 1) // bool +inventory.AddItem(definition) // add by ItemDefinition +inventory.AddItem("Raw Name", sprite) // add by name +inventory.IsOpen // bool — checked by gun + WeaponManager +``` + +--- + +## 7. Weapon Manager — `WeaponManager` + +Lives on the Player. Manages weapon instances and which is active. + +**Setup:** +- Add `WeaponManager` to Player +- Set `Weapon Holder` to the Camera transform +- Any `SimpleGun` child of the holder is auto-registered but starts **inactive** — player must pick up and equip from inventory + +**Weapon Position fields:** +- `weaponPositionOffset` / `weaponRotationOffset` / `weaponScale` — global fallback if the weapon has no `WeaponViewmodel` component +- Per-weapon positioning is preferred — add `WeaponViewmodel` to the weapon prefab instead + +**Live positioning workflow (in Editor):** +1. Hit Play, equip the gun +2. On WeaponManager Inspector → click **"Select Active Weapon in Scene"** +3. Use gizmos in Scene view to position it +4. Click **"Sync Transform → Offset Fields"** — saves to the `WeaponViewmodel` on the prefab automatically +5. Stop Play — values persist + +**Useful methods:** +```csharp +weaponManager.EquipByIndex(0) +weaponManager.EquipByName("Gun Splat") +weaponManager.CycleWeapon(1) // next +weaponManager.CycleWeapon(-1) // previous +weaponManager.ActiveWeaponName // string +weaponManager.Unequip() // holster all +``` + +--- + +## 8. WeaponViewmodel — `WeaponViewmodel` + +Add to a **weapon prefab** to define how it sits in the player's hands. WeaponManager reads from it on equip instead of using the global offset. + +**Fields:** +- `positionOffset` — local position relative to weapon holder +- `rotationOffset` — euler angles +- `scale` — local scale + +Each weapon prefab gets its own values, so Gun Splat can sit differently from any future weapon. + +--- + +## 9. Equippable Items — `BootsEffect` + +**Add `BootsEffect` to the Player** for the Bunny Hop Boots to work. + +Lives on the Player. Watches `Inventory.items` each frame for the boots' `isEquipped` state and adjusts `Player.maxStamina` accordingly. + +| State | Effect | +|---|---| +| Boots equipped | `maxStamina` → 200, stamina topped up | +| Boots unequipped | `maxStamina` → 100, stamina clamped back down | + +The stamina bar stretches/shrinks live to reflect the new max. + +**Inspector fields:** +- `bootsItemName` — must match the ItemDefinition `itemName` exactly (default `"Bunny Hop Boots"`) +- `boostedMaxStamina` — default 200 + +--- + +## Setup Scripts (Editor Menu: OGG → Setup) + +### Create Gun Splat Weapon +Creates: +- `Assets/Prefabs/Weapons/GunSplat.prefab` — held weapon with `SimpleGun` +- `Assets/Prefabs/Pickups/GunSplat_Pickup.prefab` — world pickup with spin/bob +- `Assets/Items/GunSplat_Item.asset` — `ItemDefinition` (type = Weapon) + +### Create Bunny Hop Boots +Creates: +- `Assets/Items/BhopBoots_Item.asset` — `ItemDefinition` (type = Misc, isEquippable = true) +- `Assets/Prefabs/Pickups/BhopBoots_Pickup.prefab` — world pickup + +**After running either script:** drag the pickup prefab into your scene. + +--- + +## Adding a New Weapon — Step by Step + +1. Create an `ItemDefinition` asset — set `type = Weapon`, assign your gun prefab to `weaponPrefab` +2. Add `SimpleGun` to the gun prefab root +3. Add `WeaponViewmodel` to the gun prefab — set position/rotation/scale or dial it in with the live Editor workflow +4. Create a pickup: any mesh + `PickupItem` component + drag in the ItemDefinition +5. Place the pickup in the scene + +--- + +## Adding a New Equippable Item + +1. Create an `ItemDefinition` — set `type = Misc`, tick `isEquippable = true` +2. Write a component that lives on the Player, watches `inventory.items` for `entry.isEquipped`, and applies/removes the effect +3. Place a pickup in the scene referencing the definition + +--- + +## Adding a Consumable + +```csharp +Inventory inv = GetComponent(); +if (inv.HasItem("Medkit")) { + health += 50f; + inv.RemoveItem("Medkit"); +} +``` + +--- + +## Known Rough Edges + +- **No weapon switch animation** — instant swap. Hook into `WeaponManager.EquipByIndex()` when ready. +- **No save system** — inventory is runtime only, nothing persists between sessions. +- **Starting gun** — any `SimpleGun` that's a child of the Camera at Start gets auto-registered but starts inactive. Player needs to find it as a pickup in the scene. +- **Inventory is IMGUI** — not Canvas. Performance fine at this scale. All data logic is decoupled from drawing so a Canvas swap later would be straightforward. +- **`SimpleGun` assumes it's always active** — non-SimpleGun weapons will slot-switch fine but need their own input handling to shoot. + +--- + +## Controls Reference + +| Key | Action | +|---|---| +| W A S D | Move | +| Left Shift | Sprint | +| Space | Jump | +| Left Click | Shoot | +| Right Click | Context menu (inventory open) | +| R | Reload | +| E | Pick up nearby item | +| I | Open / close inventory | +| F | Equip selected item | +| Scroll Wheel | Cycle weapons | +| 1 – 9 | Equip weapon by slot | +| Escape | Unlock cursor | diff --git a/OGG.slnx b/OGG.slnx index 20919c7..d629b17 100644 --- a/OGG.slnx +++ b/OGG.slnx @@ -1,3 +1,4 @@  +