This commit is contained in:
2025-12-04 14:38:38 +01:00
28 changed files with 7454 additions and 429 deletions

View File

@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: 6f3207429a65b3e4b83935ac19791077
m_currentHash:
serializedVersion: 2
Hash: ab5e2545873f5b7934dc716832e70d83
Hash: 00000000000000000000000000000000
m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0
m_CatalogRequestsTimeout: 0
@@ -108,6 +108,7 @@ MonoBehaviour:
m_LabelNames:
- default
- BlokkemonCard
- StatueDecorations
m_SchemaTemplates: []
m_GroupTemplateObjects:
- {fileID: 11400000, guid: ea0a5135f5495eb4693a23d94617fe92, type: 2}

View File

@@ -15,6 +15,11 @@ MonoBehaviour:
m_GroupName: Settings
m_GUID: c62e6f02418e19949bca4cccdd5fa02e
m_SerializeEntries:
- m_GUID: 1476365959c6ead48985cb10ae82696f
m_Address: Settings/Developer/FortFightDeveloperSettings
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 328ce914b893df646be3ad3c62755453
m_Address: Settings/Developer/DivingDeveloperSettings
m_ReadOnly: 0

View File

@@ -9,7 +9,7 @@ namespace AppleHills.Core.Settings.Editor
{
private Vector2 scrollPosition;
private List<BaseDeveloperSettings> allDeveloperSettings = new List<BaseDeveloperSettings>();
private string[] tabNames = new string[] { "Diving", "Debug", "Other Systems" }; // Added Debug tab
private string[] tabNames = new string[] { "Diving", "Fort Fight", "Debug", "Other Systems" };
private int selectedTab = 0;
private Dictionary<string, SerializedObject> serializedSettingsObjects = new Dictionary<string, SerializedObject>();
private GUIStyle headerStyle;
@@ -45,6 +45,7 @@ namespace AppleHills.Core.Settings.Editor
// If any settings are missing, create them
CreateSettingsIfMissing<DivingDeveloperSettings>("DivingDeveloperSettings");
CreateSettingsIfMissing<FortFightDeveloperSettings>("FortFightDeveloperSettings");
CreateSettingsIfMissing<DebugSettings>("DebugSettings");
// Add more developer settings types here as needed
@@ -114,10 +115,13 @@ namespace AppleHills.Core.Settings.Editor
case 0: // Diving
DrawSettingsEditor<DivingDeveloperSettings>();
break;
case 1: // Debug
case 1: // Fort Fight
DrawSettingsEditor<FortFightDeveloperSettings>();
break;
case 2: // Debug
DrawSettingsEditor<DebugSettings>();
break;
case 2: // Other Systems
case 3: // Other Systems
EditorGUILayout.HelpBox("Other developer settings will appear here as they are added.", MessageType.Info);
break;
// Add additional cases as more developer settings types are added

View File

@@ -10,11 +10,16 @@ namespace AppleHills.Editor
[InitializeOnLoad]
public static class EditorSettingsProvider
{
// Gameplay Settings
private static PlayerFollowerSettings _playerFollowerSettings;
private static InteractionSettings _interactionSettings;
private static DivingMinigameSettings _divingMinigameSettings;
private static Minigames.FortFight.Core.FortFightSettings _fortFightSettings;
// Developer Settings
private static FortFightDeveloperSettings _fortFightDeveloperSettings;
private static DivingDeveloperSettings _divingDeveloperSettings;
// Static constructor will be called when Unity loads/reloads scripts
static EditorSettingsProvider()
{
@@ -54,11 +59,16 @@ namespace AppleHills.Editor
public static void LoadAllSettings()
{
// Load gameplay settings
_playerFollowerSettings = AssetDatabase.LoadAssetAtPath<PlayerFollowerSettings>("Assets/Settings/PlayerFollowerSettings.asset");
_interactionSettings = AssetDatabase.LoadAssetAtPath<InteractionSettings>("Assets/Settings/InteractionSettings.asset");
_divingMinigameSettings = AssetDatabase.LoadAssetAtPath<DivingMinigameSettings>("Assets/Settings/MinigameSettings.asset");
_fortFightSettings = AssetDatabase.LoadAssetAtPath<Minigames.FortFight.Core.FortFightSettings>("Assets/Settings/FortFightSettings.asset");
// Load developer settings
_fortFightDeveloperSettings = AssetDatabase.LoadAssetAtPath<FortFightDeveloperSettings>("Assets/Settings/Developer/FortFightDeveloperSettings.asset");
_divingDeveloperSettings = AssetDatabase.LoadAssetAtPath<DivingDeveloperSettings>("Assets/Settings/Developer/DivingDeveloperSettings.asset");
// Re-register the delegates in case they were lost
AppleHills.SettingsAccess.SetupEditorProviders(
GetPlayerStopDistance,
@@ -112,5 +122,18 @@ namespace AppleHills.Editor
return null;
}
/// <summary>
/// Get developer settings in editor mode (for OnDrawGizmos, etc.)
/// </summary>
public static T GetDeveloperSettings<T>() where T : BaseDeveloperSettings
{
if (typeof(T) == typeof(FortFightDeveloperSettings))
return _fortFightDeveloperSettings as T;
else if (typeof(T) == typeof(DivingDeveloperSettings))
return _divingDeveloperSettings as T;
return null;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e2da0db3ebb55914c84eca336cdde30e
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -168,3 +168,4 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 516daf2ce7384aaa94fd5e0f7a3cf078, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Projectiles.TrashPiece
impactEffectPrefab: {fileID: 2996232906017550342, guid: e2da0db3ebb55914c84eca336cdde30e, type: 3}

File diff suppressed because one or more lines are too long

View File

@@ -992,7 +992,8 @@ Transform:
m_LocalPosition: {x: 14.5, y: 3.4, z: 0}
m_LocalScale: {x: 16.29907, y: 16.29907, z: 16.29907}
m_ConstrainProportionsScale: 0
m_Children: []
m_Children:
- {fileID: 1389618396}
m_Father: {fileID: 799036564}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &377753197
@@ -2728,7 +2729,7 @@ Transform:
m_GameObject: {fileID: 841922113}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -10.26, y: 4.1, z: 0}
m_LocalPosition: {x: -10.26, y: 7.8, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -2876,7 +2877,6 @@ MonoBehaviour:
maxDragDistance: 5
projectileSpawnPoint: {fileID: 1668202570}
trajectoryPreview: {fileID: 0}
showDebugLogs: 1
--- !u!1 &846792101
GameObject:
m_ObjectHideFlags: 0
@@ -3174,11 +3174,10 @@ GameObject:
- component: {fileID: 878268908}
- component: {fileID: 878268911}
- component: {fileID: 878268910}
- component: {fileID: 878268909}
- component: {fileID: 878268912}
- component: {fileID: 878268913}
m_Layer: 0
m_Name: ForFightGameManager
m_Name: ForFightGameManagers
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -3199,19 +3198,6 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &878268909
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 878268907}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d82cbd88db94eec4ba7c19ef60b9fbbc, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.AI.FortFightAIController
aiThinkTime: 2
--- !u!114 &878268910
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -3239,7 +3225,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 517ef0a4f14e16f42987a95684371b73, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.FortFightGameManager
aiController: {fileID: 878268909}
aiController: {fileID: 1543340063}
modeSelectionPage: {fileID: 2036414581}
gameplayPage: {fileID: 1585033674}
gameOverUI: {fileID: 805585730}
@@ -3257,8 +3243,6 @@ MonoBehaviour:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.FortManager
premadeFortPrefabs:
- {fileID: 2303456945894359403, guid: 1481432e299ad794e937cb82505cfbb2, type: 3}
debugPlayerFortPrefab: {fileID: 0}
debugEnemyFortPrefab: {fileID: 0}
playerSpawnPoint: {fileID: 1009687014}
enemySpawnPoint: {fileID: 799036564}
useDebugForts: 0
@@ -3275,7 +3259,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.AmmunitionManager
defaultProjectileType: 0
showDebugLogs: 1
--- !u!1 &1007359450
GameObject:
m_ObjectHideFlags: 0
@@ -3580,142 +3563,6 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1192355915}
m_CullTransparentMesh: 1
--- !u!1 &1205967276
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1205967277}
- component: {fileID: 1205967279}
- component: {fileID: 1205967278}
m_Layer: 5
m_Name: Text (TMP)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1205967277
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1205967276}
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: 1355330056}
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: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1205967278
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1205967276}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.TextMeshPro::TMPro.TextMeshProUGUI
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_text: PigMan Is thinking.....
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 4aca0db6ec111b5418bdc747168f9474, type: 2}
m_sharedMaterial: {fileID: -1441574381962284772, guid: 4aca0db6ec111b5418bdc747168f9474, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []
m_fontColor32:
serializedVersion: 2
rgba: 4294967295
m_fontColor: {r: 1, g: 1, b: 1, a: 1}
m_enableVertexGradient: 0
m_colorMode: 3
m_fontColorGradient:
topLeft: {r: 1, g: 1, b: 1, a: 1}
topRight: {r: 1, g: 1, b: 1, a: 1}
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
bottomRight: {r: 1, g: 1, b: 1, a: 1}
m_fontColorGradientPreset: {fileID: 0}
m_spriteAsset: {fileID: 0}
m_tintAllSprites: 0
m_StyleSheet: {fileID: 0}
m_TextStyleHashCode: -1183493901
m_overrideHtmlColors: 0
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 97.2
m_fontSizeBase: 97.2
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
m_fontSizeMax: 72
m_fontStyle: 0
m_HorizontalAlignment: 2
m_VerticalAlignment: 512
m_textAlignment: 65535
m_characterSpacing: 0
m_wordSpacing: 0
m_lineSpacing: 0
m_lineSpacingMax: 0
m_paragraphSpacing: 0
m_charWidthMaxAdj: 0
m_TextWrappingMode: 1
m_wordWrappingRatios: 0.4
m_overflowMode: 0
m_linkedTextComponent: {fileID: 0}
parentLinkedComponent: {fileID: 0}
m_enableKerning: 0
m_ActiveFontFeatures: 6e72656b
m_enableExtraPadding: 0
checkPaddingRequired: 0
m_isRichText: 1
m_EmojiFallbackSupport: 1
m_parseCtrlCharacters: 1
m_isOrthographic: 1
m_isCullingEnabled: 0
m_horizontalMapping: 0
m_verticalMapping: 0
m_uvLineOffset: 0
m_geometrySortingOrder: 0
m_IsTextObjectScaleStatic: 0
m_VertexBufferAutoSizeReduction: 0
m_useMaxVisibleDescender: 1
m_pageToDisplay: 1
m_margin: {x: 0, y: 0, z: 0, w: 0}
m_isUsingLegacyAnimationComponent: 0
m_isVolumetricText: 0
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!222 &1205967279
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1205967276}
m_CullTransparentMesh: 1
--- !u!1 &1209958790
GameObject:
m_ObjectHideFlags: 0
@@ -3780,7 +3627,6 @@ MonoBehaviour:
autoBindToFort: 1
isPlayerFort: 0
fortManager: {fileID: 0}
debugDisplay: 1
debugHpText: {fileID: 1956621113}
--- !u!114 &1209958793
MonoBehaviour:
@@ -4093,82 +3939,6 @@ RectTransform:
m_AnchoredPosition: {x: -5, y: 0}
m_SizeDelta: {x: -20, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &1355330055
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1355330056}
- component: {fileID: 1355330058}
- component: {fileID: 1355330057}
m_Layer: 5
m_Name: AiActionPanel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1355330056
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1355330055}
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: 1205967277}
m_Father: {fileID: 1585033672}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 500, y: 300}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1355330057
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1355330055}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.5019608}
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: 0}
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!222 &1355330058
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1355330055}
m_CullTransparentMesh: 1
--- !u!1 &1366022891
GameObject:
m_ObjectHideFlags: 0
@@ -4271,8 +4041,106 @@ MonoBehaviour:
autoBindToFort: 1
isPlayerFort: 1
fortManager: {fileID: 0}
debugDisplay: 1
debugHpText: {fileID: 1726433071}
--- !u!1 &1389618395
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1389618396}
- component: {fileID: 1389618398}
- component: {fileID: 1389618397}
m_Layer: 0
m_Name: ThinkingVisual
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!4 &1389618396
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1389618395}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0.014, y: 0.408, z: 0}
m_LocalScale: {x: 0.066619, y: 0.066619, z: 0.066619}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 377753196}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &1389618397
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1389618395}
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: 9dfc825aed78fcd4ba02077103263b40, 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_Sprite: {fileID: -6274306579908041956, guid: ea31d8058ec0c6947aef62e4d9a5ebc9, type: 3}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 2.91, y: 3.92}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!222 &1389618398
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1389618395}
m_CullTransparentMesh: 1
--- !u!1 &1410755663
GameObject:
m_ObjectHideFlags: 0
@@ -4512,7 +4380,7 @@ Transform:
m_GameObject: {fileID: 1460473366}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 10, y: 4, z: 0}
m_LocalPosition: {x: 10, y: 7.700001, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
@@ -4660,7 +4528,53 @@ MonoBehaviour:
maxDragDistance: 5
projectileSpawnPoint: {fileID: 497509525}
trajectoryPreview: {fileID: 0}
showDebugLogs: 1
--- !u!1 &1543340062
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1543340064}
- component: {fileID: 1543340063}
m_Layer: 0
m_Name: AiController
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1543340063
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1543340062}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d82cbd88db94eec4ba7c19ef60b9fbbc, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.AI.FortFightAIController
aiSlingshot: {fileID: 1460473370}
aiAnimator: {fileID: 0}
thinkingIndicator: {fileID: 1389618395}
--- !u!4 &1543340064
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1543340062}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 2.14554, y: -3.91706, 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 &1572990036
GameObject:
m_ObjectHideFlags: 0
@@ -4759,7 +4673,6 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1787332576}
- {fileID: 1355330056}
- {fileID: 1286877329}
- {fileID: 242361350}
m_Father: {fileID: 1156219952}
@@ -4799,7 +4712,6 @@ MonoBehaviour:
currentPlayerText: {fileID: 1192355917}
canvasGroup: {fileID: 1585033673}
playerActionPanel: {fileID: 0}
aiTurnPanel: {fileID: 1355330055}
--- !u!1 &1592155788
GameObject:
m_ObjectHideFlags: 0
@@ -7065,6 +6977,7 @@ SceneRoots:
- {fileID: 1156219952}
- {fileID: 1277046016}
- {fileID: 2124351765}
- {fileID: 1543340064}
- {fileID: 1674657453}
- {fileID: 878268908}
- {fileID: 1007359451}

