From 02265e75caad42ae4eeed996c9abb2de51f28614 Mon Sep 17 00:00:00 2001 From: yvmou Date: Sun, 20 Sep 2026 17:09:58 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8=E6=96=B0?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/DefaultPrefabObjects.asset | 1 + Assets/_Game/Input.meta | 8 + .../_Game/Input/PlayerControls.inputactions | 158 +++++++++++++++ .../Input/PlayerControls.inputactions.meta | 14 ++ Assets/_Game/Prefabs/ACube.prefab | 189 ++++++++++++++++++ Assets/_Game/Prefabs/ACube.prefab.meta | 7 + Assets/_Game/Prefabs/Player.prefab | 50 ++++- Assets/_Game/Scenes/MainScene.unity | 70 +------ Assets/_Game/Scripts/MouseLook.cs | 45 +++-- Assets/_Game/Scripts/PlayerController.cs | 91 +++++---- Assets/_Game/Scripts/PlayerCubeCreator.cs | 54 +++++ .../_Game/Scripts/PlayerCubeCreator.cs.meta | 2 + Assets/_Game/Scripts/PlayerInputNames.cs | 70 +++++++ Assets/_Game/Scripts/PlayerInputNames.cs.meta | 2 + 14 files changed, 644 insertions(+), 117 deletions(-) create mode 100644 Assets/_Game/Input.meta create mode 100644 Assets/_Game/Input/PlayerControls.inputactions create mode 100644 Assets/_Game/Input/PlayerControls.inputactions.meta create mode 100644 Assets/_Game/Prefabs/ACube.prefab create mode 100644 Assets/_Game/Prefabs/ACube.prefab.meta create mode 100644 Assets/_Game/Scripts/PlayerCubeCreator.cs create mode 100644 Assets/_Game/Scripts/PlayerCubeCreator.cs.meta create mode 100644 Assets/_Game/Scripts/PlayerInputNames.cs create mode 100644 Assets/_Game/Scripts/PlayerInputNames.cs.meta diff --git a/Assets/DefaultPrefabObjects.asset b/Assets/DefaultPrefabObjects.asset index 3b5f8aa..ce7bcf9 100644 --- a/Assets/DefaultPrefabObjects.asset +++ b/Assets/DefaultPrefabObjects.asset @@ -23,6 +23,7 @@ MonoBehaviour: - {fileID: 201277550, guid: 26a567abbe21227428f5c3d309d1d73c, type: 3} - {fileID: -8731164649411638372, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - {fileID: 4512293259955182956, guid: 44611030e61220d42ab7c37ba3c0ea92, type: 3} + - {fileID: 8499328425325690813, guid: 5d76bafdfa1223f40b69ba0a0c4de184, type: 3} - {fileID: 8192566354860284824, guid: 6331b3542e64a564c81bc39cedf70c8d, type: 3} - {fileID: 4512293259955182956, guid: f32d4c19de900e64cb73cedcb8ba6f70, type: 3} - {fileID: 611616139817875448, guid: bf5f023b4017a5e41a9815ec5745df3d, type: 3} diff --git a/Assets/_Game/Input.meta b/Assets/_Game/Input.meta new file mode 100644 index 0000000..d89f1e7 --- /dev/null +++ b/Assets/_Game/Input.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 437148ba0acd7d84ea3f01d9a9472028 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Input/PlayerControls.inputactions b/Assets/_Game/Input/PlayerControls.inputactions new file mode 100644 index 0000000..8c1768e --- /dev/null +++ b/Assets/_Game/Input/PlayerControls.inputactions @@ -0,0 +1,158 @@ +{ + "name": "PlayerControls", + "maps": [ + { + "name": "Player", + "id": "a1b2c3d4-0000-4000-8000-000000000001", + "actions": [ + { + "name": "Move", + "type": "Value", + "id": "a1b2c3d4-0000-4000-8000-000000000002", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Look", + "type": "Value", + "id": "a1b2c3d4-0000-4000-8000-000000000003", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Jump", + "type": "Button", + "id": "a1b2c3d4-0000-4000-8000-000000000004", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Sprint", + "type": "Button", + "id": "a1b2c3d4-0000-4000-8000-000000000005", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Attack", + "type": "Button", + "id": "a1b2c3d4-0000-4000-8000-000000000006", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "WASD", + "id": "a1b2c3d4-0000-4000-8000-000000000007", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "a1b2c3d4-0000-4000-8000-000000000008", + "path": "/w", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "a1b2c3d4-0000-4000-8000-000000000009", + "path": "/s", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "a1b2c3d4-0000-4000-8000-00000000000a", + "path": "/a", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "a1b2c3d4-0000-4000-8000-00000000000b", + "path": "/d", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "a1b2c3d4-0000-4000-8000-00000000000c", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": "", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "a1b2c3d4-0000-4000-8000-00000000000d", + "path": "/space", + "interactions": "", + "processors": "", + "groups": "", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "a1b2c3d4-0000-4000-8000-00000000000e", + "path": "/leftShift", + "interactions": "", + "processors": "", + "groups": "", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "a1b2c3d4-0000-4000-8000-00000000000f", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": "", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + } + ] + } + ], + "controlSchemes": [] +} diff --git a/Assets/_Game/Input/PlayerControls.inputactions.meta b/Assets/_Game/Input/PlayerControls.inputactions.meta new file mode 100644 index 0000000..413523b --- /dev/null +++ b/Assets/_Game/Input/PlayerControls.inputactions.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 93d11597dae64cc4aa68018ccaa39195 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 0 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: diff --git a/Assets/_Game/Prefabs/ACube.prefab b/Assets/_Game/Prefabs/ACube.prefab new file mode 100644 index 0000000..b58e7dc --- /dev/null +++ b/Assets/_Game/Prefabs/ACube.prefab @@ -0,0 +1,189 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2655148725409191110 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 29487170300297484} + - component: {fileID: 3803348366860609995} + - component: {fileID: 969463025217515519} + - component: {fileID: 6765530772190315426} + - component: {fileID: 8499328425325690813} + - component: {fileID: 8072914292444380081} + m_Layer: 0 + m_Name: ACube + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &29487170300297484 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2655148725409191110} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -13.66379, y: 2.13, z: 0.56447} + 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!33 &3803348366860609995 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2655148725409191110} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &969463025217515519 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2655148725409191110} + 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: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2} + 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: 0 + 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!65 &6765530772190315426 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2655148725409191110} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!114 &8499328425325690813 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2655148725409191110} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 26b716c41e9b56b4baafaf13a523ba2e, type: 3} + m_Name: + m_EditorClassIdentifier: FishNet.Runtime::FishNet.Object.NetworkObject + _localLevelOfDetailCalculationType: 0 + _useLevelOfDetail: 0 + _useRootLevelOfDetail: 1 + NetworkObserver: {fileID: 0} + _enablePrediction: 0 + _predictionType: 0 + _localReconcileCorrectionType: 2 + _graphicalObject: {fileID: 0} + _detachGraphicalObject: 0 + _enableStateForwarding: 1 + _networkTransform: {fileID: 0} + _ownerInterpolation: 1 + _ownerSmoothedProperties: 255 + _adaptiveInterpolation: 3 + _spectatorSmoothedProperties: 255 + _spectatorInterpolation: 2 + _enableTeleport: 0 + _teleportThreshold: 1 + k__BackingField: 10 + k__BackingField: 0 + k__BackingField: 17143552955378212519 + SceneId: 0 + SerializedTransformProperties: + Position: {x: -13.66379, y: 2.13, z: 0.56447} + Rotation: {x: 0, y: 0, z: 0, w: 1} + Scale: {x: 1, y: 1, z: 1} + IsValid: 1 + k__BackingField: 0 + WasActiveDuringEdit: 0 + WasActiveDuringEdit_Set1: 1 + k__BackingField: 0 + k__BackingField: {fileID: 0} + k__BackingField: {fileID: 0} + NetworkBehaviours: + - {fileID: 8072914292444380081} + InitializedParentNetworkBehaviour: {fileID: 0} + InitializedNestedNetworkObjects: [] + RuntimeParentNetworkBehaviour: {fileID: 0} + RuntimeChildNetworkBehaviours: [] + _isNetworked: 1 + _isSpawnable: 1 + _isGlobal: 0 + _initializeOrder: 0 + _preventDespawnOnDisconnect: 0 + _defaultDespawnType: 0 + _initializedTimestamp: -8584117148313536437 +--- !u!114 &8072914292444380081 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2655148725409191110} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8a6a39c46bf52104ba8efe3100bce3f7, type: 3} + m_Name: + m_EditorClassIdentifier: FishNet.Runtime::FishNet.Object.EmptyNetworkBehaviour + _componentIndexCache: 0 + _addedNetworkObject: {fileID: 0} + _networkObjectCache: {fileID: 8499328425325690813} diff --git a/Assets/_Game/Prefabs/ACube.prefab.meta b/Assets/_Game/Prefabs/ACube.prefab.meta new file mode 100644 index 0000000..17fae6a --- /dev/null +++ b/Assets/_Game/Prefabs/ACube.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5d76bafdfa1223f40b69ba0a0c4de184 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Game/Prefabs/Player.prefab b/Assets/_Game/Prefabs/Player.prefab index a265caa..8b7764a 100644 --- a/Assets/_Game/Prefabs/Player.prefab +++ b/Assets/_Game/Prefabs/Player.prefab @@ -161,6 +161,8 @@ GameObject: - component: {fileID: -8731164649411638372} - component: {fileID: -1605824257395221361} - component: {fileID: -1778631990423174556} + - component: {fileID: -1909282985868566644} + - component: {fileID: -2948519365385897836} m_Layer: 0 m_Name: Player m_TagString: Player @@ -346,7 +348,7 @@ MonoBehaviour: _initializeOrder: 0 _preventDespawnOnDisconnect: 0 _defaultDespawnType: 0 - _initializedTimestamp: -8584117169363086877 + _initializedTimestamp: -8584117164126792146 --- !u!114 &-1605824257395221361 MonoBehaviour: m_ObjectHideFlags: 0 @@ -408,3 +410,49 @@ MonoBehaviour: X: 0 Y: 0 Z: 0 +--- !u!114 &-1909282985868566644 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7344258574652498121} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 42ed262caf19a9040bf314a318cfecb6, type: 3} + m_Name: + m_EditorClassIdentifier: '::' + _componentIndexCache: 255 + _addedNetworkObject: {fileID: -8731164649411638372} + _networkObjectCache: {fileID: 0} + cubePrefab: {fileID: 8499328425325690813, guid: 5d76bafdfa1223f40b69ba0a0c4de184, type: 3} +--- !u!114 &-2948519365385897836 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7344258574652498121} + m_Enabled: 0 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 62899f850307741f2a39c98a8b639597, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.InputSystem::UnityEngine.InputSystem.PlayerInput + m_Actions: {fileID: -944628639613478452, guid: 93d11597dae64cc4aa68018ccaa39195, type: 3} + m_NotificationBehavior: 0 + m_UIInputModule: {fileID: 0} + m_DeviceLostEvent: + m_PersistentCalls: + m_Calls: [] + m_DeviceRegainedEvent: + m_PersistentCalls: + m_Calls: [] + m_ControlsChangedEvent: + m_PersistentCalls: + m_Calls: [] + m_ActionEvents: [] + m_NeverAutoSwitchControlSchemes: 0 + m_DefaultControlScheme: + m_DefaultActionMap: Player + m_SplitScreenIndex: -1 + m_Camera: {fileID: 0} diff --git a/Assets/_Game/Scenes/MainScene.unity b/Assets/_Game/Scenes/MainScene.unity index b3ee6d3..f4c9904 100644 --- a/Assets/_Game/Scenes/MainScene.unity +++ b/Assets/_Game/Scenes/MainScene.unity @@ -144,77 +144,12 @@ Transform: m_GameObject: {fileID: 277106703} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -10.96996, y: 0.76613, z: -5.65952} + m_LocalPosition: {x: -18.34, y: 0.76613, z: -5.65952} 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 &383713395 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 0} - m_Modifications: - - target: {fileID: -8731164649411638372, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: SceneId - value: 2952396223 - objectReference: {fileID: 0} - - target: {fileID: -8731164649411638372, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: WasActiveDuringEdit - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalPosition.x - value: 7.09624 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalPosition.y - value: 2.94 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalPosition.z - value: -10.98281 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalRotation.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalRotation.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalRotation.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 4433491345327751435, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 7344258574652498121, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} - propertyPath: m_Name - value: Player - objectReference: {fileID: 0} - m_RemovedComponents: [] - m_RemovedGameObjects: [] - m_AddedGameObjects: [] - m_AddedComponents: [] - m_SourcePrefab: {fileID: 100100000, guid: 687b16b0a8065d74fa4380e93a8a3d81, type: 3} --- !u!1 &870123117 GameObject: m_ObjectHideFlags: 0 @@ -569,6 +504,5 @@ SceneRoots: - {fileID: 870123120} - {fileID: 1617942571} - {fileID: 1749431010} - - {fileID: 277106704} - {fileID: 1541499318} - - {fileID: 383713395} + - {fileID: 277106704} diff --git a/Assets/_Game/Scripts/MouseLook.cs b/Assets/_Game/Scripts/MouseLook.cs index cb6b729..82942ff 100644 --- a/Assets/_Game/Scripts/MouseLook.cs +++ b/Assets/_Game/Scripts/MouseLook.cs @@ -6,12 +6,18 @@ namespace Agame /// /// 第一人称视角控制:鼠标横移带动 body 偏航,纵移带动相机俯仰。 /// + /// 输入来自父节点(Player 根)上 PlayerInput 的 Look 动作。 + /// /// 网络侧前提(重要):本组件挂在 PlayerCamera 上,而 PlayerCamera 只会被 /// 为「本地拥有者」启用(见其 ApplyOwnership)。 /// 非拥有者客户端的相机是 disabled 的,因此这里不会执行,天然不需要再判所有权。 /// /// 反过来说:如果日后把 PlayerCamera 改成常驻启用,必须补上所有权判断, /// 否则每个客户端都会用自己的鼠标去转动别人玩家的视角。 + /// + /// 关于光标:锁定/释放光标属于「窗口」层面,不是 gameplay 输入, + /// 所以刻意没有做成 Action —— 那样的话玩家一旦切到别的输入上下文 + /// (比如打开菜单、禁用了 Player 这张 Map),连恢复光标的按键都收不到了。 /// public class MouseLook : MonoBehaviour { @@ -27,11 +33,17 @@ namespace Agame [Tooltip("被偏航(Horizontal)的躯体 Transform,通常是玩家根节点。")] [SerializeField] private Transform characterBody; + private InputAction _lookAction; private float _xRotation; private bool _warnedMissingBody; private void OnEnable() { + // PlayerInput 挂在父节点(Player 根)上;相机自身可能是 disabled 的, + // 所以要 includeInactive 才能取到。 + PlayerInput playerInput = GetComponentInParent(includeInactive: true); + _lookAction = PlayerInputNames.Resolve(playerInput, PlayerInputNames.Look, this); + // 跟随本对象的启用状态锁定光标:只有本地拥有者的相机会被启用, // 所以光标锁定/释放自然就只在拥有者这一侧发生。 LockCursor(true); @@ -45,23 +57,16 @@ namespace Agame private void Update() { - Mouse mouse = Mouse.current; - if (mouse == null) - return; - - // Esc 释放光标,左键重新抓取(方便编辑器里切出去)。 - Keyboard kb = Keyboard.current; - if (kb != null && kb.escapeKey.wasPressedThisFrame) - LockCursor(false); - - if (mouse.leftButton.wasPressedThisFrame && Cursor.lockState != CursorLockMode.Locked) - LockCursor(true); + UpdateCursorLock(); if (Cursor.lockState != CursorLockMode.Locked) return; - // 新输入系统下 delta 的单位是像素,不是轴值。 - Vector2 delta = mouse.delta.ReadValue(); + if (_lookAction == null) + return; + + // Look 绑定的是 /delta,读出来的就是这一帧的鼠标位移(单位:像素)。 + Vector2 delta = _lookAction.ReadValue(); // 横移:转动躯体 if (characterBody != null) @@ -81,6 +86,20 @@ namespace Agame transform.localRotation = Quaternion.Euler(_xRotation, 0f, 0f); } + /// + /// Esc 释放光标,左键重新抓取(方便编辑器里切出去)。 + /// + private void UpdateCursorLock() + { + Keyboard keyboard = Keyboard.current; + if (keyboard != null && keyboard.escapeKey.wasPressedThisFrame) + LockCursor(false); + + Mouse mouse = Mouse.current; + if (mouse != null && mouse.leftButton.wasPressedThisFrame && Cursor.lockState != CursorLockMode.Locked) + LockCursor(true); + } + private void LockCursor(bool locked) { Cursor.lockState = locked ? CursorLockMode.Locked : CursorLockMode.None; diff --git a/Assets/_Game/Scripts/PlayerController.cs b/Assets/_Game/Scripts/PlayerController.cs index eee8966..b37e8e0 100644 --- a/Assets/_Game/Scripts/PlayerController.cs +++ b/Assets/_Game/Scripts/PlayerController.cs @@ -7,18 +7,20 @@ using UnityEngine.InputSystem; namespace Agame { /// - /// 玩家移动控制 —— FishNet 客户端授权模式。 - /// 参考:Docs/联机方案选型.md §3.3,以及官方教程 "Moving Your Player Around"。 + /// 玩家移动控制 —— FishNet 客户端授权 + Input System 的 PlayerInput / Actions 方案。 /// - /// 职责边界:本组件只负责「读输入 + 驱动 CharacterController」。 - /// 位置/旋转的同步交给 Player 预制体上的 NetworkTransform(Client Authoritative), - /// 这里不写任何 RPC / SyncVar。 + /// 输入链路: + /// Assets/_Game/Input/PlayerControls.inputactions + /// → Player 根节点上的 PlayerInput 组件(仅本地拥有者启用) + /// → 本脚本在 Update 里读取 Move / Sprint / Jump /// - /// FishNet 4.7.3 下的三条硬约束: - /// 1. 必须继承 NetworkBehaviour 而非 MonoBehaviour,否则拿不到 IsOwner 与网络生命周期回调。 - /// 2. IsOwner 禁止在 Awake / Start / OnStartServer / OnStartNetwork 里访问 —— - /// FishNet 代码生成器会直接抛编译错误,按拥有者初始化的逻辑要放在 OnStartClient。 - /// 3. 只有拥有者能驱动自己的对象,少了这道判断就会去移动别人的玩家。 + /// 本脚本同时兼任「输入与相机的所有权开关」,见 ApplyOwnership 的注释。 + /// + /// FishNet 4.7.3 的三条硬约束(与输入方案无关,继续沿用): + /// 1. 必须继承 NetworkBehaviour,否则拿不到 IsOwner 与网络生命周期回调。 + /// 2. IsOwner 禁止在 Awake / Start / OnStartServer / OnStartNetwork 中访问 —— + /// FishNet 的 ILPP 会直接报编译错误。 + /// 3. 只有拥有者能驱动自己的对象,少了 IsOwner 判断就会去移动别人的玩家。 /// [RequireComponent(typeof(CharacterController))] public class PlayerController : NetworkBehaviour @@ -33,16 +35,36 @@ namespace Agame [SerializeField, Min(0f)] private float jumpHeight = 1.5f; [Header("Ownership")] - [Tooltip("仅本地拥有者需要启用的对象(玩家相机等)。非拥有者会被自动禁用,避免多相机抢占渲染。")] + [Tooltip("仅本地拥有者需要启用的对象(玩家相机等)。非拥有者会自动禁用,避免多相机抢占渲染。")] [SerializeField] private GameObject[] ownerOnlyObjects; private CharacterController _controller; + private PlayerInput _playerInput; + + private InputAction _moveAction; + private InputAction _jumpAction; + private InputAction _sprintAction; + private Vector3 _verticalVelocity; private void Awake() { // Awake 中不得访问 IsOwner,这里只做纯本地缓存。 _controller = GetComponent(); + _playerInput = GetComponent(); + + if (_playerInput == null) + { + Debug.LogError( + $"[{nameof(PlayerController)}] {name} 上找不到 PlayerInput 组件,输入将不可用。" + + $"请在玩家预制体上加 PlayerInput,并把 Actions 指向 {PlayerInputNames.AssetFileName}。", + this); + return; + } + + _moveAction = PlayerInputNames.Resolve(_playerInput, PlayerInputNames.Move, this); + _jumpAction = PlayerInputNames.Resolve(_playerInput, PlayerInputNames.Jump, this); + _sprintAction = PlayerInputNames.Resolve(_playerInput, PlayerInputNames.Sprint, this); } public override void OnStartClient() @@ -59,47 +81,39 @@ namespace Agame { base.OnOwnershipClient(prevOwner); - // 所有权可能中途变更(例如将来的主机迁移),相机等要跟着切换。 + // 所有权可能中途变更(例如将来的主机迁移),输入与相机要跟着切换。 ApplyOwnership(); } private void Update() { - // 只驱动本地拥有者,否则每个客户端都会去移动别人的玩家。 + // 只驱动本地拥有者。 if (!IsOwner) return; - Keyboard kb = Keyboard.current; - if (kb == null) - return; + Vector2 moveInput = _moveAction != null ? _moveAction.ReadValue() : Vector2.zero; + Vector3 moveDirection = BuildMoveDirection(moveInput); - float speed = kb.leftShiftKey.isPressed ? sprintSpeed : moveSpeed; - Vector3 moveDirection = ReadMoveDirection(kb); + bool isSprinting = _sprintAction != null && _sprintAction.IsPressed(); + float speed = isSprinting ? sprintSpeed : moveSpeed; _controller.Move(moveDirection * (speed * Time.deltaTime)); - UpdateJumpAndGravity(kb); + bool jumpPressed = _jumpAction != null && _jumpAction.WasPressedThisFrame(); + UpdateJumpAndGravity(jumpPressed); } /// - /// 读取 WASD 并转换为相对于角色自身朝向的方向(body 的偏航由 MouseLook 负责)。 - /// 斜向输入做归一化,避免对角线移动比正向更快。 + /// 把 Move 的二维输入转换为相对于角色自身朝向的三维方向 + /// (body 的偏航由 MouseLook 负责)。斜向输入做归一化,避免对角线移动比正向更快。 /// - private Vector3 ReadMoveDirection(Keyboard kb) + private Vector3 BuildMoveDirection(Vector2 moveInput) { - float moveX = 0f; - float moveZ = 0f; - - if (kb.aKey.isPressed) moveX -= 1f; - if (kb.dKey.isPressed) moveX += 1f; - if (kb.sKey.isPressed) moveZ -= 1f; - if (kb.wKey.isPressed) moveZ += 1f; - - Vector3 direction = transform.right * moveX + transform.forward * moveZ; + Vector3 direction = transform.right * moveInput.x + transform.forward * moveInput.y; return Vector3.ClampMagnitude(direction, 1f); } - private void UpdateJumpAndGravity(Keyboard kb) + private void UpdateJumpAndGravity(bool jumpPressed) { bool isGrounded = _controller.isGrounded; @@ -107,7 +121,7 @@ namespace Agame if (isGrounded && _verticalVelocity.y < 0f) _verticalVelocity.y = -2f; - if (isGrounded && kb.spaceKey.wasPressedThisFrame) + if (isGrounded && jumpPressed) { // 由目标跳跃高度反推初速度:v = sqrt(2 * g * h) _verticalVelocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity); @@ -118,11 +132,18 @@ namespace Agame } /// - /// 按所有权开关"只属于本地玩家"的对象。最典型的是玩家相机: - /// 若每个客户端的相机都常驻启用,N 个玩家就会互相抢渲染。 + /// 按所有权统一开关「只属于本地玩家」的东西:PlayerInput 与玩家相机。 + /// + /// 集中放在这里的原因:这两样都是「一个客户端只该有一份生效」。 + /// 散到各脚本里各自 enable 容易漏,而且会出现顺序问题 —— + /// PlayerCamera 上的 MouseLook 在 OnEnable 里就要读 Look 动作, + /// 所以必须先开 PlayerInput、再启用相机,否则第一帧读不到值。 /// private void ApplyOwnership() { + if (_playerInput != null) + _playerInput.enabled = IsOwner; + if (ownerOnlyObjects == null) return; diff --git a/Assets/_Game/Scripts/PlayerCubeCreator.cs b/Assets/_Game/Scripts/PlayerCubeCreator.cs new file mode 100644 index 0000000..9a1a4fc --- /dev/null +++ b/Assets/_Game/Scripts/PlayerCubeCreator.cs @@ -0,0 +1,54 @@ +using FishNet.Object; +using UnityEngine; +using UnityEngine.InputSystem; +using UnityEngine.Serialization; + +namespace Agame +{ + /// + /// 按攻击键在当前位置生成一个方块,生成动作经 ServerRpc 在服务端执行。 + /// + /// 输入来自 Player 根节点上 PlayerInput 的 Attack 动作 —— 与 + /// 共用同一个 PlayerInput(因此 PlayerInput 的启用/禁用也统一由 PlayerController 按所有权管理, + /// 这里只读动作,不再自己 enable 组件)。 + /// + public class PlayerCubeCreator : NetworkBehaviour + { + [Tooltip("要生成的方块预制体。必须已登记进 FishNet 的可生成预制体列表(DefaultPrefabObjects)。")] + [FormerlySerializedAs("CubePrefab")] + [SerializeField] private NetworkObject cubePrefab; + + private InputAction _attackAction; + + private void Awake() + { + // Awake 中不得访问 IsOwner,这里只做纯本地缓存。 + _attackAction = PlayerInputNames.Resolve(GetComponent(), PlayerInputNames.Attack, this); + } + + private void Update() + { + // 只让拥有者发起攻击,否则每个客户端都会去指挥别人的玩家造方块。 + if (!IsOwner) + return; + + if (_attackAction != null && _attackAction.WasPressedThisFrame()) + SpawnCube(); + } + + [ServerRpc] + private void SpawnCube() + { + if (cubePrefab == null) + { + Debug.LogError( + $"[{nameof(PlayerCubeCreator)}] {name} 的 Cube Prefab 没有指定,无法生成方块。", + this); + return; + } + + NetworkObject obj = Instantiate(cubePrefab, transform.position, Quaternion.identity); + Spawn(obj); // NetworkBehaviour 对 ServerManager.Spawn 的快捷封装 + } + } +} diff --git a/Assets/_Game/Scripts/PlayerCubeCreator.cs.meta b/Assets/_Game/Scripts/PlayerCubeCreator.cs.meta new file mode 100644 index 0000000..35330b9 --- /dev/null +++ b/Assets/_Game/Scripts/PlayerCubeCreator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 42ed262caf19a9040bf314a318cfecb6 \ No newline at end of file diff --git a/Assets/_Game/Scripts/PlayerInputNames.cs b/Assets/_Game/Scripts/PlayerInputNames.cs new file mode 100644 index 0000000..b75dfbe --- /dev/null +++ b/Assets/_Game/Scripts/PlayerInputNames.cs @@ -0,0 +1,70 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +namespace Agame +{ + /// + /// 输入资产 Assets/_Game/Input/PlayerControls.inputactions 的名称与取值入口。 + /// + /// 把 Action 名集中在这里,是为了避免 "Move"/"Look"/"Attack" 这类字符串散落在三个脚本里 —— + /// 将来在输入资产里改名,只需要动这一处。 + /// + /// 约定:三个玩家脚本一律从 PlayerInput 实例上取 Action,而不是各自序列化一个资产引用。 + /// 因为 PlayerInput 可能为每个玩家实例化一份 Actions 副本,从实例上取才能拿到正确的那一份。 + /// + public static class PlayerInputNames + { + /// 输入资产的文件名,仅用于错误提示。 + public const string AssetFileName = "PlayerControls.inputactions"; + + /// Action Map 名称,需与输入资产中的 Map 一致。 + public const string ActionMap = "Player"; + + public const string Move = "Move"; + public const string Look = "Look"; + public const string Jump = "Jump"; + public const string Sprint = "Sprint"; + public const string Attack = "Attack"; + + /// + /// 从 PlayerInput 上解析指定 Action,解析失败时返回 null 并给出明确的错误信息。 + /// 这里刻意用 LogError 而不是直接抛 NullReferenceException —— + /// "输入没反应但又不报错"是这类问题里最难排查的一种,必须留下痕迹。 + /// + public static InputAction Resolve(PlayerInput playerInput, string actionName, Object context) + { + string ownerName = context != null ? context.name : "(未知对象)"; + + if (playerInput == null) + { + Debug.LogError( + $"[{nameof(PlayerInputNames)}] {ownerName} 上找不到 PlayerInput 组件,无法读取输入。" + + $"请在玩家预制体上加 PlayerInput 组件,并把 Actions 指向 {AssetFileName}。", + context); + return null; + } + + InputActionAsset asset = playerInput.actions; + if (asset == null) + { + Debug.LogError( + $"[{nameof(PlayerInputNames)}] {ownerName} 的 PlayerInput 没有指定 Actions 资产," + + $"请把 {AssetFileName} 拖进 PlayerInput 的 Actions 槽。", + context); + return null; + } + + InputAction action = asset.FindAction(actionName, throwIfNotFound: false); + if (action == null) + { + Debug.LogError( + $"[{nameof(PlayerInputNames)}] {AssetFileName} 里找不到名为 \"{actionName}\" 的动作," + + "请确认 Map 与 Action 名和脚本里的一致。", + context); + return null; + } + + return action; + } + } +} diff --git a/Assets/_Game/Scripts/PlayerInputNames.cs.meta b/Assets/_Game/Scripts/PlayerInputNames.cs.meta new file mode 100644 index 0000000..ed9bbe9 --- /dev/null +++ b/Assets/_Game/Scripts/PlayerInputNames.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 379ed2c2823ca764982411eec577adce \ No newline at end of file