commit 70db1cbf16f1c9ac8b6890ec1ddf6e127e62c86e Author: j.foucher Date: Wed Jul 22 12:41:12 2026 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bd62be --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# --- Unreal Engine --- +**/Binaries/ +**/DerivedDataCache/ +**/Intermediate/ +**/Saved/ +**/Build/Receipts/ + +# Fichiers generes Visual Studio / Rider +**/.vs/ +**/.idea/ +*.sln +*.suo +*.opensdf +*.sdf +*.VC.db +*.VC.opendb +Unreal/PS_StratVision/.vsconfig diff --git a/Unreal/PS_StratVision/Config/DefaultEditor.ini b/Unreal/PS_StratVision/Config/DefaultEditor.ini new file mode 100644 index 0000000..e69de29 diff --git a/Unreal/PS_StratVision/Config/DefaultEngine.ini b/Unreal/PS_StratVision/Config/DefaultEngine.ini new file mode 100644 index 0000000..2ff1319 --- /dev/null +++ b/Unreal/PS_StratVision/Config/DefaultEngine.ini @@ -0,0 +1,102 @@ + + +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/Engine/Maps/Templates/OpenWorld + +[/Script/Engine.RendererSettings] +r.AllowStaticLighting=False + +r.GenerateMeshDistanceFields=True + +r.DynamicGlobalIlluminationMethod=1 + +r.ReflectionMethod=1 + +r.SkinCache.CompileShaders=True + +r.RayTracing=True + +r.RayTracing.RayTracingProxies.ProjectEnabled=True + +r.Substrate=True + +r.Substrate.ProjectGBufferFormat=0 + +r.Shadow.Virtual.Enable=1 + +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True + +r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8 + +r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8 + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +-D3D12TargetedShaderFormats=PCD3D_SM5 ++D3D12TargetedShaderFormats=PCD3D_SM6 +-D3D11TargetedShaderFormats=PCD3D_SM5 ++D3D11TargetedShaderFormats=PCD3D_SM5 +Compiler=Default +AudioSampleRate=48000 +AudioCallbackBufferFrameSize=1024 +AudioNumBuffersToEnqueue=1 +AudioMaxChannels=0 +AudioNumSourceWorkers=4 +SpatializationPlugin= +SourceDataOverridePlugin= +ReverbPlugin= +OcclusionPlugin= +CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) +CacheSizeKB=65536 +MaxChunkSizeOverrideKB=0 +bResampleForDevice=False +MaxSampleRate=48000.000000 +HighSampleRate=32000.000000 +MedSampleRate=24000.000000 +LowSampleRate=12000.000000 +MinSampleRate=8000.000000 +CompressionQualityModifier=1.000000 +AutoStreamingThreshold=0.000000 +SoundCueCookQualityIndex=-1 + +[/Script/LinuxTargetPlatform.LinuxTargetSettings] +-TargetedRHIs=SF_VULKAN_SM5 ++TargetedRHIs=SF_VULKAN_SM6 + +[/Script/MacTargetPlatform.MacTargetSettings] +-TargetedRHIs=SF_METAL_SM5 ++TargetedRHIs=SF_METAL_SM6 + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] +CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' + +[/Script/Engine.UserInterfaceSettings] +bAuthorizeAutomaticWidgetVariableCreation=False +FontDPIPreset=Standard +FontDPI=72 + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/PS_StratVision") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/PS_StratVision") + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=2855BBB34B7B4D426D13CCAB940A07F3 +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + diff --git a/Unreal/PS_StratVision/Config/DefaultGame.ini b/Unreal/PS_StratVision/Config/DefaultGame.ini new file mode 100644 index 0000000..c031c47 --- /dev/null +++ b/Unreal/PS_StratVision/Config/DefaultGame.ini @@ -0,0 +1,6 @@ + +[/Script/CommonUI.CommonUISettings] +CommonButtonAcceptKeyHandling=TriggerClick + +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=D2B643384A3F6CB0851AE99C0EA8D2A6 diff --git a/Unreal/PS_StratVision/Config/DefaultInput.ini b/Unreal/PS_StratVision/Config/DefaultInput.ini new file mode 100644 index 0000000..a919105 --- /dev/null +++ b/Unreal/PS_StratVision/Config/DefaultInput.ini @@ -0,0 +1,84 @@ +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +bCaptureMouseOnLaunch=True +bEnableLegacyInputScales=True +bEnableMotionControls=True +bFilterInputByPlatformUser=False +bShouldFlushPressedKeysOnViewportFocusLost=True +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +bEnableGestureRecognizer=False +bUseAutocorrect=False +DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 +DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput +DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent +DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + diff --git a/Unreal/PS_StratVision/PS_StratVision.uproject b/Unreal/PS_StratVision/PS_StratVision.uproject new file mode 100644 index 0000000..c4e7cbd --- /dev/null +++ b/Unreal/PS_StratVision/PS_StratVision.uproject @@ -0,0 +1,26 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.7", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "PS_StratVision", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true, + "TargetAllowList": [ + "Editor" + ] + }, + { + "Name": "PS_StrategicVision", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/.gitignore b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/.gitignore new file mode 100644 index 0000000..156d316 --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/.gitignore @@ -0,0 +1,3 @@ +# Artefacts de build du plugin (regeneres a la compilation) +Binaries/ +Intermediate/ diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/PS_StrategicVision.uplugin b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/PS_StrategicVision.uplugin new file mode 100644 index 0000000..3c1052a --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/PS_StrategicVision.uplugin @@ -0,0 +1,30 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "0.1.0", + "FriendlyName": "PS Strategic Vision", + "Description": "Affichage strategique pour vues spectateur et replays : cones de vision, et autres visualisations a venir.", + "Category": "Visualization", + "CreatedBy": "j.foucher", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": false, + "IsExperimentalVersion": false, + "Installed": false, + "Modules": [ + { + "Name": "PS_StrategicVision", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "ProceduralMeshComponent", + "Enabled": true + } + ] +} diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/README.md b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/README.md new file mode 100644 index 0000000..d118178 --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/README.md @@ -0,0 +1,101 @@ +# PS Strategic Vision + +Plugin Unreal Engine 5.7 d'affichage stratégique pour vues spectateur et replays : +cônes de vision des personnages, et d'autres visualisations à venir. + +## Installation dans un projet + +Copier (ou cloner) ce dossier dans `Plugins/PS_StrategicVision/` du projet, puis +activer le plugin dans le `.uproject` ou via *Edit > Plugins*. + +En submodule git : + +```bash +git submodule add Plugins/PS_StrategicVision +``` + +Le plugin est en C++ : le projet hôte doit être un projet C++ (il sera compilé +avec le projet). + +## Contenu + +| Classe | Rôle | +|---|---| +| `UPSVisionConeComponent` | Component à poser sur un personnage : affiche son cône de vision (mesh procédural, bloqué par les murs). | +| `UPSStrategicDisplayComponent` | Classe de base commune à tous les affichages stratégiques (camp, activation, couleur). Les futurs components en hériteront. | +| `UPSStrategicVisionSubsystem` | Subsystem monde : interrupteurs globaux (tout, par catégorie, par camp). | +| `UPSStrategicVisionSettings` | Réglages projet : couleurs des camps, matériau par défaut. | + +## Utilisation + +### 1. Poser le cône de vision + +Dans le Blueprint du personnage (ou en C++), ajouter le component **PS Vision Cone** +(idéalement attaché au root / à la capsule). Régler : + +- **Team** : `Player`, `Ally`, `Enemy` ou `Neutral` — détermine la couleur + (configurable dans *Project Settings > Plugins > PS Strategic Vision*). +- **View Distance** / **View Angle Degrees** : portée et ouverture du cône. +- **Trace Channel** : canal de collision utilisé pour bloquer le cône sur les murs + (`Visibility` par défaut). +- **Flatten On Ground** : le cône reste à plat et se projette au sol (recommandé + pour une vue spectateur top-down). + +Le cône suit automatiquement la position et le yaw du component. Pour qu'il suive +le regard de la tête plutôt que le corps, attacher le component au bone/socket voulu. + +### 2. Activer / désactiver selon l'état de jeu + +Par component : + +- `SetDisplayEnabled(bool)` — interrupteur local du component. + +Globalement, via le subsystem (Blueprint : nœud `Get PSStrategicVisionSubsystem`, +ou `UPSStrategicVisionSubsystem::Get(WorldContext)` en C++) : + +- `SetGlobalDisplayEnabled(bool)` — tout l'affichage stratégique (ex : entrée/sortie + du mode spectateur ou replay). +- `SetVisionConesEnabled(bool)` — uniquement les cônes de vision. +- `SetCategoryEnabled(FName, bool)` — par catégorie (pour les futurs components). +- `SetTeamEnabled(EPSStrategicTeam, bool)` — par camp (ex : ne montrer que les ennemis). + +Un component n'est visible que si son interrupteur local ET les interrupteurs +globaux le permettent. Quand un cône est masqué, il ne fait plus aucune trace +(coût nul). + +### 3. Matériau (optionnel) + +Sans configuration, le cône utilise un matériau translucide du moteur +(`M_SimpleUnlitTranslucent`) teinté à la couleur du camp — fonctionnel +immédiatement, suffisant en développement. + +Pour un rendu personnalisé (dégradé, scanlines, bord lumineux...), créer un +matériau dans le Content du plugin à `/PS_StrategicVision/Materials/M_VisionCone` +(chemin par défaut des settings) ou l'assigner via **Material Override** sur le +component. Le component fournit au matériau : + +- paramètre vecteur `Color` : couleur du camp (alpha = opacité) ; +- paramètre scalaire `Opacity` : opacité ; +- vertex colors : couleur du camp ; +- UV : U = position angulaire (0..1), V = distance normalisée (0..1) — pratique + pour un fondu vers le bord du cône. + +Recette minimale : matériau *Translucent* / *Unlit*, node **VertexColor** → Emissive +Color, **VertexColor.A** → Opacity. Laisser *Two Sided* décoché (la géométrie est +déjà double face). + +> Note packaging : si le matériau n'est référencé que par les settings (soft +> reference), l'ajouter à la liste des assets toujours cookés, ou le référencer en +> dur via Material Override. + +### Performance + +- `ArcSegments` (48 par défaut) = nombre de line traces par mise à jour. +- `UpdateInterval` : 0 = chaque frame ; augmenter (ex : 0.05) si beaucoup de + personnages affichent leur cône simultanément. + +## Roadmap + +- [x] Cône de vision bloqué par les obstacles +- [ ] Autres affichages stratégiques (trajectoires, zones d'alerte, ...) sous forme + de components héritant de `UPSStrategicDisplayComponent` diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/PS_StrategicVision.Build.cs b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/PS_StrategicVision.Build.cs new file mode 100644 index 0000000..0924c3e --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/PS_StrategicVision.Build.cs @@ -0,0 +1,18 @@ +using UnrealBuildTool; + +public class PS_StrategicVision : ModuleRules +{ + public PS_StrategicVision(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] + { + "Core", + "CoreUObject", + "Engine", + "DeveloperSettings", + "ProceduralMeshComponent", + }); + } +} diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicDisplayComponent.cpp b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicDisplayComponent.cpp new file mode 100644 index 0000000..1ea01ad --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicDisplayComponent.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#include "PSStrategicDisplayComponent.h" + +#include "Engine/World.h" +#include "PSStrategicVisionSettings.h" +#include "PSStrategicVisionSubsystem.h" + +UPSStrategicDisplayComponent::UPSStrategicDisplayComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} + +void UPSStrategicDisplayComponent::SetTeam(EPSStrategicTeam NewTeam) +{ + if (Team != NewTeam) + { + Team = NewTeam; + RefreshDisplayState(); + } +} + +void UPSStrategicDisplayComponent::SetDisplayEnabled(bool bEnabled) +{ + if (bDisplayEnabled != bEnabled) + { + bDisplayEnabled = bEnabled; + RefreshDisplayState(); + } +} + +bool UPSStrategicDisplayComponent::IsDisplayVisible() const +{ + if (!bDisplayEnabled) + { + return false; + } + + if (const UWorld* World = GetWorld()) + { + if (World->IsGameWorld()) + { + if (const UPSStrategicVisionSubsystem* Subsystem = World->GetSubsystem()) + { + return Subsystem->ShouldDisplay(GetDisplayCategory(), Team); + } + } + } + + // Monde editeur (preview) : on suit uniquement l'activation locale. + return true; +} + +FLinearColor UPSStrategicDisplayComponent::GetTeamColor() const +{ + return GetDefault()->GetTeamColor(Team); +} + +void UPSStrategicDisplayComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (UPSStrategicVisionSubsystem* Subsystem = UPSStrategicVisionSubsystem::Get(this)) + { + Subsystem->OnDisplayStateChanged.AddUniqueDynamic(this, &UPSStrategicDisplayComponent::HandleGlobalDisplayStateChanged); + } + + RefreshDisplayState(); +} + +void UPSStrategicDisplayComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (UPSStrategicVisionSubsystem* Subsystem = UPSStrategicVisionSubsystem::Get(this)) + { + Subsystem->OnDisplayStateChanged.RemoveDynamic(this, &UPSStrategicDisplayComponent::HandleGlobalDisplayStateChanged); + } + + Super::EndPlay(EndPlayReason); +} + +#if WITH_EDITOR +void UPSStrategicDisplayComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + RefreshDisplayState(); +} +#endif + +void UPSStrategicDisplayComponent::RefreshDisplayState() +{ + OnDisplayStateChanged(); +} + +void UPSStrategicDisplayComponent::HandleGlobalDisplayStateChanged() +{ + RefreshDisplayState(); +} diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionSettings.cpp b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionSettings.cpp new file mode 100644 index 0000000..946b5ba --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionSettings.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#include "PSStrategicVisionSettings.h" + +#include "Materials/MaterialInterface.h" + +UPSStrategicVisionSettings::UPSStrategicVisionSettings() +{ + TeamColors.Add(EPSStrategicTeam::Player, FLinearColor(0.05f, 0.45f, 1.0f)); + TeamColors.Add(EPSStrategicTeam::Ally, FLinearColor(0.1f, 0.9f, 0.5f)); + TeamColors.Add(EPSStrategicTeam::Enemy, FLinearColor(1.0f, 0.12f, 0.08f)); + TeamColors.Add(EPSStrategicTeam::Neutral, FLinearColor(0.75f, 0.75f, 0.75f)); + + // Materiau optionnel a creer dans le Content du plugin (voir README). + DefaultConeMaterial = TSoftObjectPtr( + FSoftObjectPath(TEXT("/PS_StrategicVision/Materials/M_VisionCone.M_VisionCone"))); +} + +FLinearColor UPSStrategicVisionSettings::GetTeamColor(EPSStrategicTeam Team) const +{ + if (const FLinearColor* Color = TeamColors.Find(Team)) + { + return *Color; + } + return FLinearColor::White; +} diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionSubsystem.cpp b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionSubsystem.cpp new file mode 100644 index 0000000..ea0b16f --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionSubsystem.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#include "PSStrategicVisionSubsystem.h" + +#include "Engine/Engine.h" +#include "Engine/World.h" + +UPSStrategicVisionSubsystem* UPSStrategicVisionSubsystem::Get(const UObject* WorldContextObject) +{ + if (GEngine) + { + if (const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull)) + { + return World->GetSubsystem(); + } + } + return nullptr; +} + +void UPSStrategicVisionSubsystem::SetGlobalDisplayEnabled(bool bEnabled) +{ + if (bGlobalEnabled != bEnabled) + { + bGlobalEnabled = bEnabled; + NotifyStateChanged(); + } +} + +void UPSStrategicVisionSubsystem::SetCategoryEnabled(FName Category, bool bEnabled) +{ + const bool bChanged = bEnabled ? (DisabledCategories.Remove(Category) > 0) : !DisabledCategories.Contains(Category); + if (!bEnabled) + { + DisabledCategories.Add(Category); + } + if (bChanged) + { + NotifyStateChanged(); + } +} + +void UPSStrategicVisionSubsystem::SetTeamEnabled(EPSStrategicTeam Team, bool bEnabled) +{ + const bool bChanged = bEnabled ? (DisabledTeams.Remove(Team) > 0) : !DisabledTeams.Contains(Team); + if (!bEnabled) + { + DisabledTeams.Add(Team); + } + if (bChanged) + { + NotifyStateChanged(); + } +} + +void UPSStrategicVisionSubsystem::NotifyStateChanged() +{ + OnDisplayStateChanged.Broadcast(); +} diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionTypes.cpp b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionTypes.cpp new file mode 100644 index 0000000..7ff0f0c --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSStrategicVisionTypes.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#include "PSStrategicVisionTypes.h" + +const FName FPSStrategicDisplayCategories::VisionCone(TEXT("VisionCone")); diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSVisionConeComponent.cpp b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSVisionConeComponent.cpp new file mode 100644 index 0000000..0573c08 --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PSVisionConeComponent.cpp @@ -0,0 +1,270 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#include "PSVisionConeComponent.h" + +#include "CollisionQueryParams.h" +#include "Engine/HitResult.h" +#include "Engine/World.h" +#include "GameFramework/Actor.h" +#include "Materials/Material.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Materials/MaterialInterface.h" +#include "ProceduralMeshComponent.h" +#include "PSStrategicVisionSettings.h" + +namespace +{ + // Materiaux moteur utilises en dernier recours si aucun materiau n'est configure. + const TCHAR* GFallbackTranslucentMaterialPath = TEXT("/Engine/EngineDebugMaterials/M_SimpleUnlitTranslucent.M_SimpleUnlitTranslucent"); + const TCHAR* GFallbackOpaqueMaterialPath = TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"); + + const FName GColorParamName(TEXT("Color")); + const FName GOpacityParamName(TEXT("Opacity")); +} + +UPSVisionConeComponent::UPSVisionConeComponent() +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + // Apres le mouvement des acteurs, pour que le cone suive sans retard. + PrimaryComponentTick.TickGroup = TG_PostUpdateWork; + bTickInEditor = true; +} + +void UPSVisionConeComponent::OnRegister() +{ + Super::OnRegister(); + + EnsureConeMesh(); + EnsureMaterial(); + ApplyTeamColor(); +} + +void UPSVisionConeComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + if (ConeMesh) + { + ConeMesh->DestroyComponent(); + ConeMesh = nullptr; + } + + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +void UPSVisionConeComponent::BeginPlay() +{ + Super::BeginPlay(); + SetComponentTickInterval(UpdateInterval); +} + +void UPSVisionConeComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (IsDisplayVisible()) + { + RebuildCone(); + } +} + +void UPSVisionConeComponent::OnDisplayStateChanged() +{ + const bool bShouldDisplay = IsDisplayVisible(); + + if (ConeMesh) + { + ConeMesh->SetVisibility(bShouldDisplay); + } + + // Pas de traces ni de rebuild quand le cone est cache. + SetComponentTickEnabled(bShouldDisplay); + SetComponentTickInterval(UpdateInterval); + + if (bShouldDisplay) + { + EnsureMaterial(); + ApplyTeamColor(); + RebuildCone(); + } +} + +void UPSVisionConeComponent::EnsureConeMesh() +{ + if (ConeMesh || !GetWorld()) + { + return; + } + + ConeMesh = NewObject(this, NAME_None, RF_Transient); + ConeMesh->SetupAttachment(this); + // Rotation absolue : le fan est construit en espace monde, seul le point + // d'ancrage suit le personnage entre deux mises a jour. + ConeMesh->SetUsingAbsoluteRotation(true); + ConeMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); + ConeMesh->SetGenerateOverlapEvents(false); + ConeMesh->SetCastShadow(false); + ConeMesh->bReceivesDecals = false; + ConeMesh->SetTranslucentSortPriority(100); + ConeMesh->RegisterComponent(); + ConeMesh->SetWorldRotation(FQuat::Identity); +} + +void UPSVisionConeComponent::EnsureMaterial() +{ + if (!ConeMesh) + { + return; + } + + UMaterialInterface* BaseMaterial = MaterialOverride; + + if (!BaseMaterial) + { + BaseMaterial = GetDefault()->DefaultConeMaterial.LoadSynchronous(); + } + if (!BaseMaterial) + { + BaseMaterial = LoadObject(nullptr, GFallbackTranslucentMaterialPath); + } + if (!BaseMaterial) + { + BaseMaterial = LoadObject(nullptr, GFallbackOpaqueMaterialPath); + } + if (!BaseMaterial) + { + return; + } + + if (!ConeMID || ConeMID->Parent != BaseMaterial) + { + ConeMID = UMaterialInstanceDynamic::Create(BaseMaterial, this); + } + ConeMesh->SetMaterial(0, ConeMID); +} + +void UPSVisionConeComponent::ApplyTeamColor() +{ + if (!ConeMID) + { + return; + } + + FLinearColor Color = GetTeamColor(); + Color.A = Opacity; + ConeMID->SetVectorParameterValue(GColorParamName, Color); + ConeMID->SetScalarParameterValue(GOpacityParamName, Opacity); +} + +void UPSVisionConeComponent::RebuildCone() +{ + UWorld* World = GetWorld(); + if (!World || !ConeMesh) + { + return; + } + + const FVector CompLocation = GetComponentLocation(); + const FRotator CompRotation = GetComponentRotation(); + + const FVector Forward = bFlattenOnGround + ? FRotator(0.f, CompRotation.Yaw, 0.f).Vector() + : GetForwardVector(); + const FVector Up = bFlattenOnGround ? FVector::UpVector : GetUpVector(); + + FCollisionQueryParams QueryParams(TEXT("PSVisionCone"), bTraceComplex); + if (const AActor* Owner = GetOwner()) + { + QueryParams.AddIgnoredActor(Owner); + } + + // Hauteur de base du fan : projete au sol si demande, sinon a la hauteur du component. + float BaseZ = GroundOffset; + if (bFlattenOnGround) + { + FHitResult GroundHit; + const FVector GroundStart = CompLocation + FVector(0.f, 0.f, TraceHeight); + const FVector GroundEnd = CompLocation - FVector(0.f, 0.f, 500.f); + if (World->LineTraceSingleByChannel(GroundHit, GroundStart, GroundEnd, TraceChannel, QueryParams)) + { + BaseZ = (GroundHit.ImpactPoint.Z - CompLocation.Z) + GroundOffset; + } + } + + const int32 RayCount = FMath::Clamp(ArcSegments, 4, 256) + 1; + const float HalfAngle = FMath::Clamp(ViewAngleDegrees, 1.f, 360.f) * 0.5f; + const FVector TraceOrigin = CompLocation + FVector(0.f, 0.f, TraceHeight); + + FLinearColor VertexColor = GetTeamColor(); + VertexColor.A = Opacity; + + TArray Vertices; + TArray Normals; + TArray UVs; + TArray Colors; + Vertices.Reserve(RayCount + 1); + Normals.Reserve(RayCount + 1); + UVs.Reserve(RayCount + 1); + Colors.Reserve(RayCount + 1); + + // Sommet central (origine du cone). + Vertices.Add(bFlattenOnGround ? FVector(0.f, 0.f, BaseZ) : Up * GroundOffset); + Normals.Add(Up); + UVs.Add(FVector2D(0.5f, 0.f)); + Colors.Add(VertexColor); + + for (int32 RayIndex = 0; RayIndex < RayCount; ++RayIndex) + { + const float Alpha = static_cast(RayIndex) / static_cast(RayCount - 1); + const float AngleDeg = FMath::Lerp(-HalfAngle, HalfAngle, Alpha); + const FVector Direction = Forward.RotateAngleAxis(AngleDeg, Up); + + float HitDistance = ViewDistance; + FHitResult Hit; + if (World->LineTraceSingleByChannel(Hit, TraceOrigin, TraceOrigin + Direction * ViewDistance, TraceChannel, QueryParams)) + { + HitDistance = Hit.Distance; + } + + FVector LocalPoint = Direction * HitDistance; + if (bFlattenOnGround) + { + LocalPoint.Z = BaseZ; + } + else + { + LocalPoint += Up * GroundOffset; + } + + Vertices.Add(LocalPoint); + Normals.Add(Up); + UVs.Add(FVector2D(Alpha, HitDistance / ViewDistance)); + Colors.Add(VertexColor); + } + + const TArray NoTangents; + + if (Vertices.Num() == LastVertexCount) + { + // Meme topologie : mise a jour des sommets uniquement (moins couteux). + ConeMesh->UpdateMeshSection_LinearColor(0, Vertices, Normals, UVs, Colors, NoTangents); + } + else + { + // Triangles dans les deux sens pour rester visible sous tous les angles de camera. + TArray Triangles; + Triangles.Reserve((RayCount - 1) * 6); + for (int32 SegmentIndex = 0; SegmentIndex < RayCount - 1; ++SegmentIndex) + { + Triangles.Add(0); + Triangles.Add(1 + SegmentIndex); + Triangles.Add(2 + SegmentIndex); + + Triangles.Add(0); + Triangles.Add(2 + SegmentIndex); + Triangles.Add(1 + SegmentIndex); + } + + ConeMesh->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UVs, Colors, NoTangents, false); + LastVertexCount = Vertices.Num(); + } +} diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PS_StrategicVisionModule.cpp b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PS_StrategicVisionModule.cpp new file mode 100644 index 0000000..2587551 --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Private/PS_StrategicVisionModule.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE(FDefaultModuleImpl, PS_StrategicVision) diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicDisplayComponent.h b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicDisplayComponent.h new file mode 100644 index 0000000..a53694f --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicDisplayComponent.h @@ -0,0 +1,75 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/SceneComponent.h" +#include "PSStrategicVisionTypes.h" +#include "PSStrategicDisplayComponent.generated.h" + +/** + * Classe de base des affichages strategiques poses sur les personnages + * (cone de vision, et autres visualisations a venir). + * + * Gere le camp (couleur), l'activation locale du component et la + * synchronisation avec l'etat global du UPSStrategicVisionSubsystem. + */ +UCLASS(Abstract, ClassGroup = (PSStrategicVision), HideCategories = (Physics, Collision, Mobility)) +class PS_STRATEGICVISION_API UPSStrategicDisplayComponent : public USceneComponent +{ + GENERATED_BODY() + +public: + UPSStrategicDisplayComponent(); + + /** Change le camp (met a jour la couleur de l'affichage). */ + UFUNCTION(BlueprintCallable, Category = "Strategic Display") + void SetTeam(EPSStrategicTeam NewTeam); + + UFUNCTION(BlueprintPure, Category = "Strategic Display") + EPSStrategicTeam GetTeam() const { return Team; } + + /** Active / desactive cet affichage precis (independamment de l'etat global). */ + UFUNCTION(BlueprintCallable, Category = "Strategic Display") + void SetDisplayEnabled(bool bEnabled); + + UFUNCTION(BlueprintPure, Category = "Strategic Display") + bool IsDisplayEnabled() const { return bDisplayEnabled; } + + /** Visibilite effective : activation locale ET etat global du subsystem. */ + UFUNCTION(BlueprintPure, Category = "Strategic Display") + bool IsDisplayVisible() const; + + /** Couleur du camp courant, lue dans les settings du plugin. */ + UFUNCTION(BlueprintPure, Category = "Strategic Display") + FLinearColor GetTeamColor() const; + + /** Categorie declaree par chaque type d'affichage (cf. FPSStrategicDisplayCategories). */ + virtual FName GetDisplayCategory() const { return NAME_None; } + +protected: + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + /** Appele quand la visibilite effective ou le camp change. */ + virtual void OnDisplayStateChanged() {} + + /** Reevalue la visibilite effective et previent la classe fille. */ + void RefreshDisplayState(); + + /** Camp du personnage porteur, determine la couleur de l'affichage. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Strategic Display") + EPSStrategicTeam Team = EPSStrategicTeam::Enemy; + + /** Etat d'activation local de cet affichage. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Strategic Display") + bool bDisplayEnabled = true; + +private: + UFUNCTION() + void HandleGlobalDisplayStateChanged(); +}; diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionSettings.h b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionSettings.h new file mode 100644 index 0000000..d2e653d --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionSettings.h @@ -0,0 +1,42 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/DeveloperSettings.h" +#include "PSStrategicVisionTypes.h" +#include "PSStrategicVisionSettings.generated.h" + +class UMaterialInterface; + +/** + * Reglages du plugin PS Strategic Vision. + * Accessibles dans Project Settings > Plugins > PS Strategic Vision. + */ +UCLASS(Config = Game, DefaultConfig, meta = (DisplayName = "PS Strategic Vision")) +class PS_STRATEGICVISION_API UPSStrategicVisionSettings : public UDeveloperSettings +{ + GENERATED_BODY() + +public: + UPSStrategicVisionSettings(); + + virtual FName GetCategoryName() const override { return TEXT("Plugins"); } + + /** Couleur associee a chaque camp. */ + UPROPERTY(EditAnywhere, Config, BlueprintReadOnly, Category = "Teams") + TMap TeamColors; + + /** + * Materiau par defaut des cones de vision. La couleur est appliquee via le + * parametre vecteur "Color" (et le parametre scalaire "Opacity" s'il existe), + * ainsi que via les vertex colors du mesh. + * Si vide ou introuvable, un materiau translucide du moteur est utilise. + */ + UPROPERTY(EditAnywhere, Config, BlueprintReadOnly, Category = "Rendering") + TSoftObjectPtr DefaultConeMaterial; + + /** Couleur du camp demande (blanc si non configuree). */ + UFUNCTION(BlueprintPure, Category = "PS Strategic Vision") + FLinearColor GetTeamColor(EPSStrategicTeam Team) const; +}; diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionSubsystem.h b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionSubsystem.h new file mode 100644 index 0000000..dab6a67 --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionSubsystem.h @@ -0,0 +1,72 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "PSStrategicVisionTypes.h" +#include "PSStrategicVisionSubsystem.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FPSStrategicDisplayStateChanged); + +/** + * Subsystem monde pilotant l'affichage strategique global. + * Permet d'activer / desactiver tous les affichages, une categorie (ex : cones + * de vision) ou un camp entier selon l'etat de jeu (vue spectateur, replay...). + * Tous les UPSStrategicDisplayComponent s'y abonnent automatiquement. + */ +UCLASS() +class PS_STRATEGICVISION_API UPSStrategicVisionSubsystem : public UWorldSubsystem +{ + GENERATED_BODY() + +public: + /** Raccourci d'acces au subsystem depuis n'importe quel contexte monde. */ + UFUNCTION(BlueprintPure, Category = "PS Strategic Vision", meta = (WorldContext = "WorldContextObject")) + static UPSStrategicVisionSubsystem* Get(const UObject* WorldContextObject); + + /** Interrupteur general de tous les affichages strategiques. */ + UFUNCTION(BlueprintCallable, Category = "PS Strategic Vision") + void SetGlobalDisplayEnabled(bool bEnabled); + + UFUNCTION(BlueprintPure, Category = "PS Strategic Vision") + bool IsGlobalDisplayEnabled() const { return bGlobalEnabled; } + + /** Active / desactive une categorie d'affichage (ex : "VisionCone"). */ + UFUNCTION(BlueprintCallable, Category = "PS Strategic Vision") + void SetCategoryEnabled(FName Category, bool bEnabled); + + UFUNCTION(BlueprintPure, Category = "PS Strategic Vision") + bool IsCategoryEnabled(FName Category) const { return !DisabledCategories.Contains(Category); } + + /** Active / desactive les affichages d'un camp (ex : ne montrer que les ennemis). */ + UFUNCTION(BlueprintCallable, Category = "PS Strategic Vision") + void SetTeamEnabled(EPSStrategicTeam Team, bool bEnabled); + + UFUNCTION(BlueprintPure, Category = "PS Strategic Vision") + bool IsTeamEnabled(EPSStrategicTeam Team) const { return !DisabledTeams.Contains(Team); } + + /** Raccourci : active / desactive tous les cones de vision. */ + UFUNCTION(BlueprintCallable, Category = "PS Strategic Vision|Vision Cone") + void SetVisionConesEnabled(bool bEnabled) { SetCategoryEnabled(FPSStrategicDisplayCategories::VisionCone, bEnabled); } + + UFUNCTION(BlueprintPure, Category = "PS Strategic Vision|Vision Cone") + bool AreVisionConesEnabled() const { return IsCategoryEnabled(FPSStrategicDisplayCategories::VisionCone); } + + /** Etat combine utilise par les components pour savoir s'ils doivent s'afficher. */ + bool ShouldDisplay(FName Category, EPSStrategicTeam Team) const + { + return bGlobalEnabled && IsCategoryEnabled(Category) && IsTeamEnabled(Team); + } + + /** Notifie a chaque changement d'etat global (les components s'y abonnent). */ + UPROPERTY(BlueprintAssignable, Category = "PS Strategic Vision") + FPSStrategicDisplayStateChanged OnDisplayStateChanged; + +private: + void NotifyStateChanged(); + + bool bGlobalEnabled = true; + TSet DisabledCategories; + TSet DisabledTeams; +}; diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionTypes.h b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionTypes.h new file mode 100644 index 0000000..38b92e0 --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSStrategicVisionTypes.h @@ -0,0 +1,25 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "PSStrategicVisionTypes.generated.h" + +/** Camp d'un personnage, utilise pour colorer / filtrer les affichages strategiques. */ +UENUM(BlueprintType) +enum class EPSStrategicTeam : uint8 +{ + Player, + Ally, + Enemy, + Neutral +}; + +/** + * Categories d'affichage strategique. Chaque type de component declare sa categorie, + * ce qui permet de l'activer / desactiver globalement via le subsystem. + */ +struct PS_STRATEGICVISION_API FPSStrategicDisplayCategories +{ + static const FName VisionCone; +}; diff --git a/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSVisionConeComponent.h b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSVisionConeComponent.h new file mode 100644 index 0000000..e52de8a --- /dev/null +++ b/Unreal/PS_StratVision/Plugins/PS_StrategicVision/Source/PS_StrategicVision/Public/PSVisionConeComponent.h @@ -0,0 +1,106 @@ +// Copyright (c) 2026 j.foucher. All rights reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/EngineTypes.h" +#include "PSStrategicDisplayComponent.h" +#include "PSVisionConeComponent.generated.h" + +class UMaterialInstanceDynamic; +class UMaterialInterface; +class UProceduralMeshComponent; + +/** + * Affiche le cone de vision d'un personnage sous forme de mesh procedural. + * Le cone est decoupe par les obstacles (murs) via des line traces, colore + * selon le camp, et activable / desactivable localement ou globalement + * (vue spectateur, replay) via le UPSStrategicVisionSubsystem. + * + * A attacher sur le personnage : le cone suit la position du component et + * s'oriente selon son yaw (rotation horizontale). + */ +UCLASS(ClassGroup = (PSStrategicVision), meta = (BlueprintSpawnableComponent, DisplayName = "PS Vision Cone")) +class PS_STRATEGICVISION_API UPSVisionConeComponent : public UPSStrategicDisplayComponent +{ + GENERATED_BODY() + +public: + UPSVisionConeComponent(); + + virtual FName GetDisplayCategory() const override { return FPSStrategicDisplayCategories::VisionCone; } + + /** Portee de vision, en cm. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone", meta = (ClampMin = "10.0", Units = "cm")) + float ViewDistance = 1500.f; + + /** Angle total du cone, en degres (ex : 90 = 45 de chaque cote). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone", meta = (ClampMin = "1.0", ClampMax = "360.0", Units = "deg")) + float ViewAngleDegrees = 90.f; + + /** Opacite du cone (0 = invisible, 1 = opaque). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone", meta = (ClampMin = "0.0", ClampMax = "1.0")) + float Opacity = 0.35f; + + /** + * Si vrai, le cone reste a plat au sol : seul le yaw du component est pris + * en compte et le mesh est projete a la hauteur du sol detecte. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone") + bool bFlattenOnGround = true; + + /** Decalage vertical du mesh au-dessus du sol, pour eviter le z-fighting. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone", meta = (Units = "cm")) + float GroundOffset = 3.f; + + /** Nombre de segments de l'arc : plus eleve = plus precis mais plus de traces. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone|Quality", meta = (ClampMin = "4", ClampMax = "256")) + int32 ArcSegments = 48; + + /** Intervalle de mise a jour en secondes (0 = chaque frame). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone|Quality", meta = (ClampMin = "0.0", Units = "s")) + float UpdateInterval = 0.f; + + /** Canal de collision utilise pour bloquer le cone sur les obstacles. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone|Collision") + TEnumAsByte TraceChannel = ECC_Visibility; + + /** Trace contre la geometrie complexe (plus precis, plus couteux). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone|Collision") + bool bTraceComplex = false; + + /** Hauteur (relative au component) a laquelle les traces de vision sont lancees. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone|Collision", meta = (Units = "cm")) + float TraceHeight = 50.f; + + /** + * Materiau du cone pour ce component (prioritaire sur celui des settings). + * La couleur du camp est envoyee dans le parametre vecteur "Color", l'opacite + * dans le parametre scalaire "Opacity", et dans les vertex colors du mesh. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Vision Cone|Rendering") + TObjectPtr MaterialOverride; + + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + +protected: + virtual void OnRegister() override; + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + virtual void BeginPlay() override; + virtual void OnDisplayStateChanged() override; + +private: + /** Mesh procedural interne portant le fan du cone. */ + UPROPERTY(Transient) + TObjectPtr ConeMesh; + + UPROPERTY(Transient) + TObjectPtr ConeMID; + + int32 LastVertexCount = 0; + + void EnsureConeMesh(); + void EnsureMaterial(); + void ApplyTeamColor(); + void RebuildCone(); +}; diff --git a/Unreal/PS_StratVision/Source/PS_StratVision.Target.cs b/Unreal/PS_StratVision/Source/PS_StratVision.Target.cs new file mode 100644 index 0000000..b0e68a6 --- /dev/null +++ b/Unreal/PS_StratVision/Source/PS_StratVision.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class PS_StratVisionTarget : TargetRules +{ + public PS_StratVisionTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + DefaultBuildSettings = BuildSettingsVersion.V6; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7; + ExtraModuleNames.Add("PS_StratVision"); + } +} diff --git a/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.Build.cs b/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.Build.cs new file mode 100644 index 0000000..954c0cd --- /dev/null +++ b/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.Build.cs @@ -0,0 +1,23 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class PS_StratVision : ModuleRules +{ + public PS_StratVision(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.cpp b/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.cpp new file mode 100644 index 0000000..607c272 --- /dev/null +++ b/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.cpp @@ -0,0 +1,6 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "PS_StratVision.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, PS_StratVision, "PS_StratVision" ); diff --git a/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.h b/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.h new file mode 100644 index 0000000..677c8e2 --- /dev/null +++ b/Unreal/PS_StratVision/Source/PS_StratVision/PS_StratVision.h @@ -0,0 +1,6 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + diff --git a/Unreal/PS_StratVision/Source/PS_StratVisionEditor.Target.cs b/Unreal/PS_StratVision/Source/PS_StratVisionEditor.Target.cs new file mode 100644 index 0000000..87479ca --- /dev/null +++ b/Unreal/PS_StratVision/Source/PS_StratVisionEditor.Target.cs @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class PS_StratVisionEditorTarget : TargetRules +{ + public PS_StratVisionEditorTarget( TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V6; + IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7; + ExtraModuleNames.Add("PS_StratVision"); + } +}