View File

@@ -0,0 +1,73 @@
using UnityEngine;
namespace AppleHills.Core.Settings
{
/// <summary>
/// Developer settings for Fort Fight minigame technical configuration.
/// These settings are separate from gameplay/design settings and focus on debugging and testing.
/// </summary>
[CreateAssetMenu(fileName = "FortFightDeveloperSettings", menuName = "AppleHills/Developer Settings/Fort Fight", order = 2)]
public class FortFightDeveloperSettings : BaseDeveloperSettings
{
[Header("AI Debug Visualization")]
[Tooltip("Show AI debug visuals (target circles, trajectory lines)")]
[SerializeField] private bool showAIDebugVisuals = true;
[Tooltip("Color for AI target indicator")]
[SerializeField] private Color aiDebugTargetColor = Color.red;
[Tooltip("Radius of AI target circle")]
[SerializeField] private float aiDebugCircleRadius = 0.5f;
[Tooltip("Color for AI trajectory line")]
[SerializeField] private Color aiDebugTrajectoryColor = Color.yellow;
[Header("Debug Logging")]
[Tooltip("Show debug logs from SlingshotController")]
[SerializeField] private bool slingshotShowDebugLogs = true;
[Tooltip("Show debug logs from AmmunitionManager")]
[SerializeField] private bool ammunitionShowDebugLogs = true;
[Tooltip("Show debug info from FortController")]
[SerializeField] private bool fortShowDebugInfo = true;
[Header("UI Debug Display")]
[Tooltip("Show numerical HP values in FortHealthUI")]
[SerializeField] private bool healthUIDebugDisplay = false;
[Header("Testing & Debug Prefabs")]
[Tooltip("Debug prefab for player fort (testing purposes)")]
[SerializeField] private GameObject debugPlayerFortPrefab;
[Tooltip("Debug prefab for enemy fort (testing purposes)")]
[SerializeField] private GameObject debugEnemyFortPrefab;
// AI Debug Visualization properties
public bool ShowAIDebugVisuals => showAIDebugVisuals;
public Color AIDebugTargetColor => aiDebugTargetColor;
public float AIDebugCircleRadius => aiDebugCircleRadius;
public Color AIDebugTrajectoryColor => aiDebugTrajectoryColor;
// Debug Logging properties
public bool SlingshotShowDebugLogs => slingshotShowDebugLogs;
public bool AmmunitionShowDebugLogs => ammunitionShowDebugLogs;
public bool FortShowDebugInfo => fortShowDebugInfo;
// UI Debug Display properties
public bool HealthUIDebugDisplay => healthUIDebugDisplay;
// Testing & Debug Prefabs properties
public GameObject DebugPlayerFortPrefab => debugPlayerFortPrefab;
public GameObject DebugEnemyFortPrefab => debugEnemyFortPrefab;
public override void OnValidate()
{
base.OnValidate();
// Validate radius
aiDebugCircleRadius = Mathf.Max(0.1f, aiDebugCircleRadius);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d354649c25014aa2a7b3b45841834f74
timeCreated: 1764841328

View File

@@ -223,6 +223,10 @@ namespace AppleHills.Core.Settings
System.Collections.Generic.List<Minigames.FortFight.Settings.BlockMaterialConfig> MaterialConfigs { get; }
System.Collections.Generic.List<Minigames.FortFight.Settings.BlockSizeConfig> SizeConfigs { get; }
// AI Difficulty Settings
Minigames.FortFight.Data.AIDifficulty DefaultAIDifficulty { get; } // Default difficulty level for AI
Minigames.FortFight.Data.AIDifficultyData GetAIDifficultyData(Minigames.FortFight.Data.AIDifficulty difficulty);
// Weak point settings
float WeakPointExplosionRadius { get; }
float WeakPointExplosionDamage { get; }

View File

@@ -90,5 +90,35 @@ namespace AppleHills
}
// Add more methods as needed for other settings
/// <summary>
/// Get developer settings for editor-mode usage (OnDrawGizmos, etc.)
/// This handles both editor and play mode seamlessly.
/// </summary>
public static T GetDeveloperSettingsForEditor<T>() where T : AppleHills.Core.Settings.BaseDeveloperSettings
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
// In editor mode (not playing), use reflection to access EditorSettingsProvider
// This avoids direct reference to Editor assembly from runtime code
var editorProviderType = System.Type.GetType("AppleHills.Editor.EditorSettingsProvider, Assembly-CSharp-Editor");
if (editorProviderType != null)
{
var method = editorProviderType.GetMethod("GetDeveloperSettings",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (method != null)
{
var genericMethod = method.MakeGenericMethod(typeof(T));
return genericMethod.Invoke(null, null) as T;
}
}
return null;
}
#endif
// In play mode, use GameManager
return GameManager.GetDeveloperSettings<T>();
}
}
}

