initial commit

This commit is contained in:
2026-07-22 12:41:12 +02:00
commit 70db1cbf16
26 changed files with 1233 additions and 0 deletions

17
.gitignore vendored Normal file
View File

@@ -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

View File

@@ -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=

View File

@@ -0,0 +1,6 @@
[/Script/CommonUI.CommonUISettings]
CommonButtonAcceptKeyHandling=TriggerClick
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=D2B643384A3F6CB0851AE99C0EA8D2A6

View File

@@ -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

View File

@@ -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
}
]
}

View File

@@ -0,0 +1,3 @@
# Artefacts de build du plugin (regeneres a la compilation)
Binaries/
Intermediate/

View File

@@ -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
}
]
}

View File

@@ -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 <url-du-repo> 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`

View File

@@ -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",
});
}
}

View File

@@ -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<UPSStrategicVisionSubsystem>())
{
return Subsystem->ShouldDisplay(GetDisplayCategory(), Team);
}
}
}
// Monde editeur (preview) : on suit uniquement l'activation locale.
return true;
}
FLinearColor UPSStrategicDisplayComponent::GetTeamColor() const
{
return GetDefault<UPSStrategicVisionSettings>()->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();
}

View File

@@ -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<UMaterialInterface>(
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;
}

View File

@@ -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<UPSStrategicVisionSubsystem>();
}
}
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();
}

View File

@@ -0,0 +1,5 @@
// Copyright (c) 2026 j.foucher. All rights reserved.
#include "PSStrategicVisionTypes.h"
const FName FPSStrategicDisplayCategories::VisionCone(TEXT("VisionCone"));

View File

@@ -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<UProceduralMeshComponent>(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<UPSStrategicVisionSettings>()->DefaultConeMaterial.LoadSynchronous();
}
if (!BaseMaterial)
{
BaseMaterial = LoadObject<UMaterialInterface>(nullptr, GFallbackTranslucentMaterialPath);
}
if (!BaseMaterial)
{
BaseMaterial = LoadObject<UMaterialInterface>(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<FVector> Vertices;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FLinearColor> 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<float>(RayIndex) / static_cast<float>(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<FProcMeshTangent> 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<int32> 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();
}
}

View File

@@ -0,0 +1,5 @@
// Copyright (c) 2026 j.foucher. All rights reserved.
#include "Modules/ModuleManager.h"
IMPLEMENT_MODULE(FDefaultModuleImpl, PS_StrategicVision)

View File

@@ -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();
};

View File

@@ -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<EPSStrategicTeam, FLinearColor> 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<UMaterialInterface> DefaultConeMaterial;
/** Couleur du camp demande (blanc si non configuree). */
UFUNCTION(BlueprintPure, Category = "PS Strategic Vision")
FLinearColor GetTeamColor(EPSStrategicTeam Team) const;
};

View File

@@ -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<FName> DisabledCategories;
TSet<EPSStrategicTeam> DisabledTeams;
};

View File

@@ -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;
};

View File

@@ -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<ECollisionChannel> 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<UMaterialInterface> 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<UProceduralMeshComponent> ConeMesh;
UPROPERTY(Transient)
TObjectPtr<UMaterialInstanceDynamic> ConeMID;
int32 LastVertexCount = 0;
void EnsureConeMesh();
void EnsureMaterial();
void ApplyTeamColor();
void RebuildCone();
};

View File

@@ -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");
}
}

View File

@@ -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
}
}

View File

@@ -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" );

View File

@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"

View File

@@ -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");
}
}