From 18394a6b464ab16474fdaa7218044b2e46e2e58c Mon Sep 17 00:00:00 2001 From: 13659 <1365957941@qq.com> Date: Wed, 27 May 2026 10:42:39 +0800 Subject: [PATCH] feat: Phase 1 complete - attack system, enemy damage, timers, and study notes summary --- Assets/Animations/Player.controller | 254 +++- Assets/Animations/playerAttack.anim | 123 ++ Assets/Animations/playerAttack.anim.meta | 8 + Assets/Animations/playerFall.anim | 78 ++ Assets/Animations/playerFall.anim.meta | 8 + Assets/Animations/playerJump.anim | 78 ++ Assets/Animations/playerJump.anim.meta | 8 + Assets/Enemy.cs | 42 + Assets/Enemy.cs.meta | 2 + Assets/Materials.meta | 8 + .../Slippy_Material.physicsMaterial2D | 14 + .../Slippy_Material.physicsMaterial2D.meta | 8 + Assets/Player.cs | 61 +- Assets/PlayerAnimationEvents.cs | 15 + Assets/PlayerAnimationEvents.cs.meta | 2 + Assets/Scenes/SampleScene.unity | 208 +++- ProjectSettings/TagManager.asset | 2 +- StudyNotes.md | 1022 +++++++++++++++++ 18 files changed, 1885 insertions(+), 56 deletions(-) create mode 100644 Assets/Animations/playerAttack.anim create mode 100644 Assets/Animations/playerAttack.anim.meta create mode 100644 Assets/Animations/playerFall.anim create mode 100644 Assets/Animations/playerFall.anim.meta create mode 100644 Assets/Animations/playerJump.anim create mode 100644 Assets/Animations/playerJump.anim.meta create mode 100644 Assets/Enemy.cs create mode 100644 Assets/Enemy.cs.meta create mode 100644 Assets/Materials.meta create mode 100644 Assets/Materials/Slippy_Material.physicsMaterial2D create mode 100644 Assets/Materials/Slippy_Material.physicsMaterial2D.meta create mode 100644 Assets/PlayerAnimationEvents.cs create mode 100644 Assets/PlayerAnimationEvents.cs.meta diff --git a/Assets/Animations/Player.controller b/Assets/Animations/Player.controller index e65efc1..d2ff100 100644 --- a/Assets/Animations/Player.controller +++ b/Assets/Animations/Player.controller @@ -1,5 +1,30 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1101 &-8820503692433972253 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: attack + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 4761740218506164828} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0 + m_TransitionOffset: 0 + m_ExitTime: 0.61206895 + m_HasExitTime: 0 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 --- !u!1107 &-7853685596089614426 AnimatorStateMachine: serializedVersion: 6 @@ -10,11 +35,14 @@ AnimatorStateMachine: m_Name: Base Layer m_ChildStates: - serializedVersion: 1 - m_State: {fileID: -2999887273125739820} - m_Position: {x: 320, y: 40, z: 0} + m_State: {fileID: 8428018393159066403} + m_Position: {x: 550, y: 110, z: 0} - serializedVersion: 1 - m_State: {fileID: 8541810184871255671} - m_Position: {x: 315, y: 175, z: 0} + m_State: {fileID: 2731809886444445990} + m_Position: {x: 270, y: 110, z: 0} + - serializedVersion: 1 + m_State: {fileID: 4761740218506164828} + m_Position: {x: 270, y: 230, z: 0} m_ChildStateMachines: [] m_AnyStateTransitions: [] m_EntryTransitions: [] @@ -22,37 +50,41 @@ AnimatorStateMachine: m_StateMachineBehaviours: [] m_AnyStatePosition: {x: 50, y: 20, z: 0} m_EntryPosition: {x: 50, y: 120, z: 0} - m_ExitPosition: {x: 610, y: 110, z: 0} + m_ExitPosition: {x: 770, y: 90, z: 0} m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} - m_DefaultState: {fileID: -2999887273125739820} ---- !u!1102 &-2999887273125739820 -AnimatorState: - serializedVersion: 6 + m_DefaultState: {fileID: 2731809886444445990} +--- !u!206 &-7108901356451733881 +BlendTree: m_ObjectHideFlags: 1 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: playerIdle - m_Speed: 1 - m_CycleOffset: 0 - m_Transitions: - - {fileID: -1263164013022993944} - m_StateMachineBehaviours: [] - m_Position: {x: 50, y: 50, z: 0} - m_IKOnFeet: 0 - m_WriteDefaultValues: 1 - m_Mirror: 0 - m_SpeedParameterActive: 0 - m_MirrorParameterActive: 0 - m_CycleOffsetParameterActive: 0 - m_TimeParameterActive: 0 - m_Motion: {fileID: 7400000, guid: fe96eea6ebc2c8b4dbe787f4b59ef43f, type: 2} - m_Tag: - m_SpeedParameter: - m_MirrorParameter: - m_CycleOffsetParameter: - m_TimeParameter: ---- !u!1101 &-1263164013022993944 + m_Name: Blend Tree + m_Childs: + - serializedVersion: 2 + m_Motion: {fileID: 7400000, guid: 122b1fcaba4d2944e908c03459129cbf, type: 2} + m_Threshold: -1 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: Blend + m_Mirror: 0 + - serializedVersion: 2 + m_Motion: {fileID: 7400000, guid: d11062b0858b3c64cb6c4406048bbd03, type: 2} + m_Threshold: 1 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: Blend + m_Mirror: 0 + m_BlendParameter: yVelocity + m_BlendParameterY: Blend + m_MinThreshold: -1 + m_MaxThreshold: 1 + m_UseAutomaticThresholds: 0 + m_NormalizedBlendValues: 0 + m_BlendType: 0 +--- !u!1101 &-1775290645410636153 AnimatorStateTransition: m_ObjectHideFlags: 1 m_CorrespondingSourceObject: {fileID: 0} @@ -60,23 +92,62 @@ AnimatorStateTransition: m_PrefabAsset: {fileID: 0} m_Name: m_Conditions: - - m_ConditionMode: 1 - m_ConditionEvent: isMoving + - m_ConditionMode: 2 + m_ConditionEvent: isGrounded m_EventTreshold: 0 m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: 8541810184871255671} + m_DstState: {fileID: 8428018393159066403} m_Solo: 0 m_Mute: 0 m_IsExit: 0 serializedVersion: 3 m_TransitionDuration: 0 m_TransitionOffset: 0 - m_ExitTime: 0.5833334 + m_ExitTime: 0.61206895 m_HasExitTime: 0 m_HasFixedDuration: 1 m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 +--- !u!206 &-1313122025294676891 +BlendTree: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Blend Tree + m_Childs: + - serializedVersion: 2 + m_Motion: {fileID: 7400000, guid: ac4d453786085a145949201d8c33f0e0, type: 2} + m_Threshold: -1 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: yVelocity + m_Mirror: 0 + - serializedVersion: 2 + m_Motion: {fileID: 7400000, guid: fe96eea6ebc2c8b4dbe787f4b59ef43f, type: 2} + m_Threshold: 0 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: yVelocity + m_Mirror: 0 + - serializedVersion: 2 + m_Motion: {fileID: 7400000, guid: ac4d453786085a145949201d8c33f0e0, type: 2} + m_Threshold: 1 + m_Position: {x: 0, y: 0} + m_TimeScale: 1 + m_CycleOffset: 0 + m_DirectBlendParameter: yVelocity + m_Mirror: 0 + m_BlendParameter: xVelocity + m_BlendParameterY: yVelocity + m_MinThreshold: -1 + m_MaxThreshold: 1 + m_UseAutomaticThresholds: 0 + m_NormalizedBlendValues: 0 + m_BlendType: 0 --- !u!91 &9100000 AnimatorController: m_ObjectHideFlags: 0 @@ -86,12 +157,30 @@ AnimatorController: m_Name: Player serializedVersion: 5 m_AnimatorParameters: - - m_Name: isMoving + - m_Name: isGrounded m_Type: 4 m_DefaultFloat: 0 m_DefaultInt: 0 m_DefaultBool: 0 m_Controller: {fileID: 9100000} + - m_Name: yVelocity + m_Type: 1 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: xVelocity + m_Type: 1 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: attack + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} m_AnimatorLayers: - serializedVersion: 5 m_Name: Base Layer @@ -105,7 +194,7 @@ AnimatorController: m_IKPass: 0 m_SyncedLayerAffectsTiming: 0 m_Controller: {fileID: 9100000} ---- !u!1101 &7343199574911539791 +--- !u!1101 &563274122570338863 AnimatorStateTransition: m_ObjectHideFlags: 1 m_CorrespondingSourceObject: {fileID: 0} @@ -113,35 +202,36 @@ AnimatorStateTransition: m_PrefabAsset: {fileID: 0} m_Name: m_Conditions: - - m_ConditionMode: 2 - m_ConditionEvent: isMoving + - m_ConditionMode: 1 + m_ConditionEvent: isGrounded m_EventTreshold: 0 m_DstStateMachine: {fileID: 0} - m_DstState: {fileID: -2999887273125739820} + m_DstState: {fileID: 2731809886444445990} m_Solo: 0 m_Mute: 0 m_IsExit: 0 serializedVersion: 3 m_TransitionDuration: 0 m_TransitionOffset: 0 - m_ExitTime: 0.625 + m_ExitTime: 0.1428572 m_HasExitTime: 0 m_HasFixedDuration: 1 m_InterruptionSource: 0 m_OrderedInterruption: 1 m_CanTransitionToSelf: 1 ---- !u!1102 &8541810184871255671 +--- !u!1102 &2731809886444445990 AnimatorState: serializedVersion: 6 m_ObjectHideFlags: 1 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_Name: PlayerMove + m_Name: idle/move m_Speed: 1 m_CycleOffset: 0 m_Transitions: - - {fileID: 7343199574911539791} + - {fileID: -1775290645410636153} + - {fileID: -8820503692433972253} m_StateMachineBehaviours: [] m_Position: {x: 50, y: 50, z: 0} m_IKOnFeet: 0 @@ -151,7 +241,83 @@ AnimatorState: m_MirrorParameterActive: 0 m_CycleOffsetParameterActive: 0 m_TimeParameterActive: 0 - m_Motion: {fileID: 7400000, guid: ac4d453786085a145949201d8c33f0e0, type: 2} + m_Motion: {fileID: -1313122025294676891} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &4761740218506164828 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: playerAttack + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 7527007860389132503} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 4963fd28dae460640a761546b7b5c596, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1101 &7527007860389132503 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: [] + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 2731809886444445990} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0 + m_TransitionOffset: 0 + m_ExitTime: 1 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1102 &8428018393159066403 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: jump/fall + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 563274122570338863} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: -7108901356451733881} m_Tag: m_SpeedParameter: m_MirrorParameter: diff --git a/Assets/Animations/playerAttack.anim b/Assets/Animations/playerAttack.anim new file mode 100644 index 0000000..bf1d78a --- /dev/null +++ b/Assets/Animations/playerAttack.anim @@ -0,0 +1,123 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: playerAttack + serializedVersion: 7 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: + - serializedVersion: 2 + curve: + - time: 0 + value: {fileID: -1059185708, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.06666667 + value: {fileID: 558846297, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.13333334 + value: {fileID: 752029924, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.2 + value: {fileID: 184008166, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.26666668 + value: {fileID: 820566783, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.33333334 + value: {fileID: 590827802, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.4 + value: {fileID: -1161772646, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.46666667 + value: {fileID: 180173413, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.53333336 + value: {fileID: 32573988, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.6 + value: {fileID: 820566783, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.6666667 + value: {fileID: 184008166, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + attribute: m_Sprite + path: + classID: 212 + script: {fileID: 0} + flags: 2 + m_SampleRate: 15 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: + - serializedVersion: 2 + path: 0 + attribute: 0 + script: {fileID: 0} + typeID: 212 + customType: 23 + isPPtrCurve: 1 + isIntCurve: 0 + isSerializeReferenceCurve: 0 + pptrCurveMapping: + - {fileID: -1059185708, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 558846297, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 752029924, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 184008166, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 820566783, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 590827802, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: -1161772646, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 180173413, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 32573988, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 820566783, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 184008166, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 0.73333335 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 1 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: + - time: 0 + functionName: DisableMovementAndJump + data: + objectReferenceParameter: {fileID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 + - time: 0.33333334 + functionName: DamageEnemies + data: + objectReferenceParameter: {fileID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 + - time: 0.6666667 + functionName: EnableMovementAndJump + data: + objectReferenceParameter: {fileID: 0} + floatParameter: 0 + intParameter: 0 + messageOptions: 0 diff --git a/Assets/Animations/playerAttack.anim.meta b/Assets/Animations/playerAttack.anim.meta new file mode 100644 index 0000000..40c8c79 --- /dev/null +++ b/Assets/Animations/playerAttack.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4963fd28dae460640a761546b7b5c596 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Animations/playerFall.anim b/Assets/Animations/playerFall.anim new file mode 100644 index 0000000..7cd2e95 --- /dev/null +++ b/Assets/Animations/playerFall.anim @@ -0,0 +1,78 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: playerFall + serializedVersion: 7 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: + - serializedVersion: 2 + curve: + - time: 0 + value: {fileID: -270005676, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.083333336 + value: {fileID: -963690940, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.16666667 + value: {fileID: -1648917730, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + attribute: m_Sprite + path: + classID: 212 + script: {fileID: 0} + flags: 2 + m_SampleRate: 12 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: + - serializedVersion: 2 + path: 0 + attribute: 0 + script: {fileID: 0} + typeID: 212 + customType: 23 + isPPtrCurve: 1 + isIntCurve: 0 + isSerializeReferenceCurve: 0 + pptrCurveMapping: + - {fileID: -270005676, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: -963690940, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: -1648917730, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 0.25 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 1 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Assets/Animations/playerFall.anim.meta b/Assets/Animations/playerFall.anim.meta new file mode 100644 index 0000000..fa15008 --- /dev/null +++ b/Assets/Animations/playerFall.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 122b1fcaba4d2944e908c03459129cbf +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Animations/playerJump.anim b/Assets/Animations/playerJump.anim new file mode 100644 index 0000000..61ad4a5 --- /dev/null +++ b/Assets/Animations/playerJump.anim @@ -0,0 +1,78 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: playerJump + serializedVersion: 7 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: + - serializedVersion: 2 + curve: + - time: 0 + value: {fileID: 1486326522, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.083333336 + value: {fileID: 284068559, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - time: 0.16666667 + value: {fileID: 2143798682, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + attribute: m_Sprite + path: + classID: 212 + script: {fileID: 0} + flags: 2 + m_SampleRate: 12 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: + - serializedVersion: 2 + path: 0 + attribute: 0 + script: {fileID: 0} + typeID: 212 + customType: 23 + isPPtrCurve: 1 + isIntCurve: 0 + isSerializeReferenceCurve: 0 + pptrCurveMapping: + - {fileID: 1486326522, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 284068559, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + - {fileID: 2143798682, guid: 2a192e342a6409b4d9677ef04a238b79, type: 3} + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 0.25 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 1 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Assets/Animations/playerJump.anim.meta b/Assets/Animations/playerJump.anim.meta new file mode 100644 index 0000000..8ac87fd --- /dev/null +++ b/Assets/Animations/playerJump.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d11062b0858b3c64cb6c4406048bbd03 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Enemy.cs b/Assets/Enemy.cs new file mode 100644 index 0000000..83a53dc --- /dev/null +++ b/Assets/Enemy.cs @@ -0,0 +1,42 @@ +using UnityEngine; + +public class Enemy : MonoBehaviour +{ + private SpriteRenderer sr; + [SerializeField]private float redColorDuration = 1; + public float currentTimeInGame; + public float lastTimeWasDamaged; + + private void Awake() + { + sr = GetComponent(); + } + + private void Update() + { + ChanceColorIfNeedes(); + } + + private void ChanceColorIfNeedes() + { + currentTimeInGame = Time.time; + if (currentTimeInGame > lastTimeWasDamaged + redColorDuration) + { + if (sr.color != Color.white) + { + TurnWhite(); + } + } + } + + public void TakeDamage() + { + sr.color = Color.red; + lastTimeWasDamaged = Time.time; + } + + private void TurnWhite() + { + sr.color = Color.white; + } +} diff --git a/Assets/Enemy.cs.meta b/Assets/Enemy.cs.meta new file mode 100644 index 0000000..285a0a5 --- /dev/null +++ b/Assets/Enemy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 59e2d11d5851d78438d59f16c215baf0 \ No newline at end of file diff --git a/Assets/Materials.meta b/Assets/Materials.meta new file mode 100644 index 0000000..73748e6 --- /dev/null +++ b/Assets/Materials.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7937e918b2888f340887cadf1433afde +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Materials/Slippy_Material.physicsMaterial2D b/Assets/Materials/Slippy_Material.physicsMaterial2D new file mode 100644 index 0000000..388131d --- /dev/null +++ b/Assets/Materials/Slippy_Material.physicsMaterial2D @@ -0,0 +1,14 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!62 &6200000 +PhysicsMaterial2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Slippy_Material + serializedVersion: 2 + friction: 0 + bounciness: 0 + m_FrictionCombine: 1 + m_BounceCombine: 4 diff --git a/Assets/Materials/Slippy_Material.physicsMaterial2D.meta b/Assets/Materials/Slippy_Material.physicsMaterial2D.meta new file mode 100644 index 0000000..f23b6a5 --- /dev/null +++ b/Assets/Materials/Slippy_Material.physicsMaterial2D.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 743a4a351990cf740a3bfa0f9028536e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 6200000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Player.cs b/Assets/Player.cs index 3fb0df4..6859729 100644 --- a/Assets/Player.cs +++ b/Assets/Player.cs @@ -5,11 +5,20 @@ public class Player : MonoBehaviour private Animator anim; private Rigidbody2D rb; + + [Header("Attack details")] + [SerializeField] private float attackRadius; + [SerializeField] private Transform attackPoint; + [SerializeField] private LayerMask whatIsEnemy; + + [Header("Movement details")] - [SerializeField] private float moveSpeed = 4.58f; + [SerializeField] private float moveSpeed = 8f; [SerializeField] private float jumpForce = 12; private bool facingRight = true; private float xInput; + private bool canMove = true; + private bool canJump = true; [Header("Collision details")] [SerializeField] private float groundCheckDistance; @@ -32,31 +41,66 @@ public class Player : MonoBehaviour HandleFlip(); } + public void DamageEnemies() + { + Collider2D[] enemyColliders = Physics2D.OverlapCircleAll(attackPoint.position, attackRadius, whatIsEnemy); + foreach (Collider2D enemy in enemyColliders) + { + enemy.GetComponent().TakeDamage(); + } + } + + public void EnableMovementAndJump(bool enable) + { + canMove = enable; + canJump = enable; + } + private void HandleInput() { xInput = Input.GetAxisRaw("Horizontal"); if (Input.GetKeyDown(KeyCode.K)) { - jump(); + TryToJump(); + } + if (Input.GetKeyDown(KeyCode.J)) + { + TryToAttack(); } } + private void TryToAttack() + { + if(isGrounded && canMove) + { + anim.SetTrigger("attack"); + } + } + private void HandleAnimations() { - bool isMoving = rb.linearVelocity.x != 0; - anim.SetBool("isMoving", isMoving); + anim.SetFloat("xVelocity", rb.linearVelocity.x); + anim.SetFloat("yVelocity", rb.linearVelocity.y); + anim.SetBool("isGrounded", isGrounded); } private void HandleMovement() { - rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y); + if(canMove) + { + rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y); + } + else + { + rb.linearVelocity = new Vector2(0, rb.linearVelocity.y); + } } - private void jump() + private void TryToJump() { - if (isGrounded) + if (isGrounded && canJump) { rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); } @@ -69,7 +113,7 @@ public class Player : MonoBehaviour private void HandleFlip() { - if(rb.linearVelocity.x > 0 && facingRight == false) + if (rb.linearVelocity.x > 0 && facingRight == false) { Flip(); } @@ -88,6 +132,7 @@ public class Player : MonoBehaviour private void OnDrawGizmos() { Gizmos.DrawLine(transform.position, transform.position + new Vector3(0, -groundCheckDistance)); + Gizmos.DrawWireSphere(attackPoint.position, attackRadius); } diff --git a/Assets/PlayerAnimationEvents.cs b/Assets/PlayerAnimationEvents.cs new file mode 100644 index 0000000..e3ef291 --- /dev/null +++ b/Assets/PlayerAnimationEvents.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +public class PlayAnimationEvents : MonoBehaviour +{ + private Player player; + + private void Awake() + { + player = GetComponentInParent(); + } + public void DamageEnemies() => player.DamageEnemies(); + private void DisableMovementAndJump() => player.EnableMovementAndJump(false); + private void EnableMovementAndJump() => player.EnableMovementAndJump(true); + +} diff --git a/Assets/PlayerAnimationEvents.cs.meta b/Assets/PlayerAnimationEvents.cs.meta new file mode 100644 index 0000000..40d0208 --- /dev/null +++ b/Assets/PlayerAnimationEvents.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2babfd50ef367814a809844b3888ac22 \ No newline at end of file diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index a3093c9..2ed74ca 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -119,6 +119,37 @@ NavMeshSettings: debug: m_Flags: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &155500594 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 155500595} + m_Layer: 0 + m_Name: AttackPoint + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &155500595 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 155500594} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.32, y: -0.06, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 420449211} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &420449208 GameObject: m_ObjectHideFlags: 0 @@ -174,11 +205,12 @@ Transform: m_GameObject: {fileID: 420449208} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 4.01, y: -1.26, z: 0} + m_LocalPosition: {x: 3.88, y: -1.63, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1414990379} + - {fileID: 155500595} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &420449213 @@ -193,6 +225,11 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: cb2ed43676cd08e4da098563b4cef1e9, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::Player + attackRadius: 0.97 + attackPoint: {fileID: 155500595} + whatIsEnemy: + serializedVersion: 2 + m_Bits: 128 moveSpeed: 8 jumpForce: 12 groundCheckDistance: 1.4 @@ -209,7 +246,7 @@ CapsuleCollider2D: m_Enabled: 1 serializedVersion: 3 m_Density: 1 - m_Material: {fileID: 0} + m_Material: {fileID: 6200000, guid: 743a4a351990cf740a3bfa0f9028536e, type: 2} m_IncludeLayers: serializedVersion: 2 m_Bits: 0 @@ -323,7 +360,7 @@ Transform: m_GameObject: {fileID: 454696404} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -0.14, y: -3.14, z: 0} + m_LocalPosition: {x: 0.34, y: -3.62, z: 0} m_LocalScale: {x: 16.002186, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -623,6 +660,157 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &915961694 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 915961698} + - component: {fileID: 915961697} + - component: {fileID: 915961696} + - component: {fileID: 915961695} + m_Layer: 7 + m_Name: Enemy + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!61 &915961695 +BoxCollider2D: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 915961694} + m_Enabled: 1 + serializedVersion: 3 + m_Density: 1 + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_ForceSendLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ForceReceiveLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_ContactCaptureLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_CallbackLayers: + serializedVersion: 2 + m_Bits: 4294967295 + m_IsTrigger: 0 + m_UsedByEffector: 0 + m_CompositeOperation: 0 + m_CompositeOrder: 0 + m_Offset: {x: 0, y: 0} + m_SpriteTilingProperty: + border: {x: 0, y: 0, z: 0, w: 0} + pivot: {x: 0.5, y: 0.5} + oldSize: {x: 1, y: 1} + newSize: {x: 1, y: 1} + adaptiveTilingThreshold: 0.5 + drawMode: 0 + adaptiveTiling: 0 + m_AutoTiling: 0 + m_Size: {x: 1, y: 1} + m_EdgeRadius: 0 +--- !u!114 &915961696 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 915961694} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59e2d11d5851d78438d59f16c215baf0, type: 3} + m_Name: + m_EditorClassIdentifier: '::' +--- !u!212 &915961697 +SpriteRenderer: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 915961694} + m_Enabled: 1 + m_CastShadows: 0 + m_ReceiveShadows: 0 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 0 + 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: a97c105638bdf8b4a8650670310a4cd3, 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: 0 + 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_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, type: 3} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_FlipX: 0 + m_FlipY: 0 + m_DrawMode: 0 + m_Size: {x: 1, y: 1} + m_AdaptiveModeThreshold: 0.5 + m_SpriteTileMode: 0 + m_WasSpriteAssigned: 1 + m_SpriteSortPoint: 0 +--- !u!4 &915961698 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 915961694} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -6.06, y: -1.85, z: 0} + 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 &1414990378 GameObject: m_ObjectHideFlags: 0 @@ -634,6 +822,7 @@ GameObject: - component: {fileID: 1414990379} - component: {fileID: 1414990380} - component: {fileID: 1414990381} + - component: {fileID: 1414990382} m_Layer: 0 m_Name: Animator m_TagString: Untagged @@ -737,6 +926,18 @@ Animator: m_AllowConstantClipSamplingOptimization: 1 m_KeepAnimatorStateOnDisable: 0 m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &1414990382 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1414990378} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2babfd50ef367814a809844b3888ac22, type: 3} + m_Name: + m_EditorClassIdentifier: '::' --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -745,3 +946,4 @@ SceneRoots: - {fileID: 619394802} - {fileID: 454696406} - {fileID: 420449211} + - {fileID: 915961698} diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset index acda2db..9fcbef5 100644 --- a/ProjectSettings/TagManager.asset +++ b/ProjectSettings/TagManager.asset @@ -12,7 +12,7 @@ TagManager: - Water - UI - Ground - - + - Enemy - - - diff --git a/StudyNotes.md b/StudyNotes.md index ad5efa5..a6359f2 100644 --- a/StudyNotes.md +++ b/StudyNotes.md @@ -783,3 +783,1025 @@ Player (MonoBehaviour) │ └── OnDrawGizmos() → Scene 视图画射线(仅编辑器) ``` + +--- + +## 2026-05-26 · 第十次更新 + +**文件:** `Assets/Player.cs` + `Assets/Animations/Player.controller` + +### Player.cs 当前代码 + +```csharp +using UnityEngine; + +public class Player : MonoBehaviour +{ + private Animator anim; + private Rigidbody2D rb; + + [Header("Movement details")] + [SerializeField] private float moveSpeed = 8f; + [SerializeField] private float jumpForce = 12; + private bool facingRight = true; + private float xInput; + + [Header("Collision details")] + [SerializeField] private float groundCheckDistance; + [SerializeField] private LayerMask whatIsGround; + private bool isGrounded; + + private void Awake() + { + rb = GetComponent(); + anim = GetComponentInChildren(); + } + + private void Update() + { + HandleCollision(); + HandleInput(); + HandleMovement(); + HandleAnimations(); + HandleFlip(); + } + + private void HandleInput() + { + xInput = Input.GetAxisRaw("Horizontal"); + + if (Input.GetKeyDown(KeyCode.K)) + { + jump(); + } + } + + private void HandleAnimations() + { + anim.SetFloat("xVelocity", rb.linearVelocity.x); + anim.SetFloat("yVelocity", rb.linearVelocity.y); + anim.SetBool("isGrounded", isGrounded); + } + + private void HandleMovement() + { + rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y); + } + + private void jump() + { + if (isGrounded) + { + rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); + } + } + + private void HandleCollision() + { + isGrounded = Physics2D.Raycast(transform.position, Vector2.down, groundCheckDistance, whatIsGround); + } + + private void HandleFlip() + { + if (rb.linearVelocity.x > 0 && facingRight == false) + { + Flip(); + } + else if (rb.linearVelocity.x < 0 && facingRight == true) + { + Flip(); + } + } + + private void Flip() + { + transform.Rotate(0, 180, 0); + facingRight = !facingRight; + } + + private void OnDrawGizmos() + { + Gizmos.DrawLine(transform.position, transform.position + new Vector3(0, -groundCheckDistance)); + } +} +``` + +### 本次改动 · 新增知识点 + +#### 1. `HandleAnimations` 大改:Bool → Float 驱动 + +- **旧方案:** 只传一个 `isMoving` 布尔值 → 动画只能"跑"或"不跑",很僵硬。 +- **新方案:** 传入三个参数,让 Blend Tree 做平滑混合: + +```csharp +anim.SetFloat("xVelocity", rb.linearVelocity.x); // 水平速度 → 驱动 跑/停 混合 +anim.SetFloat("yVelocity", rb.linearVelocity.y); // 垂直速度 → 驱动 上升/下落 混合 +anim.SetBool("isGrounded", isGrounded); // 是否着地 → 切换 地面/空中 状态 +``` + +#### 2. Blend Tree(混合树)概念 + +- Blend Tree 是 Animator 中的一种**动画混合器**,根据一个或多个浮点参数,在多个动画之间平滑过渡。 +- 类比:调音台上的推子,推到哪里就播对应位置的动画。 + +**当前配置了两个 Blend Tree:** + +``` +Animator 状态机 +├── idle/move (地面状态) ── 1D Blend Tree,参数: xVelocity +│ ├── Threshold = -1 → 向左跑动画 +│ ├── Threshold = 0 → Idle 动画 +│ └── Threshold = 1 → 向右跑动画 +│ +└── jump/fall (空中状态) ── 1D Blend Tree,参数: yVelocity + ├── Threshold = -1 → 下落动画 + ├── Threshold = 0 → 空中顶点动画 + └── Threshold = 1 → 上升跳跃动画 +``` + +#### 3. 1D Blend Tree 工作原理 + +- `m_BlendType: 0` = 1D 简单混合。 +- 只有一个驱动参数(如 `xVelocity`),沿着一条轴混合动画。 +- 参数值在 threshold 之间时,自动插值混合两个相邻动画。 + +``` +xVelocity = 0.0 → 100% Idle +xVelocity = 0.3 → 70% Idle + 30% 跑 +xVelocity = 0.7 → 30% Idle + 70% 跑 +xVelocity = 1.0 → 100% 跑 +``` + +#### 4. 状态切换:`isGrounded` 驱动的 Transition + +从 Animator Controller 文件中可以看到两条 Transition: + +| 从 | 到 | 条件 | 效果 | +|---|---|---|---| +| idle/move → jump/fall | `isGrounded == false` | 离开地面 → 切到空中动画 | +| jump/fall → idle/move | `isGrounded == true` | 落地 → 切回地面动画 | + +- `m_ConditionMode: 1` = If(为 true 时触发) +- `m_ConditionMode: 2` = IfNot(为 false 时触发) +- `m_HasExitTime: 0` = 不等待动画播完,条件满足**立即切换**(关键!跳跃需要即时响应) + +#### 5. Animator 参数类型(YAML 对照) + +从 Controller 文件中可看到三种参数类型的序列化 ID: + +| 类型 | YAML `m_Type` | C# 方法 | +|---|---|---| +| Float | 1 | `SetFloat()` | +| Int | 3 | `SetInteger()` | +| Bool | 4 | `SetBool()` | +| Trigger | 9 | `SetTrigger()` | + +#### 6. 新旧动画方案对比 + +| | 旧方案 | 新方案(Blend Tree)| +|---|---|---| +| 参数 | 1 个 Bool (`isMoving`) | 2 个 Float + 1 个 Bool | +| 动画数量 | 2 个(Idle / Run)| 6 个(Idle、左跑、右跑、上升、顶点、下落)| +| 过渡效果 | 硬切 | 平滑混合 | +| 空中动画 | ❌ 无 | ✅ 有(跳跃/下落独立)| +| 表现力 | 基础 | 流畅自然 | + +#### 7. `moveSpeed` 调整:4.58 → 8 + +- 速度翻倍,角色移动更快。 +- 配合 Blend Tree,`xVelocity` 参数值范围变大,动画混合范围需要与之匹配。 + +#### 8. 当前 Animator 架构图 + +``` + ┌──────────────────────┐ + │ Animator Controller │ + │ 参数: │ + │ · xVelocity (Float) │ + │ · yVelocity (Float) │ + │ · isGrounded (Bool) │ + └──────────────────────┘ + │ + ┌───────────────┴───────────────┐ + ▼ ▼ + ┌─────────────────┐ ┌─────────────────┐ + │ idle/move │ │ jump/fall │ + │ (默认状态) │ isGrounded │ (空中状态) │ + │ │◄────────────│ │ + │ Blend Tree 1D │────────────►│ Blend Tree 1D │ + │ 参数: xVelocity │ !isGrounded│ 参数: yVelocity │ + │ │ │ │ + │ -1 → 左跑 │ │ -1 → 下落 │ + │ 0 → Idle │ │ 0 → 顶点 │ + │ 1 → 右跑 │ │ 1 → 上升 │ + └─────────────────┘ └─────────────────┘ +``` + +--- + +## 2026-05-26 · 第十一次更新 + +**文件:** `Assets/Player.cs` + `Assets/PlayerAnimationEvents.cs`(新文件) + +### Player.cs 当前代码 + +```csharp +using UnityEngine; + +public class Player : MonoBehaviour +{ + private Animator anim; + private Rigidbody2D rb; + + [Header("Movement details")] + [SerializeField] private float moveSpeed = 8f; + [SerializeField] private float jumpForce = 12; + private bool facingRight = true; + private float xInput; + private bool canMove = true; // ← 新增 + private bool canJump = true; // ← 新增 + + [Header("Collision details")] + [SerializeField] private float groundCheckDistance; + [SerializeField] private LayerMask whatIsGround; + private bool isGrounded; + + // ... Awake、Update 同上 ... + + public void EnableMovementAndJump(bool enable) // ← 新增 + { + canMove = enable; + canJump = enable; + } + + private void HandleInput() + { + xInput = Input.GetAxisRaw("Horizontal"); + + if (Input.GetKeyDown(KeyCode.K)) + { + TryToJump(); // ← 改名 + } + if (Input.GetKeyDown(KeyCode.J)) // ← 新增攻击 + { + TryToAttack(); + } + } + + private void TryToAttack() // ← 新增方法 + { + if(isGrounded) + { + anim.SetTrigger("attack"); + rb.linearVelocity = new Vector2(0, rb.linearVelocity.y); + } + } + + private void HandleMovement() + { + if(canMove) // ← 加了 canMove 判断 + { + rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y); + } + } + + private void TryToJump() // ← 改名 + 加 canJump 判断 + { + if (isGrounded && canJump) + { + rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); + } + } + + // ... HandleCollision、HandleFlip、Flip、OnDrawGizmos 同上 ... +} +``` + +### PlayerAnimationEvents.cs(新文件) + +```csharp +using UnityEngine; + +public class PlayAnimationEvents : MonoBehaviour +{ + private Player player; + + private void Awake() + { + player = GetComponentInParent(); + } + + private void DisableMovementAndJump() => player.EnableMovementAndJump(false); + private void EnableMovementAndJump() => player.EnableMovementAndJump(true); +} +``` + +--- + +### 本次改动 · 新增知识点 + +#### 1. 攻击系统(初版) + +- 按 J 键触发攻击:`Input.GetKeyDown(KeyCode.J)` +- `anim.SetTrigger("attack")` — 播放一次性攻击动画 +- 攻击时将水平速度归零:`new Vector2(0, rb.linearVelocity.y)`,阻止攻击时滑行 +- 只能在地面攻击:`if(isGrounded)` + +#### 2. `canMove` / `canJump` 控制标志(Lock 模式) + +- 两个 bool 变量控制角色能否移动和跳跃。 +- 攻击动画播放期间设为 `false`,动画结束恢复 `true`。 +- 这是一种常见的"动作锁定"设计,防止在特定动画期间执行不该执行的动作。 + +```csharp +攻击开始 → canMove = false, canJump = false → 不能移动/跳跃 +攻击结束 → canMove = true, canJump = true → 恢复正常 +``` + +#### 3. `EnableMovementAndJump(bool)` 公共接口 + +- `public` 方法,供其他脚本调用(这里是 `PlayAnimationEvents`)。 +- 封装了两个状态一起控制,避免外部重复设置。 + +#### 4. `GetComponentInParent()` —— 向上查找组件 + +| 方法 | 搜索方向 | 场景 | +|---|---|---| +| `GetComponent()` | 当前物体 | Rigidbody2D 在自己身上 | +| `GetComponentInChildren()` | 向下(自身+子物体)| Animator 在模型子物体上 | +| `GetComponentInParent()` | 向上(自身+父物体)| Player 脚本在父物体上 | + +- `PlayAnimationEvents` 挂载在角色模型子物体上(和 Animator 同级),需要通过 `GetComponentInParent` 找到父物体上的 Player 脚本。 + +#### 5. Animation Events(动画事件) + +- 在攻击动画的时间轴上插入事件,指定时间点调用 `PlayAnimationEvents` 的方法: + - **动画开始帧** → 调用 `DisableMovementAndJump()` → 锁定移动 + - **动画结束帧** → 调用 `EnableMovementAndJump()` → 解锁移动 +- 挂载位置:`PlayAnimationEvents` 脚本挂在和 Animator **同一个 GameObject** 上。 + +``` +攻击动画时间轴 +├── 第0帧 ── DisableMovementAndJump() 锁定 +├── ... 动画播放中 ... (canMove=false, canJump=false) +└── 最后帧 ── EnableMovementAndJump() 解锁 +``` + +#### 6. 表达式体方法(Expression-bodied) + +```csharp +private void DisableMovementAndJump() => player.EnableMovementAndJump(false); +// ↑ 等价于 { player.EnableMovementAndJump(false); } +``` + +- `=>` 是 C# 的表达式体语法糖,单行方法更简洁。 + +#### 7. ⚠️ 已知 Bug:攻击时仍能移动 + +- 当前在 `TryToAttack()` 中虽然把速度归零了(`new Vector2(0, ...)`),但 `HandleMovement()` 在**同一帧**后面又用 `canMove` 重新设置了速度。 +- 如果 `DisableMovementAndJump` 没有被及时调用(存在一帧延迟),玩家**在攻击第一帧仍能移动**。 +- 这是用户提到的 bug,预计下次更新修复。 + +#### 8. 当前 Update 执行顺序 & 控制流 + +``` +Update() +├── HandleCollision() → isGrounded +├── HandleInput() +│ ├── xInput = GetAxisRaw("Horizontal") +│ ├── K → TryToJump() → isGrounded && canJump? +│ └── J → TryToAttack() → isGrounded? → SetTrigger("attack"), velocity.x=0 +├── HandleMovement() → canMove? → 设置 velocity +├── HandleAnimations() → 更新 xVelocity/yVelocity/isGrounded +└── HandleFlip() → 自动转向 +``` + +--- + +## 2026-05-26 · 第十二次更新 + +**文件:** `Assets/Player.cs`(`PlayAnimationEvents.cs` 未变) + +### 改动对比 + +| 位置 | 旧代码 | 新代码 | +|---|---|---| +| `TryToAttack()` | `anim.SetTrigger("attack");` + `velocity.x=0` | 只剩 `anim.SetTrigger("attack");` | +| `HandleMovement()` | `if(canMove) { 设置速度 }` | `if(canMove) { 设置速度 } else { velocity.x=0 }` | + +```csharp +// TryToAttack — 去掉了速度归零 +private void TryToAttack() +{ + if(isGrounded) + { + anim.SetTrigger("attack"); + } +} + +// HandleMovement — 新增 else 分支 +private void HandleMovement() +{ + if(canMove) + { + rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y); + } + else + { + rb.linearVelocity = new Vector2(0, rb.linearVelocity.y); // ← 攻击时停住 + } +} +``` + +### 知识点 + +#### 职责分离:为什么把速度归零从 TryToAttack 移到 HandleMovement + +- **旧方案:** `TryToAttack` 里直接操作速度 → 攻击逻辑和移动逻辑混在一起。 +- **新方案:** `HandleMovement` 统一管理"能不能移动",`canMove=false` 时自动归零。 +- 好处:不需要每个动作单独处理速度归零,后续加技能/受伤等,只要设 `canMove=false` 就行。 + +--- + +### 🐛 Bug 分析:快速双击 J 攻击两次 + +**现象:** 快速按两次 J(第二次在第一次攻击动画期间),攻击动画播放了两次。 + +**根因:** `TryToAttack()` 只检查了 `isGrounded`,没有检查是否**正在攻击中**。 + +``` +时间线: +帧1 按下J → TryToAttack() → isGrounded=true ✓ → SetTrigger("attack") → 攻击开始 + → Animation Event → canMove=false +帧2 再次按下J → TryToAttack() → isGrounded=true ✓ → SetTrigger("attack") ⚠️ 又设了一次! +帧N 第一次攻击动画结束 → Animator 发现还有一次 trigger 没消费 → 再播一次攻击 ❌ +``` + +**核心问题:** Unity 的 Trigger 会**累积**。第一次攻击动画还没播完,第二次 `SetTrigger` 就被攒着,等第一次结束自动触发第二次。 + +**修复方案(下次更新):** 在 `TryToAttack()` 中加 `canMove` 判断: + +```csharp +private void TryToAttack() +{ + if(isGrounded && canMove) // ← 攻击期间 canMove=false,阻止第二次触发 + { + anim.SetTrigger("attack"); + } +} +``` + +> 💡 `canMove` 就是天然的"是否正在攻击"标志——攻击时已被 Animation Event 设为 false。 + +#### 当前控制流(含 Bug 标注) + +``` +Update() +├── HandleCollision() +├── HandleInput() +│ └── J → TryToAttack() +│ └── isGrounded? → SetTrigger("attack") ⚠️ 没有防重入 +├── HandleMovement() +│ ├── canMove=true → velocity = (xInput*speed, vy) +│ └── canMove=false → velocity = (0, vy) ✅ 好的设计 +├── HandleAnimations() +└── HandleFlip() +``` + +--- + +## 2026-05-26 · 第十三次更新(Bug 修复) + +**文件:** `Assets/Player.cs`(仅一行改动) + +### 改动 + +```csharp +// 修复前 +private void TryToAttack() +{ + if(isGrounded) // ← 只检查地面 + { + anim.SetTrigger("attack"); + } +} + +// 修复后 +private void TryToAttack() +{ + if(isGrounded && canMove) // ← 加上 canMove 防重入 + { + anim.SetTrigger("attack"); + } +} +``` + +### Bug 修复原理 + +- 攻击动画开始时,Animation Event 调用 `DisableMovementAndJump()` → `canMove = false` +- 此时再按 J → `TryToAttack()` 检查 `canMove` → `false` → 不执行 `SetTrigger` +- 动画结束后 `canMove` 恢复 `true`,才能再次攻击 ✅ + +**一句话:** `canMove` 既是移动开关,也是"是否正在攻击"的天然标志,一行改动解决双触 Bug。 + +--- + +## 2026-05-27 · 第十四次更新 + +**文件:** `Assets/Player.cs`、`Assets/PlayerAnimationEvents.cs`、`Assets/Enemy.cs`(新文件) + +### Player.cs 新增 + +```csharp +// 新增字段 +[Header("Attack details")] +[SerializeField] private float attackRadius; +[SerializeField] private Transform attackPoint; +[SerializeField] private LayerMask whatIsEnemy; + +// 新增方法 +public void DamageEnemies() +{ + Collider2D[] enemyColliders = Physics2D.OverlapCircleAll( + attackPoint.position, attackRadius, whatIsEnemy); + foreach (Collider2D enemy in enemyColliders) + { + enemy.GetComponent().TakeDamage(); + } +} + +// OnDrawGizmos 新增 +Gizmos.DrawWireSphere(attackPoint.position, attackRadius); +``` + +### PlayerAnimationEvents.cs 新增 + +```csharp +public void DamageEnemies() => player.DamageEnemies(); +``` + +### Enemy.cs(新文件) + +```csharp +using UnityEngine; + +public class Enemy : MonoBehaviour +{ + private SpriteRenderer sr; + [SerializeField] private float redColorDuration = 1; + + private void Awake() + { + sr = GetComponent(); + } + + public void TakeDamage() + { + sr.color = Color.red; + Invoke(nameof(TurnWhite), redColorDuration); + } + + private void TurnWhite() + { + sr.color = Color.white; + } +} +``` + +--- + +### 本次改动 · 新增知识点 + +#### 1. 攻击判定:`Physics2D.OverlapCircleAll()` + +- 以某点为中心画一个圆,返回圆内所有碰撞体。 +- 用于攻击范围判定(圆形 AOE 伤害)。 + +``` +参数:Physics2D.OverlapCircleAll(中心点, 半径, 层级过滤) + + ╭─────────────────╮ + │ attackRadius │ + │ ┌─────┐ │ + │ │ 玩家 │ │ ← attackPoint (子空物体) + │ └──┬──┘ │ + │ │ │ + │ ╔════╧════╗ │ ← 敌人碰撞体在圆内 → 被检测到 + │ ║ 敌人 ║ │ + │ ╚═════════╝ │ + ╰─────────────────╯ +``` + +- 返回 `Collider2D[]` 数组,可能有 0~N 个敌人被击中。 + +#### 2. `Transform` 作为位置标记 + +- `attackPoint` 是一个**空 GameObject** 的 Transform,拖到攻击判定位置。 +- 这是一种常见做法:在角色子物体下放一个空物体标记攻击中心点,代码通过 `attackPoint.position` 获取世界坐标。 + +``` +Player (父物体) +├── Graphics (模型) +└── AttackPoint (空物体) ← Transform attackPoint,放在拳头/武器位置 +``` + +#### 3. 攻击流程全链路 + +``` +按下 J + → TryToAttack() + → anim.SetTrigger("attack") // 播放攻击动画 + → 动画播放到"伤害帧" + → Animation Event: DamageEnemies() + → Physics2D.OverlapCircleAll() // 搜敌人 + → foreach enemy + → GetComponent().TakeDamage() // 扣血/反馈 +``` + +> 🔑 关键设计:伤害判定不在代码里固定时间触发,而是通过**动画事件**在挥刀命中的那一帧触发。 + +#### 4. `SpriteRenderer` 组件 + +- 2D 精灵渲染器,控制角色/物体的显示。 +- `sr.color = Color.red;` — 直接改颜色,用于受伤闪烁效果。 +- `sr.color` 类型是 `Color`(RGBA)。 + +#### 5. `Invoke()` 延迟调用 + +```csharp +Invoke(nameof(TurnWhite), redColorDuration); +``` + +| 参数 | 含义 | +|---|---| +| `nameof(TurnWhite)` | 要调用的方法名(字符串安全版本) | +| `redColorDuration` | 延迟秒数(这里是 1 秒) | + +- 1 秒后自动调用 `TurnWhite()`,颜色恢复白色。 +- `nameof()` 是 C# 关键字,编译时检查方法名是否存在,比写死字符串 `"TurnWhite"` 更安全。 + +#### 6. `nameof()` vs 硬编码字符串 + +```csharp +Invoke("TurnWhite", 1f); // ❌ 拼错编译不报错,运行时才崩 +Invoke(nameof(TurnWhite), 1f); // ✅ 拼错直接编译报错 +``` + +#### 7. `Gizmos.DrawWireSphere()` —— 攻击范围可视化 + +```csharp +Gizmos.DrawWireSphere(attackPoint.position, attackRadius); +``` + +- 在 Scene 视图中画一个线框球体。 +- 方便调整 `attackRadius` 和 `attackPoint` 的位置,确保攻击范围覆盖敌人。 + +#### 8. Color 常用预设 + +| 代码 | 颜色 | +|---|---| +| `Color.white` | 白色(默认) | +| `Color.red` | 红色(受伤) | +| `Color.green` | 绿色(回血) | +| `Color.yellow` | 黄色(警告) | +| `Color.clear` | 完全透明 | + +#### 9. `foreach` 循环 + +```csharp +foreach (Collider2D enemy in enemyColliders) +{ + enemy.GetComponent().TakeDamage(); +} +``` + +- 遍历数组中每个被击中的敌人,分别调用 `TakeDamage()`。 +- 如果圆内没有敌人(数组为空),循环直接跳过,不会报错。 + +#### 10. 当前架构图(完整版) + +``` +Player.cs +├── 字段 +│ ├── [Movement] moveSpeed, jumpForce, facingRight, xInput, canMove, canJump +│ ├── [Collision] groundCheckDistance, whatIsGround, isGrounded +│ └── [Attack] attackRadius, attackPoint, whatIsEnemy ← 新增 +│ +├── Update() +│ ├── HandleCollision() → Raycast 地检 +│ ├── HandleInput() → J攻击/K跳跃 +│ ├── HandleMovement() → canMove控制速度 +│ ├── HandleAnimations() → 传参给Animator +│ └── HandleFlip() → 自动转向 +│ +├── DamageEnemies() → OverlapCircleAll → Enemy.TakeDamage() +└── OnDrawGizmos() → 地检线 + 攻击范围球 + +-------------------- + +PlayAnimationEvents.cs (挂在模型子物体) +├── DamageEnemies() → 动画事件调用 → player.DamageEnemies() +├── DisableMovementAndJump() → 动画事件调用 → 锁定 +└── EnableMovementAndJump() → 动画事件调用 → 解锁 + +-------------------- + +Enemy.cs (挂在敌人物体) +├── sr = GetComponent() +├── TakeDamage() → 变红 → Invoke(nameof(TurnWhite), 1s) +└── TurnWhite() → 恢复白色 +``` + +--- + +## 2026-05-27 · 第十五次更新(计时器专题) + +**文件:** `Assets/Enemy.cs` + +> 🎯 本次围绕同一功能(受伤红色闪烁后恢复白色),演示了三种计时器实现方式。 + +--- + +### 方式一:`Invoke()` 延迟调用 + +```csharp +public class Enemy : MonoBehaviour +{ + [SerializeField] private float redColorDuration = 1; + + public void TakeDamage() + { + sr.color = Color.red; + Invoke(nameof(TurnWhite), redColorDuration); + } + + private void TurnWhite() + { + sr.color = Color.white; + } +} +``` + +**特点:** + +| 优点 | 缺点 | +|---|---| +| 代码极简,一行搞定 | 再次受伤不会重置计时 | +| 不依赖 Update | 无法暂停/取消(除非用 `CancelInvoke`) | +| Unity 原生支持 | 不适合需要精确中途控制的场景 | + +--- + +### 方式二:`Time.deltaTime` 倒计时 + +```csharp +public float timer; + +private void Update() +{ + timer -= Time.deltaTime; + if(timer < 0 && sr.color != Color.white) + { + sr.color = Color.white; + } +} + +public void TakeDamage() +{ + sr.color = Color.red; + timer = redColorDuration; // 每次受伤重置倒计时 +} +``` + +**特点:** + +| 优点 | 缺点 | +|---|---| +| 再次受伤自动重置计时 ✅ | 需要额外一个 `timer` 字段 | +| 可以暂停(不执行 `timer -=`) | Update 中多了一行减法 | +| 可随时查看剩余时间 | 不适合大量计时器(Update 会臃肿) | + +--- + +### 方式三:`Time.time` 时间戳对比(最终版) + +```csharp +public float currentTimeInGame; +public float lastTimeWasDamaged; + +private void Update() +{ + currentTimeInGame = Time.time; + if(currentTimeInGame > lastTimeWasDamaged + redColorDuration) + { + if (sr.color != Color.white) + { + TurnWhite(); + } + } +} + +public void TakeDamage() +{ + sr.color = Color.red; + lastTimeWasDamaged = Time.time; // 记录受伤时刻 +} +``` + +**特点:** + +| 优点 | 缺点 | +|---|---| +| 再次受伤自动延长红色时间 ✅ | 需要两个字段 | +| 不依赖逐帧递减(无浮点累积误差) | 代码稍多 | +| 逻辑清晰:当前时间 vs 受伤时刻 | — | + +--- + +### 三种计时器对比总结 + +| | `Invoke()` | `deltaTime` 递减 | `Time.time` 对比 | +|---|---|---|---| +| 代码量 | ⭐ 最少 | ⭐⭐ 中等 | ⭐⭐⭐ 较多 | +| 重置计时 | ❌ 需手动 CancelInvoke | ✅ 天然支持 | ✅ 天然支持 | +| 浮点精度 | ✅ | ⚠️ 累积误差 | ✅ 无累积 | +| Update 占用 | ❌ 不占 | ⚠️ 每帧减法 | ⚠️ 每帧比较 | +| 适用场景 | 一次性延迟 | 简单倒计时 | 精确时间判断、冷却 | + +### `Time.deltaTime` vs `Time.time` + +| 属性 | 含义 | 用途 | +|---|---|---| +| `Time.deltaTime` | 上一帧到当前帧的耗时(秒) | 帧率无关的增量计算 | +| `Time.time` | 游戏开始到现在的总时间(秒) | 时间戳、计时基准 | + +> 💡 `deltaTime` 容易累积浮点误差,长时间运行后可能不准。`Time.time` 直接用绝对时间对比,更精确。 + +### 使用建议 + +``` +一次性动作(音效、粒子消失) → Invoke() +简单冷却、Buff 持续时间 → deltaTime 递减 +攻击间隔、精确冷却 → Time.time 对比 +大量计时器 → 用协程(Coroutine) 或 Timer 管理器 +``` + +--- + +## 🏁 第一阶段总结(2026-05-23 ~ 2026-05-27) + +> 15 次更新,3 个 C# 脚本,从零搭建了一个 2D 横版动作角色的核心系统。 + +--- + +### 📁 文件清单 + +| 文件 | 职责 | +|---|---| +| `Assets/Player.cs` | 角色主逻辑:移动、跳跃、攻击、翻转、动画控制 | +| `Assets/PlayerAnimationEvents.cs` | 动画事件桥接:攻击判定、移动锁定 | +| `Assets/Enemy.cs` | 敌人逻辑:受伤反馈、计时器 | + +--- + +### 🧱 技能树总览 + +#### 一、Unity 基础 + +| 知识点 | 首次出现 | +|---|---| +| `MonoBehaviour` 继承 | ① | +| `Update()` / `Awake()` 生命周期 | ① ③ | +| `public` 字段与 Inspector 序列化 | ① | +| `[SerializeField]` 私有字段可见 | ③ | +| `[Header("...")]` Inspector 分组 | ⑨ | +| `[ContextMenu]` 右键测试 | ⑦ | + +#### 二、输入系统 + +| 知识点 | 首次出现 | +|---|---| +| `Input.GetAxisRaw("Horizontal")` | ① | +| `GetAxis` vs `GetAxisRaw` 区别 | ① | +| `Input.GetKey(KeyCode)` | ④ | +| `Input.GetKeyDown(KeyCode)` | ④ | +| `KeyCode` 枚举 | ④ | +| `Debug.Log()` 调试输出 | ④ | + +#### 三、物理与移动 + +| 知识点 | 首次出现 | +|---|---| +| `Rigidbody2D` 组件 | ② | +| `rb.linearVelocity` 物理移动 | ② | +| `Vector2` 构造函数 | ② | +| 物理移动 vs Transform 移动 | ② | +| `rb.linearVelocity.y` 保持垂直速度 | ② | +| `Physics2D.Raycast()` 射线检测 | ⑨ | +| `LayerMask` 层级过滤 | ⑨ | +| `Vector2.down` 等快捷向量 | ⑨ | +| `transform.Rotate()` 旋转 | ⑦ | + +#### 四、代码架构 + +| 知识点 | 首次出现 | +|---|---| +| `private` 字段封装 | ③ | +| `GetComponent()` 获取组件 | ③ | +| `GetComponentInChildren()` | ⑥ | +| `GetComponentInParent()` | ⑪ | +| Update 拆分多方法 | ⑥ | +| `canMove`/`canJump` 控制标志 | ⑪ | +| 职责分离(移动逻辑归位) | ⑫ | +| 表达式体方法 `=>` | ⑪ | + +#### 五、动画系统 + +| 知识点 | 首次出现 | +|---|---| +| `Animator` 组件 | ⑥ | +| `anim.SetBool()` | ⑥ | +| `anim.SetFloat()` | ⑩ | +| `anim.SetTrigger()` | ⑪ | +| Animator 参数四类型 | ⑥ | +| Blend Tree 1D 混合树 | ⑩ | +| 动画状态切换 Transition | ⑩ | +| Animation Events 动画事件 | ⑪ | + +#### 六、战斗系统 + +| 知识点 | 首次出现 | +|---|---| +| `Physics2D.OverlapCircleAll()` | ⑭ | +| `Transform` 作位置标记 | ⑭ | +| 攻击流程全链路 | ⑭ | +| `SpriteRenderer` 组件 | ⑭ | +| `sr.color` 颜色修改 | ⑭ | +| `foreach` 遍历 | ⑭ | +| 攻击防重入(`canMove` 天然锁) | ⑬ | + +#### 七、计时器 + +| 知识点 | 首次出现 | +|---|---| +| `Invoke()` 延迟调用 | ⑭ | +| `Time.deltaTime` 倒计时 | ⑮ | +| `Time.time` 时间戳对比 | ⑮ | +| `nameof()` 编译安全 | ⑭ | +| 三种计时器对比 & 选型 | ⑮ | + +#### 八、调试与可视化 + +| 知识点 | 首次出现 | +|---|---| +| `Debug.Log()` | ④ | +| `OnDrawGizmos()` | ⑨ | +| `Gizmos.DrawLine()` | ⑨ | +| `Gizmos.DrawWireSphere()` | ⑭ | +| `[ContextMenu]` | ⑦ | + +--- + +### 🏗️ 最终架构 + +``` + ┌──────────────────────┐ + │ Player.cs │ + │ ────────────────── │ + │ [Movement] 移动/跳跃 │ + │ [Collision] 地面检测 │ + │ [Attack] 攻击判定 │ + │ │ + │ Update() │ + │ ├─ HandleCollision │ + │ ├─ HandleInput │ + │ ├─ HandleMovement │ + │ ├─ HandleAnimations │ + │ └─ HandleFlip │ + └──────┬───────────────┘ + │ Animation Events + ┌──────▼───────────────┐ + │ PlayAnimationEvents │ + │ ├─ DamageEnemies() │ + │ ├─ DisableMove() │ + │ └─ EnableMove() │ + └──────┬───────────────┘ + │ OverlapCircleAll + ┌──────▼───────────────┐ + │ Enemy.cs │ + │ └─ TakeDamage() │ + │ └─ 变红 → 计时恢复│ + └──────────────────────┘ +``` + +--- + +### 📊 数据统计 + +- **更新次数:** 15 次 +- **C# 脚本:** 3 个 +- **掌握 API:** 40+ 个 +- **学习天数:** 5 天(5/23 ~ 5/27) +- **从零实现:** 移动 → 跳跃 → 翻转 → 动画 → 攻击 → 敌人 → 计时 + +🚀 第一阶段完成,准备进入第二阶段!