View File

@@ -1,24 +1,51 @@
using System.Collections;
using System.Collections.Generic;
using Core;
using Core.Lifecycle;
using Minigames.FortFight.Core;
using Minigames.FortFight.Data;
using Minigames.FortFight.Fort;
using UnityEngine;
namespace Minigames.FortFight.AI
{
/// <summary>
/// AI controller for the PigMan opponent.
/// Phase 1: Stubbed implementation - just simulates taking a turn.
/// Phase 4: Full implementation with trajectory calculation and target selection.
/// AI controller for Fort Fight opponent.
/// Implements trajectory calculation, target selection, and shot execution with configurable difficulty.
/// Includes debug visualization and thinking animations.
/// </summary>
public class FortFightAIController : ManagedBehaviour
{
[Header("AI Settings (Stubbed)")]
[SerializeField] private float aiThinkTime = 1.5f; // Time AI "thinks" before acting
#region Inspector Properties
private TurnManager turnManager;
private bool isThinking = false;
[Header("AI References")]
[SerializeField] private SlingshotController aiSlingshot;
[Tooltip("Optional: Animator for AI character thinking animations")]
[SerializeField] private Animator aiAnimator;
[Header("UI References")]
[SerializeField] private GameObject thinkingIndicator;
#endregion
#region Private State
private TurnManager _turnManager;
private AmmunitionManager _ammoManager;
private FortManager _fortManager;
private bool _isExecutingTurn = false;
// Current target info
private FortBlock _targetBlock;
private Vector2 _targetPosition;
private ProjectileType _selectedAmmo;
// Settings cache
private AppleHills.Core.Settings.IFortFightSettings _cachedSettings;
private AppleHills.Core.Settings.FortFightDeveloperSettings _cachedDevSettings;
private AIDifficultyData _currentDifficultyData;
#endregion
#region Initialization
@@ -27,19 +54,57 @@ namespace Minigames.FortFight.AI
/// </summary>
public void Initialize()
{
// Get reference to turn manager via singleton
turnManager = TurnManager.Instance;
// Load settings
_cachedSettings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IFortFightSettings>();
_cachedDevSettings = GameManager.GetDeveloperSettings<AppleHills.Core.Settings.FortFightDeveloperSettings>();
if (turnManager == null)
if (_cachedSettings == null)
{
Logging.Error("[FortFightAIController] Failed to load FortFightSettings!");
}
// Load difficulty configuration from settings
if (_cachedSettings != null)
{
AIDifficulty difficulty = _cachedSettings.DefaultAIDifficulty;
_currentDifficultyData = _cachedSettings.GetAIDifficultyData(difficulty);
Logging.Debug($"[FortFightAIController] Loaded AI difficulty: {difficulty} - Angle: ±{_currentDifficultyData.angleDeviation}°, Force: ±{_currentDifficultyData.forceDeviation * 100}%");
}
// Get references to managers via singletons
_turnManager = TurnManager.Instance;
_ammoManager = AmmunitionManager.Instance;
_fortManager = FortManager.Instance;
if (_turnManager == null)
{
Logging.Error("[FortFightAIController] TurnManager not found!");
return;
}
// Subscribe to turn events
turnManager.OnTurnStarted += OnTurnStarted;
if (_ammoManager == null)
{
Logging.Error("[FortFightAIController] AmmunitionManager not found!");
return;
}
Logging.Debug("[FortFightAIController] AI initialized");
if (_fortManager == null)
{
Logging.Error("[FortFightAIController] FortManager not found!");
return;
}
if (aiSlingshot == null)
{
Logging.Error("[FortFightAIController] AI Slingshot not assigned!");
}
// Subscribe to turn events
_turnManager.OnTurnStarted += OnTurnStarted;
bool debugVisualsEnabled = _cachedDevSettings?.ShowAIDebugVisuals ?? false;
AIDifficulty activeDifficulty = _cachedSettings?.DefaultAIDifficulty ?? AIDifficulty.Medium;
Logging.Debug($"[FortFightAIController] AI initialized - Difficulty: {activeDifficulty}, Debug Visuals: {debugVisualsEnabled}");
}
#endregion
@@ -52,49 +117,403 @@ namespace Minigames.FortFight.AI
private void OnTurnStarted(PlayerData currentPlayer, TurnState turnState)
{
// Only act if it's AI's turn
if (turnState == TurnState.AITurn && !isThinking)
if (turnState == TurnState.AITurn && !_isExecutingTurn)
{
StartCoroutine(ExecuteAITurn());
}
}
/// <summary>
/// Execute the AI's turn (stubbed for Phase 1)
/// Execute the AI's turn with thinking phases and shot execution
/// </summary>
private IEnumerator ExecuteAITurn()
{
isThinking = true;
_isExecutingTurn = true;
Logging.Debug($"[FortFightAIController] AI is thinking... (for {aiThinkTime}s)");
Logging.Debug("[FortFightAIController] === AI TURN START ===");
// Simulate AI "thinking"
yield return new WaitForSeconds(aiThinkTime);
// Phase 1: Thinking - Target Selection
yield return StartCoroutine(ThinkingPhase("Analyzing targets..."));
_targetBlock = SelectBestTarget();
// STUBBED: Perform AI action
Logging.Debug("[FortFightAIController] AI takes action! (STUBBED - no actual projectile fired yet)");
if (_targetBlock == null)
{
Logging.Warning("[FortFightAIController] No valid target found! Skipping turn.");
_isExecutingTurn = false;
yield break;
}
// TODO Phase 4: AI should trigger its slingshot to fire projectile here
// Turn will automatically advance when AI's projectile settles (via ProjectileTurnAction)
// Do NOT manually call EndTurn() - it's now private and automatic
_targetPosition = _targetBlock.transform.position;
Logging.Debug($"[FortFightAIController] Target selected: {_targetBlock.gameObject.name} at {_targetPosition}");
// NOTE: For now, AI turn will hang until Phase 4 AI projectile system is implemented
// To test without AI, use TwoPlayer mode
// Phase 2: Thinking - Ammunition Selection
yield return StartCoroutine(ThinkingPhase("Selecting ammunition..."));
_selectedAmmo = ChooseProjectile();
Logging.Debug($"[FortFightAIController] Ammo selected: {_selectedAmmo}");
isThinking = false;
Logging.Warning("[FortFightAIController] AI turn stubbed - Phase 4 needed for AI projectile firing");
// Phase 3: Thinking - Trajectory Calculation
yield return StartCoroutine(ThinkingPhase("Calculating trajectory..."));
Vector2 launchVector = CalculateTrajectoryWithDeviation(aiSlingshot.transform.position, _targetPosition);
Logging.Debug($"[FortFightAIController] Trajectory calculated: {launchVector}");
// Phase 4: Execute Shot
yield return new WaitForSeconds(0.3f); // Brief pause before shot
ExecuteShot(launchVector);
Logging.Debug("[FortFightAIController] === AI TURN SHOT EXECUTED ===");
_isExecutingTurn = false;
// Turn will automatically end when projectile settles (handled by TurnManager)
}
/// <summary>
/// Thinking phase with random duration and optional animation trigger
/// </summary>
private IEnumerator ThinkingPhase(string thinkingAction)
{
float thinkTime = Random.Range(_currentDifficultyData.thinkTimeMin, _currentDifficultyData.thinkTimeMax);
Logging.Debug($"[FortFightAIController] {thinkingAction} ({thinkTime:F1}s)");
// TODO: Play some funny stuff here?
// TODO: Placeholder "thinking" exlamation point
if (thinkingIndicator != null)
{
thinkingIndicator.SetActive(true);
}
yield return new WaitForSeconds(thinkTime);
if (thinkingIndicator != null)
{
thinkingIndicator.SetActive(false);
}
}
#endregion
#region AI Logic
/// <summary>
/// Select the best target block to aim at
/// Priority: Weak points > Low HP blocks > Random block
/// </summary>
private FortBlock SelectBestTarget()
{
if (_fortManager.PlayerFort == null)
{
Logging.Error("[FortFightAIController] Player fort not found!");
return null;
}
List<FortBlock> weakPoints = _fortManager.PlayerFort.GetWeakPoints();
// Priority 1: Target weak points
if (weakPoints != null && weakPoints.Count > 0)
{
FortBlock target = weakPoints[Random.Range(0, weakPoints.Count)];
Logging.Debug($"[FortFightAIController] Targeting weak point: {target.gameObject.name}");
return target;
}
// Priority 2: Target lowest HP block
List<FortBlock> allBlocks = new List<FortBlock>();
foreach (Transform child in _fortManager.PlayerFort.transform)
{
FortBlock block = child.GetComponent<FortBlock>();
if (block != null && !block.IsDestroyed)
{
allBlocks.Add(block);
}
}
if (allBlocks.Count == 0)
{
Logging.Warning("[FortFightAIController] No blocks available to target!");
return null;
}
// Sort by HP and pick from bottom 30%
allBlocks.Sort((a, b) => a.CurrentHp.CompareTo(b.CurrentHp));
int targetRange = Mathf.Max(1, allBlocks.Count / 3);
FortBlock lowestHpTarget = allBlocks[Random.Range(0, targetRange)];
Logging.Debug($"[FortFightAIController] Targeting low HP block: {lowestHpTarget.gameObject.name} (HP: {lowestHpTarget.CurrentHp})");
return lowestHpTarget;
}
/// <summary>
/// Choose the best projectile type for current situation
/// </summary>
private ProjectileType ChooseProjectile()
{
// Get AI player index
int playerIndex = _turnManager.CurrentPlayer.PlayerIndex;
// Get available projectiles (check cooldowns via ammo manager)
var availableTypes = new List<ProjectileType>();
foreach (ProjectileType type in System.Enum.GetValues(typeof(ProjectileType)))
{
// Check if ammo is available (not on cooldown)
if (_ammoManager.IsAmmoAvailable(type, playerIndex))
{
availableTypes.Add(type);
}
}
if (availableTypes.Count == 0)
{
Logging.Warning("[FortFightAIController] No ammo available! Using Toaster as default.");
return ProjectileType.Toaster;
}
float enemyHpPercent = _fortManager.PlayerFort.HpPercentage;
// Strategic selection based on fort HP
if (enemyHpPercent > 70f && availableTypes.Contains(ProjectileType.TrashBag))
{
return ProjectileType.TrashBag; // Spread damage early game
}
else if (enemyHpPercent > 40f && availableTypes.Contains(ProjectileType.Vacuum))
{
return ProjectileType.Vacuum; // Destruction machine mid-game
}
else if (_targetBlock != null && _targetBlock.IsWeakPoint && availableTypes.Contains(ProjectileType.CeilingFan))
{
return ProjectileType.CeilingFan; // Drop on weak points
}
// Default to first available
return availableTypes[Random.Range(0, availableTypes.Count)];
}
/// <summary>
/// Calculate ballistic trajectory to hit target using proper parabolic flight physics
/// Returns velocity vector needed to hit the target
/// </summary>
private Vector2 CalculateTrajectoryWithDeviation(Vector2 from, Vector2 to)
{
// Get gravity from Physics2D settings
float gravity = Mathf.Abs(Physics2D.gravity.y);
if (gravity < 0.1f) gravity = 9.81f; // Fallback to standard gravity
// Calculate displacement
Vector2 displacement = to - from;
float horizontalDist = displacement.x;
float verticalDist = displacement.y;
// Calculate perfect launch velocity using ballistic trajectory equations
// We'll use a 45-degree angle as base (optimal for distance) and adjust
Vector2 perfectVelocity = CalculateBallisticVelocity(horizontalDist, verticalDist, gravity);
if (perfectVelocity == Vector2.zero)
{
// Fallback if physics calculation fails
Logging.Warning("[FortFightAIController] Ballistic calculation failed, using fallback");
perfectVelocity = displacement.normalized * 15f;
}
// Apply difficulty-based deviation to velocity
Vector2 deviatedVelocity = ApplyDeviation(perfectVelocity);
// Log detailed trajectory info
float angle = Mathf.Atan2(deviatedVelocity.y, deviatedVelocity.x) * Mathf.Rad2Deg;
float speed = deviatedVelocity.magnitude;
Logging.Debug($"[FortFightAIController] Ballistic Trajectory: angle={angle:F1}°, speed={speed:F1} m/s, distance={displacement.magnitude:F1}m, gravity={gravity:F1}");
return deviatedVelocity;
}
/// <summary>
/// Calculate required velocity to hit target using ballistic equations
/// Uses quadratic formula to solve for launch angle given distance and height
/// </summary>
private Vector2 CalculateBallisticVelocity(float dx, float dy, float gravity)
{
// Try multiple launch angles and pick the first valid solution
// We prefer lower angles (30-60 degrees) for more direct shots
float[] preferredAngles = { 45f, 40f, 50f, 35f, 55f, 30f, 60f };
foreach (float angleDegrees in preferredAngles)
{
float angleRad = angleDegrees * Mathf.Deg2Rad;
// Calculate required speed for this angle
// Formula: v = sqrt((g * dx^2) / (2 * cos^2(θ) * (dx * tan(θ) - dy)))
float cosTheta = Mathf.Cos(angleRad);
float tanTheta = Mathf.Tan(angleRad);
float denominator = 2f * cosTheta * cosTheta * (dx * tanTheta - dy);
if (denominator > 0)
{
float speedSquared = (gravity * dx * dx) / denominator;
if (speedSquared > 0)
{
float speed = Mathf.Sqrt(speedSquared);
// Clamp speed to reasonable range (5-25 m/s)
speed = Mathf.Clamp(speed, 5f, 25f);
// Calculate velocity components
float vx = speed * cosTheta * Mathf.Sign(dx);
float vy = speed * Mathf.Sin(angleRad);
return new Vector2(vx, vy);
}
}
}
// If no valid solution found, use simple physics
// Estimate time of flight and calculate velocity
float distance = Mathf.Sqrt(dx * dx + dy * dy);
float estimatedTime = Mathf.Sqrt(2f * distance / gravity);
if (estimatedTime > 0)
{
float vx = dx / estimatedTime;
float vy = dy / estimatedTime + 0.5f * gravity * estimatedTime;
return new Vector2(vx, vy);
}
return Vector2.zero;
}
/// <summary>
/// Apply difficulty-based deviation to velocity
/// </summary>
private Vector2 ApplyDeviation(Vector2 perfectVelocity)
{
float angleDeviation = GetAngleDeviation();
float speedDeviation = GetForceDeviation(); // Reuse for speed deviation
// Convert velocity to polar coordinates
float speed = perfectVelocity.magnitude;
float angle = Mathf.Atan2(perfectVelocity.y, perfectVelocity.x) * Mathf.Rad2Deg;
// Apply angle deviation
angle += Random.Range(-angleDeviation, angleDeviation);
// Apply speed deviation
speed *= Random.Range(1f - speedDeviation, 1f + speedDeviation);
// Convert back to cartesian
float angleRad = angle * Mathf.Deg2Rad;
return new Vector2(Mathf.Cos(angleRad), Mathf.Sin(angleRad)) * speed;
}
/// <summary>
/// Get angle deviation based on current difficulty data
/// </summary>
private float GetAngleDeviation()
{
return _currentDifficultyData.angleDeviation;
}
/// <summary>
/// Get force deviation based on current difficulty data
/// </summary>
private float GetForceDeviation()
{
return _currentDifficultyData.forceDeviation;
}
/// <summary>
/// Execute the shot by launching projectile from AI slingshot with calculated velocity
/// </summary>
private void ExecuteShot(Vector2 launchVelocity)
{
if (aiSlingshot == null)
{
Logging.Error("[FortFightAIController] AI Slingshot not assigned! Cannot execute shot.");
return;
}
// Set ammunition type for AI player
int playerIndex = _turnManager.CurrentPlayer.PlayerIndex;
_ammoManager.SelectAmmo(_selectedAmmo, playerIndex);
// Get the selected ammo config and set it on the slingshot
ProjectileConfig ammoConfig = _ammoManager.GetSelectedAmmoConfig(playerIndex);
if (ammoConfig != null)
{
aiSlingshot.SetAmmo(ammoConfig);
}
else
{
Logging.Error("[FortFightAIController] Failed to get ammo config!");
return;
}
// Fire projectile using velocity-based launch (proper ballistic physics)
aiSlingshot.LaunchWithVelocity(launchVelocity);
// Trigger shot animation if animator exists
if (aiAnimator != null)
{
aiAnimator.SetTrigger("Shoot");
}
Logging.Debug($"[FortFightAIController] Shot executed: {_selectedAmmo} with velocity {launchVelocity}");
}
#endregion
#region Debug Visualization
private void OnDrawGizmos()
{
// Get developer settings (works in both editor and play mode)
var devSettings = AppleHills.SettingsAccess.GetDeveloperSettingsForEditor<AppleHills.Core.Settings.FortFightDeveloperSettings>();
if (devSettings == null || !devSettings.ShowAIDebugVisuals) return;
// Only draw during play mode when AI is actively executing
if (!Application.isPlaying) return;
// Draw target circle
if (_targetBlock != null && _isExecutingTurn)
{
Gizmos.color = devSettings.AIDebugTargetColor;
DrawCircle(_targetPosition, devSettings.AIDebugCircleRadius);
// Draw line from slingshot to target
if (aiSlingshot != null)
{
Gizmos.color = devSettings.AIDebugTrajectoryColor;
Gizmos.DrawLine(aiSlingshot.transform.position, _targetPosition);
}
}
}
/// <summary>
/// Draw a circle gizmo (approximation)
/// </summary>
private void DrawCircle(Vector2 center, float radius)
{
int segments = 32;
float angleStep = 360f / segments;
Vector3 prevPoint = center + new Vector2(radius, 0);
for (int i = 1; i <= segments; i++)
{
float angle = angleStep * i * Mathf.Deg2Rad;
Vector3 newPoint = center + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
Gizmos.DrawLine(prevPoint, newPoint);
prevPoint = newPoint;
}
}
#endregion
#region Cleanup
internal override void OnManagedDestroy()
{
base.OnManagedDestroy();
if (turnManager != null)
if (_turnManager != null)
{
turnManager.OnTurnStarted -= OnTurnStarted;
_turnManager.OnTurnStarted -= OnTurnStarted;
}
}

View File

@@ -44,23 +44,33 @@ namespace Minigames.FortFight.Core
[Tooltip("Default projectile type selected at game start")]
[SerializeField] private ProjectileType defaultProjectileType = ProjectileType.Toaster;
[Header("Debug")]
[SerializeField] private bool showDebugLogs = true;
#endregion
#region Settings
private IFortFightSettings cachedSettings;
private IFortFightSettings _cachedSettings;
private IFortFightSettings CachedSettings
{
get
{
if (cachedSettings == null)
if (_cachedSettings == null)
{
cachedSettings = GameManager.GetSettingsObject<IFortFightSettings>();
_cachedSettings = GameManager.GetSettingsObject<IFortFightSettings>();
}
return cachedSettings;
return _cachedSettings;
}
}
private FortFightDeveloperSettings _cachedDevSettings;
private FortFightDeveloperSettings CachedDevSettings
{
get
{
if (_cachedDevSettings == null)
{
_cachedDevSettings = GameManager.GetDeveloperSettings<FortFightDeveloperSettings>();
}
return _cachedDevSettings;
}
}
@@ -73,6 +83,8 @@ namespace Minigames.FortFight.Core
}
}
private bool ShowDebugLogs => CachedDevSettings?.AmmunitionShowDebugLogs ?? false;
#endregion
#region Events
@@ -97,7 +109,7 @@ namespace Minigames.FortFight.Core
#region State
// Per-player ammunition state (encapsulates cooldowns, selection, usage)
private Dictionary<int, PlayerAmmoState> playerStates = new Dictionary<int, PlayerAmmoState>();
private Dictionary<int, PlayerAmmoState> _playerStates = new Dictionary<int, PlayerAmmoState>();
#endregion
@@ -127,12 +139,12 @@ namespace Minigames.FortFight.Core
for (int playerIndex = 0; playerIndex < MaxPlayers; playerIndex++)
{
// Create player state with default ammo
playerStates[playerIndex] = new PlayerAmmoState(playerIndex, defaultProjectileType);
_playerStates[playerIndex] = new PlayerAmmoState(playerIndex, defaultProjectileType);
// Initialize cooldowns for all projectile types
foreach (var config in configs)
{
playerStates[playerIndex].InitializeCooldown(config.projectileType);
_playerStates[playerIndex].InitializeCooldown(config.projectileType);
}
// Select default ammo (triggers event)
@@ -146,14 +158,14 @@ namespace Minigames.FortFight.Core
/// </summary>
public void DecrementCooldowns(int playerIndex)
{
if (!playerStates.ContainsKey(playerIndex))
if (!_playerStates.ContainsKey(playerIndex))
{
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
return;
}
// Decrement cooldowns and get completed types
List<ProjectileType> completedCooldowns = playerStates[playerIndex].DecrementCooldowns();
List<ProjectileType> completedCooldowns = _playerStates[playerIndex].DecrementCooldowns();
// Fire events for completed cooldowns
var settings = CachedSettings;
@@ -162,7 +174,7 @@ namespace Minigames.FortFight.Core
var config = settings?.GetProjectileConfig(type);
if (config != null)
{
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} cooldown completed");
if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} cooldown completed");
OnAmmoCooldownCompleted?.Invoke(type);
}
}
@@ -179,7 +191,7 @@ namespace Minigames.FortFight.Core
/// </summary>
public bool SelectAmmo(ProjectileType type, int playerIndex)
{
if (!playerStates.ContainsKey(playerIndex))
if (!_playerStates.ContainsKey(playerIndex))
{
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
return false;
@@ -196,12 +208,12 @@ namespace Minigames.FortFight.Core
if (!IsAmmoAvailable(type, playerIndex))
{
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} is on cooldown");
if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} is on cooldown");
return false;
}
playerStates[playerIndex].SelectedAmmo = type;
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex} selected: {config.displayName}");
_playerStates[playerIndex].SelectedAmmo = type;
if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex} selected: {config.displayName}");
OnAmmoSelected?.Invoke(type, playerIndex);
return true;
@@ -212,9 +224,9 @@ namespace Minigames.FortFight.Core
/// </summary>
public ProjectileType GetSelectedAmmoType(int playerIndex)
{
if (playerStates.ContainsKey(playerIndex))
if (_playerStates.ContainsKey(playerIndex))
{
return playerStates[playerIndex].SelectedAmmo;
return _playerStates[playerIndex].SelectedAmmo;
}
return defaultProjectileType;
}
@@ -233,12 +245,12 @@ namespace Minigames.FortFight.Core
/// </summary>
public bool IsAmmoAvailable(ProjectileType type, int playerIndex)
{
if (!playerStates.ContainsKey(playerIndex))
if (!_playerStates.ContainsKey(playerIndex))
{
return false;
}
return playerStates[playerIndex].IsAmmoAvailable(type);
return _playerStates[playerIndex].IsAmmoAvailable(type);
}
/// <summary>
@@ -246,12 +258,12 @@ namespace Minigames.FortFight.Core
/// </summary>
public int GetCooldownRemaining(ProjectileType type, int playerIndex)
{
if (!playerStates.ContainsKey(playerIndex))
if (!_playerStates.ContainsKey(playerIndex))
{
return 0;
}
return playerStates[playerIndex].GetCooldown(type);
return _playerStates[playerIndex].GetCooldown(type);
}
#endregion
@@ -263,7 +275,7 @@ namespace Minigames.FortFight.Core
/// </summary>
public void UseAmmo(ProjectileType type, int playerIndex)
{
if (!playerStates.ContainsKey(playerIndex))
if (!_playerStates.ContainsKey(playerIndex))
{
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
return;
@@ -279,10 +291,10 @@ namespace Minigames.FortFight.Core
}
// Set cooldown and record usage
playerStates[playerIndex].SetCooldown(type, config.cooldownTurns);
playerStates[playerIndex].RecordUsage(type);
_playerStates[playerIndex].SetCooldown(type, config.cooldownTurns);
_playerStates[playerIndex].RecordUsage(type);
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} used - cooldown: {config.cooldownTurns} turns");
if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex}: {config.displayName} used - cooldown: {config.cooldownTurns} turns");
OnAmmoCooldownStarted?.Invoke(type, config.cooldownTurns);
}
@@ -298,7 +310,7 @@ namespace Minigames.FortFight.Core
{
var configs = AvailableConfigs;
foreach (var playerState in playerStates.Values)
foreach (var playerState in _playerStates.Values)
{
foreach (var config in configs)
{
@@ -306,7 +318,7 @@ namespace Minigames.FortFight.Core
}
}
if (showDebugLogs) Logging.Debug("[AmmunitionManager] All cooldowns reset for all players");
if (ShowDebugLogs) Logging.Debug("[AmmunitionManager] All cooldowns reset for all players");
}
/// <summary>

