(client) feat:状态UI
This commit is contained in:
209
Client/Assets/Resources/Prefab/UI/BuffIconUI.prefab
Normal file
209
Client/Assets/Resources/Prefab/UI/BuffIconUI.prefab
Normal file
@ -0,0 +1,209 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &6060365352283784422
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3192591419784585419}
|
||||
- component: {fileID: 3481945618963852047}
|
||||
- component: {fileID: 9180115334091918173}
|
||||
- component: {fileID: 8174970956233947585}
|
||||
m_Layer: 5
|
||||
m_Name: BuffIconUI
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &3192591419784585419
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6060365352283784422}
|
||||
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: 3140503542561239742}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 62.421, y: 43.9}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3481945618963852047
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6060365352283784422}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &9180115334091918173
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6060365352283784422}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 3286163911610860551, guid: fb99d95e0a6d23f4e9410d6c113af2bd, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 1
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &8174970956233947585
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6060365352283784422}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 17587b13f9d4467dbff77cf9762dc8fe, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
icon: {fileID: 8897095238084646223}
|
||||
--- !u!1001 &4221518541276266867
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 3192591419784585419}
|
||||
m_Modifications:
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_Pivot.x
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_Pivot.y
|
||||
value: 0.5
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_AnchorMax.x
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_AnchorMax.y
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_AnchorMin.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_AnchorMin.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_SizeDelta.x
|
||||
value: -30
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_SizeDelta.y
|
||||
value: -10
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: -0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_AnchoredPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_AnchoredPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1665787281938857491, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: UIAnimator
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5156899681550006548, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
propertyPath: m_PreserveAspect
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
--- !u!224 &3140503542561239742 stripped
|
||||
RectTransform:
|
||||
m_CorrespondingSourceObject: {fileID: 1225146130447774669, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
m_PrefabInstance: {fileID: 4221518541276266867}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!114 &8897095238084646223 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: 4750466675787535420, guid: e9ac5cdd8d06ee841931abb21e710490, type: 3}
|
||||
m_PrefabInstance: {fileID: 4221518541276266867}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c6f899d1c5ef450bb6f3e670fa55cffd, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
7
Client/Assets/Resources/Prefab/UI/BuffIconUI.prefab.meta
Normal file
7
Client/Assets/Resources/Prefab/UI/BuffIconUI.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ca0674a45754094baf7e91a0869238f
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
93
Client/Assets/Resources/Prefab/UI/UIAnimator.prefab
Normal file
93
Client/Assets/Resources/Prefab/UI/UIAnimator.prefab
Normal file
@ -0,0 +1,93 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &1665787281938857491
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1225146130447774669}
|
||||
- component: {fileID: 8697738588197658250}
|
||||
- component: {fileID: 5156899681550006548}
|
||||
- component: {fileID: 4750466675787535420}
|
||||
m_Layer: 5
|
||||
m_Name: UIAnimator
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1225146130447774669
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1665787281938857491}
|
||||
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: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -40, y: -40}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8697738588197658250
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1665787281938857491}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &5156899681550006548
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1665787281938857491}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 620e16cb0aec3684db9ef80bdcc85695, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &4750466675787535420
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1665787281938857491}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c6f899d1c5ef450bb6f3e670fa55cffd, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_sprites: []
|
||||
_fps: 2
|
||||
_staticSprite: {fileID: 21300000, guid: 620e16cb0aec3684db9ef80bdcc85695, type: 3}
|
7
Client/Assets/Resources/Prefab/UI/UIAnimator.prefab.meta
Normal file
7
Client/Assets/Resources/Prefab/UI/UIAnimator.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9ac5cdd8d06ee841931abb21e710490
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -9,7 +9,7 @@ ScriptedImporter:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: a57477913897c46af95d590f580878bd, type: 3}
|
||||
svgType: 1
|
||||
texturedSpriteMeshType: 0
|
||||
texturedSpriteMeshType: 1
|
||||
svgPixelsPerUnit: 100
|
||||
gradientResolution: 64
|
||||
alignment: 0
|
||||
@ -32,7 +32,7 @@ ScriptedImporter:
|
||||
textureWidth: 256
|
||||
textureHeight: 256
|
||||
wrapMode: 0
|
||||
filterMode: 1
|
||||
filterMode: 0
|
||||
sampleCount: 4
|
||||
preserveSVGImageAspect: 0
|
||||
useSVGPixelsPerUnit: 0
|
||||
|
@ -28,11 +28,11 @@ ScriptedImporter:
|
||||
maxTangentAngleEnabled: 0
|
||||
maxTangentAngle: 5
|
||||
keepTextureAspectRatio: 1
|
||||
textureSize: 256
|
||||
textureSize: 512
|
||||
textureWidth: 256
|
||||
textureHeight: 256
|
||||
wrapMode: 0
|
||||
filterMode: 1
|
||||
filterMode: 0
|
||||
sampleCount: 4
|
||||
preserveSVGImageAspect: 0
|
||||
useSVGPixelsPerUnit: 0
|
||||
|
@ -28,11 +28,11 @@ ScriptedImporter:
|
||||
maxTangentAngleEnabled: 0
|
||||
maxTangentAngle: 5
|
||||
keepTextureAspectRatio: 1
|
||||
textureSize: 256
|
||||
textureSize: 512
|
||||
textureWidth: 256
|
||||
textureHeight: 256
|
||||
wrapMode: 0
|
||||
filterMode: 1
|
||||
filterMode: 0
|
||||
sampleCount: 4
|
||||
preserveSVGImageAspect: 0
|
||||
useSVGPixelsPerUnit: 0
|
||||
|
@ -28,11 +28,11 @@ ScriptedImporter:
|
||||
maxTangentAngleEnabled: 0
|
||||
maxTangentAngle: 5
|
||||
keepTextureAspectRatio: 1
|
||||
textureSize: 256
|
||||
textureSize: 512
|
||||
textureWidth: 256
|
||||
textureHeight: 256
|
||||
wrapMode: 0
|
||||
filterMode: 1
|
||||
filterMode: 0
|
||||
sampleCount: 4
|
||||
preserveSVGImageAspect: 0
|
||||
useSVGPixelsPerUnit: 0
|
||||
|
@ -28,11 +28,11 @@ ScriptedImporter:
|
||||
maxTangentAngleEnabled: 0
|
||||
maxTangentAngle: 5
|
||||
keepTextureAspectRatio: 1
|
||||
textureSize: 256
|
||||
textureSize: 512
|
||||
textureWidth: 256
|
||||
textureHeight: 256
|
||||
wrapMode: 0
|
||||
filterMode: 1
|
||||
filterMode: 0
|
||||
sampleCount: 4
|
||||
preserveSVGImageAspect: 0
|
||||
useSVGPixelsPerUnit: 0
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,8 @@ namespace Base
|
||||
public float globalVolume = 1.0f;
|
||||
public WindowMode currentWindowMode = WindowMode.Fullscreen;
|
||||
public Vector2Int windowResolution = new(1920, 1080);
|
||||
|
||||
public string[] loadOrder;
|
||||
}
|
||||
|
||||
// 当前游戏设置
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System; // Added for Action
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UI;
|
||||
@ -19,6 +20,13 @@ namespace Base
|
||||
|
||||
private bool needUpdate = false;
|
||||
|
||||
/// <summary>
|
||||
/// 当UI窗口的可见性状态发生改变时触发的事件。
|
||||
/// 参数1: 发生改变的UIBase实例。
|
||||
/// 参数2: 窗口的新可见状态 (true为显示,false为隐藏)。
|
||||
/// </summary>
|
||||
public event Action<UIBase, bool> OnWindowVisibilityChanged; // <--- 新增
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已注册的UI窗口的总数量。
|
||||
/// </summary>
|
||||
@ -93,7 +101,7 @@ namespace Base
|
||||
// 确保窗口不为空且其GameObject未被销毁
|
||||
if (window != null && window.gameObject != null)
|
||||
{
|
||||
window.Hide();
|
||||
window.Hide(); // 隐藏操作会触发 OnWindowVisibilityChanged 事件
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,6 +181,9 @@ namespace Base
|
||||
if (itick != null)
|
||||
Base.Clock.AddTickUI(itick);
|
||||
|
||||
// 触发事件通知窗口可见性已改变
|
||||
OnWindowVisibilityChanged?.Invoke(windowToShow, true); // <--- 修改点 2
|
||||
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
@ -209,6 +220,9 @@ namespace Base
|
||||
if (windowToHide is ITickUI iTick)
|
||||
Base.Clock.RemoveTickUI(iTick);
|
||||
|
||||
// 触发事件通知窗口可见性已改变
|
||||
OnWindowVisibilityChanged?.Invoke(windowToHide, false); // <--- 修改点 3
|
||||
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
@ -241,7 +255,7 @@ namespace Base
|
||||
// 再次检查窗口是否仍然可见,因为其他操作可能已经隐藏了它
|
||||
if (visibleWindow != null && visibleWindow.IsVisible)
|
||||
{
|
||||
Hide(visibleWindow);
|
||||
Hide(visibleWindow); // Hide() 方法会触发 OnWindowVisibilityChanged 事件
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,6 +295,10 @@ namespace Base
|
||||
private void OnDestroy()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
// 在销毁时清空所有订阅者,防止因MonoSingleton持久化导致下一场景重新加载时出现旧的订阅者(如果单例不销毁,事件本身也不会自动清空订阅)
|
||||
// 如果 UIInputControl 是一个会随场景销毁的普通 MonoBehaviour 而不是持久化的 MonoSingleton, 某些情况下清除订阅者可以防止跨场景的引用问题。
|
||||
// 但对于 MonoSingleton,它通常是持久化的,所以事件在重新加载场景后仍然保留。明确清空可以避免不必要的资源占用,虽然在应用程序关闭时会自动释放。
|
||||
// OnWindowVisibilityChanged = null; // 谨慎使用,如果外部有长期订阅的需求,清空可能导致问题。通常更推荐外部在OnDestroy中取消订阅。
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -19,10 +19,6 @@ namespace Data
|
||||
// ======================================================================
|
||||
// 百分比偏移 (Multiplied as a factor, e.g., 0.1 for +10%)
|
||||
// ======================================================================
|
||||
// 注意:百分比偏移通常是乘法因子。
|
||||
// 0f 表示没有百分比变化,0.1f 表示增加10%,-0.1f 表示减少10%。
|
||||
// 应用时: baseValue * (1 + percentageOffset)
|
||||
// 或者对于更直接的乘法,可以直接用 multiplyRatio,例如 1.1 表示增加 10%。
|
||||
public float healthPercentOffset = 0f; // 例如 0.1 表示 +10%
|
||||
public float moveSpeedPercentOffset = 0f;
|
||||
public float attackPercentOffset = 0f;
|
||||
@ -32,93 +28,38 @@ namespace Data
|
||||
|
||||
public float attackTargetCountPercentOffset = 0f;
|
||||
|
||||
// ======================================================================
|
||||
// 数组偏移 / 集合偏移 (更复杂的情况,如果原始属性是数组/集合)
|
||||
// 因为你的Attributes类目前都是单一值,所以这里先按单一值处理。
|
||||
// 如果 future Attributes.attackTargets 变成 List<string> 或者 int[],
|
||||
// 这里就需要 List<string> addedAttackTargets 或 List<int> addedAttackTargetCounts
|
||||
// 来表示要添加/移除的元素。
|
||||
// 示例:如果将来有一个属性是效果列表,这里可以定义要添加的效果
|
||||
// public List<string> addedEffects = new List<string>();
|
||||
// public List<string> removedEffects = new List<string>();
|
||||
// 如果 `attackTargetCount` 实际代表可以攻击的目标ID数组,那么:
|
||||
// public List<int> addedAttackTargets = new List<int>();
|
||||
// public List<int> removedAttackTargets = new List<int>();
|
||||
// 注意:对于 `attackTargetCount` 来说,它本身是一个 int,
|
||||
// 上面的 `attackTargetCountOffset` 已经足够处理“增加攻击目标数量”的需求。
|
||||
// "数组偏移"通常指的是当原始属性本身是一个集合时,你想要修改这个集合的元素。
|
||||
// 如果 `Attributes` 类保持其当前形式 (都是单一数值),那么不需要专门的数组偏移。
|
||||
// ======================================================================
|
||||
// 构造函数 (可选,用于方便初始化)
|
||||
// ======================================================================
|
||||
public AttributesOffsetDef()
|
||||
{
|
||||
}
|
||||
|
||||
// 可以添加带参数的构造函数,方便快速设置
|
||||
public AttributesOffsetDef(float healthAbs = 0f, float moveSpeedAbs = 0f, float attackAbs = 0f,
|
||||
float defenseAbs = 0f, float attackSpeedAbs = 0f, float attackRangeAbs = 0f,
|
||||
float attackTargetCountAbs = 0f,
|
||||
float healthPct = 0f, float moveSpeedPct = 0f, float attackPct = 0f,
|
||||
float defensePct = 0f, float attackSpeedPct = 0f, float attackRangePct = 0f,
|
||||
float attackTargetCountPct = 0f)
|
||||
{
|
||||
healthOffset = healthAbs;
|
||||
moveSpeedOffset = moveSpeedAbs;
|
||||
attackOffset = attackAbs;
|
||||
defenseOffset = defenseAbs;
|
||||
attackSpeedOffset = attackSpeedAbs;
|
||||
attackRangeOffset = attackRangeAbs;
|
||||
attackTargetCountOffset = attackTargetCountAbs;
|
||||
healthPercentOffset = healthPct;
|
||||
moveSpeedPercentOffset = moveSpeedPct;
|
||||
attackPercentOffset = attackPct;
|
||||
defensePercentOffset = defensePct;
|
||||
attackSpeedPercentOffset = attackSpeedPct;
|
||||
attackRangePercentOffset = attackRangePct;
|
||||
attackTargetCountPercentOffset = attackTargetCountPct;
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// 应用偏移的方法
|
||||
// ======================================================================
|
||||
/// <summary>
|
||||
/// 将此偏移应用到给定的Attributes实例上。
|
||||
/// 重载 + 操作符,用于合并两个 AttributesOffsetDef 实例。
|
||||
/// </summary>
|
||||
/// <param name="baseAttributes">要应用偏移的基础Attributes。</param>
|
||||
/// <returns>应用偏移后的新Attributes实例。</returns>
|
||||
public Attributes ApplyTo(Attributes baseAttributes)
|
||||
/// <param name="a">第一个属性修正定义。</param>
|
||||
/// <param name="b">第二个属性修正定义。</param>
|
||||
/// <returns>一个新的 AttributesOffsetDef 实例,包含两个输入的累加值。</returns>
|
||||
public static AttributesOffsetDef operator +(AttributesOffsetDef a, AttributesOffsetDef b)
|
||||
{
|
||||
// 创建一个新的Attributes实例以避免修改原始实例
|
||||
// 或者如果需要直接修改,可以返回 void
|
||||
Attributes modifiedAttributes = new Attributes
|
||||
// 处理 null 情况,如果其中一个为 null,则返回另一个的副本(或一个空实例如果两者都为 null)。
|
||||
if (a == null && b == null) return new AttributesOffsetDef();
|
||||
if (a == null) return b; // 如果 a 是 null,返回 b 的值
|
||||
if (b == null) return a; // 如果 b 是 null,返回 a 的值
|
||||
var combined = new AttributesOffsetDef
|
||||
{
|
||||
health = baseAttributes.health,
|
||||
moveSpeed = baseAttributes.moveSpeed,
|
||||
attack = baseAttributes.attack,
|
||||
defense = baseAttributes.defense,
|
||||
attackSpeed = baseAttributes.attackSpeed,
|
||||
attackRange = baseAttributes.attackRange,
|
||||
attackTargetCount = baseAttributes.attackTargetCount
|
||||
// 绝对值偏移累加
|
||||
healthOffset = a.healthOffset + b.healthOffset,
|
||||
moveSpeedOffset = a.moveSpeedOffset + b.moveSpeedOffset,
|
||||
attackOffset = a.attackOffset + b.attackOffset,
|
||||
defenseOffset = a.defenseOffset + b.defenseOffset,
|
||||
attackSpeedOffset = a.attackSpeedOffset + b.attackSpeedOffset,
|
||||
attackRangeOffset = a.attackRangeOffset + b.attackRangeOffset,
|
||||
attackTargetCountOffset = a.attackTargetCountOffset + b.attackTargetCountOffset,
|
||||
// 百分比偏移累加
|
||||
healthPercentOffset = a.healthPercentOffset + b.healthPercentOffset,
|
||||
moveSpeedPercentOffset = a.moveSpeedPercentOffset + b.moveSpeedPercentOffset,
|
||||
attackPercentOffset = a.attackPercentOffset + b.attackPercentOffset,
|
||||
defensePercentOffset = a.defensePercentOffset + b.defensePercentOffset,
|
||||
attackSpeedPercentOffset = a.attackSpeedPercentOffset + b.attackSpeedPercentOffset,
|
||||
attackRangePercentOffset = a.attackRangePercentOffset + b.attackRangePercentOffset,
|
||||
attackTargetCountPercentOffset = a.attackTargetCountPercentOffset + b.attackTargetCountPercentOffset
|
||||
};
|
||||
// 首先应用百分比偏移
|
||||
modifiedAttributes.health = (int)(modifiedAttributes.health * (1f + healthPercentOffset));
|
||||
modifiedAttributes.moveSpeed *= (1f + moveSpeedPercentOffset);
|
||||
modifiedAttributes.attack = (int)(modifiedAttributes.attack * (1f + attackPercentOffset));
|
||||
modifiedAttributes.defense = (int)(modifiedAttributes.defense * (1f + defensePercentOffset));
|
||||
modifiedAttributes.attackSpeed = (int)(modifiedAttributes.attackSpeed * (1f + attackSpeedPercentOffset));
|
||||
modifiedAttributes.attackRange = (int)(modifiedAttributes.attackRange * (1f + attackRangePercentOffset));
|
||||
modifiedAttributes.attackTargetCount =
|
||||
(int)(modifiedAttributes.attackTargetCount * (1f + attackTargetCountPercentOffset));
|
||||
// 然后应用绝对值偏移
|
||||
modifiedAttributes.health += (int)healthOffset;
|
||||
modifiedAttributes.moveSpeed += moveSpeedOffset;
|
||||
modifiedAttributes.attack += (int)attackOffset;
|
||||
modifiedAttributes.defense += (int)defenseOffset;
|
||||
modifiedAttributes.attackSpeed += (int)attackSpeedOffset;
|
||||
modifiedAttributes.attackRange += (int)attackRangeOffset;
|
||||
modifiedAttributes.attackTargetCount += (int)attackTargetCountOffset;
|
||||
return modifiedAttributes;
|
||||
return combined;
|
||||
}
|
||||
}
|
||||
}
|
@ -150,6 +150,8 @@ namespace Data
|
||||
public string packID;
|
||||
public string packRootPath;
|
||||
|
||||
public int priority = -1;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
|
@ -13,7 +13,8 @@ namespace Data
|
||||
public BehaviorTreeDef behaviorTree;
|
||||
public AffiliationDef affiliation;
|
||||
|
||||
|
||||
public DrawNodeDef deathAnimation;
|
||||
public EventDef[] deathEffects;
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,5 +4,6 @@ namespace Data
|
||||
{
|
||||
public HediffEventDef hediffEvent;
|
||||
public EntityEventDef entityEvent;
|
||||
public string value;
|
||||
}
|
||||
}
|
@ -3,6 +3,6 @@ namespace Data
|
||||
public class HediffStageDef:Define
|
||||
{
|
||||
public float start;
|
||||
public AffiliationDef attributesOffset = new();
|
||||
public AttributesOffsetDef attributesOffset = new();
|
||||
}
|
||||
}
|
11
Client/Assets/Scripts/Data/SkillTreeDef.cs
Normal file
11
Client/Assets/Scripts/Data/SkillTreeDef.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Data
|
||||
{
|
||||
public class SkillTreeDef:Define
|
||||
{
|
||||
public string tag="Default";
|
||||
public AffiliationDef faction;
|
||||
public SkillTreeDef[] prerequisites;
|
||||
public WeaponDef[] unlockedWeapons;
|
||||
public HediffDef[] unlockedHediffs;
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Data/SkillTreeDef.cs.meta
Normal file
3
Client/Assets/Scripts/Data/SkillTreeDef.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08ca1b39c5b342ceb23bd565d433ac66
|
||||
timeCreated: 1756705012
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Data;
|
||||
|
||||
namespace Entity
|
||||
@ -11,6 +12,7 @@ namespace Entity
|
||||
public int attackSpeed = 2;
|
||||
public int attackRange = 3;
|
||||
public int attackTargetCount = 1;
|
||||
|
||||
public Attributes(AttributesDef def)
|
||||
{
|
||||
health = def.health;
|
||||
@ -21,7 +23,99 @@ namespace Entity
|
||||
attackRange = def.attackRange;
|
||||
attackTargetCount = def.attackTargetCount;
|
||||
}
|
||||
|
||||
public Attributes(Attributes other)
|
||||
{
|
||||
health = other.health;
|
||||
moveSpeed = other.moveSpeed;
|
||||
attack = other.attack;
|
||||
defense = other.defense;
|
||||
attackSpeed = other.attackSpeed;
|
||||
attackRange = other.attackRange;
|
||||
attackTargetCount = other.attackTargetCount;
|
||||
}
|
||||
|
||||
public Attributes()
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据给定的属性偏移,生成一个新的 Attributes 实例。
|
||||
/// 原有的 Attributes 实例保持不变。
|
||||
/// </summary>
|
||||
/// <param name="offset">要应用的属性偏移定义。</param>
|
||||
/// <returns>一个新的 Attributes 实例,包含了应用偏移后的值。</returns>
|
||||
public Attributes GetModifiedAttributes(AttributesOffsetDef offset)
|
||||
{
|
||||
// 1. 创建当前 Attributes 实例的一个副本
|
||||
var newAttributes = new Attributes(this);
|
||||
|
||||
if (offset == null)
|
||||
{
|
||||
return newAttributes; // 如果没有偏移,直接返回副本
|
||||
}
|
||||
|
||||
// 2. 在副本上应用绝对值偏移
|
||||
newAttributes.health += (int)offset.healthOffset;
|
||||
newAttributes.moveSpeed += offset.moveSpeedOffset;
|
||||
newAttributes.attack += (int)offset.attackOffset;
|
||||
newAttributes.defense += (int)offset.defenseOffset;
|
||||
newAttributes.attackSpeed += (int)offset.attackSpeedOffset;
|
||||
newAttributes.attackRange += (int)offset.attackRangeOffset;
|
||||
newAttributes.attackTargetCount += (int)offset.attackTargetCountOffset;
|
||||
|
||||
// 3. 在副本上应用百分比偏移 (基于应用绝对值偏移后的结果)
|
||||
newAttributes.health = (int)(newAttributes.health * (1 + offset.healthPercentOffset));
|
||||
newAttributes.moveSpeed *= (1 + offset.moveSpeedPercentOffset);
|
||||
newAttributes.attack = (int)(newAttributes.attack * (1 + offset.attackPercentOffset));
|
||||
newAttributes.defense = (int)(newAttributes.defense * (1 + offset.defensePercentOffset));
|
||||
newAttributes.attackSpeed = (int)(newAttributes.attackSpeed * (1 + offset.attackSpeedPercentOffset));
|
||||
newAttributes.attackRange = (int)(newAttributes.attackRange * (1 + offset.attackRangePercentOffset));
|
||||
newAttributes.attackTargetCount =
|
||||
(int)(newAttributes.attackTargetCount * (1 + offset.attackTargetCountPercentOffset));
|
||||
|
||||
// 4. 确保属性不低于最小值
|
||||
newAttributes.health = Math.Max(0, newAttributes.health);
|
||||
newAttributes.moveSpeed = Math.Max(0f, newAttributes.moveSpeed);
|
||||
newAttributes.attack = Math.Max(0, newAttributes.attack);
|
||||
newAttributes.defense = Math.Max(0, newAttributes.defense);
|
||||
newAttributes.attackSpeed = Math.Max(0, newAttributes.attackSpeed);
|
||||
newAttributes.attackRange = Math.Max(0, newAttributes.attackRange);
|
||||
newAttributes.attackTargetCount = Math.Max(1, newAttributes.attackTargetCount);
|
||||
|
||||
// 5. 返回修改后的新 Attributes 实例
|
||||
return newAttributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合并两个 Attributes 实例,生成一个新的 Attributes 实例,
|
||||
/// 其中每个属性值都取自传入两个实例中对应属性的最小值。
|
||||
/// 这对于应用属性上限或限制非常有用。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个 Attributes 实例。</param>
|
||||
/// <param name="b">第二个 Attributes 实例。</param>
|
||||
/// <returns>一个新的 Attributes 实例,其属性是输入实例中对应属性的最小值。</returns>
|
||||
public static Attributes Min(Attributes a, Attributes b)
|
||||
{
|
||||
// 处理 null 情况
|
||||
if (a == null && b == null) return new Attributes(); // 两者都为null,返回默认空属性
|
||||
if (a == null) return new Attributes(b); // a为null,返回b的副本
|
||||
if (b == null) return new Attributes(a); // b为null,返回a的副本
|
||||
|
||||
// 创建一个新的 Attributes 实例来存储结果
|
||||
var result = new Attributes
|
||||
{
|
||||
health = Math.Min(a.health, b.health),
|
||||
moveSpeed = Math.Min(a.moveSpeed, b.moveSpeed),
|
||||
attack = Math.Min(a.attack, b.attack),
|
||||
defense = Math.Min(a.defense, b.defense),
|
||||
attackSpeed = Math.Min(a.attackSpeed, b.attackSpeed),
|
||||
attackRange = Math.Min(a.attackRange, b.attackRange),
|
||||
attackTargetCount = Math.Min(a.attackTargetCount, b.attackTargetCount)
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ using Utils;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class Character : Entity
|
||||
public class Character : LivingEntity
|
||||
{
|
||||
private int _currentSelected; // 私有字段用于存储实际值
|
||||
|
||||
|
@ -52,7 +52,13 @@ namespace Entity
|
||||
/// <summary>
|
||||
/// 实体的属性定义,包括生命值、攻击力、防御力等。
|
||||
/// </summary>
|
||||
public Attributes attributes = new();
|
||||
public virtual Attributes attributes { get; protected set; }
|
||||
|
||||
private Attributes _baseAttributes;
|
||||
public virtual Attributes baseAttributes
|
||||
{
|
||||
get { return _baseAttributes ??= new Attributes(entityDef.attributes); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体当前的移动方向。
|
||||
@ -169,8 +175,6 @@ namespace Entity
|
||||
// 协程引用
|
||||
private Coroutine _attackCoroutine;
|
||||
|
||||
protected List<Hediff> hediffs;
|
||||
|
||||
|
||||
[SerializeField] private float _hitBarUIShowTime = 5;
|
||||
private float _hitBarUIShowTimer = 0;
|
||||
@ -462,7 +466,7 @@ namespace Entity
|
||||
/// <summary>
|
||||
/// 更新实体的逻辑,包括玩家控制和自动行为。
|
||||
/// </summary>
|
||||
public void Tick()
|
||||
public virtual void Tick()
|
||||
{
|
||||
if (_walkingTimer > 0)
|
||||
{
|
||||
@ -523,7 +527,7 @@ namespace Entity
|
||||
if (IsAttacking || IsDead) return; // 死亡时无法攻击
|
||||
|
||||
// 尝试获取当前武器
|
||||
WeaponResource currentWeapon = GetCurrentWeapon();
|
||||
var currentWeapon = GetCurrentWeapon();
|
||||
|
||||
// 如果没有武器,可以选择进行徒手攻击或者直接返回
|
||||
// 暂时设定为:如果没有武器,则不进行攻击
|
||||
@ -777,7 +781,7 @@ namespace Entity
|
||||
}
|
||||
|
||||
// STEP 4: 等待到攻击判定时间
|
||||
float elapsedTime = 0f;
|
||||
var elapsedTime = 0f;
|
||||
while (elapsedTime < weapon.AttackDetectionTime)
|
||||
{
|
||||
if (IsDead)
|
||||
@ -799,7 +803,7 @@ namespace Entity
|
||||
|
||||
ExecuteWeaponAction(weapon);
|
||||
|
||||
float remainingAnimationTime = weapon.AttackAnimationTime - elapsedTime;
|
||||
var remainingAnimationTime = weapon.AttackAnimationTime - elapsedTime;
|
||||
if (remainingAnimationTime > 0)
|
||||
{
|
||||
yield return new WaitForSeconds(remainingAnimationTime);
|
||||
@ -884,10 +888,10 @@ namespace Entity
|
||||
|
||||
// 获取子弹方向。这里使用实体当前的移动方向作为子弹发射方向
|
||||
// 更复杂的逻辑可能根据鼠标位置、目标位置等确定
|
||||
Vector3 bulletDirection = direction; // 实体当前的朝向
|
||||
var bulletDirection = direction; // 实体当前的朝向
|
||||
if (PlayerControlled && Input.GetMouseButton(0)) // 玩家控制时,如果鼠标按下,尝试朝鼠标方向发射
|
||||
{
|
||||
Vector3 mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||
var mouseWorldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||
mouseWorldPos.z = transform.position.z; // 保持Z轴一致
|
||||
bulletDirection = (mouseWorldPos - transform.position).normalized;
|
||||
}
|
||||
|
@ -1,15 +1,276 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Data;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义一个接口,用于提供属性偏移量。
|
||||
/// 任何实现此接口的组件都可以贡献实体的属性修正。
|
||||
/// </summary>
|
||||
public interface IAttributesOffsetProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取此提供者当前的属性偏移量定义。
|
||||
/// </summary>
|
||||
/// <returns>此提供者所带来的属性偏移量。</returns>
|
||||
AttributesOffsetDef GetAttributesOffset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个实体上运行时存在的健康状态(Hediff)。
|
||||
/// Hediff 可以是疾病、增益、减益、伤口等,它们会影响实体的属性、行为或状态。
|
||||
/// </summary>
|
||||
public class Hediff
|
||||
{
|
||||
public float Duration{get;private set;}
|
||||
/// <summary>
|
||||
/// 此 Hediff 的定义数据。
|
||||
/// </summary>
|
||||
public HediffDef def { get; private set; }
|
||||
/// <summary>
|
||||
/// 此 Hediff 附加到的活体实体。
|
||||
/// </summary>
|
||||
public LivingEntity parent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 此 Hediff 存在的当前年龄(以秒为单位)。
|
||||
/// </summary>
|
||||
public float Age { get; private set; } = 0f;
|
||||
/// <summary>
|
||||
/// 当前激活的 Hediff 阶段的索引。
|
||||
/// </summary>
|
||||
private int _currentStageIndex = -1;
|
||||
|
||||
public Hediff(HediffDef def)
|
||||
/// <summary>
|
||||
/// 附加到此 Hediff 上的所有组件列表。
|
||||
/// </summary>
|
||||
public List<HediffComp> Comps { get; private set; } = new List<HediffComp>();
|
||||
|
||||
/// <summary>
|
||||
/// 标志,指示此 Hediff 是否应该被父实体移除。
|
||||
/// </summary>
|
||||
public bool ShouldRemove { get; private set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 内部缓存,用于存储计算出的总属性偏移量。
|
||||
/// </summary>
|
||||
private AttributesOffsetDef _cachedTotalAttributesOffset;
|
||||
/// <summary>
|
||||
/// 标志,指示 Hediff 的属性偏移量是否需要重新计算。
|
||||
/// </summary>
|
||||
private bool _attribsDirty = true;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,创建一个新的运行时 Hediff 实例。
|
||||
/// </summary>
|
||||
/// <param name="definition">此 Hediff 的定义。</param>
|
||||
/// <exception cref="ArgumentNullException">如果传入的定义为空。</exception>
|
||||
public Hediff(HediffDef definition)
|
||||
{
|
||||
if (definition == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(definition), "Hediff 定义不能为空。");
|
||||
}
|
||||
|
||||
this.def = definition;
|
||||
this._attribsDirty = true; // 构造时标记需要计算属性
|
||||
|
||||
// 确保阶段列表按开始时间排序,以便正确判断当前阶段。
|
||||
if (this.def.stages == null) this.def.stages = new List<HediffStageDef>();
|
||||
this.def.stages = this.def.stages.OrderBy(s => s.start).ToList();
|
||||
|
||||
// 实例化所有定义的组件。
|
||||
if (def.comps != null)
|
||||
{
|
||||
foreach (var compDef in def.comps)
|
||||
{
|
||||
if (compDef.compClass != null && typeof(HediffComp).IsAssignableFrom(compDef.compClass))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用 Activator.CreateInstance 动态创建组件实例,并传入构造函数参数。
|
||||
// HediffComp 的构造函数需要接受 Hediff parentHediff 和 HediffCompDef def。
|
||||
var comp = (HediffComp)Activator.CreateInstance(compDef.compClass, this, compDef);
|
||||
Comps.Add(comp);
|
||||
comp.Initialize(); // 初始化组件
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"实例化健康状态组件 '{compDef.compClass?.Name ?? "空"}' 失败,所属健康状态 '{def.defName ?? def.GetType().Name}':{ex.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"警告:健康状态组件定义 '{compDef.compClass?.Name ?? "空"}' 无效或未继承自 HediffComp,所属健康状态 '{def.defName ?? def.GetType().Name}'。");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化时确定第一个阶段,这会触发 SetDirty()。
|
||||
UpdateStageIndex();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取此 Hediff 当前激活的阶段定义。
|
||||
/// 如果没有激活的阶段或阶段列表为空,则返回 null。
|
||||
/// </summary>
|
||||
public HediffStageDef CurrentStage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_currentStageIndex >= 0 && _currentStageIndex < def.stages.Count)
|
||||
{
|
||||
return def.stages[_currentStageIndex];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取此 Hediff 当前累计的总属性偏移量。
|
||||
/// 只有当属性被标记为“脏”时才重新计算,否则返回缓存值。
|
||||
/// </summary>
|
||||
public AttributesOffsetDef CurrentTotalAttributesOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
// 只有当属性需要更新时才重新计算。
|
||||
if (_attribsDirty || _cachedTotalAttributesOffset == null)
|
||||
{
|
||||
_cachedTotalAttributesOffset = CalculateTotalAttributesOffset();
|
||||
_attribsDirty = false; // 计算完成后重置标志。
|
||||
}
|
||||
return _cachedTotalAttributesOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部方法,用于实际计算 Hediff 的所有总属性偏移量。
|
||||
/// 这包括当前阶段的偏移量和所有组件提供的偏移量。
|
||||
/// </summary>
|
||||
/// <returns>此 Hediff 当前的总属性偏移量。</returns>
|
||||
private AttributesOffsetDef CalculateTotalAttributesOffset()
|
||||
{
|
||||
var totalOffset = new AttributesOffsetDef();
|
||||
if (CurrentStage != null)
|
||||
{
|
||||
totalOffset += CurrentStage.attributesOffset;
|
||||
}
|
||||
|
||||
// 累加来自组件的属性偏移。
|
||||
foreach (var comp in Comps)
|
||||
{
|
||||
if (comp is IAttributesOffsetProvider attributesOffsetProvider)
|
||||
{
|
||||
totalOffset += attributesOffsetProvider.GetAttributesOffset();
|
||||
}
|
||||
}
|
||||
return totalOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记 Hediff 的属性偏移量需要重新计算。
|
||||
/// 当 Hediff 的内部状态改变并可能影响属性时调用。
|
||||
/// 同时会通知其父 LivingEntity,使其也更新属性缓存。
|
||||
/// </summary>
|
||||
internal void SetDirty()
|
||||
{
|
||||
_attribsDirty = true;
|
||||
// 如果此 Hediff 附加到了一个 LivingEntity 上,也通知 LivingEntity 属性可能改变。
|
||||
if (parent)
|
||||
{
|
||||
parent.SetAttribsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 Hediff 的年龄,检查阶段变化并更新所有组件。
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">自上次更新以来的时间(秒)。</param>
|
||||
public void Tick(float deltaTime)
|
||||
{
|
||||
if (ShouldRemove)
|
||||
return; // 已经标记为移除,无需继续更新。
|
||||
|
||||
Age += deltaTime; // 增加 Hediff 的年龄。
|
||||
|
||||
UpdateStageIndex(); // 检查是否有阶段变化(此方法内部会调用 SetDirty)。
|
||||
|
||||
// 更新所有组件。
|
||||
foreach (var comp in Comps)
|
||||
{
|
||||
comp.Tick(deltaTime);
|
||||
}
|
||||
|
||||
// 检查 Hediff 是否到期(如果 def.time > 0 表示有时限,否则为永久)。
|
||||
if (def.time > 0 && Age >= def.time)
|
||||
{
|
||||
ShouldRemove = true;
|
||||
SetDirty(); // Hediff 将被移除,其贡献的属性将不再有效,标记为脏。
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前年龄更新阶段索引。
|
||||
/// 如果阶段发生变化,会调用 SetDirty()。
|
||||
/// </summary>
|
||||
private void UpdateStageIndex()
|
||||
{
|
||||
var originalStageIndex = _currentStageIndex; // 获取当前阶段索引。
|
||||
var newStageIndex = _currentStageIndex;
|
||||
|
||||
// 从后往前遍历阶段,找到当前年龄所属的最高阶段。
|
||||
for (var i = def.stages.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (def.stages[i].start <= Age)
|
||||
{
|
||||
newStageIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果阶段发生变化,则标记为脏。
|
||||
if (newStageIndex != originalStageIndex)
|
||||
{
|
||||
_currentStageIndex = newStageIndex;
|
||||
// 阶段发生变化,属性偏移可能改变,标记为脏。
|
||||
SetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当此 Hediff 被添加到实体时调用。
|
||||
/// 进行初始化设置,如存储父实体引用,并标记属性为脏以强制重新计算。
|
||||
/// </summary>
|
||||
/// <param name="entity">此 Hediff 附加到的 LivingEntity 实例。</param>
|
||||
internal void OnAdded(LivingEntity entity)
|
||||
{
|
||||
this.parent = entity; // 存储父实体引用。
|
||||
SetDirty(); // 刚添加也需要标记为脏以确保属性在下次计算时被纳入。
|
||||
|
||||
foreach (var comp in Comps)
|
||||
{
|
||||
comp.OnAdded();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当此 Hediff 从实体移除时调用。
|
||||
/// 进行清理操作,如清除父实体引用,并标记属性为脏以强制重新计算。
|
||||
/// </summary>
|
||||
/// <param name="entity">此 Hediff 从中移除的 LivingEntity 实例。</param>
|
||||
internal void OnRemoved(LivingEntity entity)
|
||||
{
|
||||
foreach (var comp in Comps)
|
||||
{
|
||||
comp.OnRemoved();
|
||||
}
|
||||
this.parent = null; // 清除对父实体的引用。
|
||||
SetDirty(); // 移除后也需要标记为脏,确保属性计算考虑去除此Hediff。
|
||||
}
|
||||
}
|
||||
}
|
47
Client/Assets/Scripts/Entity/HediffComp.cs
Normal file
47
Client/Assets/Scripts/Entity/HediffComp.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using Data;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
// 运行时健康状态组件的抽象基类
|
||||
public abstract class HediffComp
|
||||
{
|
||||
protected Hediff parentHediff; // 对父 Hediff 的引用
|
||||
protected HediffCompDef def; // 对组件定义的引用
|
||||
|
||||
public HediffComp(Hediff parentHediff, HediffCompDef def)
|
||||
{
|
||||
this.parentHediff = parentHediff;
|
||||
this.def = def;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组件初始化时调用,在构造函数之后。
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每帧更新时调用。
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">自上次更新以来的时间(秒)。</param>
|
||||
public virtual void Tick(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当其父 Hediff 被添加到实体时调用。
|
||||
/// </summary>
|
||||
public virtual void OnAdded()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当其父 Hediff 从实体移除时调用。
|
||||
/// </summary>
|
||||
public virtual void OnRemoved()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
3
Client/Assets/Scripts/Entity/HediffComp.cs.meta
Normal file
3
Client/Assets/Scripts/Entity/HediffComp.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3f6a16d500840bea821c21ea612a5c6
|
||||
timeCreated: 1756612166
|
162
Client/Assets/Scripts/Entity/LivingEntity.cs
Normal file
162
Client/Assets/Scripts/Entity/LivingEntity.cs
Normal file
@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Data;
|
||||
using Managers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
// 假设 Entity 基类如下结构
|
||||
// public abstract class Entity
|
||||
// {
|
||||
// public virtual Attributes baseAttributes { get; protected set; } = new Attributes();
|
||||
// public virtual Attributes attributes { get; protected set; } = new Attributes();
|
||||
// public virtual void Tick(float deltaTime) { /* Base entity update logic */ }
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个具有生命周期、属性和可受健康状态(Hediff)影响的实体。
|
||||
/// </summary>
|
||||
public class LivingEntity : Entity
|
||||
{
|
||||
// 存储应用于此实体的所有健康状态(Hediff)列表。
|
||||
protected List<Hediff> hediffs = new List<Hediff>();
|
||||
// 标记实体属性是否需要重新计算。当Hediff发生变化时,此标记会被设置为true。
|
||||
private bool _needUpdateAttributes = true;
|
||||
|
||||
// 缓存实体的基础属性,这些属性不受动态Hediff影响,但可能受基类或Def影响。
|
||||
private Attributes _cachedBaseAttributes;
|
||||
/// <summary>
|
||||
/// 获取实体的基础属性。这些属性通常来源于实体的定义(Def),并可能受到常驻的基础健康状态(Base Hediffs)影响。
|
||||
/// </summary>
|
||||
public override Attributes baseAttributes
|
||||
{
|
||||
get
|
||||
{
|
||||
// 仅在 _cachedBaseAttributes 为 null 时计算一次
|
||||
if(_cachedBaseAttributes == null)
|
||||
{
|
||||
var defAttributes = base.baseAttributes;
|
||||
var hediffOffset = new AttributesOffsetDef();
|
||||
|
||||
// 这里假设 SaveManager.Instance.baseHediffs 指的是“所有实体共通的基础Hediff”
|
||||
// 并且这些基础 Hediff 也会影响 baseAttributes
|
||||
foreach (var hediff in SaveManager.Instance.baseHediffs)
|
||||
{
|
||||
hediffOffset += hediff.CurrentTotalAttributesOffset;
|
||||
}
|
||||
_cachedBaseAttributes = defAttributes.GetModifiedAttributes(hediffOffset);
|
||||
}
|
||||
return _cachedBaseAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存实体当前的最终属性,该属性是基础属性加上所有健康状态(Hediff)修正后的结果。
|
||||
private Attributes _cachedAttributes;
|
||||
/// <summary>
|
||||
/// 获取实体当前的最终属性,包括所有健康状态(Hediff)的修正。
|
||||
/// </summary>
|
||||
public override Attributes attributes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_needUpdateAttributes || _cachedAttributes == null)
|
||||
{
|
||||
// 1. 获取旧的属性值(在重新计算之前,也就是当前的缓存值)
|
||||
// 仅用于需要与“当前值”进行比较或钳制的情况,例如最大生命值Buff移除时
|
||||
var oldCachedAttributes = _cachedAttributes;
|
||||
if (oldCachedAttributes == null) // 如果是第一次计算,初始化一个默认值或者使用baseAttributes
|
||||
{
|
||||
oldCachedAttributes = baseAttributes;
|
||||
}
|
||||
|
||||
// 2. 计算完全修正后的“理论”最大属性值(基于 baseAttributes 和所有 Hediff 偏移)
|
||||
var totalModifiedAttributes = baseAttributes;
|
||||
|
||||
var hediffOffset = new AttributesOffsetDef();
|
||||
foreach (var hediff in hediffs)
|
||||
{
|
||||
hediffOffset += hediff.CurrentTotalAttributesOffset;
|
||||
}
|
||||
|
||||
// 应用所有 hediff 的偏移到 totalModifiedAttributes
|
||||
totalModifiedAttributes = totalModifiedAttributes.GetModifiedAttributes(hediffOffset);
|
||||
_cachedAttributes = Attributes.Min(oldCachedAttributes, totalModifiedAttributes);
|
||||
// 标记为已更新
|
||||
_needUpdateAttributes = false;
|
||||
}
|
||||
return _cachedAttributes;
|
||||
}
|
||||
protected set => _cachedAttributes = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 供内部使用的属性标记方法。当 Hediff 自身状态改变并影响属性时,通过此方法通知 LivingEntity。
|
||||
/// </summary>
|
||||
internal void SetAttribsDirty()
|
||||
{
|
||||
_needUpdateAttributes = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每帧调用的更新函数,传入时间增量。
|
||||
/// </summary>
|
||||
public override void Tick()
|
||||
{
|
||||
base.Tick(); // 调用基类的Tick方法
|
||||
|
||||
// 遍历并更新所有健康状态,从后向前循环以安全地移除已完成的Hediff
|
||||
for (var i = hediffs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var hediff = hediffs[i];
|
||||
hediff.Tick(Time.deltaTime); // 调用单个Hediff的Tick方法
|
||||
|
||||
// 检查Hediff是否已达到移除条件
|
||||
if (hediff.ShouldRemove)
|
||||
{
|
||||
RemoveHediff(hediff); // 使用RemoveHediff方法确保OnRemoved被调用并设置_needUpdateAttributes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个新的健康状态到实体上。
|
||||
/// </summary>
|
||||
/// <param name="hediff">要添加的 Hediff 实例。</param>
|
||||
public void AddHediff(Hediff hediff)
|
||||
{
|
||||
if (hediff == null)
|
||||
{
|
||||
Debug.LogWarning("尝试向活体实体添加一个空的健康状态(Hediff)。");
|
||||
return;
|
||||
}
|
||||
|
||||
hediffs.Add(hediff);
|
||||
// 通知Hediff它被添加到一个实体上,进行初始化等操作,并传入自身引用
|
||||
hediff.OnAdded(this);
|
||||
_needUpdateAttributes = true; // 添加新Hediff,需要更新属性缓存
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除一个特定的健康状态。
|
||||
/// </summary>
|
||||
/// <param name="hediff">要移除的 Hediff 实例。</param>
|
||||
public void RemoveHediff(Hediff hediff)
|
||||
{
|
||||
if (hediff == null)
|
||||
{
|
||||
Debug.LogWarning("尝试从活体实体移除一个空的健康状态(Hediff)。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试从列表中移除Hediff
|
||||
if (hediffs.Remove(hediff))
|
||||
{
|
||||
// 通知Hediff它被从实体上移除,进行清理等操作,并传入自身引用
|
||||
hediff.OnRemoved(this);
|
||||
_needUpdateAttributes = true; // 移除Hediff,需要更新属性缓存
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Entity/LivingEntity.cs.meta
Normal file
3
Client/Assets/Scripts/Entity/LivingEntity.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 440140899cba41b3a023f86e27e69909
|
||||
timeCreated: 1756632414
|
@ -4,7 +4,7 @@ using Managers;
|
||||
|
||||
namespace Entity
|
||||
{
|
||||
public class Monster:Entity
|
||||
public class Monster:LivingEntity
|
||||
{
|
||||
private WeaponResource weapon;
|
||||
public override void Init(EntityDef entityDef)
|
||||
|
@ -1,12 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Item
|
||||
{
|
||||
public class ItemBase
|
||||
{
|
||||
public ItemResource resource;
|
||||
|
||||
public int count=0;
|
||||
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01a2843d5856483fa5b6967e2e01db62
|
||||
timeCreated: 1755061705
|
@ -67,17 +67,73 @@ namespace Managers
|
||||
if (packs.Count > 0)
|
||||
return;
|
||||
|
||||
// 获取所有定义包的文件夹路径
|
||||
// // 获取所有定义包的文件夹路径
|
||||
// var packFolder = Configs.ConfigProcessor.GetSubFolders(new(dataSetFilePath));
|
||||
// foreach (var folder in packFolder)
|
||||
// {
|
||||
// var pack = new DefinePack();
|
||||
// if (pack.LoadPack(folder))
|
||||
// {
|
||||
// packs.Add(pack.packID, pack);
|
||||
// }
|
||||
// }
|
||||
var packFolder = Configs.ConfigProcessor.GetSubFolders(new(dataSetFilePath));
|
||||
|
||||
// 获取当前的加载顺序
|
||||
var currentOrder = Base.Setting.Instance.CurrentSettings.loadOrder; // 假设为 string[]
|
||||
var isFirstLaunch = currentOrder == null || currentOrder.Length == 0;
|
||||
|
||||
var newOrder = new List<string>(); // 用于最终写回
|
||||
|
||||
foreach (var folder in packFolder)
|
||||
{
|
||||
var pack = new DefinePack();
|
||||
if (pack.LoadPack(folder))
|
||||
{
|
||||
// 根据加载顺序设置 priority
|
||||
int priority;
|
||||
|
||||
if (isFirstLaunch)
|
||||
{
|
||||
// 第一次启动,按遍历顺序设置 priority
|
||||
// 暂时使用新增时的顺序作为 priority,顺序从 newOrder 的索引确定
|
||||
priority = newOrder.Count;
|
||||
// 记录该 pack 的 ID,后续写回 loadOrder
|
||||
newOrder.Add(pack.packID);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非首次启动,使用现有 loadOrder 中的位置来设置 priority
|
||||
var idx = Array.IndexOf(currentOrder, pack.packID);
|
||||
if (idx >= 0)
|
||||
{
|
||||
priority = idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 未出现在现有顺序中,放在末尾
|
||||
priority = currentOrder.Length;
|
||||
// 可选:也将其加入 newOrder 以更新 loadOrder
|
||||
}
|
||||
}
|
||||
|
||||
pack.priority = priority;
|
||||
packs.Add(pack.packID, pack);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是第一次启动,写回 loadOrder(顺序为 newOrder)
|
||||
if (isFirstLaunch)
|
||||
{
|
||||
// 将 newOrder 转换为 string[],并写回设置
|
||||
Base.Setting.Instance.CurrentSettings.loadOrder = new string[newOrder.Count];
|
||||
newOrder.CopyTo(Base.Setting.Instance.CurrentSettings.loadOrder);
|
||||
// 可能需要保存设置到磁盘/持久化
|
||||
// Example: Settings.Save(Base.Setting.Instance.CurrentSettings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 字段信息缓存,用于优化反射性能。
|
||||
Dictionary<Type, FieldInfo[]> fieldCache = new();
|
||||
|
||||
@ -130,6 +186,7 @@ namespace Managers
|
||||
anonymousDefines.Add(typeName, new List<Define>());
|
||||
anonymousDefines[typeName].Add(defRef);
|
||||
}
|
||||
|
||||
ProcessDefine(defRef);
|
||||
}
|
||||
}
|
||||
@ -208,7 +265,8 @@ namespace Managers
|
||||
var value = FindDefine(defRef.Item3.description, defRef.Item3.defName);
|
||||
if (value == null)
|
||||
{
|
||||
Debug.LogError($"未找到引用,出错的定义:定义类型:{defRef.Item1.GetType().Name}, 定义名:{defRef.Item1.defName} ; 类型:{defRef.Item3.description}, 定义名:{defRef.Item3.defName}");
|
||||
Debug.LogError(
|
||||
$"未找到引用,出错的定义:定义类型:{defRef.Item1.GetType().Name}, 定义名:{defRef.Item1.defName} ; 类型:{defRef.Item3.description}, 定义名:{defRef.Item3.defName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
10
Client/Assets/Scripts/Managers/SaveManager.cs
Normal file
10
Client/Assets/Scripts/Managers/SaveManager.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Entity;
|
||||
|
||||
namespace Managers
|
||||
{
|
||||
public class SaveManager:Utils.Singleton<SaveManager>
|
||||
{
|
||||
public List<Hediff> baseHediffs = new();
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Managers/SaveManager.cs.meta
Normal file
3
Client/Assets/Scripts/Managers/SaveManager.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: daa6e65f38a5495492e994e89eba53f4
|
||||
timeCreated: 1756655387
|
138
Client/Assets/Scripts/Prefab/BaseAnimator.cs
Normal file
138
Client/Assets/Scripts/Prefab/BaseAnimator.cs
Normal file
@ -0,0 +1,138 @@
|
||||
// Base/BaseAnimator.cs
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Base; // 假设ITick接口在这个命名空间
|
||||
|
||||
namespace Base
|
||||
{
|
||||
// 抽象基类,封装通用动画逻辑
|
||||
public abstract class BaseAnimator : MonoBehaviour, ITick
|
||||
{
|
||||
// 通用公开字段(可在编辑器中设置)
|
||||
[SerializeField] protected Sprite[] _sprites; // 动画精灵序列
|
||||
[SerializeField] protected float _fps = 2; // 每秒帧数
|
||||
[SerializeField] protected Sprite _staticSprite; // 暂停时的静态精灵
|
||||
|
||||
// 通用内部状态
|
||||
protected bool _isPaused; // 暂停状态
|
||||
protected float _frameTimer; // 帧计时器
|
||||
protected int _currentFrameIndex; // 当前帧索引
|
||||
|
||||
// 抽象方法:子类必须实现以获取并验证其特有的显示组件
|
||||
protected abstract void ValidateComponent();
|
||||
|
||||
// 抽象方法:子类必须实现以设置实际显示组件的Sprite
|
||||
protected abstract void SetDisplaySprite(Sprite sprite);
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
ValidateComponent(); // 子类获取组件
|
||||
ValidateStartFrame(); // 初始化第一帧
|
||||
}
|
||||
|
||||
// ITick接口实现
|
||||
public void Tick()
|
||||
{
|
||||
var deltaTime = Time.deltaTime;
|
||||
if (_isPaused)
|
||||
{
|
||||
HandlePausedState();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayAnimation(deltaTime);
|
||||
}
|
||||
|
||||
protected void ValidateStartFrame()
|
||||
{
|
||||
// 确保有精灵时可显示有效帧
|
||||
if (_sprites != null && _sprites.Length > 0)
|
||||
{
|
||||
_currentFrameIndex = Mathf.Clamp(_currentFrameIndex, 0, _sprites.Length - 1);
|
||||
SetDisplaySprite(_sprites[_currentFrameIndex]); // 调用抽象方法设置Sprite
|
||||
}
|
||||
else
|
||||
{
|
||||
SetDisplaySprite(null); // 调用抽象方法清空Sprite
|
||||
}
|
||||
}
|
||||
|
||||
protected void HandlePausedState()
|
||||
{
|
||||
// 优先使用静态精灵,否则保持当前帧
|
||||
if (_staticSprite)
|
||||
{
|
||||
SetDisplaySprite(_staticSprite); // 调用抽象方法设置Sprite
|
||||
}
|
||||
// 否则,保持当前显示的Sprite,不需要额外操作(SetDisplaySprite已在NextFrame中设置)
|
||||
}
|
||||
|
||||
protected void PlayAnimation(float deltaTime)
|
||||
{
|
||||
if (_sprites == null || _sprites.Length == 0)
|
||||
{
|
||||
// 如果没有精灵,确保显示组件的Sprite被清除
|
||||
SetDisplaySprite(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新帧计时器
|
||||
_frameTimer += deltaTime;
|
||||
var frameDuration = 1f / _fps;
|
||||
|
||||
// 检查帧切换条件
|
||||
while (_frameTimer >= frameDuration)
|
||||
{
|
||||
_frameTimer -= frameDuration;
|
||||
NextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
protected void NextFrame()
|
||||
{
|
||||
if (_sprites == null || _sprites.Length == 0) return;
|
||||
|
||||
// 循环播放动画
|
||||
_currentFrameIndex = (_currentFrameIndex + 1) % _sprites.Length;
|
||||
SetDisplaySprite(_sprites[_currentFrameIndex]); // 调用抽象方法更新Sprite
|
||||
}
|
||||
|
||||
// 外部控制方法
|
||||
public void SetPaused(bool paused) => _isPaused = paused;
|
||||
|
||||
public void SetSprites(Sprite[] newSprites)
|
||||
{
|
||||
_sprites = newSprites;
|
||||
|
||||
// 如果有新的精灵数组,则立即显示第一帧
|
||||
if (_sprites != null && _sprites.Length > 0)
|
||||
{
|
||||
_currentFrameIndex = 0; // 重置当前帧索引为第一帧
|
||||
SetDisplaySprite(_sprites[_currentFrameIndex]); // 立即显示第一帧
|
||||
}
|
||||
else
|
||||
{
|
||||
SetDisplaySprite(null); // 如果没有精灵,则清空渲染器
|
||||
}
|
||||
|
||||
// 重置帧计时器,以确保从头开始播放
|
||||
_frameTimer = 0f;
|
||||
}
|
||||
|
||||
public void Restore()
|
||||
{
|
||||
_currentFrameIndex = 0;
|
||||
if (_sprites != null && _sprites.Length > 0)
|
||||
{
|
||||
SetDisplaySprite(_sprites[_currentFrameIndex]); // 恢复到第一帧
|
||||
}
|
||||
else
|
||||
{
|
||||
SetDisplaySprite(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetFPS(float newFPS) => _fps = Mathf.Max(0.1f, newFPS);
|
||||
public void SetStaticSprite(Sprite sprite) => _staticSprite = sprite;
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Prefab/BaseAnimator.cs.meta
Normal file
3
Client/Assets/Scripts/Prefab/BaseAnimator.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84dda25302c44253949e7818cd62b7e7
|
||||
timeCreated: 1756778358
|
10
Client/Assets/Scripts/Prefab/BuffIconUI.cs
Normal file
10
Client/Assets/Scripts/Prefab/BuffIconUI.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Prefab
|
||||
{
|
||||
public class BuffIconUI: MonoBehaviour
|
||||
{
|
||||
public UIImageAnimator icon;
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Prefab/BuffIconUI.cs.meta
Normal file
3
Client/Assets/Scripts/Prefab/BuffIconUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17587b13f9d4467dbff77cf9762dc8fe
|
||||
timeCreated: 1756780876
|
@ -1,115 +1,36 @@
|
||||
using System;
|
||||
using Base;
|
||||
using UnityEngine;
|
||||
using Base; // 引入Base命名空间以使用BaseAnimator
|
||||
|
||||
namespace Prefab
|
||||
{
|
||||
[RequireComponent(typeof(SpriteRenderer))]
|
||||
public class SpriteAnimator : MonoBehaviour, ITick
|
||||
public class SpriteAnimator : BaseAnimator
|
||||
{
|
||||
// 公开字段(可在编辑器中设置)
|
||||
[SerializeField] private Sprite[] _sprites; // 动画精灵序列
|
||||
[SerializeField] private float _fps = 2; // 每秒帧数
|
||||
[SerializeField] private Sprite _staticSprite; // 暂停时的静态精灵
|
||||
|
||||
private SpriteRenderer _renderer; // 渲染器组件
|
||||
private bool _isPaused; // 暂停状态
|
||||
private float _frameTimer; // 帧计时器
|
||||
private int _currentFrameIndex; // 当前帧索引
|
||||
|
||||
private void Awake()
|
||||
protected override void ValidateComponent()
|
||||
{
|
||||
_renderer = GetComponent<SpriteRenderer>();
|
||||
ValidateStartFrame();
|
||||
}
|
||||
|
||||
// ITick接口实现
|
||||
public void Tick()
|
||||
{
|
||||
var deltaTime=Time.deltaTime;
|
||||
if (_isPaused)
|
||||
if (_renderer == null)
|
||||
{
|
||||
HandlePausedState();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayAnimation(deltaTime);
|
||||
}
|
||||
|
||||
private void ValidateStartFrame()
|
||||
{
|
||||
// 确保有精灵时可显示有效帧
|
||||
if (_sprites != null && _sprites.Length > 0)
|
||||
{
|
||||
_currentFrameIndex = Mathf.Clamp(_currentFrameIndex, 0, _sprites.Length - 1);
|
||||
_renderer.sprite = _sprites[_currentFrameIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.sprite = null;
|
||||
Debug.LogError("SpriteAnimator requires a SpriteRenderer component.", this);
|
||||
enabled = false; // 禁用脚本如果组件缺失
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePausedState()
|
||||
protected override void SetDisplaySprite(Sprite sprite)
|
||||
{
|
||||
// 优先使用静态精灵,否则保持当前帧
|
||||
if (_staticSprite)
|
||||
if (_renderer != null)
|
||||
{
|
||||
_renderer.sprite = _staticSprite;
|
||||
_renderer.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayAnimation(float deltaTime)
|
||||
{
|
||||
if (_sprites == null || _sprites.Length == 0) return;
|
||||
|
||||
// 更新帧计时器
|
||||
_frameTimer += deltaTime;
|
||||
var frameDuration = 1f / _fps;
|
||||
|
||||
// 检查帧切换条件
|
||||
while (_frameTimer >= frameDuration)
|
||||
{
|
||||
_frameTimer -= frameDuration;
|
||||
NextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
private void NextFrame()
|
||||
{
|
||||
// 循环播放动画
|
||||
_currentFrameIndex = (_currentFrameIndex + 1) % _sprites.Length;
|
||||
_renderer.sprite = _sprites[_currentFrameIndex];
|
||||
}
|
||||
|
||||
// 外部控制方法
|
||||
public void SetPaused(bool paused) => _isPaused = paused;
|
||||
public void SetSprites(Sprite[] newSprites)
|
||||
{
|
||||
_sprites = newSprites;
|
||||
|
||||
// 如果有新的精灵数组,则立即显示第一帧
|
||||
if (_sprites != null && _sprites.Length > 0)
|
||||
{
|
||||
_currentFrameIndex = 0; // 重置当前帧索引为第一帧
|
||||
_renderer.sprite = _sprites[_currentFrameIndex]; // 立即显示第一帧
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.sprite = null; // 如果没有精灵,则清空渲染器
|
||||
}
|
||||
|
||||
// 重置帧计时器,以确保从头开始播放
|
||||
_frameTimer = 0f;
|
||||
}
|
||||
|
||||
public void Restore()
|
||||
{
|
||||
_currentFrameIndex = 0;
|
||||
_renderer.sprite = _sprites[_currentFrameIndex];
|
||||
}
|
||||
|
||||
public void SetFPS(float newFPS) => _fps = Mathf.Max(0.1f, newFPS);
|
||||
public void SetStaticSprite(Sprite sprite) => _staticSprite = sprite;
|
||||
// [可选] 如果SpriteAnimator需要任何Awake后特有的初始化逻辑,可以在这里重写
|
||||
// protected override void Awake()
|
||||
// {
|
||||
// base.Awake(); // 确保调用基类的Awake
|
||||
// // 子类特有的初始化
|
||||
// }
|
||||
}
|
||||
}
|
38
Client/Assets/Scripts/Prefab/UIImageAnimator.cs
Normal file
38
Client/Assets/Scripts/Prefab/UIImageAnimator.cs
Normal file
@ -0,0 +1,38 @@
|
||||
// UI/UIImageAnimator.cs
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Base; // 引入Base命名空间以使用BaseAnimator
|
||||
|
||||
namespace UI
|
||||
{
|
||||
[RequireComponent(typeof(Image))]
|
||||
public class UIImageAnimator : BaseAnimator // 继承BaseAnimator
|
||||
{
|
||||
private Image _image; // UI Image组件
|
||||
|
||||
protected override void ValidateComponent()
|
||||
{
|
||||
_image = GetComponent<Image>();
|
||||
if (_image == null)
|
||||
{
|
||||
Debug.LogError("UIImageAnimator requires an Image component.", this);
|
||||
enabled = false; // 禁用脚本如果组件缺失
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetDisplaySprite(Sprite sprite)
|
||||
{
|
||||
if (_image)
|
||||
{
|
||||
_image.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
// [可选] 如果UIImageAnimator需要任何Awake后特有的初始化逻辑,可以在这里重写
|
||||
// protected override void Awake()
|
||||
// {
|
||||
// base.Awake(); // 确保调用基类的Awake
|
||||
// // 子类特有的初始化
|
||||
// }
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/Prefab/UIImageAnimator.cs.meta
Normal file
3
Client/Assets/Scripts/Prefab/UIImageAnimator.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6f899d1c5ef450bb6f3e670fa55cffd
|
||||
timeCreated: 1756778168
|
@ -5,6 +5,9 @@ using Map;
|
||||
using UnityEngine;
|
||||
using Utils;
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Program 类作为单例模式的核心管理器,负责维护和管理游戏或应用中的维度(Dimension)实例和焦点状态。
|
||||
/// 它提供了维度注册、注销、获取以及焦点维度设置的功能。
|
||||
|
16
Client/Assets/Scripts/UI/AttackModeUI.cs
Normal file
16
Client/Assets/Scripts/UI/AttackModeUI.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class AttackModeUI:MonoBehaviour
|
||||
{
|
||||
public UIImageAnimator icon;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
icon.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/AttackModeUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/AttackModeUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d81179f82c542edb275daabff5bfe12
|
||||
timeCreated: 1756781456
|
20
Client/Assets/Scripts/UI/BuffIconListUI.cs
Normal file
20
Client/Assets/Scripts/UI/BuffIconListUI.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Prefab;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class BuffIconListUI: MonoBehaviour
|
||||
{
|
||||
public BuffIconUI prefab;
|
||||
public Transform container;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
foreach (Transform child in container)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/BuffIconListUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/BuffIconListUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f6f033d6fe84e769e101fb355ef3ffa
|
||||
timeCreated: 1756780958
|
16
Client/Assets/Scripts/UI/CoinCountUI.cs
Normal file
16
Client/Assets/Scripts/UI/CoinCountUI.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class CoinCountUI:MonoBehaviour
|
||||
{
|
||||
public TMP_Text text;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
3
Client/Assets/Scripts/UI/CoinCountUI.cs.meta
Normal file
3
Client/Assets/Scripts/UI/CoinCountUI.cs.meta
Normal file
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85cfb4a924a04c6e9f6f7801207dbee3
|
||||
timeCreated: 1756776391
|
@ -1,275 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using Base;
|
||||
using Entity;
|
||||
using UnityEngine;
|
||||
// 确保 Character 类在此命名空间下
|
||||
|
||||
|
||||
namespace UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 负责管理和显示角色的装备用户界面。
|
||||
/// 该组件会监听当前关注实体的变化,并根据所关注角色的库存数据动态更新装备槽位的显示,
|
||||
/// 同时采用对象池技术高效管理 ItemUI 实例的创建和复用。
|
||||
/// 除了显示,现在还支持通过滚轮选择物品,并同步更新焦点角色的 CurrentSelected 字段。
|
||||
/// </summary>
|
||||
public class EquipmentUI : MonoBehaviour, ITick
|
||||
|
||||
public class EquipmentUI : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("所有 ItemUI 实例的父级 GameObject,用于布局。")]
|
||||
private GameObject uiParent;
|
||||
public ItemUI currentUse;
|
||||
public ItemUI two;
|
||||
public ItemUI three;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("用于实例化装备槽位的 ItemUI 预制件。")]
|
||||
private ItemUI itemUIPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// 当前界面所关联和关注的角色实体。
|
||||
/// </summary>
|
||||
private Character focusedEntity;
|
||||
|
||||
/// <summary>
|
||||
/// ItemUI 实例的对象池,用于高效管理和复用 ItemUI。
|
||||
/// </summary>
|
||||
private List<ItemUI> itemUIPool = new();
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour 的 Start 生命周期方法。
|
||||
/// 在此方法中,注册当游戏主要程序中关注的实体发生变化时,调用 <see cref="UpdateFocusedEntity"/> 方法进行更新。
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
Program.Instance.OnFocusedEntityChanged += UpdateFocusedEntity;
|
||||
uiParent.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MonoBehaviour 的 OnDestroy 生命周期方法。
|
||||
/// 在此方法中,取消注册所有已订阅的事件监听器,并清理对象池中创建的所有 ItemUI 实例,
|
||||
/// 以防止内存泄漏和不必要的引用。
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
Program.Instance.OnFocusedEntityChanged -= UpdateFocusedEntity;
|
||||
|
||||
// 如果当前有关注的角色,取消注册其库存改变事件。
|
||||
// 确保 focusedEntity 不为 null 且 Inventory 不为 null。
|
||||
if (focusedEntity != null && focusedEntity.Inventory != null)
|
||||
{
|
||||
focusedEntity.Inventory.OnInventoryChanged -= UpdateUI;
|
||||
}
|
||||
|
||||
// 销毁对象池中所有 ItemUI 的 GameObject。
|
||||
foreach (var itemUI in itemUIPool)
|
||||
{
|
||||
if (itemUI != null && itemUI.gameObject != null)
|
||||
{
|
||||
Destroy(itemUI.gameObject);
|
||||
}
|
||||
}
|
||||
itemUIPool.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当游戏程序中关注的实体发生变化时调用此方法。
|
||||
/// 该方法会更新当前 EquipmentUI 所关联的角色,并相应地注册或取消注册库存改变事件。
|
||||
/// </summary>
|
||||
/// <param name="entity">新的关注实体,可能为 null 或非 Character 类型。</param>
|
||||
private void UpdateFocusedEntity(Entity.Entity entity)
|
||||
{
|
||||
// 如果之前有关注的角色,先取消注册其库存改变事件。
|
||||
// 确保 focusedEntity 不为 null 且 Inventory 不为 null。
|
||||
if (focusedEntity != null && focusedEntity.Inventory != null)
|
||||
{
|
||||
focusedEntity.Inventory.OnInventoryChanged -= UpdateUI;
|
||||
}
|
||||
|
||||
// 尝试将新的实体转换为角色类型。
|
||||
var newCharacter = entity as Character;
|
||||
if (newCharacter != null)
|
||||
{
|
||||
focusedEntity = newCharacter;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果传入的 entity 不是 Character 类型,或者为 null,则清除当前的 focusedEntity。
|
||||
focusedEntity = null;
|
||||
}
|
||||
|
||||
// 如果现在有关注的角色,注册其库存改变事件。
|
||||
if (focusedEntity != null)
|
||||
{
|
||||
focusedEntity.Inventory.OnInventoryChanged += UpdateUI;
|
||||
}
|
||||
|
||||
// 立即更新UI以反映新的关注实体(或没有关注实体)的状态。
|
||||
UpdateUI();
|
||||
|
||||
// 在更新UI后,确保UI的选中状态与角色当前选中字段同步
|
||||
// 只有当有焦点角色且库存不为空时才更新选中状态
|
||||
if (focusedEntity != null && focusedEntity.Inventory != null && focusedEntity.Inventory.Capacity > 0)
|
||||
{
|
||||
// 确保 CurrentSelected 在有效范围内,否则重置为0
|
||||
if (focusedEntity.CurrentSelected < 0 || focusedEntity.CurrentSelected >= focusedEntity.Inventory.Capacity)
|
||||
{
|
||||
focusedEntity.CurrentSelected = 0;
|
||||
}
|
||||
UpdateSelectionUI(focusedEntity.CurrentSelected);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有焦点实体或库存为空,则清空所有选中状态
|
||||
UpdateSelectionUI(-1); // 传入一个无效索引以取消所有选中
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前关注角色的库存数据更新装备UI的显示。
|
||||
/// 该方法通过对象池机制高效地管理 ItemUI 实例的创建、复用和禁用。
|
||||
/// </summary>
|
||||
private void UpdateUI()
|
||||
{
|
||||
// 如果没有关注的角色或其库存,则禁用所有 ItemUI。
|
||||
if (focusedEntity == null || focusedEntity.Inventory == null)
|
||||
{
|
||||
foreach (var itemUI in itemUIPool)
|
||||
{
|
||||
if (itemUI != null && itemUI.gameObject != null)
|
||||
{
|
||||
itemUI.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
uiParent.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用于创建物品UI的预制件是否已在 Inspector 中赋值。
|
||||
if (itemUIPrefab == null)
|
||||
{
|
||||
Debug.LogError("ItemUIPrefab 未在 EquipmentUI 中指定。无法创建物品用户界面。", this);
|
||||
foreach (var itemUI in itemUIPool) itemUI.gameObject.SetActive(false);
|
||||
uiParent.SetActive(false); // 确保父级也被禁用
|
||||
return;
|
||||
}
|
||||
|
||||
var requiredUIs = focusedEntity.Inventory.Capacity;
|
||||
var currentUIPoolSize = itemUIPool.Count; // 当前对象池中 ItemUI 实例的总数。
|
||||
|
||||
// 遍历所有必要的物品槽位,复用对象池中的 ItemUI,或在不足时创建新的 ItemUI。
|
||||
for (var i = 0; i < requiredUIs; i++)
|
||||
{
|
||||
ItemUI itemUI;
|
||||
if (i < currentUIPoolSize)
|
||||
{
|
||||
itemUI = itemUIPool[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用 Instantiate(GameObject, Transform) 以确保父级设置正确且避免转换问题。
|
||||
var itemObj = Instantiate(itemUIPrefab.gameObject, uiParent.transform);
|
||||
itemUI = itemObj.GetComponent<ItemUI>();
|
||||
itemUIPool.Add(itemUI);
|
||||
currentUIPoolSize++; // 更新池的大小计数。
|
||||
}
|
||||
|
||||
// 确保 ItemUI GameObject 处于激活状态,并使用当前物品槽位的数据进行初始化。
|
||||
itemUI.gameObject.SetActive(true);
|
||||
itemUI.Init(focusedEntity.Inventory.GetSlot(i), i);
|
||||
// 移除此处 itemUI.Select = false; 选中状态将由 UpdateSelectionUI 统一管理
|
||||
}
|
||||
|
||||
// 如果库存槽位数量减少,禁用对象池中多余的 ItemUI 实例。
|
||||
for (var i = requiredUIs; i < currentUIPoolSize; i++)
|
||||
{
|
||||
if (itemUIPool[i] != null && itemUIPool[i].gameObject != null)
|
||||
{
|
||||
itemUIPool[i].gameObject.SetActive(false);
|
||||
itemUIPool[i].Select = false; // 禁用时也确保清除选中状态
|
||||
}
|
||||
}
|
||||
uiParent.SetActive(true);
|
||||
|
||||
// 首次更新UI时,或者当Inventory改变时,需要确保 CurrentSelected 的UI状态是正确的
|
||||
// 但如果 UpdateFocusedEntity 已经处理了,这里可以省略,或者确保只在必要时调用
|
||||
// 考虑到 UpdateUI 也会被 Inventory.OnInventoryChanged 调用,这里再次确保同步是合理的。
|
||||
if (focusedEntity != null && focusedEntity.Inventory != null && focusedEntity.Inventory.Capacity > 0)
|
||||
{
|
||||
UpdateSelectionUI(focusedEntity.CurrentSelected);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateSelectionUI(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当当前选中物品改变时,更新所有 ItemUI 的选中状态。
|
||||
/// </summary>
|
||||
/// <param name="selectedItemIndex">当前选中的物品索引。传入 -1 将取消所有 ItemUI 的选中状态。</param>
|
||||
private void UpdateSelectionUI(int selectedItemIndex)
|
||||
{
|
||||
// 如果对象池为空,则无需更新
|
||||
if (itemUIPool == null || itemUIPool.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < itemUIPool.Count; i++)
|
||||
{
|
||||
var itemUI = itemUIPool[i];
|
||||
if (itemUI != null && itemUI.gameObject != null)
|
||||
{
|
||||
// 只有在 ItemUI 激活状态下才设置其选中状态,避免对禁用UI的操作
|
||||
// 或者如果传入-1,即使激活也全部设置为false
|
||||
if (itemUI.gameObject.activeSelf || selectedItemIndex == -1) // 确保当取消所有选中时,循环到所有激活的,甚至当前禁用的ItemUI
|
||||
{
|
||||
itemUI.Select = (i == selectedItemIndex && itemUI.gameObject.activeSelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每帧调用的更新方法,用于处理滚轮输入以选择物品。
|
||||
/// </summary>
|
||||
public void Tick()
|
||||
{
|
||||
// 如果没有焦点实体、没有库存或库存为空,不进行选择操作
|
||||
if (focusedEntity == null || focusedEntity.Inventory == null || focusedEntity.Inventory.Capacity == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scrollInput = Input.GetAxis("Mouse ScrollWheel");
|
||||
|
||||
if (scrollInput != 0) // 检测到滚轮输入
|
||||
{
|
||||
var currentSelection = focusedEntity.CurrentSelected;
|
||||
var inventoryCapacity = focusedEntity.Inventory.Capacity;
|
||||
|
||||
if (scrollInput > 0) // 滚轮向上,选择前一个
|
||||
{
|
||||
currentSelection--;
|
||||
if (currentSelection < 0)
|
||||
{
|
||||
currentSelection = inventoryCapacity - 1; // 循环到最后一个
|
||||
}
|
||||
}
|
||||
else // 滚轮向下,选择后一个
|
||||
{
|
||||
currentSelection++;
|
||||
if (currentSelection >= inventoryCapacity)
|
||||
{
|
||||
currentSelection = 0; // 循环到第一个
|
||||
}
|
||||
}
|
||||
|
||||
// 如果选择发生变化,则更新焦点实体和UI
|
||||
if (focusedEntity.CurrentSelected != currentSelection)
|
||||
{
|
||||
focusedEntity.CurrentSelected = currentSelection;
|
||||
UpdateSelectionUI(currentSelection); // 更新UI选中状态
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,91 +1,34 @@
|
||||
|
||||
using System;
|
||||
using Base;
|
||||
using TMPro;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class ItemUI:MonoBehaviour,IPointerEnterHandler,IPointerExitHandler,IPointerClickHandler,ITick
|
||||
public class ItemUI:MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Image textureUI;
|
||||
[SerializeField] private TMP_Text countUI;
|
||||
[SerializeField] private TMP_Text nameUI;
|
||||
[SerializeField] private GameObject selectedOutline;
|
||||
public Entity.InventorySlot inventorySlot;
|
||||
public UIImageAnimator icon;
|
||||
|
||||
private Entity.InventorySlot _item;
|
||||
private float timer = 0;
|
||||
private float switchTime = 0;
|
||||
private int texturePtr = 0;
|
||||
|
||||
public event Action OnPlayerSelect;
|
||||
|
||||
public bool Select
|
||||
private void Start()
|
||||
{
|
||||
get => selectedOutline.activeSelf;
|
||||
set => selectedOutline.SetActive(value);
|
||||
icon.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public int SlotIndex { get; private set; } = -1;
|
||||
|
||||
public void Init(Entity.InventorySlot item,int index)
|
||||
public void SetDisplayItem(Entity.InventorySlot slot)
|
||||
{
|
||||
if (item == null)
|
||||
inventorySlot = slot;
|
||||
if (inventorySlot == null)
|
||||
{
|
||||
switchTime = -1;
|
||||
textureUI.gameObject.SetActive(false);
|
||||
countUI.text = "";
|
||||
nameUI.text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
textureUI.gameObject.SetActive(true);
|
||||
_item = item;
|
||||
textureUI.sprite = item.Item.Icon[0];
|
||||
countUI.text = item.Quantity.ToString();
|
||||
nameUI.text = item.Item.Name;
|
||||
nameUI.gameObject.SetActive(false);
|
||||
Select = false;
|
||||
SlotIndex = index;
|
||||
if (item.Item.FPS > 0)
|
||||
{
|
||||
switchTime = 1f / item.Item.FPS;
|
||||
icon.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
switchTime = 0;
|
||||
icon.gameObject.SetActive(true);
|
||||
icon.SetSprites(inventorySlot.Item.Icon.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
nameUI.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
nameUI.gameObject.SetActive(false);
|
||||
}
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
OnPlayerSelect?.Invoke();
|
||||
}
|
||||
public void Tick()
|
||||
{
|
||||
if (switchTime > 0)
|
||||
{
|
||||
timer+=Time.deltaTime;
|
||||
if (timer >= switchTime)
|
||||
{
|
||||
timer-=switchTime;
|
||||
texturePtr++;
|
||||
texturePtr%=_item.Item.Icon.Count;
|
||||
textureUI.sprite=_item.Item.Icon[texturePtr];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ using UnityEngine;
|
||||
|
||||
namespace UI
|
||||
{
|
||||
public class LogUI : UIBase
|
||||
public class LogUI : FullScreenUI
|
||||
{
|
||||
public Transform contentPanel; // 日志内容容器
|
||||
public TextPrefab textPrefab; // 文本预制体引用
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Base;
|
||||
using Map;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
@ -8,7 +10,13 @@ namespace UI
|
||||
{
|
||||
[SerializeField] private BarUI focusedEntityHP;
|
||||
[SerializeField] private BarUI lastEntityHP;
|
||||
|
||||
[SerializeField] private BarUI BaseBuildingHP;
|
||||
[SerializeField] private MiniMap miniMap;
|
||||
[SerializeField] private EquipmentUI equipmentUI;
|
||||
[SerializeField] private CoinCountUI coinCountUI;
|
||||
[SerializeField] private BuffIconListUI focuseEntityBuffIconList;
|
||||
[SerializeField] private BuffIconListUI lastEntityBuffIconList;
|
||||
[SerializeField] private AttackModeUI attackMode;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
@ -20,5 +28,52 @@ namespace UI
|
||||
}
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
focusedEntityHP.gameObject.SetActive(true);
|
||||
lastEntityHP.gameObject.SetActive(true);
|
||||
BaseBuildingHP.gameObject.SetActive(true);
|
||||
miniMap.gameObject.SetActive(true);
|
||||
equipmentUI.gameObject.SetActive(true);
|
||||
coinCountUI.gameObject.SetActive(true);
|
||||
focuseEntityBuffIconList.gameObject.SetActive(true);
|
||||
lastEntityBuffIconList.gameObject.SetActive(true);
|
||||
attackMode.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
focusedEntityHP.gameObject.SetActive(false);
|
||||
lastEntityHP.gameObject.SetActive(false);
|
||||
BaseBuildingHP.gameObject.SetActive(false);
|
||||
miniMap.gameObject.SetActive(false);
|
||||
equipmentUI.gameObject.SetActive(false);
|
||||
coinCountUI.gameObject.SetActive(false);
|
||||
focuseEntityBuffIconList.gameObject.SetActive(false);
|
||||
lastEntityBuffIconList.gameObject.SetActive(false);
|
||||
attackMode.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
UIInputControl.Instance.OnWindowVisibilityChanged += UIChange;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
UIInputControl.Instance.OnWindowVisibilityChanged -= UIChange;
|
||||
}
|
||||
|
||||
private void UIChange(UIBase ui, bool open)
|
||||
{
|
||||
if (ui.exclusive && open)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user