Simple Pigman AI

This commit is contained in:
Michal Pikulski
2025-12-04 08:52:59 +01:00
committed by Michal Pikulski
parent e60d516e7e
commit edb83ef13d
27 changed files with 7452 additions and 428 deletions

View File

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

View File

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

View File

@@ -10,11 +10,16 @@ namespace AppleHills.Editor
[InitializeOnLoad] [InitializeOnLoad]
public static class EditorSettingsProvider public static class EditorSettingsProvider
{ {
// Gameplay Settings
private static PlayerFollowerSettings _playerFollowerSettings; private static PlayerFollowerSettings _playerFollowerSettings;
private static InteractionSettings _interactionSettings; private static InteractionSettings _interactionSettings;
private static DivingMinigameSettings _divingMinigameSettings; private static DivingMinigameSettings _divingMinigameSettings;
private static Minigames.FortFight.Core.FortFightSettings _fortFightSettings; 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 constructor will be called when Unity loads/reloads scripts
static EditorSettingsProvider() static EditorSettingsProvider()
{ {
@@ -54,11 +59,16 @@ namespace AppleHills.Editor
public static void LoadAllSettings() public static void LoadAllSettings()
{ {
// Load gameplay settings
_playerFollowerSettings = AssetDatabase.LoadAssetAtPath<PlayerFollowerSettings>("Assets/Settings/PlayerFollowerSettings.asset"); _playerFollowerSettings = AssetDatabase.LoadAssetAtPath<PlayerFollowerSettings>("Assets/Settings/PlayerFollowerSettings.asset");
_interactionSettings = AssetDatabase.LoadAssetAtPath<InteractionSettings>("Assets/Settings/InteractionSettings.asset"); _interactionSettings = AssetDatabase.LoadAssetAtPath<InteractionSettings>("Assets/Settings/InteractionSettings.asset");
_divingMinigameSettings = AssetDatabase.LoadAssetAtPath<DivingMinigameSettings>("Assets/Settings/MinigameSettings.asset"); _divingMinigameSettings = AssetDatabase.LoadAssetAtPath<DivingMinigameSettings>("Assets/Settings/MinigameSettings.asset");
_fortFightSettings = AssetDatabase.LoadAssetAtPath<Minigames.FortFight.Core.FortFightSettings>("Assets/Settings/FortFightSettings.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 // Re-register the delegates in case they were lost
AppleHills.SettingsAccess.SetupEditorProviders( AppleHills.SettingsAccess.SetupEditorProviders(
GetPlayerStopDistance, GetPlayerStopDistance,
@@ -112,5 +122,18 @@ namespace AppleHills.Editor
return null; 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_Script: {fileID: 11500000, guid: 516daf2ce7384aaa94fd5e0f7a3cf078, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Projectiles.TrashPiece 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_LocalPosition: {x: 14.5, y: 3.4, z: 0}
m_LocalScale: {x: 16.29907, y: 16.29907, z: 16.29907} m_LocalScale: {x: 16.29907, y: 16.29907, z: 16.29907}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children:
- {fileID: 1389618396}
m_Father: {fileID: 799036564} m_Father: {fileID: 799036564}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &377753197 --- !u!212 &377753197
@@ -2728,7 +2729,7 @@ Transform:
m_GameObject: {fileID: 841922113} m_GameObject: {fileID: 841922113}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 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_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
@@ -2876,7 +2877,6 @@ MonoBehaviour:
maxDragDistance: 5 maxDragDistance: 5
projectileSpawnPoint: {fileID: 1668202570} projectileSpawnPoint: {fileID: 1668202570}
trajectoryPreview: {fileID: 0} trajectoryPreview: {fileID: 0}
showDebugLogs: 1
--- !u!1 &846792101 --- !u!1 &846792101
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -3174,11 +3174,10 @@ GameObject:
- component: {fileID: 878268908} - component: {fileID: 878268908}
- component: {fileID: 878268911} - component: {fileID: 878268911}
- component: {fileID: 878268910} - component: {fileID: 878268910}
- component: {fileID: 878268909}
- component: {fileID: 878268912} - component: {fileID: 878268912}
- component: {fileID: 878268913} - component: {fileID: 878268913}
m_Layer: 0 m_Layer: 0
m_Name: ForFightGameManager m_Name: ForFightGameManagers
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
@@ -3199,19 +3198,6 @@ Transform:
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 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 --- !u!114 &878268910
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -3239,7 +3225,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 517ef0a4f14e16f42987a95684371b73, type: 3} m_Script: {fileID: 11500000, guid: 517ef0a4f14e16f42987a95684371b73, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.FortFightGameManager m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.FortFightGameManager
aiController: {fileID: 878268909} aiController: {fileID: 1543340063}
modeSelectionPage: {fileID: 2036414581} modeSelectionPage: {fileID: 2036414581}
gameplayPage: {fileID: 1585033674} gameplayPage: {fileID: 1585033674}
gameOverUI: {fileID: 805585730} gameOverUI: {fileID: 805585730}
@@ -3257,8 +3243,6 @@ MonoBehaviour:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.FortManager m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.FortManager
premadeFortPrefabs: premadeFortPrefabs:
- {fileID: 2303456945894359403, guid: 1481432e299ad794e937cb82505cfbb2, type: 3} - {fileID: 2303456945894359403, guid: 1481432e299ad794e937cb82505cfbb2, type: 3}
debugPlayerFortPrefab: {fileID: 0}
debugEnemyFortPrefab: {fileID: 0}
playerSpawnPoint: {fileID: 1009687014} playerSpawnPoint: {fileID: 1009687014}
enemySpawnPoint: {fileID: 799036564} enemySpawnPoint: {fileID: 799036564}
useDebugForts: 0 useDebugForts: 0
@@ -3275,7 +3259,6 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.AmmunitionManager m_EditorClassIdentifier: AppleHillsScripts::Minigames.FortFight.Core.AmmunitionManager
defaultProjectileType: 0 defaultProjectileType: 0
showDebugLogs: 1
--- !u!1 &1007359450 --- !u!1 &1007359450
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -3580,142 +3563,6 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1192355915} m_GameObject: {fileID: 1192355915}
m_CullTransparentMesh: 1 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 --- !u!1 &1209958790
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -3780,7 +3627,6 @@ MonoBehaviour:
autoBindToFort: 1 autoBindToFort: 1
isPlayerFort: 0 isPlayerFort: 0
fortManager: {fileID: 0} fortManager: {fileID: 0}
debugDisplay: 1
debugHpText: {fileID: 1956621113} debugHpText: {fileID: 1956621113}
--- !u!114 &1209958793 --- !u!114 &1209958793
MonoBehaviour: MonoBehaviour:
@@ -4093,82 +3939,6 @@ RectTransform:
m_AnchoredPosition: {x: -5, y: 0} m_AnchoredPosition: {x: -5, y: 0}
m_SizeDelta: {x: -20, y: 0} m_SizeDelta: {x: -20, y: 0}
m_Pivot: {x: 0.5, y: 0.5} 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 --- !u!1 &1366022891
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -4271,8 +4041,106 @@ MonoBehaviour:
autoBindToFort: 1 autoBindToFort: 1
isPlayerFort: 1 isPlayerFort: 1
fortManager: {fileID: 0} fortManager: {fileID: 0}
debugDisplay: 1
debugHpText: {fileID: 1726433071} 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 --- !u!1 &1410755663
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -4512,7 +4380,7 @@ Transform:
m_GameObject: {fileID: 1460473366} m_GameObject: {fileID: 1460473366}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} 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_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
@@ -4660,7 +4528,53 @@ MonoBehaviour:
maxDragDistance: 5 maxDragDistance: 5
projectileSpawnPoint: {fileID: 497509525} projectileSpawnPoint: {fileID: 497509525}
trajectoryPreview: {fileID: 0} 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 --- !u!1 &1572990036
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -4759,7 +4673,6 @@ RectTransform:
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
- {fileID: 1787332576} - {fileID: 1787332576}
- {fileID: 1355330056}
- {fileID: 1286877329} - {fileID: 1286877329}
- {fileID: 242361350} - {fileID: 242361350}
m_Father: {fileID: 1156219952} m_Father: {fileID: 1156219952}
@@ -4799,7 +4712,6 @@ MonoBehaviour:
currentPlayerText: {fileID: 1192355917} currentPlayerText: {fileID: 1192355917}
canvasGroup: {fileID: 1585033673} canvasGroup: {fileID: 1585033673}
playerActionPanel: {fileID: 0} playerActionPanel: {fileID: 0}
aiTurnPanel: {fileID: 1355330055}
--- !u!1 &1592155788 --- !u!1 &1592155788
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -7065,6 +6977,7 @@ SceneRoots:
- {fileID: 1156219952} - {fileID: 1156219952}
- {fileID: 1277046016} - {fileID: 1277046016}
- {fileID: 2124351765} - {fileID: 2124351765}
- {fileID: 1543340064}
- {fileID: 1674657453} - {fileID: 1674657453}
- {fileID: 878268908} - {fileID: 878268908}
- {fileID: 1007359451} - {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.BlockMaterialConfig> MaterialConfigs { get; }
System.Collections.Generic.List<Minigames.FortFight.Settings.BlockSizeConfig> SizeConfigs { 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 // Weak point settings
float WeakPointExplosionRadius { get; } float WeakPointExplosionRadius { get; }
float WeakPointExplosionDamage { get; } float WeakPointExplosionDamage { get; }

View File

@@ -90,5 +90,35 @@ namespace AppleHills
} }
// Add more methods as needed for other settings // 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;
using System.Collections.Generic;
using Core; using Core;
using Core.Lifecycle; using Core.Lifecycle;
using Minigames.FortFight.Core; using Minigames.FortFight.Core;
using Minigames.FortFight.Data; using Minigames.FortFight.Data;
using Minigames.FortFight.Fort;
using UnityEngine; using UnityEngine;
namespace Minigames.FortFight.AI namespace Minigames.FortFight.AI
{ {
/// <summary> /// <summary>
/// AI controller for the PigMan opponent. /// AI controller for Fort Fight opponent.
/// Phase 1: Stubbed implementation - just simulates taking a turn. /// Implements trajectory calculation, target selection, and shot execution with configurable difficulty.
/// Phase 4: Full implementation with trajectory calculation and target selection. /// Includes debug visualization and thinking animations.
/// </summary> /// </summary>
public class FortFightAIController : ManagedBehaviour public class FortFightAIController : ManagedBehaviour
{ {
[Header("AI Settings (Stubbed)")] #region Inspector Properties
[SerializeField] private float aiThinkTime = 1.5f; // Time AI "thinks" before acting
private TurnManager turnManager; [Header("AI References")]
private bool isThinking = false; [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 #region Initialization
@@ -27,19 +54,57 @@ namespace Minigames.FortFight.AI
/// </summary> /// </summary>
public void Initialize() public void Initialize()
{ {
// Get reference to turn manager via singleton // Load settings
turnManager = TurnManager.Instance; _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!"); Logging.Error("[FortFightAIController] TurnManager not found!");
return; return;
} }
// Subscribe to turn events if (_ammoManager == null)
turnManager.OnTurnStarted += OnTurnStarted; {
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 #endregion
@@ -52,49 +117,403 @@ namespace Minigames.FortFight.AI
private void OnTurnStarted(PlayerData currentPlayer, TurnState turnState) private void OnTurnStarted(PlayerData currentPlayer, TurnState turnState)
{ {
// Only act if it's AI's turn // Only act if it's AI's turn
if (turnState == TurnState.AITurn && !isThinking) if (turnState == TurnState.AITurn && !_isExecutingTurn)
{ {
StartCoroutine(ExecuteAITurn()); StartCoroutine(ExecuteAITurn());
} }
} }
/// <summary> /// <summary>
/// Execute the AI's turn (stubbed for Phase 1) /// Execute the AI's turn with thinking phases and shot execution
/// </summary> /// </summary>
private IEnumerator ExecuteAITurn() 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" // Phase 1: Thinking - Target Selection
yield return new WaitForSeconds(aiThinkTime); yield return StartCoroutine(ThinkingPhase("Analyzing targets..."));
_targetBlock = SelectBestTarget();
// STUBBED: Perform AI action if (_targetBlock == null)
Logging.Debug("[FortFightAIController] AI takes action! (STUBBED - no actual projectile fired yet)"); {
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 _targetPosition = _targetBlock.transform.position;
// Turn will automatically advance when AI's projectile settles (via ProjectileTurnAction) Logging.Debug($"[FortFightAIController] Target selected: {_targetBlock.gameObject.name} at {_targetPosition}");
// Do NOT manually call EndTurn() - it's now private and automatic
// NOTE: For now, AI turn will hang until Phase 4 AI projectile system is implemented // Phase 2: Thinking - Ammunition Selection
// To test without AI, use TwoPlayer mode yield return StartCoroutine(ThinkingPhase("Selecting ammunition..."));
_selectedAmmo = ChooseProjectile();
Logging.Debug($"[FortFightAIController] Ammo selected: {_selectedAmmo}");
isThinking = false; // Phase 3: Thinking - Trajectory Calculation
Logging.Warning("[FortFightAIController] AI turn stubbed - Phase 4 needed for AI projectile firing"); 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 #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 #region Cleanup
internal override void OnManagedDestroy() internal override void OnManagedDestroy()
{ {
base.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")] [Tooltip("Default projectile type selected at game start")]
[SerializeField] private ProjectileType defaultProjectileType = ProjectileType.Toaster; [SerializeField] private ProjectileType defaultProjectileType = ProjectileType.Toaster;
[Header("Debug")]
[SerializeField] private bool showDebugLogs = true;
#endregion #endregion
#region Settings #region Settings
private IFortFightSettings cachedSettings; private IFortFightSettings _cachedSettings;
private IFortFightSettings CachedSettings private IFortFightSettings CachedSettings
{ {
get 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 #endregion
#region Events #region Events
@@ -97,7 +109,7 @@ namespace Minigames.FortFight.Core
#region State #region State
// Per-player ammunition state (encapsulates cooldowns, selection, usage) // 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 #endregion
@@ -127,12 +139,12 @@ namespace Minigames.FortFight.Core
for (int playerIndex = 0; playerIndex < MaxPlayers; playerIndex++) for (int playerIndex = 0; playerIndex < MaxPlayers; playerIndex++)
{ {
// Create player state with default ammo // Create player state with default ammo
playerStates[playerIndex] = new PlayerAmmoState(playerIndex, defaultProjectileType); _playerStates[playerIndex] = new PlayerAmmoState(playerIndex, defaultProjectileType);
// Initialize cooldowns for all projectile types // Initialize cooldowns for all projectile types
foreach (var config in configs) foreach (var config in configs)
{ {
playerStates[playerIndex].InitializeCooldown(config.projectileType); _playerStates[playerIndex].InitializeCooldown(config.projectileType);
} }
// Select default ammo (triggers event) // Select default ammo (triggers event)
@@ -146,14 +158,14 @@ namespace Minigames.FortFight.Core
/// </summary> /// </summary>
public void DecrementCooldowns(int playerIndex) public void DecrementCooldowns(int playerIndex)
{ {
if (!playerStates.ContainsKey(playerIndex)) if (!_playerStates.ContainsKey(playerIndex))
{ {
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!"); Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
return; return;
} }
// Decrement cooldowns and get completed types // Decrement cooldowns and get completed types
List<ProjectileType> completedCooldowns = playerStates[playerIndex].DecrementCooldowns(); List<ProjectileType> completedCooldowns = _playerStates[playerIndex].DecrementCooldowns();
// Fire events for completed cooldowns // Fire events for completed cooldowns
var settings = CachedSettings; var settings = CachedSettings;
@@ -162,7 +174,7 @@ namespace Minigames.FortFight.Core
var config = settings?.GetProjectileConfig(type); var config = settings?.GetProjectileConfig(type);
if (config != null) 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); OnAmmoCooldownCompleted?.Invoke(type);
} }
} }
@@ -179,7 +191,7 @@ namespace Minigames.FortFight.Core
/// </summary> /// </summary>
public bool SelectAmmo(ProjectileType type, int playerIndex) public bool SelectAmmo(ProjectileType type, int playerIndex)
{ {
if (!playerStates.ContainsKey(playerIndex)) if (!_playerStates.ContainsKey(playerIndex))
{ {
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!"); Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
return false; return false;
@@ -196,12 +208,12 @@ namespace Minigames.FortFight.Core
if (!IsAmmoAvailable(type, playerIndex)) 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; return false;
} }
playerStates[playerIndex].SelectedAmmo = type; _playerStates[playerIndex].SelectedAmmo = type;
if (showDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex} selected: {config.displayName}"); if (ShowDebugLogs) Logging.Debug($"[AmmunitionManager] Player {playerIndex} selected: {config.displayName}");
OnAmmoSelected?.Invoke(type, playerIndex); OnAmmoSelected?.Invoke(type, playerIndex);
return true; return true;
@@ -212,9 +224,9 @@ namespace Minigames.FortFight.Core
/// </summary> /// </summary>
public ProjectileType GetSelectedAmmoType(int playerIndex) public ProjectileType GetSelectedAmmoType(int playerIndex)
{ {
if (playerStates.ContainsKey(playerIndex)) if (_playerStates.ContainsKey(playerIndex))
{ {
return playerStates[playerIndex].SelectedAmmo; return _playerStates[playerIndex].SelectedAmmo;
} }
return defaultProjectileType; return defaultProjectileType;
} }
@@ -233,12 +245,12 @@ namespace Minigames.FortFight.Core
/// </summary> /// </summary>
public bool IsAmmoAvailable(ProjectileType type, int playerIndex) public bool IsAmmoAvailable(ProjectileType type, int playerIndex)
{ {
if (!playerStates.ContainsKey(playerIndex)) if (!_playerStates.ContainsKey(playerIndex))
{ {
return false; return false;
} }
return playerStates[playerIndex].IsAmmoAvailable(type); return _playerStates[playerIndex].IsAmmoAvailable(type);
} }
/// <summary> /// <summary>
@@ -246,12 +258,12 @@ namespace Minigames.FortFight.Core
/// </summary> /// </summary>
public int GetCooldownRemaining(ProjectileType type, int playerIndex) public int GetCooldownRemaining(ProjectileType type, int playerIndex)
{ {
if (!playerStates.ContainsKey(playerIndex)) if (!_playerStates.ContainsKey(playerIndex))
{ {
return 0; return 0;
} }
return playerStates[playerIndex].GetCooldown(type); return _playerStates[playerIndex].GetCooldown(type);
} }
#endregion #endregion
@@ -263,7 +275,7 @@ namespace Minigames.FortFight.Core
/// </summary> /// </summary>
public void UseAmmo(ProjectileType type, int playerIndex) public void UseAmmo(ProjectileType type, int playerIndex)
{ {
if (!playerStates.ContainsKey(playerIndex)) if (!_playerStates.ContainsKey(playerIndex))
{ {
Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!"); Logging.Warning($"[AmmunitionManager] Player {playerIndex} state not found!");
return; return;
@@ -279,10 +291,10 @@ namespace Minigames.FortFight.Core
} }
// Set cooldown and record usage // Set cooldown and record usage
playerStates[playerIndex].SetCooldown(type, config.cooldownTurns); _playerStates[playerIndex].SetCooldown(type, config.cooldownTurns);
playerStates[playerIndex].RecordUsage(type); _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); OnAmmoCooldownStarted?.Invoke(type, config.cooldownTurns);
} }
@@ -298,7 +310,7 @@ namespace Minigames.FortFight.Core
{ {
var configs = AvailableConfigs; var configs = AvailableConfigs;
foreach (var playerState in playerStates.Values) foreach (var playerState in _playerStates.Values)
{ {
foreach (var config in configs) 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> /// <summary>

View File

@@ -32,6 +32,18 @@ namespace Minigames.FortFight.Core
new BlockSizeConfig { size = BlockSize.Large, hpMultiplier = 2f, massMultiplier = 2f } 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")] [Header("Weak Point Settings")]
[Tooltip("Radius of explosion effect from weak points")] [Tooltip("Radius of explosion effect from weak points")]
[SerializeField] private float weakPointExplosionRadius = 2.5f; [SerializeField] private float weakPointExplosionRadius = 2.5f;
@@ -133,6 +145,8 @@ namespace Minigames.FortFight.Core
public List<BlockMaterialConfig> MaterialConfigs => materialConfigs; public List<BlockMaterialConfig> MaterialConfigs => materialConfigs;
public List<BlockSizeConfig> SizeConfigs => sizeConfigs; public List<BlockSizeConfig> SizeConfigs => sizeConfigs;
public AIDifficulty DefaultAIDifficulty => defaultAIDifficulty;
public float WeakPointExplosionRadius => weakPointExplosionRadius; public float WeakPointExplosionRadius => weakPointExplosionRadius;
public float WeakPointExplosionDamage => weakPointExplosionDamage; public float WeakPointExplosionDamage => weakPointExplosionDamage;
public float WeakPointExplosionForce => weakPointExplosionForce; public float WeakPointExplosionForce => weakPointExplosionForce;
@@ -209,6 +223,23 @@ namespace Minigames.FortFight.Core
return sizeConfigs.FirstOrDefault(c => c.size == size); 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 #endregion
#region Validation #region Validation

View File

@@ -34,9 +34,6 @@ namespace Minigames.FortFight.Core
[Header("Fort Prefabs")] [Header("Fort Prefabs")]
[SerializeField] private GameObject[] premadeFortPrefabs; [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")] [Header("Spawn Points")]
[SerializeField] private Transform playerSpawnPoint; [SerializeField] private Transform playerSpawnPoint;
@@ -45,6 +42,26 @@ namespace Minigames.FortFight.Core
[Header("Settings")] [Header("Settings")]
[SerializeField] private bool useDebugForts = false; [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 #endregion
#region Events #region Events
@@ -121,9 +138,9 @@ namespace Minigames.FortFight.Core
Logging.Debug("[FortManager] Spawning forts for both players"); Logging.Debug("[FortManager] Spawning forts for both players");
// Spawn player fort // Spawn player fort
if (useDebugForts && debugPlayerFortPrefab != null) if (useDebugForts && DebugPlayerFortPrefab != null)
{ {
PlayerFort = SpawnFort(debugPlayerFortPrefab, playerSpawnPoint, "Player Fort"); PlayerFort = SpawnFort(DebugPlayerFortPrefab, playerSpawnPoint, "Player Fort");
} }
else else
{ {
@@ -136,9 +153,9 @@ namespace Minigames.FortFight.Core
} }
// Spawn enemy fort // Spawn enemy fort
if (useDebugForts && debugEnemyFortPrefab != null) if (useDebugForts && DebugEnemyFortPrefab != null)
{ {
EnemyFort = SpawnFort(debugEnemyFortPrefab, enemySpawnPoint, "Enemy Fort"); EnemyFort = SpawnFort(DebugEnemyFortPrefab, enemySpawnPoint, "Enemy Fort");
} }
else else
{ {

View File

@@ -28,27 +28,38 @@ namespace Minigames.FortFight.Core
[Tooltip("Trajectory preview component")] [Tooltip("Trajectory preview component")]
[SerializeField] private TrajectoryPreview trajectoryPreview; [SerializeField] private TrajectoryPreview trajectoryPreview;
[Header("Debug")]
[SerializeField] private bool showDebugLogs = true;
#endregion #endregion
#region Settings #region Settings
private IFortFightSettings cachedSettings; private IFortFightSettings _cachedSettings;
private IFortFightSettings CachedSettings private IFortFightSettings CachedSettings
{ {
get 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 float MaxForce => CachedSettings?.BaseLaunchForce ?? 20f;
private bool ShowDebugLogs => CachedDevSettings?.SlingshotShowDebugLogs ?? false;
#endregion #endregion
@@ -63,12 +74,12 @@ namespace Minigames.FortFight.Core
#region State #region State
private bool isDragging; private bool _isDragging;
private Vector2 dragStartPosition; private Vector2 _dragStartPosition;
private ProjectileConfig currentAmmo; private ProjectileConfig _currentAmmo;
private ProjectileBase activeProjectile; private ProjectileBase _activeProjectile;
public bool IsDragging => isDragging; public bool IsDragging => _isDragging;
public bool IsEnabled { get; private set; } = true; public bool IsEnabled { get; private set; } = true;
#endregion #endregion
@@ -118,13 +129,13 @@ namespace Minigames.FortFight.Core
public void OnHoldMove(Vector2 worldPosition) public void OnHoldMove(Vector2 worldPosition)
{ {
if (!IsEnabled || !isDragging) return; if (!IsEnabled || !_isDragging) return;
UpdateDrag(worldPosition); UpdateDrag(worldPosition);
} }
public void OnHoldEnd(Vector2 worldPosition) public void OnHoldEnd(Vector2 worldPosition)
{ {
if (!IsEnabled || !isDragging) return; if (!IsEnabled || !_isDragging) return;
EndDrag(worldPosition); EndDrag(worldPosition);
} }
@@ -134,16 +145,16 @@ namespace Minigames.FortFight.Core
private void StartDrag(Vector2 worldPosition) 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; return;
} }
isDragging = true; _isDragging = true;
// Use the projectile spawn point as the anchor, not the touch position // 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 // This makes it work like Angry Birds - pull back from slingshot to launch forward
dragStartPosition = projectileSpawnPoint.position; _dragStartPosition = projectileSpawnPoint.position;
// Show trajectory preview // Show trajectory preview
if (trajectoryPreview != null) if (trajectoryPreview != null)
@@ -151,14 +162,14 @@ namespace Minigames.FortFight.Core
trajectoryPreview.Show(); 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) private void UpdateDrag(Vector2 currentWorldPosition)
{ {
// Calculate drag vector from spawn point to current drag position // Calculate drag vector from spawn point to current drag position
// Pull back (away from spawn) = launch forward (toward spawn direction) // Pull back (away from spawn) = launch forward (toward spawn direction)
Vector2 dragVector = dragStartPosition - currentWorldPosition; Vector2 dragVector = _dragStartPosition - currentWorldPosition;
// Calculate force and direction // Calculate force and direction
float dragDistance = dragVector.magnitude; float dragDistance = dragVector.magnitude;
@@ -172,10 +183,10 @@ namespace Minigames.FortFight.Core
Vector2 direction = dragVector.normalized; Vector2 direction = dragVector.normalized;
// Update trajectory preview with projectile mass // Update trajectory preview with projectile mass
if (trajectoryPreview != null && currentAmmo != null) if (trajectoryPreview != null && _currentAmmo != null)
{ {
Vector2 worldStartPos = projectileSpawnPoint.position; Vector2 worldStartPos = projectileSpawnPoint.position;
float mass = currentAmmo.GetMass(); float mass = _currentAmmo.GetMass();
// Debug: Log trajectory calculation (uncomment for debugging) // Debug: Log trajectory calculation (uncomment for debugging)
// if (showDebugLogs && Time.frameCount % 30 == 0) // Log every 30 frames to avoid spam // 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) private void EndDrag(Vector2 currentWorldPosition)
{ {
isDragging = false; _isDragging = false;
// Hide trajectory // Hide trajectory
if (trajectoryPreview != null) if (trajectoryPreview != null)
@@ -198,7 +209,7 @@ namespace Minigames.FortFight.Core
} }
// Calculate final launch parameters from spawn point to final drag position // Calculate final launch parameters from spawn point to final drag position
Vector2 dragVector = dragStartPosition - currentWorldPosition; Vector2 dragVector = _dragStartPosition - currentWorldPosition;
float dragDistance = dragVector.magnitude; float dragDistance = dragVector.magnitude;
float dragRatio = Mathf.Clamp01(dragDistance / maxDragDistance); float dragRatio = Mathf.Clamp01(dragDistance / maxDragDistance);
@@ -216,9 +227,9 @@ namespace Minigames.FortFight.Core
// Launch projectile if force exceeds minimum // Launch projectile if force exceeds minimum
if (force >= minForce) if (force >= minForce)
{ {
if (showDebugLogs && currentAmmo != null) if (ShowDebugLogs && _currentAmmo != null)
{ {
float mass = currentAmmo.GetMass(); float mass = _currentAmmo.GetMass();
float velocity = force / mass; float velocity = force / mass;
Logging.Debug($"[Slingshot] Launch - Force: {force:F2}, Mass: {mass:F2}, Velocity: {velocity:F2}, Dir: {direction}"); Logging.Debug($"[Slingshot] Launch - Force: {force:F2}, Mass: {mass:F2}, Velocity: {velocity:F2}, Dir: {direction}");
} }
@@ -227,7 +238,7 @@ namespace Minigames.FortFight.Core
} }
else 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> /// </summary>
public void SetAmmo(ProjectileConfig ammoConfig) public void SetAmmo(ProjectileConfig ammoConfig)
{ {
currentAmmo = ammoConfig; _currentAmmo = ammoConfig;
if (showDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}"); if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
} }
/// <summary> /// <summary>
@@ -249,28 +260,28 @@ namespace Minigames.FortFight.Core
/// </summary> /// </summary>
private void LaunchProjectile(Vector2 direction, float force) 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!"); Logging.Error("[SlingshotController] Cannot launch - no ammo or prefab!");
return; return;
} }
// Spawn projectile // Spawn projectile
GameObject projectileObj = Instantiate(currentAmmo.prefab, projectileSpawnPoint.position, Quaternion.identity); GameObject projectileObj = Instantiate(_currentAmmo.prefab, projectileSpawnPoint.position, Quaternion.identity);
activeProjectile = projectileObj.GetComponent<Projectiles.ProjectileBase>(); _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); Destroy(projectileObj);
return; return;
} }
// Initialize projectile with its type (loads damage and mass from settings) // Initialize projectile with its type (loads damage and mass from settings)
activeProjectile.Initialize(currentAmmo.projectileType); _activeProjectile.Initialize(_currentAmmo.projectileType);
// Launch it // Launch it
activeProjectile.Launch(direction, force); _activeProjectile.Launch(direction, force);
// Lock trajectory to show the shot path // Lock trajectory to show the shot path
if (trajectoryPreview != null) if (trajectoryPreview != null)
@@ -279,10 +290,46 @@ namespace Minigames.FortFight.Core
trajectoryPreview.LockTrajectory(lockDuration); 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 // 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> /// <summary>
@@ -290,7 +337,7 @@ namespace Minigames.FortFight.Core
/// </summary> /// </summary>
public ProjectileBase GetActiveProjectile() public ProjectileBase GetActiveProjectile()
{ {
return activeProjectile; return _activeProjectile;
} }
#endregion #endregion
@@ -303,7 +350,7 @@ namespace Minigames.FortFight.Core
public void Enable() public void Enable()
{ {
IsEnabled = true; IsEnabled = true;
if (showDebugLogs) Logging.Debug("[SlingshotController] Enabled"); if (ShowDebugLogs) Logging.Debug("[SlingshotController] Enabled");
} }
/// <summary> /// <summary>
@@ -312,17 +359,16 @@ namespace Minigames.FortFight.Core
public void Disable() public void Disable()
{ {
IsEnabled = false; IsEnabled = false;
isDragging = false; _isDragging = false;
if (trajectoryPreview != null) if (trajectoryPreview != null)
{ {
trajectoryPreview.Hide(); trajectoryPreview.Hide();
} }
if (showDebugLogs) Logging.Debug("[SlingshotController] Disabled"); if (ShowDebugLogs) Logging.Debug("[SlingshotController] Disabled");
} }
#endregion #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 CeilingFan, // Drops straight down
TrashBag // Explodes on impact 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 CurrentHp => currentHp;
public float MaxHp => maxHp; public float MaxHp => maxHp;
public float HpPercentage => maxHp > 0 ? (currentHp / maxHp) * 100f : 0f; public float HpPercentage => maxHp > 0 ? (currentHp / maxHp) * 100f : 0f;
public bool IsDestroyed => isDestroyed;
#endregion #endregion

View File

@@ -18,9 +18,6 @@ namespace Minigames.FortFight.Fort
[Header("Fort Configuration")] [Header("Fort Configuration")]
[SerializeField] private string fortName = "Unnamed Fort"; [SerializeField] private string fortName = "Unnamed Fort";
[Header("Debug")]
[SerializeField] private bool showDebugInfo = true;
#endregion #endregion
#region Events #region Events
@@ -45,26 +42,26 @@ namespace Minigames.FortFight.Fort
#region Properties #region Properties
public string FortName => fortName; public string FortName => fortName;
public float MaxFortHp => maxFortHp; public float MaxFortHp => _maxFortHp;
public float CurrentFortHp => currentFortHp; public float CurrentFortHp => _currentFortHp;
public float HpPercentage => maxFortHp > 0 ? (currentFortHp / maxFortHp) * 100f : 0f; public float HpPercentage => _maxFortHp > 0 ? (_currentFortHp / _maxFortHp) * 100f : 0f;
public int TotalBlockCount => blocks.Count; public int TotalBlockCount => _blocks.Count;
public int InitialBlockCount => initialBlockCount; public int InitialBlockCount => _initialBlockCount;
public bool IsDefeated { get; private set; } public bool IsDefeated { get; private set; }
// Aliases for consistency // Aliases for consistency
public float MaxHp => maxFortHp; public float MaxHp => _maxFortHp;
public float CurrentHp => currentFortHp; public float CurrentHp => _currentFortHp;
#endregion #endregion
#region Private State #region Private State
private List<FortBlock> blocks = new List<FortBlock>(); private List<FortBlock> _blocks = new List<FortBlock>();
private float maxFortHp = 0f; private float _maxFortHp = 0f;
private float currentFortHp = 0f; private float _currentFortHp = 0f;
private int initialBlockCount = 0; private int _initialBlockCount = 0;
private bool isInitialized = false; private bool _isInitialized = false;
// Cached settings // Cached settings
private IFortFightSettings _cachedSettings; 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 #endregion
#region Lifecycle #region Lifecycle
@@ -97,7 +109,7 @@ namespace Minigames.FortFight.Fort
base.OnManagedDestroy(); base.OnManagedDestroy();
// Unsubscribe from all block events // Unsubscribe from all block events
foreach (FortBlock block in blocks) foreach (FortBlock block in _blocks)
{ {
if (block != null) if (block != null)
{ {
@@ -106,7 +118,7 @@ namespace Minigames.FortFight.Fort
} }
} }
blocks.Clear(); _blocks.Clear();
// Clear events // Clear events
OnFortDamaged = null; OnFortDamaged = null;
@@ -124,7 +136,7 @@ namespace Minigames.FortFight.Fort
/// </summary> /// </summary>
private void InitializeFort() private void InitializeFort()
{ {
if (isInitialized) if (_isInitialized)
{ {
Logging.Warning($"[FortController] {fortName} already initialized!"); Logging.Warning($"[FortController] {fortName} already initialized!");
return; return;
@@ -138,7 +150,7 @@ namespace Minigames.FortFight.Fort
// Step 2: Register with central manager // Step 2: Register with central manager
RegisterWithManager(); RegisterWithManager();
isInitialized = true; _isInitialized = true;
Logging.Debug($"[FortController] {fortName} - Initialization complete"); 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) // Step 3: Initialize current HP to match max HP (sum of all blocks)
currentFortHp = maxFortHp; _currentFortHp = _maxFortHp;
initialBlockCount = blocks.Count; _initialBlockCount = _blocks.Count;
Logging.Debug($"[FortController] {fortName} - Initialized and registered {blocks.Count} blocks, Total HP: {maxFortHp:F0}"); Logging.Debug($"[FortController] {fortName} - Initialized and registered {_blocks.Count} blocks, Total HP: {_maxFortHp:F0}");
} }
/// <summary> /// <summary>
@@ -211,18 +223,18 @@ namespace Minigames.FortFight.Fort
{ {
if (block == null) return; 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 // 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 // Subscribe to block events
block.OnBlockDestroyed += HandleBlockDestroyed; block.OnBlockDestroyed += HandleBlockDestroyed;
block.OnBlockDamaged += HandleBlockDamaged; block.OnBlockDamaged += HandleBlockDamaged;
if (showDebugInfo) if (ShowDebugInfo)
{ {
Logging.Debug($"[FortController] Registered block: {block.gameObject.name} ({block.Material} {block.Size}, HP: {block.MaxHp})"); Logging.Debug($"[FortController] Registered block: {block.gameObject.name} ({block.Material} {block.Size}, HP: {block.MaxHp})");
} }
@@ -234,7 +246,7 @@ namespace Minigames.FortFight.Fort
/// </summary> /// </summary>
public List<FortBlock> GetWeakPoints() public List<FortBlock> GetWeakPoints()
{ {
return blocks.Where(b => b != null && b.IsWeakPoint).ToList(); return _blocks.Where(b => b != null && b.IsWeakPoint).ToList();
} }
/// <summary> /// <summary>
@@ -242,7 +254,7 @@ namespace Minigames.FortFight.Fort
/// </summary> /// </summary>
public List<FortBlock> GetAllBlocks() public List<FortBlock> GetAllBlocks()
{ {
return new List<FortBlock>(blocks); return new List<FortBlock>(_blocks);
} }
/// <summary> /// <summary>
@@ -250,8 +262,8 @@ namespace Minigames.FortFight.Fort
/// </summary> /// </summary>
public FortBlock GetRandomBlock() public FortBlock GetRandomBlock()
{ {
if (blocks.Count == 0) return null; if (_blocks.Count == 0) return null;
return blocks[UnityEngine.Random.Range(0, blocks.Count)]; return _blocks[UnityEngine.Random.Range(0, _blocks.Count)];
} }
#endregion #endregion
@@ -265,7 +277,7 @@ namespace Minigames.FortFight.Fort
Logging.Debug($"[FortController] {fortName} - Block destroyed: {block.gameObject.name}"); Logging.Debug($"[FortController] {fortName} - Block destroyed: {block.gameObject.name}");
// Remove from list // Remove from list
blocks.Remove(block); _blocks.Remove(block);
// Recalculate HP by summing all remaining blocks (consistent calculation method) // Recalculate HP by summing all remaining blocks (consistent calculation method)
RecalculateFortHp(); RecalculateFortHp();
@@ -277,9 +289,9 @@ namespace Minigames.FortFight.Fort
// Check defeat condition // Check defeat condition
CheckDefeatCondition(); 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> /// </summary>
private void RecalculateFortHp() private void RecalculateFortHp()
{ {
currentFortHp = 0f; _currentFortHp = 0f;
foreach (var block in blocks) foreach (var block in _blocks)
{ {
if (block != null) 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 defeatThreshold = CachedSettings?.FortDefeatThreshold ?? 0.3f;
float defeatThresholdPercent = defeatThreshold * 100f; 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 // Defeat if HP at or below threshold
if (HpPercentage <= defeatThresholdPercent) if (HpPercentage <= defeatThresholdPercent)
@@ -361,7 +373,7 @@ namespace Minigames.FortFight.Fort
private void OnGUI() private void OnGUI()
{ {
if (!showDebugInfo || !Application.isPlaying) return; if (!ShowDebugInfo || !Application.isPlaying) return;
// Display fort HP in scene view (for testing) // Display fort HP in scene view (for testing)
Vector3 screenPos = Camera.main.WorldToScreenPoint(transform.position + Vector3.up * 2f); 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; [SerializeField] private Core.FortManager fortManager;
[Header("Debug Display")] [Header("Debug Display")]
[Tooltip("Show numerical HP values (current/max)")]
[SerializeField] private bool debugDisplay = false;
[Tooltip("Text field to display 'current/max' HP values")] [Tooltip("Text field to display 'current/max' HP values")]
[SerializeField] private TextMeshProUGUI debugHpText; [SerializeField] private TextMeshProUGUI debugHpText;
@@ -41,7 +39,20 @@ namespace Minigames.FortFight.UI
#region Private State #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 #endregion
@@ -69,9 +80,9 @@ namespace Minigames.FortFight.UI
base.OnManagedDestroy(); base.OnManagedDestroy();
// Unsubscribe from fort events // Unsubscribe from fort events
if (trackedFort != null) if (_trackedFort != null)
{ {
trackedFort.OnFortDamaged -= OnFortDamaged; _trackedFort.OnFortDamaged -= OnFortDamaged;
} }
// Unsubscribe from fort manager // Unsubscribe from fort manager
@@ -154,15 +165,15 @@ namespace Minigames.FortFight.UI
} }
// Unsubscribe from previous fort // Unsubscribe from previous fort
if (trackedFort != null) if (_trackedFort != null)
{ {
trackedFort.OnFortDamaged -= OnFortDamaged; _trackedFort.OnFortDamaged -= OnFortDamaged;
} }
trackedFort = fort; _trackedFort = fort;
// Subscribe to fort events // Subscribe to fort events
trackedFort.OnFortDamaged += OnFortDamaged; _trackedFort.OnFortDamaged += OnFortDamaged;
// Initialize UI // Initialize UI
if (fortNameText != null) if (fortNameText != null)
@@ -181,7 +192,7 @@ namespace Minigames.FortFight.UI
private void OnFortDamaged(float damage, float hpPercentage) 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(); UpdateDisplay();
} }
@@ -191,15 +202,15 @@ namespace Minigames.FortFight.UI
private void UpdateDisplay() private void UpdateDisplay()
{ {
if (trackedFort == null) if (_trackedFort == null)
{ {
Logging.Warning("[FortHealthUI] UpdateDisplay called but trackedFort is null!"); Logging.Warning("[FortHealthUI] UpdateDisplay called but trackedFort is null!");
return; 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 // Update slider
if (hpSlider != null) if (hpSlider != null)
@@ -226,11 +237,11 @@ namespace Minigames.FortFight.UI
// Update debug HP display (current/max) // Update debug HP display (current/max)
if (debugHpText != null) if (debugHpText != null)
{ {
if (debugDisplay) if (DebugDisplay)
{ {
debugHpText.gameObject.SetActive(true); debugHpText.gameObject.SetActive(true);
float currentHp = trackedFort.CurrentHp; float currentHp = _trackedFort.CurrentHp;
float maxHp = trackedFort.MaxHp; float maxHp = _trackedFort.MaxHp;
debugHpText.text = $"{currentHp:F0}/{maxHp:F0}"; debugHpText.text = $"{currentHp:F0}/{maxHp:F0}";
} }
else else

View File

@@ -21,7 +21,6 @@ namespace Minigames.FortFight.UI
[Header("Optional Visual Elements")] [Header("Optional Visual Elements")]
[SerializeField] private CanvasGroup canvasGroup; [SerializeField] private CanvasGroup canvasGroup;
[SerializeField] private GameObject playerActionPanel; [SerializeField] private GameObject playerActionPanel;
[SerializeField] private GameObject aiTurnPanel;
private TurnManager turnManager; private TurnManager turnManager;
@@ -128,11 +127,6 @@ namespace Minigames.FortFight.UI
{ {
playerActionPanel.SetActive(false); playerActionPanel.SetActive(false);
} }
if (aiTurnPanel != null)
{
aiTurnPanel.SetActive(true);
}
} }
else if (turnState == TurnState.PlayerOneTurn || turnState == TurnState.PlayerTwoTurn) else if (turnState == TurnState.PlayerOneTurn || turnState == TurnState.PlayerTwoTurn)
{ {
@@ -141,11 +135,6 @@ namespace Minigames.FortFight.UI
{ {
playerActionPanel.SetActive(true); 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 - size: 2
hpMultiplier: 2 hpMultiplier: 2
massMultiplier: 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 weakPointExplosionRadius: 5
weakPointExplosionDamage: 50 weakPointExplosionDamage: 50
weakPointExplosionForce: 50 weakPointExplosionForce: 50