View File

@@ -32,6 +32,18 @@ namespace Minigames.FortFight.Core
new BlockSizeConfig { size = BlockSize.Large, hpMultiplier = 2f, massMultiplier = 2f }
};
[Header("AI Difficulty Configurations")]
[Tooltip("AI behavior parameters for each difficulty level")]
[SerializeField] private List<AIDifficultyConfig> aiDifficultyConfigs = new List<AIDifficultyConfig>
{
new AIDifficultyConfig { difficulty = AIDifficulty.Easy, data = new AIDifficultyData(45f, 0.3f, 0.5f, 2f) }, // ±45° angle, ±30% force, 0.5-2s think time
new AIDifficultyConfig { difficulty = AIDifficulty.Medium, data = new AIDifficultyData(30f, 0.2f, 0.5f, 2f) }, // ±30° angle, ±20% force, 0.5-2s think time
new AIDifficultyConfig { difficulty = AIDifficulty.Hard, data = new AIDifficultyData(10f, 0.1f, 0.5f, 2f) } // ±10° angle, ±10% force, 0.5-2s think time
};
[Tooltip("Default AI difficulty level for single-player games")]
[SerializeField] private AIDifficulty defaultAIDifficulty = AIDifficulty.Medium;
[Header("Weak Point Settings")]
[Tooltip("Radius of explosion effect from weak points")]
[SerializeField] private float weakPointExplosionRadius = 2.5f;
@@ -133,6 +145,8 @@ namespace Minigames.FortFight.Core
public List<BlockMaterialConfig> MaterialConfigs => materialConfigs;
public List<BlockSizeConfig> SizeConfigs => sizeConfigs;
public AIDifficulty DefaultAIDifficulty => defaultAIDifficulty;
public float WeakPointExplosionRadius => weakPointExplosionRadius;
public float WeakPointExplosionDamage => weakPointExplosionDamage;
public float WeakPointExplosionForce => weakPointExplosionForce;
@@ -209,6 +223,23 @@ namespace Minigames.FortFight.Core
return sizeConfigs.FirstOrDefault(c => c.size == size);
}
/// <summary>
/// Get AI difficulty configuration data by difficulty level
/// </summary>
public AIDifficultyData GetAIDifficultyData(AIDifficulty difficulty)
{
var config = aiDifficultyConfigs.FirstOrDefault(c => c.difficulty == difficulty);
if (config != null)
{
return config.data;
}
// Fallback to Medium difficulty if not found
Debug.LogWarning($"[FortFightSettings] AI difficulty data not found for {difficulty}, using Medium as fallback");
var mediumConfig = aiDifficultyConfigs.FirstOrDefault(c => c.difficulty == AIDifficulty.Medium);
return mediumConfig != null ? mediumConfig.data : new AIDifficultyData(30f, 0.2f, 0.5f, 2f);
}
#endregion
#region Validation

