├── .gitattributes ├── .gitignore ├── Config ├── DefaultOpenRTSCamera.ini └── FilterPlugin.ini ├── Content ├── AC_RTSCameraFollowMe.uasset ├── BPFL_RTSCameraSystem.uasset ├── BP_RTSCamera.uasset ├── Inputs │ ├── BeginSelection.uasset │ ├── DragCamera.uasset │ ├── MoveCameraXAxis.uasset │ ├── MoveCameraYAxis.uasset │ ├── OpenRTSCameraInputs.uasset │ ├── RotateCameraAxis.uasset │ ├── TurnCameraLeft.uasset │ ├── TurnCameraRight.uasset │ └── ZoomCamera.uasset ├── M_UnitSelection.uasset └── Textures │ └── T_UnitSelection.uasset ├── LICENSE ├── OpenRTSCamera.uplugin ├── README.md ├── Resources └── Icon128.png └── Source └── OpenRTSCamera ├── OpenRTSCamera.Build.cs ├── Private ├── OpenRTSCamera.cpp ├── RTSCamera.cpp ├── RTSCameraBoundsVolume.cpp ├── RTSHUD.cpp ├── RTSSelectable.cpp └── RTSSelector.cpp └── Public ├── OpenRTSCamera.h ├── RTSCamera.h ├── RTSCameraBoundsVolume.h ├── RTSHUD.h ├── RTSSelectable.h └── RTSSelector.h /.gitattributes: -------------------------------------------------------------------------------- 1 | Content/** filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries 2 | DerivedDataCache 3 | Intermediate 4 | Saved 5 | .vscode 6 | .vs 7 | *.VC.db 8 | *.opensdf 9 | *.opendb 10 | *.sdf 11 | *.sln 12 | *.suo 13 | *.xcodeproj 14 | *.xcworkspace -------------------------------------------------------------------------------- /Config/DefaultOpenRTSCamera.ini: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | -------------------------------------------------------------------------------- /Content/AC_RTSCameraFollowMe.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:1dceb093351e41cfe099ef87bdbfec3c42af2de2fd1fd821ea17d523df404a6b 3 | size 25843 4 | -------------------------------------------------------------------------------- /Content/BPFL_RTSCameraSystem.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b42816d6a507678971fce2c1abb5c700bc0841db7e0d388a3ceca5326cedebd3 3 | size 19929 4 | -------------------------------------------------------------------------------- /Content/BP_RTSCamera.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ad0d80f14898ade77d58d545aeb3150e1acc8287d0322c24aa7442cd98dea54e 3 | size 25195 4 | -------------------------------------------------------------------------------- /Content/Inputs/BeginSelection.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:934229f635ae789ad435022ad37978ac2c2b612d0042c7dac292eb0d1aa98e43 3 | size 1369 4 | -------------------------------------------------------------------------------- /Content/Inputs/DragCamera.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c5e8548a6319421d9a3706f94eae52d459c15ac375cc2d7223408e827b085338 3 | size 1217 4 | -------------------------------------------------------------------------------- /Content/Inputs/MoveCameraXAxis.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3636b529ac048efa8286afaba03c89ba7e121e48823813ee250e5103f9759bdf 3 | size 1385 4 | -------------------------------------------------------------------------------- /Content/Inputs/MoveCameraYAxis.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e090aa6cf95e5314c1d1946d103426226ebf785d4ebd033eeb6f475f1301d6af 3 | size 1385 4 | -------------------------------------------------------------------------------- /Content/Inputs/OpenRTSCameraInputs.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:84a18f2a1d6763ac1bcb66b692c611446e4a5875a54c008a660b6b9b145984e3 3 | size 16480 4 | -------------------------------------------------------------------------------- /Content/Inputs/RotateCameraAxis.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bf6b95bf81b9179fda502fae7e083a60499a1a8fb8a08deb25ab1a7390ec3872 3 | size 1389 4 | -------------------------------------------------------------------------------- /Content/Inputs/TurnCameraLeft.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cfd6d49c29e96ca3bfce97c8c60cc1feebd72a0f6e9d7148b72bfb17de7bf894 3 | size 1233 4 | -------------------------------------------------------------------------------- /Content/Inputs/TurnCameraRight.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2ee8f70fa21e7a6c107c6e9eb3644e49c7a392b93845a7113e05a3fde1f65167 3 | size 1237 4 | -------------------------------------------------------------------------------- /Content/Inputs/ZoomCamera.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:99567d22068edaeff25cf2de49a2832775442c3ac487adb2bb662feab6b3dcb1 3 | size 1365 4 | -------------------------------------------------------------------------------- /Content/M_UnitSelection.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ca5de716cc8e63773ee433a1a69442734ddce42a3f9aed06db566b111181948e 3 | size 9175 4 | -------------------------------------------------------------------------------- /Content/Textures/T_UnitSelection.uasset: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bc60f4605436b84e65128bb9231df62d1581b524a9806d72d6b296c46e6728a3 3 | size 59595 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jesus Bracho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OpenRTSCamera.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "CanContainContent": true, 3 | "Category": "Gameplay", 4 | "CreatedBy": "Jesus Bracho", 5 | "CreatedByURL": "https://github.com/HeyZoos", 6 | "Description": "An Unreal Engine 5 open-source RTS/MOBA camera implementation that aims to be fully featured, customizable and dependable", 7 | "DocsURL": "https://github.com/HeyZoos/OpenRTSCamera/wiki", 8 | "EnabledByDefault": true, 9 | "EngineVersion": "5.3.0", 10 | "FileVersion": 3, 11 | "FriendlyName": "OpenRTSCamera", 12 | "Installed": false, 13 | "IsBetaVersion": false, 14 | "IsExperimentalVersion": false, 15 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/94e9872ad3754e798b6ec53f4494f5de", 16 | "Modules": [ 17 | { 18 | "Name": "OpenRTSCamera", 19 | "Type": "Runtime", 20 | "LoadingPhase": "Default", 21 | "PlatformAllowList": [ 22 | "Win64" 23 | ] 24 | } 25 | ], 26 | "Plugins": [ 27 | { 28 | "Name": "EnhancedInput", 29 | "Enabled": true 30 | } 31 | ], 32 | "SupportURL": "https://github.com/HeyZoos/OpenRTSCamera/issues", 33 | "Version": 1, 34 | "VersionName": "0.21.0" 35 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenRTSCamera 2 | 3 | - [Installing from GitHub](https://github.com/HeyZoos/OpenRTSCamera/wiki/Installing-from-GitHub) 4 | - [Getting Started](https://github.com/HeyZoos/OpenRTSCamera/wiki/Getting-Started) 5 | 6 | ## Features 7 | 8 | - Smoothed Movement 9 | - Ground Height Adaptation 10 | - Edge Scrolling 11 | - [Follow Target](https://github.com/HeyZoos/OpenRTSCamera/wiki/Follow-Camera) 12 | - [Mouse + Keyboard Controls](https://github.com/HeyZoos/OpenRTSCamera/wiki/Movement-Controls) 13 | - [Gamepad Controls](https://github.com/HeyZoos/OpenRTSCamera/wiki/Movement-Controls) 14 | - [Unit Selection](https://github.com/HeyZoos/OpenRTSCamera/wiki/Unit-Selection) 15 | 16 | ### [Camera Bounds](https://github.com/HeyZoos/OpenRTSCamera/wiki/Camera-Bounds) 17 | 18 | https://user-images.githubusercontent.com/9408481/223589311-9d6b1cfd-76b4-4650-a6a3-0386a483bb96.mp4 19 | 20 | ### [Jump To](https://github.com/HeyZoos/OpenRTSCamera/wiki/Jump-To) 21 | 22 | https://user-images.githubusercontent.com/9408481/223585144-d7e9c1c2-2e36-4628-9bbd-da91229e39e1.mp4 23 | 24 | # Changelog 25 | 26 | ### 0.21.0 27 | 28 | - Add [Unit Selection](https://github.com/HeyZoos/OpenRTSCamera/wiki/Unit-Selection) 29 | 30 | ### 0.20.0 31 | 32 | - Build for Unreal Engine v5.3.X 33 | 34 | ### 0.19.0 35 | 36 | - Add "Jump To" method 37 | - Start overhauling documentation 38 | 39 | ### 0.18.0 40 | 41 | - Fix crash when using the camera in a networked context 42 | - **No longer take over view target automatically, the view target must be explicitly set, this is documented in the "Getting Started" section of the wiki** 43 | 44 | ### 0.17.0 45 | 46 | - Fix [#27](https://github.com/HeyZoos/OpenRTSCamera/issues/27) by tying camera movement to delta time (thanks [@theMyll](https://github.com/theMyll)) 47 | - **This will result in slower movement across the board, if you notice your camera moving more slowly, up the speed values by about 100x. For example, the new camera blueprint speed defaults are 5000** 48 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeyZoos/OpenRTSCamera/ff822f1fc0524750a015eedfcb3b2d5d23ac97c0/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/OpenRTSCamera/OpenRTSCamera.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class OpenRTSCamera : ModuleRules 6 | { 7 | public OpenRTSCamera(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] 13 | { 14 | } 15 | ); 16 | 17 | PrivateIncludePaths.AddRange( 18 | new string[] 19 | { 20 | } 21 | ); 22 | 23 | PublicDependencyModuleNames.AddRange( 24 | new[] 25 | { 26 | "Core", 27 | } 28 | ); 29 | 30 | PrivateDependencyModuleNames.AddRange( 31 | new[] 32 | { 33 | "CoreUObject", 34 | "Engine", 35 | "EnhancedInput", 36 | "Slate", 37 | "SlateCore", 38 | "UMG" 39 | } 40 | ); 41 | 42 | DynamicallyLoadedModuleNames.AddRange( 43 | new string[] 44 | { 45 | } 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Private/OpenRTSCamera.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #include "OpenRTSCamera.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FOpenRTSCameraModule" 6 | 7 | void FOpenRTSCameraModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FOpenRTSCameraModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FOpenRTSCameraModule, OpenRTSCamera) 21 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Private/RTSCamera.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #include "RTSCamera.h" 4 | 5 | #include "Blueprint/WidgetLayoutLibrary.h" 6 | #include "Engine/LocalPlayer.h" 7 | #include "Engine/World.h" 8 | #include "EnhancedInputComponent.h" 9 | #include "EnhancedInputSubsystems.h" 10 | #include "Kismet/GameplayStatics.h" 11 | #include "Kismet/KismetMathLibrary.h" 12 | #include "Runtime/CoreUObject/Public/UObject/ConstructorHelpers.h" 13 | 14 | URTSCamera::URTSCamera() 15 | { 16 | PrimaryComponentTick.bCanEverTick = true; 17 | this->CameraBlockingVolumeTag = FName("OpenRTSCamera#CameraBounds"); 18 | this->CollisionChannel = ECC_WorldStatic; 19 | this->DragExtent = 0.6f; 20 | this->EdgeScrollSpeed = 50; 21 | this->DistanceFromEdgeThreshold = 0.1f; 22 | this->EnableCameraLag = true; 23 | this->EnableCameraRotationLag = true; 24 | this->EnableDynamicCameraHeight = true; 25 | this->EnableEdgeScrolling = true; 26 | this->FindGroundTraceLength = 100000; 27 | this->MaximumZoomLength = 5000; 28 | this->MinimumZoomLength = 500; 29 | this->MoveSpeed = 50; 30 | this->RotateSpeed = 45; 31 | this->StartingYAngle = -45.0f; 32 | this->StartingZAngle = 0; 33 | this->ZoomCatchupSpeed = 4; 34 | this->ZoomSpeed = -200; 35 | 36 | static ConstructorHelpers::FObjectFinder 37 | MoveCameraXAxisFinder(TEXT("/OpenRTSCamera/Inputs/MoveCameraXAxis")); 38 | static ConstructorHelpers::FObjectFinder 39 | MoveCameraYAxisFinder(TEXT("/OpenRTSCamera/Inputs/MoveCameraYAxis")); 40 | static ConstructorHelpers::FObjectFinder 41 | RotateCameraAxisFinder(TEXT("/OpenRTSCamera/Inputs/RotateCameraAxis")); 42 | static ConstructorHelpers::FObjectFinder 43 | TurnCameraLeftFinder(TEXT("/OpenRTSCamera/Inputs/TurnCameraLeft")); 44 | static ConstructorHelpers::FObjectFinder 45 | TurnCameraRightFinder(TEXT("/OpenRTSCamera/Inputs/TurnCameraRight")); 46 | static ConstructorHelpers::FObjectFinder 47 | ZoomCameraFinder(TEXT("/OpenRTSCamera/Inputs/ZoomCamera")); 48 | static ConstructorHelpers::FObjectFinder 49 | DragCameraFinder(TEXT("/OpenRTSCamera/Inputs/DragCamera")); 50 | static ConstructorHelpers::FObjectFinder 51 | InputMappingContextFinder(TEXT("/OpenRTSCamera/Inputs/OpenRTSCameraInputs")); 52 | 53 | this->MoveCameraXAxis = MoveCameraXAxisFinder.Object; 54 | this->MoveCameraYAxis = MoveCameraYAxisFinder.Object; 55 | this->RotateCameraAxis = RotateCameraAxisFinder.Object; 56 | this->TurnCameraLeft = TurnCameraLeftFinder.Object; 57 | this->TurnCameraRight = TurnCameraRightFinder.Object; 58 | this->DragCamera = DragCameraFinder.Object; 59 | this->ZoomCamera = ZoomCameraFinder.Object; 60 | this->InputMappingContext = InputMappingContextFinder.Object; 61 | } 62 | 63 | void URTSCamera::BeginPlay() 64 | { 65 | Super::BeginPlay(); 66 | 67 | const auto NetMode = this->GetNetMode(); 68 | if (NetMode != NM_DedicatedServer) 69 | { 70 | this->CollectComponentDependencyReferences(); 71 | this->ConfigureSpringArm(); 72 | this->TryToFindBoundaryVolumeReference(); 73 | this->ConditionallyEnableEdgeScrolling(); 74 | this->CheckForEnhancedInputComponent(); 75 | this->BindInputMappingContext(); 76 | this->BindInputActions(); 77 | } 78 | } 79 | 80 | void URTSCamera::TickComponent( 81 | const float DeltaTime, 82 | const ELevelTick TickType, 83 | FActorComponentTickFunction* ThisTickFunction 84 | ) 85 | { 86 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 87 | const auto NetMode = this->GetNetMode(); 88 | if (NetMode != NM_DedicatedServer && this->PlayerController->GetViewTarget() == this->Owner) 89 | { 90 | this->DeltaSeconds = DeltaTime; 91 | this->ApplyMoveCameraCommands(); 92 | this->ConditionallyPerformEdgeScrolling(); 93 | this->ConditionallyKeepCameraAtDesiredZoomAboveGround(); 94 | this->SmoothTargetArmLengthToDesiredZoom(); 95 | this->FollowTargetIfSet(); 96 | this->ConditionallyApplyCameraBounds(); 97 | } 98 | } 99 | 100 | void URTSCamera::FollowTarget(AActor* Target) 101 | { 102 | this->CameraFollowTarget = Target; 103 | } 104 | 105 | void URTSCamera::UnFollowTarget() 106 | { 107 | this->CameraFollowTarget = nullptr; 108 | } 109 | 110 | void URTSCamera::OnZoomCamera(const FInputActionValue& Value) 111 | { 112 | this->DesiredZoomLength = FMath::Clamp( 113 | this->DesiredZoomLength + Value.Get() * this->ZoomSpeed, 114 | this->MinimumZoomLength, 115 | this->MaximumZoomLength 116 | ); 117 | } 118 | 119 | void URTSCamera::OnRotateCamera(const FInputActionValue& Value) 120 | { 121 | const auto WorldRotation = this->Root->GetComponentRotation(); 122 | this->Root->SetWorldRotation( 123 | FRotator::MakeFromEuler( 124 | FVector( 125 | WorldRotation.Euler().X, 126 | WorldRotation.Euler().Y, 127 | WorldRotation.Euler().Z + Value.Get() 128 | ) 129 | ) 130 | ); 131 | } 132 | 133 | void URTSCamera::OnTurnCameraLeft(const FInputActionValue&) 134 | { 135 | const auto WorldRotation = this->Root->GetRelativeRotation(); 136 | this->Root->SetRelativeRotation( 137 | FRotator::MakeFromEuler( 138 | FVector( 139 | WorldRotation.Euler().X, 140 | WorldRotation.Euler().Y, 141 | WorldRotation.Euler().Z - this->RotateSpeed 142 | ) 143 | ) 144 | ); 145 | } 146 | 147 | void URTSCamera::OnTurnCameraRight(const FInputActionValue&) 148 | { 149 | const auto WorldRotation = this->Root->GetRelativeRotation(); 150 | this->Root->SetRelativeRotation( 151 | FRotator::MakeFromEuler( 152 | FVector( 153 | WorldRotation.Euler().X, 154 | WorldRotation.Euler().Y, 155 | WorldRotation.Euler().Z + this->RotateSpeed 156 | ) 157 | ) 158 | ); 159 | } 160 | 161 | void URTSCamera::OnMoveCameraYAxis(const FInputActionValue& Value) 162 | { 163 | this->RequestMoveCamera( 164 | this->SpringArm->GetForwardVector().X, 165 | this->SpringArm->GetForwardVector().Y, 166 | Value.Get() 167 | ); 168 | } 169 | 170 | void URTSCamera::OnMoveCameraXAxis(const FInputActionValue& Value) 171 | { 172 | this->RequestMoveCamera( 173 | this->SpringArm->GetRightVector().X, 174 | this->SpringArm->GetRightVector().Y, 175 | Value.Get() 176 | ); 177 | } 178 | 179 | void URTSCamera::OnDragCamera(const FInputActionValue& Value) 180 | { 181 | if (!this->IsDragging && Value.Get()) 182 | { 183 | this->IsDragging = true; 184 | this->DragStartLocation = UWidgetLayoutLibrary::GetMousePositionOnViewport(this->GetWorld()); 185 | } 186 | 187 | else if (this->IsDragging && Value.Get()) 188 | { 189 | const auto MousePosition = UWidgetLayoutLibrary::GetMousePositionOnViewport(this->GetWorld()); 190 | auto DragExtents = UWidgetLayoutLibrary::GetViewportWidgetGeometry(this->GetWorld()).GetLocalSize(); 191 | DragExtents *= DragExtent; 192 | 193 | auto Delta = MousePosition - this->DragStartLocation; 194 | Delta.X = FMath::Clamp(Delta.X, -DragExtents.X, DragExtents.X) / DragExtents.X; 195 | Delta.Y = FMath::Clamp(Delta.Y, -DragExtents.Y, DragExtents.Y) / DragExtents.Y; 196 | 197 | this->RequestMoveCamera( 198 | this->SpringArm->GetRightVector().X, 199 | this->SpringArm->GetRightVector().Y, 200 | Delta.X 201 | ); 202 | 203 | this->RequestMoveCamera( 204 | this->SpringArm->GetForwardVector().X, 205 | this->SpringArm->GetForwardVector().Y, 206 | Delta.Y * -1 207 | ); 208 | } 209 | 210 | else if (this->IsDragging && !Value.Get()) 211 | { 212 | this->IsDragging = false; 213 | } 214 | } 215 | 216 | void URTSCamera::RequestMoveCamera(const float X, const float Y, const float Scale) 217 | { 218 | FMoveCameraCommand MoveCameraCommand; 219 | MoveCameraCommand.X = X; 220 | MoveCameraCommand.Y = Y; 221 | MoveCameraCommand.Scale = Scale; 222 | MoveCameraCommands.Push(MoveCameraCommand); 223 | } 224 | 225 | void URTSCamera::ApplyMoveCameraCommands() 226 | { 227 | for (const auto& [X, Y, Scale] : this->MoveCameraCommands) 228 | { 229 | auto Movement = FVector2D(X, Y); 230 | Movement.Normalize(); 231 | Movement *= this->MoveSpeed * Scale * this->DeltaSeconds; 232 | this->Root->SetWorldLocation( 233 | this->Root->GetComponentLocation() + FVector(Movement.X, Movement.Y, 0.0f) 234 | ); 235 | } 236 | 237 | this->MoveCameraCommands.Empty(); 238 | } 239 | 240 | void URTSCamera::CollectComponentDependencyReferences() 241 | { 242 | this->Owner = this->GetOwner(); 243 | this->Root = this->Owner->GetRootComponent(); 244 | this->Camera = Cast(this->Owner->GetComponentByClass(UCameraComponent::StaticClass())); 245 | this->SpringArm = Cast(this->Owner->GetComponentByClass(USpringArmComponent::StaticClass())); 246 | this->PlayerController = UGameplayStatics::GetPlayerController(this->GetWorld(), 0); 247 | } 248 | 249 | void URTSCamera::ConfigureSpringArm() 250 | { 251 | this->DesiredZoomLength = this->MaximumZoomLength; 252 | this->SpringArm->TargetArmLength = this->DesiredZoomLength; 253 | this->SpringArm->bDoCollisionTest = false; 254 | this->SpringArm->bEnableCameraLag = this->EnableCameraLag; 255 | this->SpringArm->bEnableCameraRotationLag = this->EnableCameraRotationLag; 256 | this->SpringArm->SetRelativeRotation( 257 | FRotator::MakeFromEuler( 258 | FVector( 259 | 0.0, 260 | this->StartingYAngle, 261 | this->StartingZAngle 262 | ) 263 | ) 264 | ); 265 | } 266 | 267 | void URTSCamera::TryToFindBoundaryVolumeReference() 268 | { 269 | TArray BlockingVolumes; 270 | UGameplayStatics::GetAllActorsOfClassWithTag( 271 | this->GetWorld(), 272 | AActor::StaticClass(), 273 | this->CameraBlockingVolumeTag, 274 | BlockingVolumes 275 | ); 276 | 277 | if (BlockingVolumes.Num() > 0) 278 | { 279 | this->BoundaryVolume = BlockingVolumes[0]; 280 | } 281 | } 282 | 283 | void URTSCamera::ConditionallyEnableEdgeScrolling() const 284 | { 285 | if (this->EnableEdgeScrolling) 286 | { 287 | FInputModeGameAndUI InputMode; 288 | InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways); 289 | InputMode.SetHideCursorDuringCapture(false); 290 | this->PlayerController->SetInputMode(InputMode); 291 | } 292 | } 293 | 294 | void URTSCamera::CheckForEnhancedInputComponent() const 295 | { 296 | if (Cast(this->PlayerController->InputComponent) == nullptr) 297 | { 298 | UKismetSystemLibrary::PrintString( 299 | this->GetWorld(), 300 | TEXT("Set Edit > Project Settings > Input > Default Classes to Enhanced Input Classes"), true, true, 301 | FLinearColor::Red, 302 | 100 303 | ); 304 | 305 | UKismetSystemLibrary::PrintString( 306 | this->GetWorld(), 307 | TEXT("Keyboard inputs will probably not function."), true, true, 308 | FLinearColor::Red, 309 | 100 310 | ); 311 | 312 | UKismetSystemLibrary::PrintString( 313 | this->GetWorld(), 314 | TEXT("Error: Enhanced input component not found."), true, true, 315 | FLinearColor::Red, 316 | 100 317 | ); 318 | } 319 | } 320 | 321 | void URTSCamera::BindInputMappingContext() const 322 | { 323 | if (PlayerController && PlayerController->GetLocalPlayer()) 324 | { 325 | if (const auto Input = PlayerController->GetLocalPlayer()->GetSubsystem()) 326 | { 327 | PlayerController->bShowMouseCursor = true; 328 | 329 | // Check if the context is already bound to prevent double binding 330 | if (!Input->HasMappingContext(this->InputMappingContext)) 331 | { 332 | Input->AddMappingContext(this->InputMappingContext, 0); 333 | } 334 | } 335 | } 336 | } 337 | 338 | void URTSCamera::BindInputActions() 339 | { 340 | if (const auto EnhancedInputComponent = Cast(this->PlayerController->InputComponent)) 341 | { 342 | EnhancedInputComponent->BindAction( 343 | this->ZoomCamera, 344 | ETriggerEvent::Triggered, 345 | this, 346 | &URTSCamera::OnZoomCamera 347 | ); 348 | 349 | EnhancedInputComponent->BindAction( 350 | this->RotateCameraAxis, 351 | ETriggerEvent::Triggered, 352 | this, 353 | &URTSCamera::OnRotateCamera 354 | ); 355 | 356 | EnhancedInputComponent->BindAction( 357 | this->TurnCameraLeft, 358 | ETriggerEvent::Triggered, 359 | this, 360 | &URTSCamera::OnTurnCameraLeft 361 | ); 362 | 363 | EnhancedInputComponent->BindAction( 364 | this->TurnCameraRight, 365 | ETriggerEvent::Triggered, 366 | this, 367 | &URTSCamera::OnTurnCameraRight 368 | ); 369 | 370 | EnhancedInputComponent->BindAction( 371 | this->MoveCameraXAxis, 372 | ETriggerEvent::Triggered, 373 | this, 374 | &URTSCamera::OnMoveCameraXAxis 375 | ); 376 | 377 | EnhancedInputComponent->BindAction( 378 | this->MoveCameraYAxis, 379 | ETriggerEvent::Triggered, 380 | this, 381 | &URTSCamera::OnMoveCameraYAxis 382 | ); 383 | 384 | EnhancedInputComponent->BindAction( 385 | this->DragCamera, 386 | ETriggerEvent::Triggered, 387 | this, 388 | &URTSCamera::OnDragCamera 389 | ); 390 | } 391 | } 392 | 393 | void URTSCamera::SetActiveCamera() const 394 | { 395 | this->PlayerController->SetViewTarget(this->GetOwner()); 396 | } 397 | 398 | void URTSCamera::JumpTo(const FVector Position) const 399 | { 400 | this->Root->SetWorldLocation(Position); 401 | } 402 | 403 | void URTSCamera::ConditionallyPerformEdgeScrolling() const 404 | { 405 | if (this->EnableEdgeScrolling && !this->IsDragging) 406 | { 407 | this->EdgeScrollLeft(); 408 | this->EdgeScrollRight(); 409 | this->EdgeScrollUp(); 410 | this->EdgeScrollDown(); 411 | } 412 | } 413 | 414 | void URTSCamera::EdgeScrollLeft() const 415 | { 416 | const auto MousePosition = UWidgetLayoutLibrary::GetMousePositionOnViewport(this->GetWorld()); 417 | const auto ViewportSize = UWidgetLayoutLibrary::GetViewportWidgetGeometry(this->GetWorld()).GetLocalSize(); 418 | const auto NormalizedMousePosition = 1 - UKismetMathLibrary::NormalizeToRange( 419 | MousePosition.X, 420 | 0.0f, 421 | ViewportSize.X * this->DistanceFromEdgeThreshold 422 | ); 423 | 424 | const auto Movement = UKismetMathLibrary::FClamp(NormalizedMousePosition, 0.0, 1.0); 425 | 426 | this->Root->AddRelativeLocation( 427 | -1 * this->Root->GetRightVector() * Movement * this->EdgeScrollSpeed * this->DeltaSeconds 428 | ); 429 | } 430 | 431 | void URTSCamera::EdgeScrollRight() const 432 | { 433 | const auto MousePosition = UWidgetLayoutLibrary::GetMousePositionOnViewport(this->GetWorld()); 434 | const auto ViewportSize = UWidgetLayoutLibrary::GetViewportWidgetGeometry(this->GetWorld()).GetLocalSize(); 435 | const auto NormalizedMousePosition = UKismetMathLibrary::NormalizeToRange( 436 | MousePosition.X, 437 | ViewportSize.X * (1 - this->DistanceFromEdgeThreshold), 438 | ViewportSize.X 439 | ); 440 | 441 | const auto Movement = UKismetMathLibrary::FClamp(NormalizedMousePosition, 0.0, 1.0); 442 | this->Root->AddRelativeLocation( 443 | this->Root->GetRightVector() * Movement * this->EdgeScrollSpeed * this->DeltaSeconds 444 | ); 445 | } 446 | 447 | void URTSCamera::EdgeScrollUp() const 448 | { 449 | const auto MousePosition = UWidgetLayoutLibrary::GetMousePositionOnViewport(this->GetWorld()); 450 | const auto ViewportSize = UWidgetLayoutLibrary::GetViewportWidgetGeometry(this->GetWorld()).GetLocalSize(); 451 | const auto NormalizedMousePosition = UKismetMathLibrary::NormalizeToRange( 452 | MousePosition.Y, 453 | 0.0f, 454 | ViewportSize.Y * this->DistanceFromEdgeThreshold 455 | ); 456 | 457 | const auto Movement = 1 - UKismetMathLibrary::FClamp(NormalizedMousePosition, 0.0, 1.0); 458 | this->Root->AddRelativeLocation( 459 | this->Root->GetForwardVector() * Movement * this->EdgeScrollSpeed * this->DeltaSeconds 460 | ); 461 | } 462 | 463 | void URTSCamera::EdgeScrollDown() const 464 | { 465 | const auto MousePosition = UWidgetLayoutLibrary::GetMousePositionOnViewport(this->GetWorld()); 466 | const auto ViewportSize = UWidgetLayoutLibrary::GetViewportWidgetGeometry(this->GetWorld()).GetLocalSize(); 467 | const auto NormalizedMousePosition = UKismetMathLibrary::NormalizeToRange( 468 | MousePosition.Y, 469 | ViewportSize.Y * (1 - this->DistanceFromEdgeThreshold), 470 | ViewportSize.Y 471 | ); 472 | 473 | const auto Movement = UKismetMathLibrary::FClamp(NormalizedMousePosition, 0.0, 1.0); 474 | this->Root->AddRelativeLocation( 475 | -1 * this->Root->GetForwardVector() * Movement * this->EdgeScrollSpeed * this->DeltaSeconds 476 | ); 477 | } 478 | 479 | void URTSCamera::FollowTargetIfSet() const 480 | { 481 | if (this->CameraFollowTarget != nullptr) 482 | { 483 | this->Root->SetWorldLocation(this->CameraFollowTarget->GetActorLocation()); 484 | } 485 | } 486 | 487 | void URTSCamera::SmoothTargetArmLengthToDesiredZoom() const 488 | { 489 | this->SpringArm->TargetArmLength = FMath::FInterpTo( 490 | this->SpringArm->TargetArmLength, 491 | this->DesiredZoomLength, 492 | this->DeltaSeconds, 493 | this->ZoomCatchupSpeed 494 | ); 495 | } 496 | 497 | void URTSCamera::ConditionallyKeepCameraAtDesiredZoomAboveGround() 498 | { 499 | if (this->EnableDynamicCameraHeight) 500 | { 501 | const auto RootWorldLocation = this->Root->GetComponentLocation(); 502 | const TArray ActorsToIgnore; 503 | 504 | auto HitResult = FHitResult(); 505 | auto DidHit = UKismetSystemLibrary::LineTraceSingle( 506 | this->GetWorld(), 507 | FVector(RootWorldLocation.X, RootWorldLocation.Y, RootWorldLocation.Z + this->FindGroundTraceLength), 508 | FVector(RootWorldLocation.X, RootWorldLocation.Y, RootWorldLocation.Z - this->FindGroundTraceLength), 509 | UEngineTypes::ConvertToTraceType(this->CollisionChannel), 510 | true, 511 | ActorsToIgnore, 512 | EDrawDebugTrace::Type::None, 513 | HitResult, 514 | true 515 | ); 516 | 517 | if (DidHit) 518 | { 519 | this->Root->SetWorldLocation( 520 | FVector( 521 | HitResult.Location.X, 522 | HitResult.Location.Y, 523 | HitResult.Location.Z 524 | ) 525 | ); 526 | } 527 | 528 | else if (!this->IsCameraOutOfBoundsErrorAlreadyDisplayed) 529 | { 530 | this->IsCameraOutOfBoundsErrorAlreadyDisplayed = true; 531 | 532 | UKismetSystemLibrary::PrintString( 533 | this->GetWorld(), 534 | "Or add a `RTSCameraBoundsVolume` actor to the scene.", 535 | true, 536 | true, 537 | FLinearColor::Red, 538 | 100 539 | ); 540 | 541 | UKismetSystemLibrary::PrintString( 542 | this->GetWorld(), 543 | "Increase trace length or change the starting position of the parent actor for the spring arm.", 544 | true, 545 | true, 546 | FLinearColor::Red, 547 | 100 548 | ); 549 | 550 | UKismetSystemLibrary::PrintString( 551 | this->GetWorld(), 552 | "Error: AC_RTSCamera needs to be placed on the ground!", 553 | true, 554 | true, 555 | FLinearColor::Red, 556 | 100 557 | ); 558 | } 559 | } 560 | } 561 | 562 | void URTSCamera::ConditionallyApplyCameraBounds() const 563 | { 564 | if (this->BoundaryVolume != nullptr) 565 | { 566 | const auto RootWorldLocation = this->Root->GetComponentLocation(); 567 | FVector Origin; 568 | FVector Extents; 569 | this->BoundaryVolume->GetActorBounds(false, Origin, Extents); 570 | this->Root->SetWorldLocation( 571 | FVector( 572 | UKismetMathLibrary::Clamp(RootWorldLocation.X, Origin.X - Extents.X, Origin.X + Extents.X), 573 | UKismetMathLibrary::Clamp(RootWorldLocation.Y, Origin.Y - Extents.Y, Origin.Y + Extents.Y), 574 | RootWorldLocation.Z 575 | ) 576 | ); 577 | } 578 | } 579 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Private/RTSCameraBoundsVolume.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #include "RTSCameraBoundsVolume.h" 4 | #include "Components/PrimitiveComponent.h" 5 | 6 | ARTSCameraBoundsVolume::ARTSCameraBoundsVolume() 7 | { 8 | this->Tags.Add("OpenRTSCamera#CameraBounds"); 9 | 10 | if (UPrimitiveComponent* PrimitiveComponent = this->FindComponentByClass()) 11 | { 12 | PrimitiveComponent->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName, false); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Private/RTSHUD.cpp: -------------------------------------------------------------------------------- 1 | #include "RTSHUD.h" 2 | #include "RTSSelector.h" 3 | #include "Engine/Canvas.h" 4 | 5 | // Constructor implementation: Initializes default values. 6 | ARTSHUD::ARTSHUD() 7 | { 8 | SelectionBoxColor = FLinearColor::Green; 9 | SelectionBoxThickness = 1.0f; 10 | bIsDrawingSelectionBox = false; 11 | bIsPerformingSelection = false; 12 | } 13 | 14 | // Implementation of the DrawHUD function. It's called every frame to draw the HUD. 15 | void ARTSHUD::DrawHUD() 16 | { 17 | Super::DrawHUD(); // Call the base class implementation. 18 | 19 | // Draw the selection box if it's active. 20 | if (bIsDrawingSelectionBox) 21 | { 22 | DrawSelectionBox(SelectionStart, SelectionEnd); 23 | } 24 | 25 | // Perform selection actions if required. 26 | if (bIsPerformingSelection) 27 | { 28 | PerformSelection(); 29 | } 30 | } 31 | 32 | // Starts the selection process, setting the initial point and activating the selection flag. 33 | void ARTSHUD::BeginSelection(const FVector2D& StartPoint) 34 | { 35 | SelectionStart = StartPoint; 36 | bIsDrawingSelectionBox = true; 37 | } 38 | 39 | // Updates the current endpoint of the selection box. 40 | void ARTSHUD::UpdateSelection(const FVector2D& EndPoint) 41 | { 42 | SelectionEnd = EndPoint; 43 | } 44 | 45 | // Ends the selection process and triggers the selection logic. 46 | void ARTSHUD::EndSelection() 47 | { 48 | bIsDrawingSelectionBox = false; 49 | bIsPerformingSelection = true; 50 | } 51 | 52 | // Default implementation of DrawSelectionBox. Draws a rectangle on the HUD. 53 | void ARTSHUD::DrawSelectionBox_Implementation(const FVector2D& StartPoint, const FVector2D& EndPoint) 54 | { 55 | if (Canvas) 56 | { 57 | // Calculate corners of the selection rectangle. 58 | const auto TopRight = FVector2D(SelectionEnd.X, SelectionStart.Y); 59 | const auto BottomLeft = FVector2D(SelectionStart.X, SelectionEnd.Y); 60 | 61 | // Draw lines to form the selection rectangle. 62 | Canvas->K2_DrawLine(SelectionStart, TopRight, SelectionBoxThickness, SelectionBoxColor); 63 | Canvas->K2_DrawLine(TopRight, SelectionEnd, SelectionBoxThickness, SelectionBoxColor); 64 | Canvas->K2_DrawLine(SelectionEnd, BottomLeft, SelectionBoxThickness, SelectionBoxColor); 65 | Canvas->K2_DrawLine(BottomLeft, SelectionStart, SelectionBoxThickness, SelectionBoxColor); 66 | } 67 | } 68 | 69 | // Default implementation of PerformSelection. Selects actors within the selection box. 70 | void ARTSHUD::PerformSelection_Implementation() 71 | { 72 | // Array to store actors that are within the selection rectangle. 73 | TArray SelectedActors; 74 | GetActorsInSelectionRectangle(SelectionStart, SelectionEnd, SelectedActors, false, false); 75 | 76 | // Find the URTSSelector component and pass the selected actors to it. 77 | if (const auto PC = GetOwningPlayerController()) 78 | { 79 | if (const auto SelectorComponent = PC->FindComponentByClass()) 80 | { 81 | SelectorComponent->HandleSelectedActors(SelectedActors); 82 | } 83 | } 84 | 85 | bIsPerformingSelection = false; 86 | } 87 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Private/RTSSelectable.cpp: -------------------------------------------------------------------------------- 1 | #include "RTSSelectable.h" 2 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Private/RTSSelector.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #include "RTSSelector.h" 4 | 5 | #include "EnhancedInputComponent.h" 6 | #include "EnhancedInputSubsystems.h" 7 | #include "RTSSelectable.h" 8 | #include "Kismet/GameplayStatics.h" 9 | 10 | // Sets default values for this component's properties 11 | URTSSelector::URTSSelector(): PlayerController(nullptr), HUD(nullptr), bIsSelecting(false) 12 | { 13 | // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features 14 | // off to improve performance if you don't need them. 15 | PrimaryComponentTick.bCanEverTick = true; 16 | 17 | // Add defaults for input actions 18 | static ConstructorHelpers::FObjectFinder 19 | BeginSelectionActionFinder(TEXT("/OpenRTSCamera/Inputs/BeginSelection")); 20 | static ConstructorHelpers::FObjectFinder 21 | InputMappingContextFinder(TEXT("/OpenRTSCamera/Inputs/OpenRTSCameraInputs")); 22 | this->BeginSelection = BeginSelectionActionFinder.Object; 23 | this->InputMappingContext = InputMappingContextFinder.Object; 24 | } 25 | 26 | 27 | // Called when the game starts 28 | void URTSSelector::BeginPlay() 29 | { 30 | Super::BeginPlay(); 31 | 32 | const auto NetMode = this->GetNetMode(); 33 | if (NetMode != NM_DedicatedServer) 34 | { 35 | this->CollectComponentDependencyReferences(); 36 | this->BindInputMappingContext(); 37 | this->BindInputActions(); 38 | OnActorsSelected.AddDynamic(this, &URTSSelector::HandleSelectedActors); 39 | } 40 | } 41 | 42 | void URTSSelector::HandleSelectedActors_Implementation(const TArray& NewSelectedActors) 43 | { 44 | // Convert NewSelectedActors to a set for efficient lookup 45 | TSet FilteredSelectedActors; 46 | for (const auto& Actor : NewSelectedActors) 47 | { 48 | if (Actor && this->CanSelectActor(Actor)) 49 | { 50 | FilteredSelectedActors.Add(Actor); 51 | } 52 | } 53 | 54 | // Iterate over currently selected actors 55 | for (const auto& Selected : this->SelectedActors) 56 | { 57 | // Check if the actor is not in the new selection 58 | if (!FilteredSelectedActors.Contains(Selected->GetOwner())) 59 | { 60 | // Call OnDeselected for actors that are no longer selected 61 | Selected->OnDeselected(); 62 | } 63 | } 64 | 65 | // Clear the current selection 66 | ClearSelectedActors(); 67 | 68 | // Add new selected actors and call OnSelected 69 | for (const auto& Actor : NewSelectedActors) 70 | { 71 | if (URTSSelectable* SelectableComponent = Actor->FindComponentByClass()) 72 | { 73 | this->SelectedActors.Add(SelectableComponent); 74 | SelectableComponent->OnSelected(); 75 | } 76 | } 77 | } 78 | 79 | void URTSSelector::ClearSelectedActors_Implementation() 80 | { 81 | this->SelectedActors.Empty(); 82 | } 83 | 84 | // Called every frame 85 | void URTSSelector::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 86 | { 87 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 88 | } 89 | 90 | void URTSSelector::CollectComponentDependencyReferences() 91 | { 92 | if (const auto PlayerControllerRef = UGameplayStatics::GetPlayerController(this->GetWorld(), 0)) 93 | { 94 | this->PlayerController = PlayerControllerRef; 95 | this->HUD = Cast(PlayerControllerRef->GetHUD()); 96 | } 97 | else 98 | { 99 | UE_LOG(LogTemp, Error, TEXT("USelector is not attached to a PlayerController.")); 100 | } 101 | } 102 | 103 | void URTSSelector::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) 104 | { 105 | if (const auto InputComponent = Cast(PlayerInputComponent)) 106 | { 107 | InputComponent->BindAction(this->BeginSelection, ETriggerEvent::Started, this, &URTSSelector::OnSelectionStart); 108 | InputComponent->BindAction(this->BeginSelection, ETriggerEvent::Completed, this, &URTSSelector::OnSelectionEnd); 109 | } 110 | } 111 | 112 | void URTSSelector::BindInputActions() 113 | { 114 | if (const auto EnhancedInputComponent = Cast(this->PlayerController->InputComponent)) 115 | { 116 | EnhancedInputComponent->BindAction( 117 | this->BeginSelection, 118 | ETriggerEvent::Started, 119 | this, 120 | &URTSSelector::OnSelectionStart 121 | ); 122 | 123 | EnhancedInputComponent->BindAction( 124 | this->BeginSelection, 125 | ETriggerEvent::Triggered, 126 | this, 127 | &URTSSelector::OnUpdateSelection 128 | ); 129 | 130 | EnhancedInputComponent->BindAction( 131 | this->BeginSelection, 132 | ETriggerEvent::Completed, 133 | this, 134 | &URTSSelector::OnSelectionEnd 135 | ); 136 | } 137 | } 138 | 139 | void URTSSelector::BindInputMappingContext() 140 | { 141 | if (PlayerController && PlayerController->GetLocalPlayer()) 142 | { 143 | if (const auto Input = PlayerController->GetLocalPlayer()->GetSubsystem()) 144 | { 145 | PlayerController->bShowMouseCursor = true; 146 | 147 | // Check if the context is already bound to prevent double binding 148 | if (!Input->HasMappingContext(this->InputMappingContext)) 149 | { 150 | Input->ClearAllMappings(); 151 | Input->AddMappingContext(this->InputMappingContext, 0); 152 | } 153 | } 154 | } 155 | } 156 | 157 | void URTSSelector::OnSelectionStart(const FInputActionValue& Value) 158 | { 159 | FVector2D MousePosition; 160 | PlayerController->GetMousePosition(MousePosition.X, MousePosition.Y); 161 | HUD->BeginSelection(MousePosition); 162 | } 163 | 164 | void URTSSelector::OnUpdateSelection(const FInputActionValue& Value) 165 | { 166 | FVector2D MousePosition; 167 | PlayerController->GetMousePosition(MousePosition.X, MousePosition.Y); 168 | SelectionEnd = MousePosition; 169 | HUD->UpdateSelection(SelectionEnd); 170 | } 171 | 172 | void URTSSelector::OnSelectionEnd(const FInputActionValue& Value) 173 | { 174 | // Call PerformSelection on the HUD to execute selection logic 175 | HUD->EndSelection(); 176 | } 177 | 178 | bool URTSSelector::CanSelectActor_Implementation(AActor *Actor) const { 179 | return true; 180 | } 181 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Public/OpenRTSCamera.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FOpenRTSCameraModule : public IModuleInterface 9 | { 10 | public: 11 | virtual void StartupModule() override; 12 | virtual void ShutdownModule() override; 13 | }; 14 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Public/RTSCamera.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "InputMappingContext.h" 7 | #include "Camera/CameraComponent.h" 8 | #include "Components/ActorComponent.h" 9 | #include "GameFramework/SpringArmComponent.h" 10 | #include "RTSCamera.generated.h" 11 | 12 | /** 13 | * We use these commands so that move camera inputs can be tied to the tick rate of the game. 14 | * https://github.com/HeyZoos/OpenRTSCamera/issues/27 15 | */ 16 | USTRUCT() 17 | struct FMoveCameraCommand 18 | { 19 | GENERATED_BODY() 20 | UPROPERTY() 21 | float X = 0; 22 | UPROPERTY() 23 | float Y = 0; 24 | UPROPERTY() 25 | float Scale = 0; 26 | }; 27 | 28 | UCLASS(Blueprintable, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 29 | class OPENRTSCAMERA_API URTSCamera : public UActorComponent 30 | { 31 | GENERATED_BODY() 32 | 33 | public: 34 | URTSCamera(); 35 | 36 | virtual void TickComponent( 37 | float DeltaTime, 38 | ELevelTick TickType, 39 | FActorComponentTickFunction* ThisTickFunction 40 | ) override; 41 | 42 | UFUNCTION(BlueprintCallable, Category = "RTSCamera") 43 | void FollowTarget(AActor* Target); 44 | 45 | UFUNCTION(BlueprintCallable, Category = "RTSCamera") 46 | void UnFollowTarget(); 47 | 48 | UFUNCTION(BlueprintCallable, Category = "RTSCamera") 49 | void SetActiveCamera() const; 50 | 51 | UFUNCTION(BlueprintCallable, Category = "RTSCamera") 52 | void JumpTo(FVector Position) const; 53 | 54 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Zoom Settings") 55 | float MinimumZoomLength; 56 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Zoom Settings") 57 | float MaximumZoomLength; 58 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Zoom Settings") 59 | float ZoomCatchupSpeed; 60 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Zoom Settings") 61 | float ZoomSpeed; 62 | 63 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera") 64 | float StartingYAngle; 65 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera") 66 | float StartingZAngle; 67 | 68 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera") 69 | float MoveSpeed; 70 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera") 71 | float RotateSpeed; 72 | 73 | /** 74 | * Controls how fast the drag will move the camera. 75 | * Higher values will make the camera move more slowly. 76 | * The drag speed is calculated as follows: 77 | * DragSpeed = MousePositionDelta / (ViewportExtents * DragExtent) 78 | * If the drag extent is small, the drag speed will hit the "max speed" of `this->MoveSpeed` more quickly. 79 | */ 80 | UPROPERTY( 81 | BlueprintReadWrite, 82 | EditAnywhere, 83 | Category = "RTSCamera", 84 | meta = (ClampMin = "0.0", ClampMax = "1.0") 85 | ) 86 | float DragExtent; 87 | 88 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera") 89 | bool EnableCameraLag; 90 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera") 91 | bool EnableCameraRotationLag; 92 | 93 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Dynamic Camera Height Settings") 94 | bool EnableDynamicCameraHeight; 95 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Dynamic Camera Height Settings") 96 | TEnumAsByte CollisionChannel; 97 | UPROPERTY( 98 | BlueprintReadWrite, 99 | EditAnywhere, 100 | Category = "RTSCamera - Dynamic Camera Height Settings", 101 | meta=(EditCondition="EnableDynamicCameraHeight") 102 | ) 103 | float FindGroundTraceLength; 104 | 105 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Edge Scroll Settings") 106 | bool EnableEdgeScrolling; 107 | UPROPERTY( 108 | BlueprintReadWrite, 109 | EditAnywhere, 110 | Category = "RTSCamera - Edge Scroll Settings", 111 | meta=(EditCondition="EnableEdgeScrolling") 112 | ) 113 | float EdgeScrollSpeed; 114 | UPROPERTY( 115 | BlueprintReadWrite, 116 | EditAnywhere, 117 | Category = "RTSCamera - Edge Scroll Settings", 118 | meta=(EditCondition="EnableEdgeScrolling") 119 | ) 120 | float DistanceFromEdgeThreshold; 121 | 122 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 123 | UInputMappingContext* InputMappingContext; 124 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 125 | UInputAction* RotateCameraAxis; 126 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 127 | UInputAction* TurnCameraLeft; 128 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 129 | UInputAction* TurnCameraRight; 130 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 131 | UInputAction* MoveCameraYAxis; 132 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 133 | UInputAction* MoveCameraXAxis; 134 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 135 | UInputAction* DragCamera; 136 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 137 | UInputAction* ZoomCamera; 138 | 139 | protected: 140 | virtual void BeginPlay() override; 141 | 142 | void OnZoomCamera(const FInputActionValue& Value); 143 | void OnRotateCamera(const FInputActionValue& Value); 144 | void OnTurnCameraLeft(const FInputActionValue& Value); 145 | void OnTurnCameraRight(const FInputActionValue& Value); 146 | void OnMoveCameraYAxis(const FInputActionValue& Value); 147 | void OnMoveCameraXAxis(const FInputActionValue& Value); 148 | void OnDragCamera(const FInputActionValue& Value); 149 | 150 | void RequestMoveCamera(float X, float Y, float Scale); 151 | void ApplyMoveCameraCommands(); 152 | 153 | UPROPERTY() 154 | AActor* Owner; 155 | UPROPERTY() 156 | USceneComponent* Root; 157 | UPROPERTY() 158 | UCameraComponent* Camera; 159 | UPROPERTY() 160 | USpringArmComponent* SpringArm; 161 | UPROPERTY() 162 | APlayerController* PlayerController; 163 | UPROPERTY() 164 | AActor* BoundaryVolume; 165 | UPROPERTY() 166 | float DesiredZoomLength; 167 | 168 | private: 169 | void CollectComponentDependencyReferences(); 170 | void ConfigureSpringArm(); 171 | void TryToFindBoundaryVolumeReference(); 172 | void ConditionallyEnableEdgeScrolling() const; 173 | void CheckForEnhancedInputComponent() const; 174 | void BindInputMappingContext() const; 175 | void BindInputActions(); 176 | 177 | void ConditionallyPerformEdgeScrolling() const; 178 | void EdgeScrollLeft() const; 179 | void EdgeScrollRight() const; 180 | void EdgeScrollUp() const; 181 | void EdgeScrollDown() const; 182 | 183 | void FollowTargetIfSet() const; 184 | void SmoothTargetArmLengthToDesiredZoom() const; 185 | void ConditionallyKeepCameraAtDesiredZoomAboveGround(); 186 | void ConditionallyApplyCameraBounds() const; 187 | 188 | UPROPERTY() 189 | FName CameraBlockingVolumeTag; 190 | UPROPERTY() 191 | AActor* CameraFollowTarget; 192 | UPROPERTY() 193 | float DeltaSeconds; 194 | UPROPERTY() 195 | bool IsCameraOutOfBoundsErrorAlreadyDisplayed; 196 | UPROPERTY() 197 | bool IsDragging; 198 | UPROPERTY() 199 | FVector2D DragStartLocation; 200 | UPROPERTY() 201 | TArray MoveCameraCommands; 202 | }; 203 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Public/RTSCameraBoundsVolume.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/CameraBlockingVolume.h" 7 | #include "RTSCameraBoundsVolume.generated.h" 8 | 9 | UCLASS() 10 | class OPENRTSCAMERA_API ARTSCameraBoundsVolume : public ACameraBlockingVolume 11 | { 12 | GENERATED_BODY() 13 | 14 | ARTSCameraBoundsVolume(); 15 | }; 16 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Public/RTSHUD.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/HUD.h" 7 | #include "RTSHUD.generated.h" 8 | 9 | UCLASS() 10 | class OPENRTSCAMERA_API ARTSHUD : public AHUD 11 | { 12 | GENERATED_BODY() 13 | 14 | public: 15 | ARTSHUD(); 16 | 17 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Selection Box") 18 | FLinearColor SelectionBoxColor; 19 | 20 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Selection Box") 21 | float SelectionBoxThickness; 22 | 23 | UFUNCTION(BlueprintCallable, Category = "Selection Box") 24 | void BeginSelection(const FVector2D& StartPoint); 25 | 26 | UFUNCTION(BlueprintCallable, Category = "Selection Box") 27 | void UpdateSelection(const FVector2D& EndPoint); 28 | 29 | UFUNCTION(BlueprintCallable, Category = "Selection Box") 30 | void EndSelection(); 31 | 32 | UFUNCTION(BlueprintNativeEvent, Category = "Selection Box") 33 | void DrawSelectionBox(const FVector2D& StartPoint, const FVector2D& EndPoint); 34 | 35 | UFUNCTION(BlueprintNativeEvent, Category = "Selection Box") 36 | void PerformSelection(); 37 | 38 | protected: 39 | virtual void DrawHUD() override; 40 | 41 | private: 42 | bool bIsDrawingSelectionBox; 43 | bool bIsPerformingSelection; 44 | FVector2D SelectionStart; 45 | FVector2D SelectionEnd; 46 | }; 47 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Public/RTSSelectable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "RTSSelectable.generated.h" 3 | 4 | UCLASS(Blueprintable, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 5 | class OPENRTSCAMERA_API URTSSelectable : public UActorComponent 6 | { 7 | GENERATED_BODY() 8 | public: 9 | UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "RTS Selection") 10 | void OnSelected(); 11 | 12 | UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "RTS Selection") 13 | void OnDeselected(); 14 | }; 15 | -------------------------------------------------------------------------------- /Source/OpenRTSCamera/Public/RTSSelector.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Jesus Bracho All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "InputAction.h" 7 | #include "InputMappingContext.h" 8 | #include "RTSHUD.h" 9 | #include "RTSSelectable.h" 10 | #include "Components/ActorComponent.h" 11 | #include "RTSSelector.generated.h" 12 | 13 | UCLASS(Blueprintable, BlueprintType, ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 14 | class OPENRTSCAMERA_API URTSSelector : public UActorComponent 15 | { 16 | GENERATED_BODY() 17 | 18 | public: 19 | URTSSelector(); 20 | 21 | // BlueprintAssignable allows binding in Blueprints 22 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnActorsSelected, const TArray&, SelectedActors); 23 | UPROPERTY(BlueprintAssignable) 24 | FOnActorsSelected OnActorsSelected; 25 | 26 | // BlueprintReadWrite allows access and modification in Blueprints 27 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 28 | UInputMappingContext* InputMappingContext; 29 | 30 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "RTSCamera - Inputs") 31 | UInputAction* BeginSelection; 32 | 33 | // Function to clear selected actors, can be overridden in Blueprints 34 | UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "RTSCamera - Selection") 35 | void ClearSelectedActors(); 36 | 37 | // Function to handle selected actors, can be overridden in Blueprints 38 | UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "RTSCamera - Selection") 39 | void HandleSelectedActors(const TArray& NewSelectedActors); 40 | 41 | // Function to filter selectable actors, can be overriden in Blueprints 42 | UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "RTSCamera - Selection") 43 | bool CanSelectActor(AActor* Actor) const; 44 | 45 | // BlueprintCallable to allow calling from Blueprints 46 | UFUNCTION(BlueprintCallable, Category = "RTSCamera - Selection") 47 | void OnSelectionStart(const FInputActionValue& Value); 48 | 49 | UFUNCTION(BlueprintCallable, Category = "RTSCamera - Selection") 50 | void OnUpdateSelection(const FInputActionValue& Value); 51 | 52 | UFUNCTION(BlueprintCallable, Category = "RTSCamera - Selection") 53 | void OnSelectionEnd(const FInputActionValue& Value); 54 | 55 | UPROPERTY(BlueprintReadOnly, Category = "RTSCamera - Selection") 56 | TArray SelectedActors; 57 | 58 | protected: 59 | virtual void BeginPlay() override; 60 | virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent); 61 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 62 | 63 | private: 64 | UPROPERTY() 65 | APlayerController* PlayerController; 66 | 67 | UPROPERTY() 68 | ARTSHUD* HUD; 69 | 70 | FVector2D SelectionStart; 71 | FVector2D SelectionEnd; 72 | 73 | bool bIsSelecting; 74 | 75 | void BindInputActions(); 76 | void BindInputMappingContext(); 77 | void CollectComponentDependencyReferences(); 78 | }; 79 | --------------------------------------------------------------------------------