View File

@@ -34,9 +34,6 @@ namespace Minigames.FortFight.Core
[Header("Fort Prefabs")]
[SerializeField] private GameObject[] premadeFortPrefabs;
[Tooltip("Leave empty to spawn random forts. Assign specific prefabs for testing.")]
[SerializeField] private GameObject debugPlayerFortPrefab;
[SerializeField] private GameObject debugEnemyFortPrefab;
[Header("Spawn Points")]
[SerializeField] private Transform playerSpawnPoint;
@@ -45,6 +42,26 @@ namespace Minigames.FortFight.Core
[Header("Settings")]
[SerializeField] private bool useDebugForts = false;
#endregion
#region Developer Settings
private AppleHills.Core.Settings.FortFightDeveloperSettings _cachedDevSettings;
private AppleHills.Core.Settings.FortFightDeveloperSettings CachedDevSettings
{
get
{
if (_cachedDevSettings == null)
{
_cachedDevSettings = GameManager.GetDeveloperSettings<AppleHills.Core.Settings.FortFightDeveloperSettings>();
}
return _cachedDevSettings;
}
}
private GameObject DebugPlayerFortPrefab => CachedDevSettings?.DebugPlayerFortPrefab;
private GameObject DebugEnemyFortPrefab => CachedDevSettings?.DebugEnemyFortPrefab;
#endregion
#region Events
@@ -121,9 +138,9 @@ namespace Minigames.FortFight.Core
Logging.Debug("[FortManager] Spawning forts for both players");
// Spawn player fort
if (useDebugForts && debugPlayerFortPrefab != null)
if (useDebugForts && DebugPlayerFortPrefab != null)
{
PlayerFort = SpawnFort(debugPlayerFortPrefab, playerSpawnPoint, "Player Fort");
PlayerFort = SpawnFort(DebugPlayerFortPrefab, playerSpawnPoint, "Player Fort");
}
else
{
@@ -136,9 +153,9 @@ namespace Minigames.FortFight.Core
}
// Spawn enemy fort
if (useDebugForts && debugEnemyFortPrefab != null)
if (useDebugForts && DebugEnemyFortPrefab != null)
{
EnemyFort = SpawnFort(debugEnemyFortPrefab, enemySpawnPoint, "Enemy Fort");
EnemyFort = SpawnFort(DebugEnemyFortPrefab, enemySpawnPoint, "Enemy Fort");
}
else
{

View File

@@ -28,27 +28,38 @@ namespace Minigames.FortFight.Core
[Tooltip("Trajectory preview component")]
[SerializeField] private TrajectoryPreview trajectoryPreview;
[Header("Debug")]
[SerializeField] private bool showDebugLogs = true;
#endregion
#region Settings
private IFortFightSettings cachedSettings;
private IFortFightSettings _cachedSettings;
private IFortFightSettings CachedSettings
{
get
{
if (cachedSettings == null)
if (_cachedSettings == null)
{
cachedSettings = GameManager.GetSettingsObject<IFortFightSettings>();
_cachedSettings = GameManager.GetSettingsObject<IFortFightSettings>();
}
return cachedSettings;
return _cachedSettings;
}
}
private FortFightDeveloperSettings _cachedDevSettings;
private FortFightDeveloperSettings CachedDevSettings
{
get
{
if (_cachedDevSettings == null)
{
_cachedDevSettings = GameManager.GetDeveloperSettings<FortFightDeveloperSettings>();
}
return _cachedDevSettings;
}
}
private float MaxForce => CachedSettings?.BaseLaunchForce ?? 20f;
private bool ShowDebugLogs => CachedDevSettings?.SlingshotShowDebugLogs ?? false;
#endregion
@@ -63,12 +74,12 @@ namespace Minigames.FortFight.Core
#region State
private bool isDragging;
private Vector2 dragStartPosition;
private ProjectileConfig currentAmmo;
private ProjectileBase activeProjectile;
private bool _isDragging;
private Vector2 _dragStartPosition;
private ProjectileConfig _currentAmmo;
private ProjectileBase _activeProjectile;
public bool IsDragging => isDragging;
public bool IsDragging => _isDragging;
public bool IsEnabled { get; private set; } = true;
#endregion
@@ -118,13 +129,13 @@ namespace Minigames.FortFight.Core
public void OnHoldMove(Vector2 worldPosition)
{
if (!IsEnabled || !isDragging) return;
if (!IsEnabled || !_isDragging) return;
UpdateDrag(worldPosition);
}
public void OnHoldEnd(Vector2 worldPosition)
{
if (!IsEnabled || !isDragging) return;
if (!IsEnabled || !_isDragging) return;
EndDrag(worldPosition);
}
@@ -134,16 +145,16 @@ namespace Minigames.FortFight.Core
private void StartDrag(Vector2 worldPosition)
{
if (currentAmmo == null)
if (_currentAmmo == null)
{
if (showDebugLogs) Logging.Warning("[SlingshotController] No ammo selected!");
if (ShowDebugLogs) Logging.Warning("[SlingshotController] No ammo selected!");
return;
}
isDragging = true;
_isDragging = true;
// Use the projectile spawn point as the anchor, not the touch position
// This makes it work like Angry Birds - pull back from slingshot to launch forward
dragStartPosition = projectileSpawnPoint.position;
_dragStartPosition = projectileSpawnPoint.position;
// Show trajectory preview
if (trajectoryPreview != null)
@@ -151,14 +162,14 @@ namespace Minigames.FortFight.Core
trajectoryPreview.Show();
}
if (showDebugLogs) Logging.Debug($"[SlingshotController] Started drag at {worldPosition}, anchor at spawn point {dragStartPosition}");
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Started drag at {worldPosition}, anchor at spawn point {_dragStartPosition}");
}
private void UpdateDrag(Vector2 currentWorldPosition)
{
// Calculate drag vector from spawn point to current drag position
// Pull back (away from spawn) = launch forward (toward spawn direction)
Vector2 dragVector = dragStartPosition - currentWorldPosition;
Vector2 dragVector = _dragStartPosition - currentWorldPosition;
// Calculate force and direction
float dragDistance = dragVector.magnitude;
@@ -172,10 +183,10 @@ namespace Minigames.FortFight.Core
Vector2 direction = dragVector.normalized;
// Update trajectory preview with projectile mass
if (trajectoryPreview != null && currentAmmo != null)
if (trajectoryPreview != null && _currentAmmo != null)
{
Vector2 worldStartPos = projectileSpawnPoint.position;
float mass = currentAmmo.GetMass();
float mass = _currentAmmo.GetMass();
// Debug: Log trajectory calculation (uncomment for debugging)
// if (showDebugLogs && Time.frameCount % 30 == 0) // Log every 30 frames to avoid spam
@@ -189,7 +200,7 @@ namespace Minigames.FortFight.Core
private void EndDrag(Vector2 currentWorldPosition)
{
isDragging = false;
_isDragging = false;
// Hide trajectory
if (trajectoryPreview != null)
@@ -198,7 +209,7 @@ namespace Minigames.FortFight.Core
}
// Calculate final launch parameters from spawn point to final drag position
Vector2 dragVector = dragStartPosition - currentWorldPosition;
Vector2 dragVector = _dragStartPosition - currentWorldPosition;
float dragDistance = dragVector.magnitude;
float dragRatio = Mathf.Clamp01(dragDistance / maxDragDistance);
@@ -216,9 +227,9 @@ namespace Minigames.FortFight.Core
// Launch projectile if force exceeds minimum
if (force >= minForce)
{
if (showDebugLogs && currentAmmo != null)
if (ShowDebugLogs && _currentAmmo != null)
{
float mass = currentAmmo.GetMass();
float mass = _currentAmmo.GetMass();
float velocity = force / mass;
Logging.Debug($"[Slingshot] Launch - Force: {force:F2}, Mass: {mass:F2}, Velocity: {velocity:F2}, Dir: {direction}");
}
@@ -227,7 +238,7 @@ namespace Minigames.FortFight.Core
}
else
{
if (showDebugLogs) Logging.Debug($"[SlingshotController] Drag too short - force {force:F2} < min {minForce:F2}");
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Drag too short - force {force:F2} < min {minForce:F2}");
}
}
@@ -240,8 +251,8 @@ namespace Minigames.FortFight.Core
/// </summary>
public void SetAmmo(ProjectileConfig ammoConfig)
{
currentAmmo = ammoConfig;
if (showDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
_currentAmmo = ammoConfig;
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
}
/// <summary>
@@ -249,28 +260,28 @@ namespace Minigames.FortFight.Core
/// </summary>
private void LaunchProjectile(Vector2 direction, float force)
{
if (currentAmmo == null || currentAmmo.prefab == null)
if (_currentAmmo == null || _currentAmmo.prefab == null)
{
Logging.Error("[SlingshotController] Cannot launch - no ammo or prefab!");
return;
}
// Spawn projectile
GameObject projectileObj = Instantiate(currentAmmo.prefab, projectileSpawnPoint.position, Quaternion.identity);
activeProjectile = projectileObj.GetComponent<Projectiles.ProjectileBase>();
GameObject projectileObj = Instantiate(_currentAmmo.prefab, projectileSpawnPoint.position, Quaternion.identity);
_activeProjectile = projectileObj.GetComponent<ProjectileBase>();
if (activeProjectile == null)
if (_activeProjectile == null)
{
Logging.Error($"[SlingshotController] Projectile prefab {currentAmmo.prefab.name} missing ProjectileBase component!");
Logging.Error($"[SlingshotController] Projectile prefab {_currentAmmo.prefab.name} missing ProjectileBase component!");
Destroy(projectileObj);
return;
}
// Initialize projectile with its type (loads damage and mass from settings)
activeProjectile.Initialize(currentAmmo.projectileType);
_activeProjectile.Initialize(_currentAmmo.projectileType);
// Launch it
activeProjectile.Launch(direction, force);
_activeProjectile.Launch(direction, force);
// Lock trajectory to show the shot path
if (trajectoryPreview != null)
@@ -279,10 +290,46 @@ namespace Minigames.FortFight.Core
trajectoryPreview.LockTrajectory(lockDuration);
}
if (showDebugLogs) Logging.Debug($"[SlingshotController] Launched {currentAmmo?.displayName ?? "projectile"} with force {force}");
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Launched {_currentAmmo?.displayName ?? "projectile"} with force {force}");
// Fire event
OnProjectileLaunched?.Invoke(activeProjectile);
OnProjectileLaunched?.Invoke(_activeProjectile);
}
/// <summary>
/// Public method for AI to simulate a drag-and-launch action
/// </summary>
public void SimulateDragLaunch(Vector2 direction, float force)
{
LaunchProjectile(direction, force);
}
/// <summary>
/// Launch projectile with a specific velocity (for AI ballistic calculations)
/// Calculates the required force based on projectile mass
/// </summary>
public void LaunchWithVelocity(Vector2 velocity)
{
if (_currentAmmo == null)
{
Logging.Error("[SlingshotController] Cannot launch - no ammo selected!");
return;
}
// Get projectile mass to calculate force
float mass = _currentAmmo.GetMass();
// Force = mass × velocity (for impulse-based launch)
Vector2 direction = velocity.normalized;
float speed = velocity.magnitude;
float force = mass * speed;
if (ShowDebugLogs)
{
Logging.Debug($"[Slingshot] LaunchWithVelocity - Velocity: {velocity}, Mass: {mass:F2}, Force: {force:F2}");
}
LaunchProjectile(direction, force);
}
/// <summary>
@@ -290,7 +337,7 @@ namespace Minigames.FortFight.Core
/// </summary>
public ProjectileBase GetActiveProjectile()
{
return activeProjectile;
return _activeProjectile;
}
#endregion
@@ -303,7 +350,7 @@ namespace Minigames.FortFight.Core
public void Enable()
{
IsEnabled = true;
if (showDebugLogs) Logging.Debug("[SlingshotController] Enabled");
if (ShowDebugLogs) Logging.Debug("[SlingshotController] Enabled");
}
/// <summary>
@@ -312,17 +359,16 @@ namespace Minigames.FortFight.Core
public void Disable()
{
IsEnabled = false;
isDragging = false;
_isDragging = false;
if (trajectoryPreview != null)
{
trajectoryPreview.Hide();
}
if (showDebugLogs) Logging.Debug("[SlingshotController] Disabled");
if (ShowDebugLogs) Logging.Debug("[SlingshotController] Disabled");
}
#endregion
}
}

View File

@@ -0,0 +1,51 @@
using System;
using UnityEngine;
namespace Minigames.FortFight.Data
{
/// <summary>
/// Configuration data for AI difficulty levels.
/// Defines how accurate and fast the AI behaves at each difficulty tier.
/// </summary>
[Serializable]
public struct AIDifficultyData
{
[Tooltip("Angle deviation in degrees (±)")]
public float angleDeviation;
[Tooltip("Force/speed deviation as percentage (0.2 = ±20%)")]
public float forceDeviation;
[Tooltip("Minimum thinking time in seconds")]
public float thinkTimeMin;
[Tooltip("Maximum thinking time in seconds")]
public float thinkTimeMax;
/// <summary>
/// Create AI difficulty data with specified parameters
/// </summary>
public AIDifficultyData(float angleDeviation, float forceDeviation, float thinkTimeMin, float thinkTimeMax)
{
this.angleDeviation = angleDeviation;
this.forceDeviation = forceDeviation;
this.thinkTimeMin = thinkTimeMin;
this.thinkTimeMax = thinkTimeMax;
}
}
/// <summary>
/// Wrapper class to serialize AI difficulty configuration in Unity inspector.
/// Maps difficulty level to its configuration data.
/// </summary>
[Serializable]
public class AIDifficultyConfig
{
[Tooltip("Difficulty level")]
public AIDifficulty difficulty;
[Tooltip("Configuration data for this difficulty")]
public AIDifficultyData data;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9da4870f05ff4968a77b4e790efbb264
timeCreated: 1764841304

View File

@@ -51,5 +51,15 @@
CeilingFan, // Drops straight down
TrashBag // Explodes on impact
}
/// <summary>
/// AI difficulty levels with varying accuracy and behavior
/// </summary>
public enum AIDifficulty
{
Easy, // Large deviations, slower thinking
Medium, // Moderate deviations, moderate thinking
Hard // Minimal deviations, faster thinking
}
}

View File

@@ -56,6 +56,7 @@ namespace Minigames.FortFight.Fort
public float CurrentHp => currentHp;
public float MaxHp => maxHp;
public float HpPercentage => maxHp > 0 ? (currentHp / maxHp) * 100f : 0f;
public bool IsDestroyed => isDestroyed;
#endregion

View File

@@ -18,9 +18,6 @@ namespace Minigames.FortFight.Fort
[Header("Fort Configuration")]
[SerializeField] private string fortName = "Unnamed Fort";
[Header("Debug")]
[SerializeField] private bool showDebugInfo = true;
#endregion
#region Events
@@ -45,26 +42,26 @@ namespace Minigames.FortFight.Fort
#region Properties
public string FortName => fortName;
public float MaxFortHp => maxFortHp;
public float CurrentFortHp => currentFortHp;
public float HpPercentage => maxFortHp > 0 ? (currentFortHp / maxFortHp) * 100f : 0f;
public int TotalBlockCount => blocks.Count;
public int InitialBlockCount => initialBlockCount;
public float MaxFortHp => _maxFortHp;
public float CurrentFortHp => _currentFortHp;
public float HpPercentage => _maxFortHp > 0 ? (_currentFortHp / _maxFortHp) * 100f : 0f;
public int TotalBlockCount => _blocks.Count;
public int InitialBlockCount => _initialBlockCount;
public bool IsDefeated { get; private set; }
// Aliases for consistency
public float MaxHp => maxFortHp;
public float CurrentHp => currentFortHp;
public float MaxHp => _maxFortHp;
public float CurrentHp => _currentFortHp;
#endregion
#region Private State
private List<FortBlock> blocks = new List<FortBlock>();
private float maxFortHp = 0f;
private float currentFortHp = 0f;
private int initialBlockCount = 0;
private bool isInitialized = false;
private List<FortBlock> _blocks = new List<FortBlock>();
private float _maxFortHp = 0f;
private float _currentFortHp = 0f;
private int _initialBlockCount = 0;
private bool _isInitialized = false;
// Cached settings
private IFortFightSettings _cachedSettings;
@@ -80,6 +77,21 @@ namespace Minigames.FortFight.Fort
}
}
private FortFightDeveloperSettings _cachedDevSettings;
private FortFightDeveloperSettings CachedDevSettings
{
get
{
if (_cachedDevSettings == null)
{
_cachedDevSettings = GameManager.GetDeveloperSettings<FortFightDeveloperSettings>();
}
return _cachedDevSettings;
}
}
private bool ShowDebugInfo => CachedDevSettings?.FortShowDebugInfo ?? false;
#endregion
#region Lifecycle
@@ -97,7 +109,7 @@ namespace Minigames.FortFight.Fort
base.OnManagedDestroy();
// Unsubscribe from all block events
foreach (FortBlock block in blocks)
foreach (FortBlock block in _blocks)
{
if (block != null)
{
@@ -106,7 +118,7 @@ namespace Minigames.FortFight.Fort
}
}
blocks.Clear();
_blocks.Clear();
// Clear events
OnFortDamaged = null;
@@ -124,7 +136,7 @@ namespace Minigames.FortFight.Fort
/// </summary>
private void InitializeFort()
{
if (isInitialized)
if (_isInitialized)
{
Logging.Warning($"[FortController] {fortName} already initialized!");
return;
@@ -138,7 +150,7 @@ namespace Minigames.FortFight.Fort
// Step 2: Register with central manager
RegisterWithManager();
isInitialized = true;
_isInitialized = true;
Logging.Debug($"[FortController] {fortName} - Initialization complete");
}
@@ -176,10 +188,10 @@ namespace Minigames.FortFight.Fort
}
// Step 3: Initialize current HP to match max HP (sum of all blocks)
currentFortHp = maxFortHp;
_currentFortHp = _maxFortHp;
initialBlockCount = blocks.Count;
Logging.Debug($"[FortController] {fortName} - Initialized and registered {blocks.Count} blocks, Total HP: {maxFortHp:F0}");
_initialBlockCount = _blocks.Count;
Logging.Debug($"[FortController] {fortName} - Initialized and registered {_blocks.Count} blocks, Total HP: {_maxFortHp:F0}");
}
/// <summary>
@@ -211,18 +223,18 @@ namespace Minigames.FortFight.Fort
{
if (block == null) return;
if (!blocks.Contains(block))
if (!_blocks.Contains(block))
{
blocks.Add(block);
_blocks.Add(block);
// Only add to max HP, current HP will be calculated once at end of initialization
maxFortHp += block.MaxHp;
_maxFortHp += block.MaxHp;
// Subscribe to block events
block.OnBlockDestroyed += HandleBlockDestroyed;
block.OnBlockDamaged += HandleBlockDamaged;
if (showDebugInfo)
if (ShowDebugInfo)
{
Logging.Debug($"[FortController] Registered block: {block.gameObject.name} ({block.Material} {block.Size}, HP: {block.MaxHp})");
}
@@ -234,7 +246,7 @@ namespace Minigames.FortFight.Fort
/// </summary>
public List<FortBlock> GetWeakPoints()
{
return blocks.Where(b => b != null && b.IsWeakPoint).ToList();
return _blocks.Where(b => b != null && b.IsWeakPoint).ToList();
}
/// <summary>
@@ -242,7 +254,7 @@ namespace Minigames.FortFight.Fort
/// </summary>
public List<FortBlock> GetAllBlocks()
{
return new List<FortBlock>(blocks);
return new List<FortBlock>(_blocks);
}
/// <summary>
@@ -250,8 +262,8 @@ namespace Minigames.FortFight.Fort
/// </summary>
public FortBlock GetRandomBlock()
{
if (blocks.Count == 0) return null;
return blocks[UnityEngine.Random.Range(0, blocks.Count)];
if (_blocks.Count == 0) return null;
return _blocks[UnityEngine.Random.Range(0, _blocks.Count)];
}
#endregion
@@ -265,7 +277,7 @@ namespace Minigames.FortFight.Fort
Logging.Debug($"[FortController] {fortName} - Block destroyed: {block.gameObject.name}");
// Remove from list
blocks.Remove(block);
_blocks.Remove(block);
// Recalculate HP by summing all remaining blocks (consistent calculation method)
RecalculateFortHp();
@@ -277,9 +289,9 @@ namespace Minigames.FortFight.Fort
// Check defeat condition
CheckDefeatCondition();
if (showDebugInfo)
if (ShowDebugInfo)
{
Logging.Debug($"[FortController] {fortName} - HP: {currentFortHp:F0}/{maxFortHp:F0} ({HpPercentage:F1}%), Blocks: {blocks.Count}/{initialBlockCount}");
Logging.Debug($"[FortController] {fortName} - HP: {_currentFortHp:F0}/{_maxFortHp:F0} ({HpPercentage:F1}%), Blocks: {_blocks.Count}/{_initialBlockCount}");
}
}
@@ -305,18 +317,18 @@ namespace Minigames.FortFight.Fort
/// </summary>
private void RecalculateFortHp()
{
currentFortHp = 0f;
foreach (var block in blocks)
_currentFortHp = 0f;
foreach (var block in _blocks)
{
if (block != null)
{
currentFortHp += block.CurrentHp;
_currentFortHp += block.CurrentHp;
}
}
if (showDebugInfo)
if (ShowDebugInfo)
{
Logging.Debug($"[FortController] {fortName} - HP recalculated: {currentFortHp:F0}/{maxFortHp:F0} ({HpPercentage:F1}%)");
Logging.Debug($"[FortController] {fortName} - HP recalculated: {_currentFortHp:F0}/{_maxFortHp:F0} ({HpPercentage:F1}%)");
}
}
@@ -335,7 +347,7 @@ namespace Minigames.FortFight.Fort
float defeatThreshold = CachedSettings?.FortDefeatThreshold ?? 0.3f;
float defeatThresholdPercent = defeatThreshold * 100f;
Logging.Debug($"[FortController] {fortName} - Checking defeat: HP={currentFortHp:F1}/{maxFortHp:F1} ({HpPercentage:F1}%) vs threshold={defeatThresholdPercent:F1}%");
Logging.Debug($"[FortController] {fortName} - Checking defeat: HP={_currentFortHp:F1}/{_maxFortHp:F1} ({HpPercentage:F1}%) vs threshold={defeatThresholdPercent:F1}%");
// Defeat if HP at or below threshold
if (HpPercentage <= defeatThresholdPercent)
@@ -361,7 +373,7 @@ namespace Minigames.FortFight.Fort
private void OnGUI()
{
if (!showDebugInfo || !Application.isPlaying) return;
if (!ShowDebugInfo || !Application.isPlaying) return;
// Display fort HP in scene view (for testing)
Vector3 screenPos = Camera.main.WorldToScreenPoint(transform.position + Vector3.up * 2f);

View File

@@ -32,8 +32,6 @@ namespace Minigames.FortFight.UI
[SerializeField] private Core.FortManager fortManager;
[Header("Debug Display")]
[Tooltip("Show numerical HP values (current/max)")]
[SerializeField] private bool debugDisplay = false;
[Tooltip("Text field to display 'current/max' HP values")]
[SerializeField] private TextMeshProUGUI debugHpText;
@@ -41,7 +39,20 @@ namespace Minigames.FortFight.UI
#region Private State
private FortController trackedFort;
private FortController _trackedFort;
private AppleHills.Core.Settings.FortFightDeveloperSettings _cachedDevSettings;
private bool DebugDisplay
{
get
{
if (_cachedDevSettings == null)
{
_cachedDevSettings = GameManager.GetDeveloperSettings<AppleHills.Core.Settings.FortFightDeveloperSettings>();
}
return _cachedDevSettings?.HealthUIDebugDisplay ?? false;
}
}
#endregion
@@ -69,9 +80,9 @@ namespace Minigames.FortFight.UI
base.OnManagedDestroy();
// Unsubscribe from fort events
if (trackedFort != null)
if (_trackedFort != null)
{
trackedFort.OnFortDamaged -= OnFortDamaged;
_trackedFort.OnFortDamaged -= OnFortDamaged;
}
// Unsubscribe from fort manager
@@ -154,15 +165,15 @@ namespace Minigames.FortFight.UI
}
// Unsubscribe from previous fort
if (trackedFort != null)
if (_trackedFort != null)
{
trackedFort.OnFortDamaged -= OnFortDamaged;
_trackedFort.OnFortDamaged -= OnFortDamaged;
}
trackedFort = fort;
_trackedFort = fort;
// Subscribe to fort events
trackedFort.OnFortDamaged += OnFortDamaged;
_trackedFort.OnFortDamaged += OnFortDamaged;
// Initialize UI
if (fortNameText != null)
@@ -181,7 +192,7 @@ namespace Minigames.FortFight.UI
private void OnFortDamaged(float damage, float hpPercentage)
{
Logging.Debug($"[FortHealthUI] OnFortDamaged received! Damage: {damage}, HP%: {hpPercentage:F1}%, Fort: {trackedFort?.FortName}");
Logging.Debug($"[FortHealthUI] OnFortDamaged received! Damage: {damage}, HP%: {hpPercentage:F1}%, Fort: {_trackedFort?.FortName}");
UpdateDisplay();
}
@@ -191,15 +202,15 @@ namespace Minigames.FortFight.UI
private void UpdateDisplay()
{
if (trackedFort == null)
if (_trackedFort == null)
{
Logging.Warning("[FortHealthUI] UpdateDisplay called but trackedFort is null!");
return;
}
float hpPercent = trackedFort.HpPercentage;
float hpPercent = _trackedFort.HpPercentage;
Logging.Debug($"[FortHealthUI] UpdateDisplay - Fort: {trackedFort.FortName}, HP: {hpPercent:F1}%");
Logging.Debug($"[FortHealthUI] UpdateDisplay - Fort: {_trackedFort.FortName}, HP: {hpPercent:F1}%");
// Update slider
if (hpSlider != null)
@@ -226,11 +237,11 @@ namespace Minigames.FortFight.UI
// Update debug HP display (current/max)
if (debugHpText != null)
{
if (debugDisplay)
if (DebugDisplay)
{
debugHpText.gameObject.SetActive(true);
float currentHp = trackedFort.CurrentHp;
float maxHp = trackedFort.MaxHp;
float currentHp = _trackedFort.CurrentHp;
float maxHp = _trackedFort.MaxHp;
debugHpText.text = $"{currentHp:F0}/{maxHp:F0}";
}
else

View File

@@ -21,7 +21,6 @@ namespace Minigames.FortFight.UI
[Header("Optional Visual Elements")]
[SerializeField] private CanvasGroup canvasGroup;
[SerializeField] private GameObject playerActionPanel;
[SerializeField] private GameObject aiTurnPanel;
private TurnManager turnManager;
@@ -128,11 +127,6 @@ namespace Minigames.FortFight.UI
{
playerActionPanel.SetActive(false);
}
if (aiTurnPanel != null)
{
aiTurnPanel.SetActive(true);
}
}
else if (turnState == TurnState.PlayerOneTurn || turnState == TurnState.PlayerTwoTurn)
{
@@ -141,11 +135,6 @@ namespace Minigames.FortFight.UI
{
playerActionPanel.SetActive(true);
}
if (aiTurnPanel != null)
{
aiTurnPanel.SetActive(false);
}
}
}

View File

@@ -0,0 +1,24 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d354649c25014aa2a7b3b45841834f74, type: 3}
m_Name: FortFightDeveloperSettings
m_EditorClassIdentifier: AppleHillsScripts::AppleHills.Core.Settings.FortFightDeveloperSettings
showAIDebugVisuals: 1
aiDebugTargetColor: {r: 1, g: 0, b: 0, a: 1}
aiDebugCircleRadius: 0.5
aiDebugTrajectoryColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1}
slingshotShowDebugLogs: 1
ammunitionShowDebugLogs: 1
fortShowDebugInfo: 1
healthUIDebugDisplay: 0
debugPlayerFortPrefab: {fileID: 0}
debugEnemyFortPrefab: {fileID: 0}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1476365959c6ead48985cb10ae82696f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -32,6 +32,26 @@ MonoBehaviour:
- size: 2
hpMultiplier: 2
massMultiplier: 2
aiDifficultyConfigs:
- difficulty: 0
data:
angleDeviation: 45
forceDeviation: 0.3
thinkTimeMin: 1.5
thinkTimeMax: 2
- difficulty: 1
data:
angleDeviation: 30
forceDeviation: 0.2
thinkTimeMin: 1
thinkTimeMax: 2
- difficulty: 2
data:
angleDeviation: 10
forceDeviation: 0.1
thinkTimeMin: 0.5
thinkTimeMax: 2
defaultAIDifficulty: 1
weakPointExplosionRadius: 5
weakPointExplosionDamage: 50
weakPointExplosionForce: 50