├── .gitignore ├── Content ├── Materials │ ├── Dangerous_Mat.uasset │ ├── Edge_Mat.uasset │ ├── Movable_Mat.uasset │ ├── Path_Mat.uasset │ └── Special_Mat.uasset └── SMesh │ ├── NavGrid_Cursor.uasset │ ├── NavGrid_Ladder.uasset │ ├── NavGrid_Path.uasset │ ├── NavGrid_Tile.uasset │ └── NavGrid_TileHighlight.uasset ├── LICENSE.txt ├── NavGrid.uplugin ├── README.md └── Source └── Navgrid ├── Classes ├── ExampleGridPawn.h ├── GridMovementComponent.h ├── GridPawn.h ├── NavGrid.h ├── NavGridGameMode.h ├── NavGridGameState.h ├── NavGridPC.h ├── NavLadderActor.h ├── NavLadderComponent.h ├── NavTileActor.h ├── NavTileComponent.h ├── TurnComponent.h └── TurnManager.h ├── NavGrid.Build.cs ├── Private ├── ExampleGridPawn.cpp ├── GridMovementComponent.cpp ├── GridPawn.cpp ├── NavGrid.cpp ├── NavGridGameMode.cpp ├── NavGridGameState.cpp ├── NavGridPC.cpp ├── NavGridPlugin.cpp ├── NavGridPlugin.h ├── NavGridPrivatePCH.h ├── NavLadderActor.cpp ├── NavLadderComponent.cpp ├── NavTileActor.cpp ├── NavTileComponent.cpp ├── TurnComponent.cpp └── TurnManager.cpp └── Public └── INavGrid.h /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries/ 2 | Intermediate/ -------------------------------------------------------------------------------- /Content/Materials/Dangerous_Mat.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/Materials/Dangerous_Mat.uasset -------------------------------------------------------------------------------- /Content/Materials/Edge_Mat.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/Materials/Edge_Mat.uasset -------------------------------------------------------------------------------- /Content/Materials/Movable_Mat.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/Materials/Movable_Mat.uasset -------------------------------------------------------------------------------- /Content/Materials/Path_Mat.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/Materials/Path_Mat.uasset -------------------------------------------------------------------------------- /Content/Materials/Special_Mat.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/Materials/Special_Mat.uasset -------------------------------------------------------------------------------- /Content/SMesh/NavGrid_Cursor.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/SMesh/NavGrid_Cursor.uasset -------------------------------------------------------------------------------- /Content/SMesh/NavGrid_Ladder.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/SMesh/NavGrid_Ladder.uasset -------------------------------------------------------------------------------- /Content/SMesh/NavGrid_Path.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/SMesh/NavGrid_Path.uasset -------------------------------------------------------------------------------- /Content/SMesh/NavGrid_Tile.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/SMesh/NavGrid_Tile.uasset -------------------------------------------------------------------------------- /Content/SMesh/NavGrid_TileHighlight.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/Content/SMesh/NavGrid_TileHighlight.uasset -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /NavGrid.uplugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsjsol/NavGrid/0e8ee2dbd8a24971e8e0059892b19881811e0ec5/NavGrid.uplugin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NavGrid 2 | An Unreal Engine 4 plugin for turn based navigation on a grid. 3 | 4 | NavGrid supports grids with arbitrary layout including ladders and multiple levels of tiles. This makes it possible to have tile based movement in complex levels, like for instance multi-floor buildings. 5 | 6 | ## Quickstart 7 | The quickest way to get started is probably to download the [demo project](https://drive.google.com/file/d/1unL1AcchLxSNteH8w9b30PYDFNt6fJBX/view?usp=sharing). The demo contains a sinle level demonstrating flat tiles, ladders and autogenerated virtual tiles. 8 | 9 | ## Compiling 10 | 1. Save/clone into the `Plugins/` directory at the project root 11 | 2. Compile. You will need to right click on the `.uproject` in your project and select `Generate Visual Studio project files` so VS is aware of the new source files. 12 | 3. Add `NavGrid` and `AIModule` to `PublicDependencyModuleNames` in your `.Build.cs` 13 | 14 | ## Project Integration 15 | A few more steps are needed after compiling the plugin: 16 | 1. Enable the plugin for your project in the plugin-browser (`Edit->Plugins`) 17 | 2. Create a Collision Channel in the project setting and set its default response to `Ignore` 18 | 3. Place some `ANavTileActor`s and a few `AExampleGridPawn`s in your level 19 | 4. Set the PlayerController class to `ANavGridPC` 20 | 7. Hit Play! 21 | 22 | ## Class Overview 23 | Examining the headers for `AGridPawn`, `ANavGridPC` and `UGridMovementComponent` are probably good starting points for figuring out how this plugin works. You probably want to extend `AGridPawn` and create you own player controller for your project. 24 | 25 | A few of the classes are summarized below. 26 | 27 | ### ANavGrid 28 | Represents the grid. It is responsible for pathfinding. 29 | 30 | Useful functions: 31 | * `TilesInRange`: Get tiles within the specified distance. Optionally do collision testing and exclude tiles with obstructions. 32 | * `GetTile`: Get a tile from world-space coordinates. 33 | 34 | Useful events: 35 | * `OnTileClicked` 36 | * `OnTileCursorOver` 37 | * `OnTileEndCursorOver` 38 | 39 | Useful properties: 40 | * `ECC_NavGridWalkable`: The channel used when tracing for tiles. Set this to the channel you created in step 4 of the quickstart. 41 | * `EnableVirtualTiles`: Enables placement of virtual tiles on empty spaces. Useful if you don't want to manually place tiles on every walkable part of your levels. 42 | 43 | ### UNavTileComponent 44 | A single tile that can be traversed by a `AGridPawn`. It will automaticly detect any neighbouring tiles. 45 | 46 | Useful functions: 47 | * `GetNeighbours`: Get all neighbouring tiles. 48 | * `Obstructed`: Given a capsule and a starting position, is there anything obstructing the movement into this tile? 49 | * `GetUnobstructedNeighbours`: Get all neighbouring tiles that a pawn can move into from this tile. 50 | * `Traversable`: Given a movement mode and a max walk angle, is it legal to enter this tile? 51 | * `LegalPositionAtEndOfTurn`: Given a movement mode and a max walk angle, is it legal to end a turn on this tile? 52 | 53 | Useful properties: 54 | * `Cost`: The amount of movement expended when moving into this tile. 55 | * `Mesh`: Static mesh used for rendering this tile. 56 | * `SelectCursor` and `HoverCursor`: Mesh that can be shown just above the tile as part of the UI. 57 | * Various `*Highlight`: Mesh that can be shown just above the tile in order to highlight it in some way. 58 | 59 | ### UNavLadderComponent 60 | A subclass of `UNavTileComponent` that can be used to represent a ladder. 61 | 62 | ### ANavTileActor and ANavLadderActor 63 | Actor containing a single `UNavTileComponent` or `UNavLadderComponent` that can be placed directly into the world. 64 | 65 | ### AGridPawn 66 | Base class for pawns that move on a NavGrid. 67 | 68 | Useful functions: 69 | * `OnTurnStart` and `OnTurnEnd`: Called when this pawn's turn begins or ends. Override to add your own code. 70 | 71 | Useful properties: 72 | * `CapsuleComponent`: The size and relative location of this is used in pathfinding when determening if a tile is obstructed or not. 73 | * `MovementComponent`: A UNavGridMovementComponent (described below) for moving on the NavGrid 74 | * `SelectedHighlight`: Mesh shown when the pawn is selected. 75 | * `SnapToGrid`: Snaps the pawn to grid at game start if set. 76 | 77 | ### UNavGridMovementComponent 78 | A movement component for moving on a navgrid. 79 | 80 | Useful functions: 81 | * `CreatePath`: Find a path to a tile. Returns `false` if the tile is unreachable. 82 | * `FollowPath`: Follow an existing path. 83 | * `PaseMoving`: Temporarily stop moving, call `FollowPath` to resume. 84 | * `ShowPath`: Visualize the path. 85 | * `HidePath`: Stop visualizing the path. 86 | * `GetMovementMode`: Get the current movement mode (none, walking, climbing up or climping down). 87 | 88 | Useful properties: 89 | * `MovementRange`: How far (in tile cost) can this pawn move in a single move. 90 | * `Max*Speed`: Max speed for various movement modes. 91 | * `bUseRootMotion`: Use root motion to determine movement speed. If the current animation does not contain root motion `Max*Speed` is used instead. 92 | * `AvailableMovementModes`: Movement modes available for this pawn. Can be useful if you for instance want to disable climbing for some pawns. 93 | 94 | Useful events: 95 | * `OnMovementEnd`: Triggered when the pawn has reached its destination. 96 | * `OnMovementModeChanged`: Triggered when the movement mode has changed. E.g. when the pawn has started climbing up a ladder instead of walking. 97 | 98 | ## Notes 99 | 100 | ### Temporal Antialiasing 101 | The path preview is thin and changes form and posision from one instance to another. If the antialiasing method is set to `TemporalAA`, Unreal will attempt to make this motion appear smooth. This might not look good if there is some distance between the camera and the path. 102 | 103 | There are two possible solutions to this: Either ensure that the camera is close when drawing a path or change the antialiasing method in `Project Settings->Rendering`. 104 | 105 | ## Changes 106 | **Version 2.2.2 - 23.07.2017** 107 | * Compile even if headers are included in 'incorrect' order 108 | * Fix building without editor 109 | * Fix building for Android 110 | * Handle touch events 111 | 112 | **Version 2.2.1 - 04.07.2017** 113 | * Place virtual tiles before we do pathfinding 114 | * Disable shadows for UI elements 115 | * Use the same height offset for UI elements 116 | 117 | **Version 2.2 - 05.06.2017** 118 | * Support Unreal Engine 4.16 119 | * Add automatic placement of 'virtual tiles' on empty areas 120 | * Add option on ANavGrid for specifying the channel used when tracing for tiles 121 | 122 | **Version 2.1 - 11.09.2016** 123 | * Support Unreal Engine 4.13 124 | * Ignore props when deciding if a tile is clicked or hovered over 125 | 126 | **Version 2.0 - 13.08.2016** 127 | * Support Unreal Engine 4.12 128 | * Add ladders 129 | * Add multiple levels of tiles 130 | * Optionaly use root motion for movement speed 131 | 132 | **Version 1.0.1 - 29.11.2015** 133 | * Prevent mapcheck warnings about StaticMesh attributes being NULL when building 134 | 135 | **Version 1.0 - 08.11.2015** 136 | * First version 137 | -------------------------------------------------------------------------------- /Source/Navgrid/Classes/ExampleGridPawn.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "GameFramework/Pawn.h" 6 | #include "GridPawn.h" 7 | #include "ExampleGridPawn.generated.h" 8 | 9 | class UStaticMeshComponent; 10 | class UArrowComponent; 11 | 12 | /* 13 | ** 14 | * A simple pawn used for demonstrating the NavGrid plugin. 15 | */ 16 | UCLASS() 17 | class NAVGRID_API AExampleGridPawn : public AGridPawn 18 | { 19 | GENERATED_BODY() 20 | 21 | public: 22 | AExampleGridPawn(); 23 | /* Just a cone so we can see the pawn */ 24 | UPROPERTY(BlueprintReadOnly, EditAnyWhere, Category = "Components") UStaticMeshComponent *StaticMesh = NULL; 25 | }; -------------------------------------------------------------------------------- /Source/Navgrid/Classes/GridMovementComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "GameFramework/PawnMovementComponent.h" 6 | #include "GridMovementComponent.generated.h" 7 | 8 | class ANavGrid; 9 | class USplineComponent; 10 | class USplineMeshComponent; 11 | class UStaticMesh; 12 | class UNavTileComponent; 13 | 14 | UENUM(BlueprintType) 15 | enum class EGridMovementMode : uint8 16 | { 17 | Stationary UMETA(DisplayName = "Stationary"), 18 | Walking UMETA(DisplayName = "Walking"), 19 | ClimbingUp UMETA(DisplayName = "Climbing up"), 20 | ClimbingDown UMETA(DisplayName = "Climbing down"), 21 | InPlaceTurn UMETA(DisplayName = "Turn in place"), 22 | }; 23 | 24 | USTRUCT() 25 | struct FPathSegment 26 | { 27 | GENERATED_BODY() 28 | FPathSegment() {} 29 | FPathSegment(TSet InMovementModes, float InStart, float InEnd); 30 | /* Legal movement modes for this segment */ 31 | TSet MovementModes; 32 | /* start and end distance along the path spline this segment covers */ 33 | float Start, End; 34 | FRotator PawnRotationHint; 35 | }; 36 | 37 | 38 | /** 39 | * A movement component that operates on a NavGrid 40 | */ 41 | UCLASS(ClassGroup = Movement, meta = (BlueprintSpawnableComponent)) 42 | class NAVGRID_API UGridMovementComponent : public UPawnMovementComponent 43 | { 44 | GENERATED_BODY() 45 | public: 46 | UGridMovementComponent(const FObjectInitializer &ObjectInitializer); 47 | virtual void BeginPlay() override; 48 | virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; 49 | virtual void StopMovementImmediately() override; 50 | 51 | protected: 52 | /* return an transform usable for following the spline path */ 53 | FTransform TransformFromPath(float DeltaTime); 54 | /* return an tranfrom usable for rotation in place */ 55 | FTransform TransformFromRotation(float DeltaTime); 56 | public: 57 | void ConsiderUpdateCurrentTile(); 58 | protected: 59 | /* The tile we're currently on */ 60 | UPROPERTY() 61 | UNavTileComponent *CurrentTile = NULL; 62 | FPathSegment CurrentPathSegment; 63 | public: 64 | 65 | /* Return the tiles that are in range */ 66 | void GetTilesInRange(TArray &OutTiles); 67 | /* Get the tile the pawn is on, returns NULL if the pawn is not on a tile */ 68 | UNavTileComponent *GetTile(); 69 | ANavGrid *GetNavGrid(); 70 | /* How far (in tile cost) the actor can move in one go */ 71 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Movement") 72 | float MovementRange = 4; 73 | /* How fast can the actor move when walking*/ 74 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Movement") 75 | float MaxWalkSpeed = 450; 76 | /* How fast can the actor move when climbing */ 77 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Movement") 78 | float MaxClimbSpeed = 200; 79 | /* How fast can the actor turn */ 80 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Movement") 81 | float MaxRotationSpeed = 720; 82 | /* MovementModes usable for this Pawn */ 83 | UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Movement") 84 | TSet AvailableMovementModes; 85 | /* Should we ignore rotation over the X axis */ 86 | UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Movement") 87 | bool LockRoll = true; 88 | /* Should we ignore rotation over the Y axis */ 89 | UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Movement") 90 | bool LockPitch = true; 91 | /* Should we ignore rotation over the Z axis */ 92 | UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Movement") 93 | bool LockYaw = false; 94 | /* Should we extract root motion for speed while moving */ 95 | UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Movement") 96 | bool bUseRootMotion = true; 97 | /* Should we extract root motion for speed and rotation even if we are not moving*/ 98 | UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Movement") 99 | bool bAlwaysUseRootMotion = false; 100 | 101 | /* Should we straighten out the path to avoid zigzaging */ 102 | UPROPERTY(BlueprintReadWrite, EditAnyWhere, Category = "Movement") 103 | bool bStringPullPath = true; 104 | void StringPull(TArray &InOutPath, TArray& OutPath); 105 | 106 | /* 107 | Spline that is used as a path. The points are in world coords. 108 | 109 | We use ESplineCoordinateSpace::Local in the getters and setters to avoid any extra coord translation 110 | */ 111 | UPROPERTY(BlueprintReadOnly, Category = "Visualization") 112 | USplineComponent *Spline = NULL; 113 | /* Mesh used to visualize the path */ 114 | UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Visualization") 115 | UStaticMesh *PathMesh = NULL; 116 | /* Distance between actor and where we start showing the path */ 117 | UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Visualization") 118 | float HorizontalOffset = 87.5; 119 | 120 | /* Create a path to TargetTile, return false if no path is found */ 121 | bool CreatePath(const UNavTileComponent &Target); 122 | /* Create a path and follow it if it exists */ 123 | bool MoveTo(const UNavTileComponent &Target); 124 | /* Turn in place */ 125 | void TurnTo(const FRotator &Forward); 126 | /* Snap actor the grid */ 127 | void SnapToGrid(); 128 | /* Advance a given distance along the path */ 129 | void AdvanceAlongPath(float InDistance); 130 | /* Get the remaining distance of the current path (zero if the pawn is currently not moving) */ 131 | float GetRemainingDistance(); 132 | /* Use actor rotation for components where we have an rotation locks, use InRotation for the rest */ 133 | FRotator ApplyRotationLocks(const FRotator &InRotation); 134 | protected: 135 | FRotator DesiredForwardRotation; 136 | public: 137 | /* Visualize path */ 138 | void ShowPath(); 139 | /* Hide path */ 140 | void HidePath(); 141 | 142 | FTransform ConsumeRootMotion(); 143 | 144 | EGridMovementMode GetMovementMode() { return MovementMode; } 145 | protected: 146 | EGridMovementMode MovementMode; 147 | void ConsiderUpdateMovementMode(); 148 | void ChangeMovementMode(EGridMovementMode NewMode); 149 | void FinishMovement(); 150 | public: 151 | /* Return the point the the pawn will reach if it continues moving for ForwardDistance */ 152 | FVector GetForwardLocation(float ForwardDistance); 153 | 154 | DECLARE_EVENT(UGridMovementComponent, FOnMovementDone); 155 | /* Triggered when movement ends */ 156 | FOnMovementDone& OnMovementEnd() { return OnMovementEndEvent; } 157 | private: 158 | FOnMovementDone OnMovementEndEvent; 159 | 160 | public: 161 | DECLARE_EVENT_TwoParams(UGridMovementComponent, FOnMovementModeChanged, EGridMovementMode, EGridMovementMode); 162 | /* Triggered when the movement mode changes */ 163 | FOnMovementModeChanged& OnMovementModeChanged() { return OnMovementModeChangedEvent; } 164 | private: 165 | FOnMovementModeChanged OnMovementModeChangedEvent; 166 | 167 | protected: 168 | UPROPERTY() 169 | TArray SplineMeshes; 170 | 171 | /* Helper: Puts a spline mesh in the range along the spline */ 172 | void AddSplineMesh(float From, float To); 173 | 174 | /* How far along the spline are we */ 175 | float Distance = 0; 176 | 177 | private: 178 | /* the grid we're currently on. You should get this via GetNavGrid() and avoid using it directly */ 179 | UPROPERTY() 180 | ANavGrid *CachedNavGrid = NULL; 181 | 182 | protected: 183 | UPROPERTY() 184 | UAnimInstance *AnimInstance; 185 | 186 | /* Return a delta FRotater that is within MaxRotationSpeed */ 187 | FRotator LimitRotation(const FRotator &OldRotation, const FRotator &NewRotation, float DeltaTime); 188 | /* The rotation of the skeletal mesh (if any). Used to handle root motion rotation */ 189 | FRotator MeshRotation; 190 | 191 | UPROPERTY() 192 | TArray PathSegments; 193 | }; 194 | -------------------------------------------------------------------------------- /Source/Navgrid/Classes/GridPawn.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "GameFramework/Pawn.h" 6 | #include "GenericTeamAgentInterface.h" 7 | 8 | #include "GridMovementComponent.h" 9 | #include "GridPawn.generated.h" 10 | 11 | class UTurnComponent; 12 | class UCapsuleComponent; 13 | class UArrowComponent; 14 | class UNavTileComponent; 15 | class ANavGrid; 16 | 17 | UENUM() 18 | enum class EGridPawnState : uint8 19 | { 20 | /* it is not this pawns turn */ 21 | WaitingForTurn UMETA(DisplayName = "Waiting for turn"), 22 | /* Ready for player input */ 23 | Ready UMETA(DisplayName = "Ready"), 24 | /* Currently performing some sort of action and is not ready for player input */ 25 | Busy UMETA(DisplayName = "Busy"), 26 | /* Dead */ 27 | Dead UMETA(DisplayName = "Dead") 28 | }; 29 | 30 | /** A pawn that can move around on a NavGrid. */ 31 | UCLASS() 32 | class NAVGRID_API AGridPawn : public APawn, public IGenericTeamAgentInterface 33 | { 34 | GENERATED_BODY() 35 | 36 | public: 37 | AGridPawn(); 38 | virtual void BeginPlay() override; 39 | virtual void OnConstruction(const FTransform &Transform) override; 40 | 41 | // IGenericTeamAgentInterface start 42 | virtual void SetGenericTeamId(const FGenericTeamId& InTeamId) override; 43 | virtual FGenericTeamId GetGenericTeamId() const override { return TeamId; } 44 | // IGenericTeamAgentInterface end 45 | protected: 46 | UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = "NavGrid") 47 | FGenericTeamId TeamId; 48 | 49 | public: 50 | UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") 51 | USceneComponent *SceneRoot; 52 | /** Bounding capsule. 53 | Used to check for collisions when spawning and for mouse over events. 54 | It should be adjusted so it envelops the entire mesh of the pawn. 55 | */ 56 | UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") 57 | UCapsuleComponent *BoundsCapsule; 58 | UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") 59 | UGridMovementComponent *MovementComponent; 60 | UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") 61 | UTurnComponent *TurnComponent; 62 | /** Collision capsule. 63 | Used to check if a pawn will collide with the environment if it moves into a tile. 64 | It should be slightly thinner than the pawn and its location along the z-axis determines the height 65 | of the obstacles the pawn can step over. 66 | */ 67 | UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") 68 | UCapsuleComponent *MovementCollisionCapsule; 69 | /* Shown when the pawn is selected/has its turn */ 70 | UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") 71 | UStaticMeshComponent *SelectedHighlight; 72 | /* An arrow pointing forward */ 73 | UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") 74 | UArrowComponent *Arrow; 75 | 76 | /* Should this pawn snap to grid at the start of each turn */ 77 | UPROPERTY(EditAnyWhere) 78 | bool SnapToGrid = true; 79 | 80 | /* Callend on round start */ 81 | UFUNCTION() 82 | virtual void OnRoundStart() {} 83 | /* Called on turn start for any component */ 84 | UFUNCTION() 85 | virtual void OnAnyTurnStart(UTurnComponent *InTurnComponent); 86 | /* Called on turn start for this pawn */ 87 | virtual void OnTurnStart(); 88 | /* Called on turn end for any component */ 89 | UFUNCTION() 90 | virtual void OnAnyTurnEnd(UTurnComponent *InTurnComponent); 91 | /* Called on turn end for this pawn */ 92 | virtual void OnTurnEnd(); 93 | /* Called at turn start for any team */ 94 | UFUNCTION() 95 | virtual void OnAnyTeamTurnStart(const FGenericTeamId &InTeamId); 96 | /* Called at turn start for this pawn's team */ 97 | virtual void OnTeamTurnStart() {} 98 | /* Called at turn end for any team */ 99 | UFUNCTION() 100 | virtual void OnAnyTeamTurnEnd(const FGenericTeamId &InTeamId); 101 | /* Called at turn end for this pawn's team */ 102 | virtual void OnTeamTurnEnd() {} 103 | /* Called when done moving */ 104 | virtual void OnMoveEnd(); 105 | /* Called when any component owner is ready for player or ai input */ 106 | UFUNCTION() 107 | virtual void OnAnyPawnReadyForInput(UTurnComponent *InTurnComponent); 108 | /* Called when this pawn is ready for player or ai input */ 109 | virtual void OnPawnReadyForInput() {} 110 | 111 | /* override this class and implement your own AI here. The default implementation just ends the turn */ 112 | virtual void PlayAITurn(); 113 | /* Get the current state for this pawn */ 114 | UFUNCTION(BlueprintCallable) 115 | virtual EGridPawnState GetState() const; 116 | /* is this pawn controlled by a human player? */ 117 | UPROPERTY(EditAnyWhere, BlueprintReadWrite) 118 | bool bHumanControlled; 119 | /* can the we request to start our turn now? The turn manager may still deny our request even if this returns true */ 120 | virtual bool CanBeSelected(); 121 | 122 | virtual bool CanMoveTo(const UNavTileComponent & Tile); 123 | virtual void MoveTo(const UNavTileComponent & Tile); 124 | 125 | /* get the tile occupied at the start of this pawns turn */ 126 | UFUNCTION(BlueprintCallable) 127 | UNavTileComponent *GetTile() const { return MovementComponent->GetTile(); } 128 | template 129 | T *GetTile() const { return Cast(GetTile()); } 130 | 131 | /* Called when the user clicks on this actor, default implementation is to change the the current turn taker to this */ 132 | UFUNCTION() 133 | virtual void Clicked(AActor *ClickedActor, FKey PressedKey); 134 | 135 | #if WITH_EDITORONLY_DATA 136 | void OnObjectSelectedInEditor(UObject *SelectedObject); 137 | 138 | protected: 139 | UPROPERTY(EditAnyWhere) 140 | bool bPreviewTiles = false; 141 | UPROPERTY(EditAnyWhere) 142 | float PreviewTileSize = 200; 143 | FTimerHandle PreviewTimerHandle; 144 | void UpdatePreviewTiles(); 145 | UPROPERTY(Transient) 146 | ANavGrid *PreviewGrid; 147 | #endif // WITH_EDITORONLY_DATA 148 | }; 149 | -------------------------------------------------------------------------------- /Source/Navgrid/Classes/NavGrid.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "Engine.h" 6 | #include "GameFramework/Actor.h" 7 | 8 | #include "NavTileComponent.h" 9 | #include "GridMovementComponent.h" 10 | 11 | #include "NavGrid.generated.h" 12 | 13 | DECLARE_LOG_CATEGORY_EXTERN(NavGrid, Log, All); 14 | 15 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTileClicked, const UNavTileComponent*, Tile); 16 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTileCursorOver, const UNavTileComponent*, Tile); 17 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEndTileCursorOver, const UNavTileComponent*, Tile); 18 | 19 | /** 20 | * A grid that pawns can move around on. 21 | * 22 | */ 23 | UCLASS() 24 | class NAVGRID_API ANavGrid : public AActor 25 | { 26 | GENERATED_BODY() 27 | 28 | public: 29 | ANavGrid(); 30 | 31 | /* Collision channel used when tracing for tiles */ 32 | static TEnumAsByte ECC_NavGridWalkable; 33 | UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = "NavGrid") 34 | float TileSize = 200; 35 | /* Z-Offset for UI elements */ 36 | UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = "NavGrid") 37 | float UIOffset = 30; 38 | /* Should virtual tiles be placed on empty areas */ 39 | UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = "NavGrid") 40 | bool EnableVirtualTiles = false; 41 | 42 | // Do not place virtual tiles on actors with this tag 43 | static FName DisableVirtualTilesTag; 44 | // getter for blueprints 45 | UFUNCTION(BlueprintPure, Category = "NavGrid") 46 | FName GetDisableVirtualTilesTag() { return DisableVirtualTilesTag; } 47 | 48 | /* Class used for virtual tiles */ 49 | UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = "NavGrid") 50 | TSubclassOf TileClass; 51 | 52 | /* Scene Component (root) */ 53 | UPROPERTY(EditAnyWhere, BlueprintReadWrite, Category = "Components") 54 | USceneComponent *SceneComponent = NULL; 55 | 56 | /* Cursor for highlighting tiles */ 57 | UPROPERTY(BlueprintReadOnly, EditAnyWhere, Category = "Components") 58 | UStaticMeshComponent *Cursor; 59 | 60 | protected: 61 | UPROPERTY() 62 | TMap TileHighlights; 63 | TMap TileHighLightPaths; 64 | public: 65 | void SetTileHighlight(UNavTileComponent &Tile, FName Type); 66 | void ClearTileHighlights(); 67 | void AddHighlightType(const FName &Type, const TCHAR *FileName); 68 | UInstancedStaticMeshComponent *GetHighlightComponent(FName Type); 69 | public: 70 | 71 | /* Number of tiles that exist in the current level */ 72 | UPROPERTY(VisibleAnywhere, Category = "NavGrid") 73 | int32 NumPersistentTiles = 0; 74 | UPROPERTY(EditAnyWhere) 75 | int32 MaxVirtualTiles = 10000; 76 | 77 | UFUNCTION(BlueprintCallable, Category = "NavGrid") 78 | static ANavGrid *GetNavGrid(AActor *ActorInWorld); 79 | static ANavGrid *GetNavGrid(UWorld *World); 80 | 81 | /* Get tile from world location, may return NULL */ 82 | virtual UNavTileComponent *GetTile(const FVector &WorldLocation, bool FindFloor = true, float UpwardTraceLength = 100, float DownwardTraceLength = 100); 83 | protected: 84 | UNavTileComponent *LineTraceTile(const FVector &WorldLocation, bool FindFloor, float UpwardTraceLength, float DownwardTraceLength); 85 | UNavTileComponent *LineTraceTile(const FVector &Start, const FVector &End); 86 | 87 | public: 88 | void TileClicked(const UNavTileComponent *Tile); 89 | void TileCursorOver(const UNavTileComponent *Tile); 90 | void EndTileCursorOver(const UNavTileComponent *Tile); 91 | 92 | protected: 93 | /* Do pathfinding and and store all tiles that Pawn can reach in TilesInRange */ 94 | virtual void CalculateTilesInRange(AGridPawn *Pawn); 95 | public: 96 | /* Find all tiles in range. Call CalculateTilesInRange if neccecary */ 97 | UFUNCTION(BlueprintCallable, Category = "Pathfinding") 98 | void GetTilesInRange(AGridPawn *Pawn, TArray &OutTiles); 99 | /* Reset all temporary data in all tiles in the world */ 100 | UFUNCTION(BlueprintCallable, Category = "Pathfinding") 101 | void ClearTiles(); 102 | protected: 103 | /* Contains tiles found in the last call to CalculateTilesInRange() */ 104 | UPROPERTY() 105 | TArray TilesInRange; 106 | /* Latest Pawn passed to CalculcateTilesInRange() */ 107 | UPROPERTY() 108 | AGridPawn *CurrentPawn; 109 | /* Starting Tile for the latest call to CalculcateTilesInRange() */ 110 | UPROPERTY() 111 | UNavTileComponent *CurrentTile; 112 | 113 | public: 114 | /* Triggered by mouse clicks on tiles*/ 115 | UPROPERTY(BlueprintAssignable, Category = "NavGrid") 116 | FOnTileClicked OnTileClicked; 117 | /* Triggered when the cursor enters a tile */ 118 | UPROPERTY(BlueprintAssignable, Category = "NavGrid") 119 | FOnTileCursorOver OnTileCursorOver; 120 | /* Triggered when the cursor leaves a tile */ 121 | UPROPERTY(BlueprintAssignable, Category = "NavGrid") 122 | FOnEndTileCursorOver OnEndTileCursorOver; 123 | 124 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Pathfinding") 125 | bool TraceTileLocation(const FVector & TraceStart, const FVector & TraceEnd, FVector & OutTilePos); 126 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Pathfinding") 127 | UNavTileComponent *PlaceTile(const FVector &Location, AActor *TileOwner = NULL); 128 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Pathfinding") 129 | UNavTileComponent *ConsiderPlaceTile(const FVector &TraceStart, const FVector &TraceEnd, AActor *TileOwner = NULL); 130 | /* Find a place to put a tile that is close to Location and that matches the grid layout */ 131 | FVector AdjustToTileLocation(const FVector &Location); 132 | 133 | protected: 134 | UPROPERTY(VisibleAnywhere, Category = "NavGrid") 135 | TArray VirtualTiles; 136 | /* place virtual tiles within the movement range of a pawn */ 137 | UFUNCTION(BlueprintCallable, Category = "Pathfinding") 138 | void GenerateVirtualTiles(const AGridPawn *Pawn); 139 | /* place a single virtual tile under a pawn */ 140 | UFUNCTION(BlueprintCallable, Category = "Pathfinding") 141 | void GenerateVirtualTile(const AGridPawn *Pawn); 142 | void DestroyVirtualTiles(); 143 | virtual void Destroyed() override; 144 | public: 145 | /** return every tile in the supplied world */ 146 | static void GetEveryTile(TArray &OutTiles, UWorld *World); 147 | }; 148 | -------------------------------------------------------------------------------- /Source/Navgrid/Classes/NavGridGameMode.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/GameModeBase.h" 7 | #include "NavGridGameMode.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | UCLASS() 13 | class NAVGRID_API ANavGridGameMode : public AGameModeBase 14 | { 15 | GENERATED_BODY() 16 | public: 17 | ANavGridGameMode(); 18 | virtual void BeginPlay() override; 19 | }; 20 | -------------------------------------------------------------------------------- /Source/Navgrid/Classes/NavGridGameState.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/GameStateBase.h" 7 | #include "GenericTeamAgentInterface.h" 8 | 9 | #include "TurnManager.h" 10 | 11 | #include "NavGridGameState.generated.h" 12 | 13 | class ANavGrid; 14 | 15 | /** 16 | * 17 | */ 18 | UCLASS() 19 | class NAVGRID_API ANavGridGameState : public AGameStateBase 20 | { 21 | GENERATED_BODY() 22 | public: 23 | UFUNCTION(BlueprintCallable, Category = "NavGrid") 24 | virtual ANavGrid* GetNavGrid(); 25 | template 26 | T* GetNavGrid() { return Cast(GetNavGrid()); } 27 | 28 | UFUNCTION(BlueprintCallable, Category = "NavGrid") 29 | virtual ATurnManager* GetTurnManager(); 30 | template 31 | T *GetTurnManager() { return Cast(GetTurnManager()); } 32 | 33 | DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPawnEnterTile, class AGridPawn *, class UNavTileComponent *); 34 | FOnPawnEnterTile &OnPawnEnterTile() { return PawnEnterTileDelegate; } 35 | private: 36 | FOnPawnEnterTile PawnEnterTileDelegate; 37 | 38 | protected: 39 | /* spawn the default turn manager object, override this if you need to modify it */ 40 | virtual ATurnManager* SpawnTurnManager(); 41 | /* spawn the default navgrid object, override this if you need to modify it */ 42 | virtual ANavGrid* SpawnNavGrid(); 43 | 44 | UPROPERTY() 45 | ANavGrid* Grid; 46 | 47 | UPROPERTY() 48 | ATurnManager *TurnManager; 49 | }; 50 | -------------------------------------------------------------------------------- /Source/Navgrid/Classes/NavGridPC.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "GameFramework/PlayerController.h" 6 | #include "GenericTeamAgentInterface.h" 7 | #include "NavGridPC.generated.h" 8 | 9 | class ANavGrid; 10 | class ATurnManager; 11 | class UTurnComponent; 12 | class AGridPawn; 13 | class UNavTileComponent; 14 | 15 | /** 16 | * An example PlayerController that lets you move a single GridPawn by 17 | * clicking on a NavGrid 18 | */ 19 | UCLASS() 20 | class NAVGRID_API ANavGridPC : public APlayerController 21 | { 22 | GENERATED_BODY() 23 | public: 24 | ANavGridPC(const FObjectInitializer& ObjectInitializer); 25 | virtual void BeginPlay() override; 26 | 27 | UFUNCTION() 28 | virtual void OnTileClicked(const UNavTileComponent *Tile); 29 | UFUNCTION() 30 | virtual void OnTileCursorOver(const UNavTileComponent *Tile); 31 | UFUNCTION() 32 | virtual void OnEndTileCursorOver(const UNavTileComponent *Tile); 33 | 34 | /* Called when a new round starts*/ 35 | UFUNCTION() 36 | virtual void OnRoundStart() {}; 37 | /* Called when a new turn starts*/ 38 | UFUNCTION() 39 | virtual void OnTurnStart(UTurnComponent *Component); 40 | /* Called when a turn ends */ 41 | UFUNCTION() 42 | virtual void OnTurnEnd(UTurnComponent *Component); 43 | /* Called at the start of each team turn */ 44 | UFUNCTION() 45 | virtual void OnTeamTurnStart(const FGenericTeamId &TeamId) {} 46 | 47 | virtual void SetTurnManager(ATurnManager * InTurnManager); 48 | virtual void SetGrid(ANavGrid * InGrid); 49 | 50 | /* The pawn we're currently controlling */ 51 | UPROPERTY(BlueprintReadWrite) 52 | AGridPawn *GridPawn; 53 | /* The NavGrid in the current game */ 54 | UPROPERTY(BlueprintReadWrite) 55 | ANavGrid *Grid; 56 | /* The TurnManager in the current game */ 57 | UPROPERTY(BlueprintReadWrite) 58 | ATurnManager *TurnManager; 59 | }; 60 | -------------------------------------------------------------------------------- /Source/Navgrid/Classes/NavLadderActor.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "GameFramework/Actor.h" 6 | #include "NavLadderActor.generated.h" 7 | 8 | class UNavLadderComponent; 9 | 10 | UCLASS() 11 | class NAVGRID_API ANavLadderActor : public AActor 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | ANavLadderActor(const FObjectInitializer &ObjectInitializer); 17 | 18 | UPROPERTY(BlueprintReadOnly, EditAnyWhere, Category = "Components") USceneComponent *SceneComponent; 19 | UPROPERTY(BlueprintReadOnly, EditAnyWhere, Category = "Components") UNavLadderComponent *NavLadderComponent; 20 | UPROPERTY(BlueprintReadOnly, EditAnyWhere, Category = "Components") UStaticMeshComponent *Mesh; 21 | }; -------------------------------------------------------------------------------- /Source/Navgrid/Classes/NavLadderComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "NavTileComponent.h" 6 | #include "NavLadderComponent.generated.h" 7 | 8 | UCLASS(meta = (BlueprintSpawnableComponent)) 9 | class NAVGRID_API UNavLadderComponent : public UNavTileComponent 10 | { 11 | GENERATED_BODY() 12 | public: 13 | UNavLadderComponent(); 14 | 15 | virtual void SetGrid(ANavGrid *InGrid) override; 16 | virtual FVector GetPawnLocation() const override { return ToWorldSpace(FVector(TileSize / 4, 0, 25)); } 17 | virtual void GetNeighbours(const UCapsuleComponent &CollisionCapsule, TArray &OutUnObstructed, TArray &OutObstructed) override; 18 | virtual bool Obstructed(const FVector &FromPos, const UCapsuleComponent &CollisionCapsule) const override; 19 | virtual void AddPathSegments(USplineComponent &OutSpline, TArray &OutPathSegments, bool EndTile) const override; 20 | 21 | virtual FVector GetSplineMeshUpVector() override; 22 | protected: 23 | // local copy of tile ANavGrid::TileSize, value is set in SetGrid() 24 | float TileSize; 25 | FVector GetTopPathPoint() const { return ToWorldSpace(FVector(TileSize / 4, 0, BoxExtent.Z - 25)); } 26 | FVector GetBottomPathPoint() const { return ToWorldSpace(FVector(TileSize / 4, 0, -100)); } 27 | FVector ToWorldSpace(const FVector &CompSpace) const { return GetComponentLocation() + GetComponentRotation().RotateVector(CompSpace); } 28 | }; -------------------------------------------------------------------------------- /Source/Navgrid/Classes/NavTileActor.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "GameFramework/Actor.h" 6 | #include "NavTileActor.generated.h" 7 | 8 | class UNavTileComponent; 9 | /** 10 | * A simple actor with a NavTileComponent and a static mesh 11 | */ 12 | UCLASS() 13 | class NAVGRID_API ANavTileActor : public AActor 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | ANavTileActor(const FObjectInitializer &ObjectInitializer); 19 | 20 | UPROPERTY(BlueprintReadOnly, EditAnyWhere, Category = "Components") USceneComponent *SceneComponent; 21 | UPROPERTY(BlueprintReadOnly, EditAnyWhere, Category = "Components") UNavTileComponent *NavTileComponent; 22 | UPROPERTY(BlueprintReadOnly, EditAnyWhere, Category = "Components") UStaticMeshComponent *Mesh; 23 | }; -------------------------------------------------------------------------------- /Source/Navgrid/Classes/NavTileComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "Components/SplineComponent.h" 6 | #include "Components/SceneComponent.h" 7 | #include "Components/BoxComponent.h" 8 | #include "GridMovementComponent.h" 9 | 10 | #include "NavTileComponent.generated.h" 11 | 12 | 13 | /** 14 | * A single tile in a navigation grid 15 | */ 16 | UCLASS(meta = (BlueprintSpawnableComponent), Blueprintable) 17 | class NAVGRID_API UNavTileComponent : public UBoxComponent 18 | { 19 | GENERATED_BODY() 20 | public: 21 | UNavTileComponent(); 22 | 23 | protected: 24 | UPROPERTY(Transient) 25 | ANavGrid *Grid; 26 | public: 27 | virtual void SetGrid(ANavGrid *InGrid); 28 | ANavGrid* GetGrid() const; 29 | 30 | // Pathing 31 | /* Cost of moving into this tile*/ 32 | UPROPERTY(BlueprintReadWrite, EditAnyWhere, Category = "Pathfinding") 33 | float Cost = 1; 34 | /* Distance from starting point of path */ 35 | float Distance; 36 | /* Previous tile in path */ 37 | UPROPERTY() 38 | UNavTileComponent *Backpointer; 39 | /* Is this node in the 'visited' set? - Helper var for pathfinding */ 40 | bool Visited; 41 | /* Rest temporary data (like distance and other vars used in pathfinding) */ 42 | virtual void Reset(); 43 | 44 | /* movement modes that are legal (or make sense) for this tile */ 45 | UPROPERTY(EditAnyWhere, BlueprintReadWrite) 46 | TSet MovementModes; 47 | 48 | /* is there anything blocking an actor from moving from FromPos to this tile? Uses the capsule for collision testing */ 49 | virtual bool Obstructed(const FVector &FromPos, const UCapsuleComponent &CollisionCapsule) const; 50 | /* is there anything blocking an actor from moving between From and To? Uses the capsule for collision testing */ 51 | virtual bool Obstructed(const FVector &From, const FVector &To, const UCapsuleComponent &CollisionCapsule) const; 52 | virtual void GetNeighbours(const UCapsuleComponent &CollisionCapsule, TArray &OutUnObstructed, TArray &OutObstructed); 53 | /* Return the neighbours that are not Obstructed() */ 54 | void GetUnobstructedNeighbours(const UCapsuleComponent &CollisionCapsule, TArray &OutNeighbours); 55 | /* Can a pawn traverse this tile? 56 | * PawnMovementModes: movement modes availabe for the pawn 57 | */ 58 | virtual bool Traversable(const TSet &PawnMovementModes) const; 59 | /* Can a pawn end its turn on this tile?*/ 60 | virtual bool LegalPositionAtEndOfTurn(const TSet &PawnMovementModes) const; 61 | 62 | /* Placement for pawn occupying this tile in world space */ 63 | UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Default") 64 | virtual FVector GetPawnLocation() const; 65 | /* Set offset in local space for pawns occupynig this tile */ 66 | UFUNCTION(BlueprintCallable, Category = "Default") 67 | void SetPawnLocationOffset(const FVector &Offset); 68 | protected: 69 | /* Offset in local space for any pawn occupying this tile */ 70 | FVector PawnLocationOffset; 71 | 72 | public: 73 | // User interface 74 | UFUNCTION() 75 | void Clicked(UPrimitiveComponent* TouchedComponent, FKey Key); 76 | UFUNCTION() 77 | void CursorOver(UPrimitiveComponent* TouchedComponent); 78 | UFUNCTION() 79 | void EndCursorOver(UPrimitiveComponent* TouchedComponent); 80 | UFUNCTION() 81 | void TouchEnter(ETouchIndex::Type Type, UPrimitiveComponent* TouchedComponent); 82 | UFUNCTION() 83 | void TouchLeave(ETouchIndex::Type Type, UPrimitiveComponent* TouchedComponent); 84 | UFUNCTION() 85 | void TouchEnd(ETouchIndex::Type Type, UPrimitiveComponent* TouchedComponent); 86 | 87 | /* 88 | * Add points for moving into this tile from FromPos 89 | * 90 | * OutSpline - the spline to add the points to 91 | * OutPathSegments - the array we should add our path segment to 92 | * EndTile - true if this is the last tile in the path 93 | */ 94 | virtual void AddPathSegments(USplineComponent &OutSpline, TArray &OutPathSegments, bool EndTile) const; 95 | /* Return a suitable upvector for a splinemesh moving across this tile */ 96 | virtual FVector GetSplineMeshUpVector(); 97 | 98 | /* Set a highlight for this tile */ 99 | virtual void SetHighlight(FName NewHighlightType); 100 | /* draw debug information on the screen*/ 101 | virtual void DrawDebug(UCapsuleComponent *CollisionCapsule, bool bPersistentLines, float LifeTime, float Thickness); 102 | }; -------------------------------------------------------------------------------- /Source/Navgrid/Classes/TurnComponent.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "Components/ActorComponent.h" 6 | #include "TurnManager.h" 7 | 8 | #include "TurnComponent.generated.h" 9 | 10 | /** 11 | * Actors with a turn component can be managed by a turn manager 12 | */ 13 | UCLASS(meta = (BlueprintSpawnableComponent)) 14 | class NAVGRID_API UTurnComponent : public UActorComponent 15 | { 16 | GENERATED_BODY() 17 | public: 18 | UTurnComponent(); 19 | virtual void BeginPlay() override; 20 | virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; 21 | 22 | ATurnManager *GetTurnManager(); 23 | protected: 24 | UPROPERTY() 25 | ATurnManager *TurnManager; 26 | UFUNCTION() 27 | void OnTurnTimeout(); 28 | FTimerHandle TurnTimeoutHandle; 29 | public: 30 | /* The number of actions this pawn can perform in a single round */ 31 | UPROPERTY(EditAnyWhere, BlueprintReadWrite) 32 | int32 StartingActionPoints; 33 | /* Remaining actions that this pawn can perform this round */ 34 | UPROPERTY(VisibleAnywhere, BlueprintReadWrite) 35 | int32 RemainingActionPoints; 36 | 37 | /* end this turn after the given amount of time has passed. Set to 0 to disable */ 38 | UPROPERTY(VisibleAnyWhere, BlueprintReadWrite) 39 | float TurnTimeout; 40 | 41 | /* Tell the manager to end the turn for this component */ 42 | void EndTurn(); 43 | void EndTeamTurn(); 44 | /* request the turn manager to start a turn for this component */ 45 | void RequestStartTurn(); 46 | /* request that the turn manager starts the turn for the next component on our team */ 47 | void RequestStartNextComponent(); 48 | 49 | /* Used be the owning actor to notify that it is ready to receive input from a player or ai */ 50 | void OwnerReadyForInput(); 51 | 52 | /* is it this components turn? */ 53 | UFUNCTION(BlueprintPure) 54 | bool MyTurn() { return IsValid(TurnManager) && TurnManager->GetCurrentComponent() == this; } 55 | 56 | /* which team this component is a part of */ 57 | UFUNCTION(BlueprintPure) 58 | FGenericTeamId TeamId() const { return FGenericTeamId::GetTeamIdentifier(GetOwner()); } 59 | 60 | UFUNCTION(BlueprintPure) 61 | AActor *GetCurrentActor() const; 62 | template 63 | T *GetCurrentActor() const { return Cast(GetCurrentActor()); } 64 | 65 | // register with the turn manager in order to get to take turns 66 | void RegisterWithTurnManager(); 67 | // unregister, this compnent will no longer get to take turns 68 | void UnregisterWithTurnManager(); 69 | 70 | // called by the turn manager 71 | void OnTurnStart(); 72 | void OnTurnEnd(); 73 | }; -------------------------------------------------------------------------------- /Source/Navgrid/Classes/TurnManager.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "GameFramework/Actor.h" 6 | #include "GenericTeamAgentInterface.h" 7 | #include "TurnManager.generated.h" 8 | 9 | class UTurnComponent; 10 | 11 | //Declare delegates 12 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTurnStart, UTurnComponent *, TurnComponent); 13 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTurnEnd, UTurnComponent *, TurnComponent); 14 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTeamTurnStart, const FGenericTeamId &, TeamId); 15 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTeamTurnEnd, const FGenericTeamId &, TeamId); 16 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReadyForInput, UTurnComponent *, TurnComponent); 17 | DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRoundStart); 18 | DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRoundEnd); 19 | 20 | /** 21 | * Coordinates a set of turn components. 22 | * 23 | * Terms: 24 | * 'Turn' is used for a singe pawn doing a single action. 25 | * 'Round' is used for all pawns managed by this turn manager having their Turn. 26 | */ 27 | UCLASS(BlueprintType, Blueprintable, NotPlaceable) 28 | class NAVGRID_API ATurnManager : public AActor 29 | { 30 | GENERATED_BODY() 31 | public: 32 | ATurnManager(); 33 | virtual void Tick(float DeltaTime) override; 34 | 35 | /* Add a turn component to be managed */ 36 | UFUNCTION(BlueprintCallable) 37 | void RegisterTurnComponent(UTurnComponent *TurnComponent); 38 | UFUNCTION(BlueprintCallable) 39 | void UnregisterTurnComponent(UTurnComponent *TurnComponent); 40 | 41 | /* minumuim number of teams needed to start a new turn */ 42 | UPROPERTY(EditAnyWhere, BlueprintReadWrite) 43 | int32 MinNumberOfTeams; 44 | 45 | /* End the turn for the current turn component */ 46 | UFUNCTION(BlueprintCallable) 47 | void EndTurn(UTurnComponent *Ender); 48 | /* End the turn for all components of the current team */ 49 | UFUNCTION(BlueprintCallable) 50 | void EndTeamTurn(FGenericTeamId InTeamId); 51 | /* request to immediatly start the turn for the supplied component. Return false if the request is denied */ 52 | UFUNCTION(BlueprintCallable) 53 | void RequestStartTurn(UTurnComponent *CallingComponent); 54 | UFUNCTION(BlueprintCallable) 55 | void RequestStartNextComponent(UTurnComponent *CallingComponent); 56 | UFUNCTION(BlueprintPure) 57 | UTurnComponent *GetCurrentComponent() const { return CurrentComponent; } 58 | UFUNCTION(BlueprintPure) 59 | FGenericTeamId GetCurrentTeam() const; 60 | 61 | AActor *GetCurrentActor() const; 62 | template 63 | T* GetCurrentActor() const { return Cast(GetCurrentActor()); } 64 | 65 | UFUNCTION(BlueprintPure) 66 | int32 GetRound() const { return Round; } 67 | 68 | protected: 69 | // find the next team member that can act this turn 70 | UTurnComponent *FindNextTeamMember(const FGenericTeamId &TeamId); 71 | UTurnComponent *FindNextComponent(); 72 | bool HasComponentsThatCanAct(); 73 | 74 | private: 75 | UPROPERTY(BlueprintAssignable) 76 | FOnTurnStart OnTurnStartDelegate; 77 | UPROPERTY(BlueprintAssignable) 78 | FOnTurnEnd OnTurnEndDelegate; 79 | UPROPERTY(BlueprintAssignable) 80 | FOnTeamTurnStart OnTeamTurnStartDelegate; 81 | UPROPERTY(BlueprintAssignable) 82 | FOnTeamTurnEnd OnTeamTurnEndDelegate; 83 | UPROPERTY(BlueprintAssignable) 84 | FOnReadyForInput OnReadyForInputDelegate; 85 | UPROPERTY(BlueprintAssignable) 86 | FOnRoundStart OnRoundStartDelegate; 87 | UPROPERTY(BlueprintAssignable) 88 | FOnRoundEnd OnRoundEndDelegate; 89 | 90 | public: 91 | virtual FOnTurnStart& OnTurnStart() { return OnTurnStartDelegate; } 92 | virtual FOnTurnEnd& OnTurnEnd() { return OnTurnEndDelegate; } 93 | virtual FOnTeamTurnStart& OnTeamTurnStart() { return OnTeamTurnStartDelegate; } 94 | virtual FOnTeamTurnEnd& OnTeamTurnEnd() { return OnTeamTurnEndDelegate; } 95 | virtual FOnReadyForInput& OnReadyForInput() { return OnReadyForInputDelegate; } 96 | virtual FOnRoundStart& OnRoundStart() { return OnRoundStartDelegate; } 97 | virtual FOnRoundEnd& OnRoundEnd() { return OnRoundEndDelegate; } 98 | 99 | protected: 100 | UPROPERTY(VisibleAnyWhere) 101 | UTurnComponent *CurrentComponent; 102 | UPROPERTY() 103 | UTurnComponent *NextComponent; 104 | TMultiMap Teams; 105 | UPROPERTY(VisibleAnyWhere) 106 | int32 Round; 107 | bool bStartNewTurn; 108 | }; -------------------------------------------------------------------------------- /Source/Navgrid/NavGrid.Build.cs: -------------------------------------------------------------------------------- 1 | using UnrealBuildTool; 2 | using System.IO; 3 | 4 | public class NavGrid : ModuleRules 5 | { 6 | public NavGrid(ReadOnlyTargetRules TargetRules) : base(TargetRules) { 7 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AIModule" }); 8 | PrivatePCHHeaderFile = "Private/NavGridPrivatePCH.h"; 9 | 10 | if (TargetRules.bBuildEditor) 11 | { 12 | PrivateDependencyModuleNames.AddRange(new string[] { "UnrealED" }); 13 | } 14 | 15 | PublicIncludePaths.AddRange( 16 | new string[] { 17 | Path.Combine(ModuleDirectory, "Public"), 18 | Path.Combine(ModuleDirectory, "Classes"), 19 | // ... add public include paths required here ... 20 | } 21 | ); 22 | 23 | 24 | PrivateIncludePaths.AddRange( 25 | new string[] { 26 | Path.Combine(ModuleDirectory, "Private"), 27 | // ... add other private include paths required here ... 28 | } 29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /Source/Navgrid/Private/ExampleGridPawn.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | #include "ExampleGridPawn.h" 5 | 6 | #include "Components/StaticMeshComponent.h" 7 | #include "Components/ArrowComponent.h" 8 | 9 | AExampleGridPawn::AExampleGridPawn() 10 | :Super() 11 | { 12 | MovementCollisionCapsule->SetCapsuleHalfHeight(30); 13 | MovementCollisionCapsule->SetRelativeLocation(FVector(0, 0, 50)); 14 | 15 | StaticMesh = CreateDefaultSubobject("StaticMesh"); 16 | UStaticMesh *Mesh = ConstructorHelpers::FObjectFinder(TEXT("StaticMesh'/Engine/BasicShapes/Cone.Cone'")).Object; 17 | StaticMesh->SetStaticMesh(Mesh); 18 | StaticMesh->SetRelativeLocation(-MovementCollisionCapsule->GetRelativeLocation() + FVector(0, 0, 50)); 19 | StaticMesh->SetupAttachment(GetRootComponent()); 20 | 21 | /* Show the arrow in-game so we can which way the pawn is facing */ 22 | Arrow->SetHiddenInGame(false); 23 | } 24 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/GridMovementComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | 5 | #include "Components/SplineComponent.h" 6 | #include "Components/SplineMeshComponent.h" 7 | #include "Animation/AnimInstance.h" 8 | 9 | FPathSegment::FPathSegment(TSet InMovementModes, float InStart, float InEnd) 10 | { 11 | MovementModes = InMovementModes; 12 | Start = InStart; 13 | End = InEnd; 14 | } 15 | 16 | UGridMovementComponent::UGridMovementComponent(const FObjectInitializer &ObjectInitializer) 17 | :Super(ObjectInitializer) 18 | { 19 | PathMesh = ConstructorHelpers::FObjectFinder(TEXT("StaticMesh'/NavGrid/SMesh/NavGrid_Path.NavGrid_Path'")).Object; 20 | 21 | AvailableMovementModes.Add(EGridMovementMode::ClimbingDown); 22 | AvailableMovementModes.Add(EGridMovementMode::ClimbingUp); 23 | AvailableMovementModes.Add(EGridMovementMode::Stationary); 24 | AvailableMovementModes.Add(EGridMovementMode::Walking); 25 | AvailableMovementModes.Add(EGridMovementMode::InPlaceTurn); 26 | 27 | Distance = 0; 28 | MovementMode = EGridMovementMode::Stationary; 29 | } 30 | 31 | void UGridMovementComponent::BeginPlay() 32 | { 33 | Super::BeginPlay(); 34 | 35 | /* I dont know why, but if we use createdefaultsubobject in the constructor this is sometimes reset to NULL*/ 36 | if (!IsValid(Spline)) 37 | { 38 | Spline = NewObject(this, "PathSpline"); 39 | check(Spline); 40 | } 41 | Spline->ClearSplinePoints(); 42 | 43 | ANavGrid* Grid = GetNavGrid(); 44 | if (!Grid) 45 | { 46 | UE_LOG(NavGrid, Error, TEXT("%s was unable to find a NavGrid in level"), *this->GetName()); 47 | } 48 | 49 | /* Grab a reference to the first AnimInstace we find */ 50 | TArray SkeletalMeshComponents; 51 | GetOwner()->GetComponents(SkeletalMeshComponents); 52 | for (USkeletalMeshComponent* Comp : SkeletalMeshComponents) 53 | { 54 | MeshRotation = Comp->GetRelativeTransform().Rotator(); 55 | AnimInstance = Comp->GetAnimInstance(); 56 | if (AnimInstance) 57 | { 58 | break; 59 | } 60 | } 61 | if (!AnimInstance && (bUseRootMotion || bAlwaysUseRootMotion)) 62 | { 63 | UE_LOG(NavGrid, Error, TEXT("%s: Unable to get reference to AnimInstance. Root motion extraction disabled"), *GetName()); 64 | bUseRootMotion = false; 65 | bAlwaysUseRootMotion = false; 66 | } 67 | } 68 | 69 | void UGridMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) 70 | { 71 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 72 | 73 | // if we are moving, find the current path segment 74 | if (MovementMode == EGridMovementMode::Walking || 75 | MovementMode == EGridMovementMode::ClimbingDown || 76 | MovementMode == EGridMovementMode::ClimbingUp) 77 | { 78 | CurrentPathSegment = FPathSegment({ EGridMovementMode::Walking }, 0, Spline->GetSplineLength()); 79 | for (FPathSegment &Segment : PathSegments) 80 | { 81 | if (Distance >= Segment.Start && Distance <= Segment.End) 82 | { 83 | CurrentPathSegment = Segment; 84 | break; 85 | } 86 | } 87 | } 88 | 89 | AActor *Owner = GetOwner(); 90 | FTransform NewTransform = Owner->GetActorTransform(); 91 | FRotator ActorRotation = Owner->GetActorRotation(); 92 | 93 | ConsiderUpdateMovementMode(); 94 | switch (MovementMode) 95 | { 96 | default: 97 | case EGridMovementMode::Stationary: 98 | if (bAlwaysUseRootMotion) 99 | { 100 | FTransform RootMotion = ConsumeRootMotion(); 101 | NewTransform.SetRotation(NewTransform.GetRotation() * RootMotion.GetRotation()); 102 | FRotator AnimToWorld = Owner->GetActorRotation() + MeshRotation; 103 | NewTransform.SetLocation(NewTransform.GetLocation() + AnimToWorld.RotateVector(RootMotion.GetLocation())); 104 | } 105 | break; //nothing to do 106 | case EGridMovementMode::InPlaceTurn: 107 | NewTransform = TransformFromRotation(DeltaTime); 108 | break; 109 | case EGridMovementMode::Walking: 110 | case EGridMovementMode::ClimbingDown: 111 | case EGridMovementMode::ClimbingUp: 112 | NewTransform = TransformFromPath(DeltaTime); 113 | ConsiderUpdateCurrentTile(); 114 | break; 115 | } 116 | 117 | /* reset rotation if we have any rotation locks */ 118 | FRotator NewRotation = ApplyRotationLocks(NewTransform.GetRotation().Rotator()); 119 | NewTransform.SetRotation(NewRotation.Quaternion()); 120 | /* never change the scale */ 121 | NewTransform.SetScale3D(Owner->GetActorScale3D()); 122 | 123 | // update velocity so it can be fetched by the pawn 124 | Velocity = (NewTransform.GetLocation() - Owner->GetActorLocation()) * (1 / DeltaTime); 125 | UpdateComponentVelocity(); 126 | // actually move the the actor 127 | Owner->SetActorTransform(NewTransform); 128 | } 129 | 130 | void UGridMovementComponent::StopMovementImmediately() 131 | { 132 | FinishMovement(); 133 | } 134 | 135 | FTransform UGridMovementComponent::TransformFromPath(float DeltaTime) 136 | { 137 | /* Check if we can get the speed from root motion */ 138 | float CurrentSpeed = 0; 139 | if (bUseRootMotion) 140 | { 141 | FTransform RootMotion = ConsumeRootMotion(); 142 | CurrentSpeed = RootMotion.GetLocation().Size(); 143 | } 144 | 145 | if (CurrentSpeed < 25 * DeltaTime) 146 | { 147 | if (MovementMode == EGridMovementMode::ClimbingDown || MovementMode == EGridMovementMode::ClimbingUp) 148 | { 149 | CurrentSpeed = MaxClimbSpeed * DeltaTime; 150 | } 151 | else 152 | { 153 | CurrentSpeed = MaxWalkSpeed * DeltaTime; 154 | } 155 | } 156 | 157 | Distance = FMath::Min(Spline->GetSplineLength(), Distance + CurrentSpeed); 158 | 159 | /* Grab our current transform so we can find the velocity if we need it later */ 160 | AActor *Owner = GetOwner(); 161 | FTransform OldTransform = Owner->GetTransform(); 162 | 163 | /* Find the next location and rotation from the spline*/ 164 | FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local); 165 | FRotator DesiredRotation; 166 | 167 | /* Restrain rotation axis if we're walking */ 168 | if (MovementMode == EGridMovementMode::Walking) 169 | { 170 | DesiredRotation = NewTransform.Rotator(); 171 | } 172 | /* Use the rotation from the ladder if we're climbing */ 173 | else if (MovementMode == EGridMovementMode::ClimbingUp || MovementMode == EGridMovementMode::ClimbingDown) 174 | { 175 | DesiredRotation = CurrentPathSegment.PawnRotationHint; 176 | } 177 | 178 | /* Find the new rotation by limiting DesiredRotation by MaxRotationSpeed */ 179 | FRotator NewRotation = LimitRotation(OldTransform.GetRotation().Rotator(), DesiredRotation, DeltaTime); 180 | NewTransform.SetRotation(NewRotation.Quaternion()); 181 | 182 | // check if we are done 183 | if (Distance >= Spline->GetSplineLength()) 184 | { 185 | FinishMovement(); 186 | } 187 | 188 | return NewTransform; 189 | } 190 | 191 | FTransform UGridMovementComponent::TransformFromRotation(float DeltaTime) 192 | { 193 | AActor *Owner = GetOwner(); 194 | FTransform NewTransform = Owner->GetActorTransform(); 195 | if (Owner->GetActorRotation().Equals(DesiredForwardRotation)) 196 | { 197 | Owner->SetActorRotation(DesiredForwardRotation); 198 | ChangeMovementMode(EGridMovementMode::Stationary); 199 | } 200 | else 201 | { 202 | FRotator NewRotation = LimitRotation(Owner->GetActorRotation(), DesiredForwardRotation, DeltaTime); 203 | NewTransform.SetRotation(NewRotation.Quaternion()); 204 | } 205 | return NewTransform; 206 | } 207 | 208 | void UGridMovementComponent::ConsiderUpdateCurrentTile() 209 | { 210 | // try to grab the tile we're on and store it in CurrentTile. Take care to not overwrite a pointer to an 211 | // actual tile with NULL as that would mean we have moved off the grid 212 | ANavGrid* Grid = GetNavGrid(); 213 | if (IsValid(Grid)) 214 | { 215 | 216 | UNavTileComponent *Tile = Grid->GetTile(GetOwner()->GetActorLocation(), true); 217 | if (!Tile && 218 | (AvailableMovementModes.Contains(EGridMovementMode::ClimbingDown) || 219 | AvailableMovementModes.Contains(EGridMovementMode::ClimbingUp))) 220 | { 221 | Tile = Grid->GetTile(GetOwner()->GetActorLocation(), false); 222 | } 223 | 224 | if (IsValid(Tile) && Tile != CurrentTile) 225 | { 226 | CurrentTile = Tile; 227 | 228 | ANavGridGameState *GameState = Cast(UGameplayStatics::GetGameState(GetOwner())); 229 | if (IsValid(GameState)) 230 | { 231 | AGridPawn *GridPawn = Cast(GetOwner()); 232 | GameState->OnPawnEnterTile().Broadcast(GridPawn, CurrentTile); 233 | } 234 | } 235 | } 236 | } 237 | 238 | void UGridMovementComponent::GetTilesInRange(TArray &OutTiles) 239 | { 240 | ANavGrid *Grid = GetNavGrid(); 241 | if (IsValid(Grid)) 242 | { 243 | ConsiderUpdateCurrentTile(); 244 | Grid->GetTilesInRange(Cast(GetOwner()), OutTiles); 245 | } 246 | } 247 | 248 | UNavTileComponent *UGridMovementComponent::GetTile() 249 | { 250 | if (!IsValid(CurrentTile)) 251 | { 252 | ConsiderUpdateCurrentTile(); 253 | } 254 | return CurrentTile; 255 | } 256 | 257 | ANavGrid * UGridMovementComponent::GetNavGrid() 258 | { 259 | if (!IsValid(CachedNavGrid)) 260 | { 261 | CachedNavGrid = ANavGrid::GetNavGrid(GetOwner()); 262 | } 263 | return CachedNavGrid; 264 | } 265 | 266 | void UGridMovementComponent::StringPull(TArray& InPath, TArray& OutPath) 267 | { 268 | QUICK_SCOPE_CYCLE_COUNTER(STAT_UGridMovementComponent_StringPull); 269 | 270 | if (InPath.Num() > 2) 271 | { 272 | AGridPawn *GridPawnOwner = Cast(GetOwner()); 273 | OutPath.Empty(); 274 | const UCapsuleComponent &Capsule = *GridPawnOwner->MovementCollisionCapsule; 275 | int32 CurrentIdx = 0; 276 | OutPath.Add(InPath[0]); 277 | for (int32 Idx = 1; Idx < InPath.Num() - 1; Idx++) 278 | { 279 | // keep points needed to get around chasms and obstacles 280 | FVector Delta = InPath[Idx]->GetPawnLocation() - InPath[CurrentIdx]->GetPawnLocation(); 281 | if (FMath::Abs(Delta.Z) > Capsule.GetRelativeLocation().Z - Capsule.GetScaledCapsuleHalfHeight() || 282 | InPath[Idx]->Obstructed(InPath[CurrentIdx]->GetPawnLocation(), Capsule)) 283 | { 284 | OutPath.AddUnique(InPath[Idx - 1]); 285 | CurrentIdx = Idx - 1; 286 | } 287 | // dont stringpull ladders 288 | else if (Cast(InPath[Idx])) 289 | { 290 | OutPath.AddUnique(InPath[Idx - 1]); 291 | OutPath.AddUnique(InPath[Idx]); 292 | if (Idx + 1 < InPath.Num()) 293 | { 294 | OutPath.AddUnique(InPath[Idx + 1]); 295 | } 296 | CurrentIdx = Idx + 1; 297 | Idx = Idx + 1; 298 | } 299 | } 300 | OutPath.AddUnique(InPath[InPath.Num() - 2]); 301 | OutPath.AddUnique(InPath[InPath.Num() - 1]); 302 | } 303 | else 304 | { 305 | OutPath = InPath; 306 | } 307 | } 308 | 309 | bool UGridMovementComponent::CreatePath(const UNavTileComponent &Target) 310 | { 311 | QUICK_SCOPE_CYCLE_COUNTER(STAT_UGridMovementComponent_CreatePath); 312 | AGridPawn *Owner = Cast(GetOwner()); 313 | 314 | if (!IsValid(CurrentTile)) 315 | { 316 | UE_LOG(NavGrid, Error, TEXT("%s: Not on grid"), *Owner->GetName()); 317 | return false; 318 | } 319 | 320 | ANavGrid* Grid = GetNavGrid(); 321 | TArray InRange; 322 | Grid->GetTilesInRange(Cast(GetOwner()), InRange); 323 | if (InRange.Contains(&Target)) 324 | { 325 | // create a list of tiles from the destination to the starting point and reverse it 326 | TArray Path; 327 | const UNavTileComponent *Current = &Target; 328 | while (Current) 329 | { 330 | Path.Add(Current); 331 | Current = Current->Backpointer; 332 | } 333 | if (bStringPullPath) 334 | { 335 | TArray StringPulledPath; 336 | StringPull(Path, StringPulledPath); 337 | Path = StringPulledPath; 338 | } 339 | Algo::Reverse(Path); 340 | 341 | // Build the path spline and path segments 342 | Spline->ClearSplinePoints(); 343 | PathSegments.Empty(); 344 | if (Path.Num() > 1) 345 | { 346 | FVector ActorLocation = GetOwner()->GetActorLocation(); 347 | // use the actor location inststead of the tile location for the first spline point 348 | Spline->AddSplinePoint(ActorLocation, ESplineCoordinateSpace::Local); 349 | Spline->SetSplinePointType(0, ESplinePointType::Linear, false); 350 | 351 | for (int32 Idx = 1; Idx < Path.Num(); Idx++) 352 | { 353 | if (Grid->GetTile(ActorLocation) != Path[Idx] && CurrentTile != Path[Idx]) 354 | { 355 | Path[Idx]->AddPathSegments(*Spline, PathSegments, Idx == Path.Num() - 1); 356 | } 357 | } 358 | return true; // success! 359 | } 360 | } 361 | 362 | return false; // no path to TargetTile 363 | } 364 | 365 | bool UGridMovementComponent::MoveTo(const UNavTileComponent &Target) 366 | { 367 | bool PathExists = CreatePath(Target); 368 | if (PathExists) 369 | { 370 | ChangeMovementMode(EGridMovementMode::Walking); 371 | } 372 | return PathExists; 373 | } 374 | 375 | void UGridMovementComponent::TurnTo(const FRotator & Forward) 376 | { 377 | if (AvailableMovementModes.Contains(EGridMovementMode::InPlaceTurn)) 378 | { 379 | DesiredForwardRotation = Forward; 380 | ChangeMovementMode(EGridMovementMode::InPlaceTurn); 381 | } 382 | } 383 | 384 | void UGridMovementComponent::SnapToGrid() 385 | { 386 | AGridPawn *GridPawnOwner = Cast(GetOwner()); 387 | check(GridPawnOwner); 388 | 389 | ConsiderUpdateCurrentTile(); 390 | 391 | // move owner to the tile's pawn location 392 | if (IsValid(CurrentTile)) 393 | { 394 | GridPawnOwner->SetActorLocation(CurrentTile->GetPawnLocation()); 395 | } 396 | } 397 | 398 | void UGridMovementComponent::AdvanceAlongPath(float InDistance) 399 | { 400 | if (Spline->GetNumberOfSplinePoints() > 0) 401 | { 402 | Distance = FMath::Min(Spline->GetSplineLength(), Distance + InDistance); 403 | FTransform NewTransform = Spline->GetTransformAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local); 404 | 405 | FRotator DesiredRotation; 406 | /* Restrain rotation axis if we're walking */ 407 | if (MovementMode == EGridMovementMode::Walking) 408 | { 409 | DesiredRotation = NewTransform.Rotator(); 410 | } 411 | /* Use the rotation from the ladder if we're climbing */ 412 | else if (MovementMode == EGridMovementMode::ClimbingUp || MovementMode == EGridMovementMode::ClimbingDown) 413 | { 414 | DesiredRotation = CurrentPathSegment.PawnRotationHint; 415 | } 416 | DesiredRotation = ApplyRotationLocks(DesiredRotation); 417 | 418 | NewTransform.SetRotation(DesiredForwardRotation.Quaternion()); 419 | 420 | /* never change the scale */ 421 | NewTransform.SetScale3D(GetOwner()->GetActorScale3D()); 422 | 423 | GetOwner()->SetActorTransform(NewTransform); 424 | ConsiderUpdateCurrentTile(); 425 | } 426 | } 427 | 428 | float UGridMovementComponent::GetRemainingDistance() 429 | { 430 | return FMath::Max(Spline->GetSplineLength() - Distance, 0.0f); 431 | } 432 | 433 | FRotator UGridMovementComponent::ApplyRotationLocks(const FRotator & InRotation) 434 | { 435 | FRotator OwnerRot = GetOwner()->GetActorRotation(); 436 | FRotator Ret; 437 | Ret.Pitch = LockPitch ? OwnerRot.Pitch : InRotation.Pitch; 438 | Ret.Roll = LockRoll ? OwnerRot.Roll : InRotation.Roll; 439 | Ret.Yaw = LockYaw ? OwnerRot.Yaw : InRotation.Yaw; 440 | return Ret; 441 | } 442 | 443 | void UGridMovementComponent::ShowPath() 444 | { 445 | if (PathMesh) 446 | { 447 | float PathDistance = HorizontalOffset; // Get some distance between the actor and the path 448 | FBoxSphereBounds Bounds = PathMesh->GetBounds(); 449 | float MeshLength = FMath::Abs(Bounds.BoxExtent.X) * 2; 450 | float SplineLength = Spline->GetSplineLength(); 451 | SplineLength -= HorizontalOffset; // Get some distance between the cursor and the path 452 | 453 | while (PathDistance < SplineLength) 454 | { 455 | AddSplineMesh(PathDistance, FMath::Min(PathDistance + MeshLength, SplineLength)); 456 | PathDistance += FMath::Min(MeshLength, SplineLength - PathDistance); 457 | } 458 | } 459 | } 460 | 461 | void UGridMovementComponent::HidePath() 462 | { 463 | for (USplineMeshComponent *SMesh : SplineMeshes) 464 | { 465 | SMesh->DestroyComponent(); 466 | } 467 | SplineMeshes.Empty(); 468 | } 469 | 470 | FTransform UGridMovementComponent::ConsumeRootMotion() 471 | { 472 | if (!AnimInstance) 473 | { 474 | return FTransform(); 475 | } 476 | 477 | FRootMotionMovementParams RootMotionParams = AnimInstance->ConsumeExtractedRootMotion(1); 478 | return RootMotionParams.GetRootMotionTransform(); 479 | } 480 | 481 | void UGridMovementComponent::ConsiderUpdateMovementMode() 482 | { 483 | // only consider changing mode when we are moving 484 | if (MovementMode == EGridMovementMode::Walking || 485 | MovementMode == EGridMovementMode::ClimbingDown || 486 | MovementMode == EGridMovementMode::ClimbingUp) 487 | { 488 | // if the maching segment is not walkable, transtion to the a climbing mode 489 | if (!CurrentPathSegment.MovementModes.Contains(EGridMovementMode::Walking)) 490 | { 491 | // try to get the tile the pawn will occupy when it moves a bit further 492 | FVector ForwardPoint = GetForwardLocation(50); 493 | FVector ActorLocation = GetOwner()->GetActorLocation(); 494 | if (ForwardPoint.Z > ActorLocation.Z) 495 | { 496 | ChangeMovementMode(EGridMovementMode::ClimbingUp); 497 | } 498 | else 499 | { 500 | ChangeMovementMode(EGridMovementMode::ClimbingDown); 501 | } 502 | } 503 | // default movement mode is walking 504 | else 505 | { 506 | ChangeMovementMode(EGridMovementMode::Walking); 507 | } 508 | } 509 | } 510 | 511 | void UGridMovementComponent::ChangeMovementMode(EGridMovementMode NewMode) 512 | { 513 | if (NewMode != MovementMode) 514 | { 515 | OnMovementModeChangedEvent.Broadcast(MovementMode, NewMode); 516 | MovementMode = NewMode; 517 | } 518 | } 519 | 520 | void UGridMovementComponent::FinishMovement() 521 | { 522 | Distance = 0; 523 | if (IsValid(Spline)) 524 | { 525 | Spline->ClearSplinePoints(); 526 | } 527 | ChangeMovementMode(EGridMovementMode::Stationary); 528 | OnMovementEndEvent.Broadcast(); 529 | } 530 | 531 | FVector UGridMovementComponent::GetForwardLocation(float ForwardDistance) 532 | { 533 | float D = FMath::Min(Spline->GetSplineLength(), Distance + ForwardDistance); 534 | return Spline->GetLocationAtDistanceAlongSpline(D, ESplineCoordinateSpace::Local); 535 | } 536 | 537 | void UGridMovementComponent::AddSplineMesh(float From, float To) 538 | { 539 | ANavGrid* Grid = GetNavGrid(); 540 | 541 | float TanScale = 25; 542 | FVector StartPos = Spline->GetLocationAtDistanceAlongSpline(From, ESplineCoordinateSpace::Local); 543 | StartPos.Z += Grid->UIOffset; 544 | FVector StartTan = Spline->GetDirectionAtDistanceAlongSpline(From, ESplineCoordinateSpace::Local) * TanScale; 545 | FVector EndPos = Spline->GetLocationAtDistanceAlongSpline(To, ESplineCoordinateSpace::Local); 546 | EndPos.Z += Grid->UIOffset; 547 | FVector EndTan = Spline->GetDirectionAtDistanceAlongSpline(To, ESplineCoordinateSpace::Local) * TanScale; 548 | FVector UpVector = EndPos - StartPos; 549 | UpVector = FVector(UpVector.Y, UpVector.Z, UpVector.X); 550 | 551 | UPROPERTY() USplineMeshComponent *SplineMesh = NewObject(this); 552 | SplineMesh->SetMobility(EComponentMobility::Movable); 553 | SplineMesh->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan); 554 | SplineMesh->SetStaticMesh(PathMesh); 555 | SplineMesh->RegisterComponentWithWorld(GetWorld()); 556 | SplineMesh->SetSplineUpDir(UpVector); 557 | SplineMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision); 558 | SplineMeshes.Add(SplineMesh); 559 | } 560 | 561 | FRotator UGridMovementComponent::LimitRotation(const FRotator &OldRotation, const FRotator &NewRotation, float DeltaTime) 562 | { 563 | FRotator Result = OldRotation.GetNormalized(); 564 | FRotator DeltaRotation = NewRotation - OldRotation; 565 | DeltaRotation.Normalize(); 566 | Result.Pitch += DeltaRotation.Pitch > 0 ? FMath::Min(DeltaRotation.Pitch, MaxRotationSpeed * DeltaTime) : 567 | FMath::Max(DeltaRotation.Pitch, MaxRotationSpeed * -DeltaTime); 568 | Result.Roll += DeltaRotation.Roll > 0 ? FMath::Min(DeltaRotation.Roll, MaxRotationSpeed * DeltaTime) : 569 | FMath::Max(DeltaRotation.Roll, MaxRotationSpeed * -DeltaTime); 570 | Result.Yaw += DeltaRotation.Yaw > 0 ? FMath::Min(DeltaRotation.Yaw, MaxRotationSpeed * DeltaTime) : 571 | FMath::Max(DeltaRotation.Yaw, MaxRotationSpeed * -DeltaTime); 572 | 573 | return Result; 574 | } 575 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/GridPawn.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | #if WITH_EDITORONLY_DATA 5 | #include "Editor.h" 6 | #endif 7 | 8 | // Sets default values 9 | AGridPawn::AGridPawn() 10 | : Super() 11 | { 12 | // Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it. 13 | PrimaryActorTick.bCanEverTick = true; 14 | 15 | SceneRoot = CreateDefaultSubobject("Root"); 16 | SetRootComponent(SceneRoot); 17 | 18 | BoundsCapsule = CreateDefaultSubobject("Capsule"); 19 | BoundsCapsule->SetCollisionProfileName("Pawn"); 20 | BoundsCapsule->SetCollisionEnabled(ECollisionEnabled::QueryOnly); 21 | BoundsCapsule->ShapeColor = FColor::Magenta; 22 | BoundsCapsule->SetupAttachment(SceneRoot); 23 | 24 | MovementComponent = CreateDefaultSubobject("MovementComponent"); 25 | MovementComponent->OnMovementEnd().AddUObject(this, &AGridPawn::OnMoveEnd); 26 | MovementComponent->SetUpdatedComponent(BoundsCapsule); 27 | 28 | TurnComponent = CreateDefaultSubobject("TurnComponent"); 29 | 30 | MovementCollisionCapsule = CreateDefaultSubobject("MovementCollisionCapsule"); 31 | MovementCollisionCapsule->SetupAttachment(SceneRoot); 32 | MovementCollisionCapsule->SetRelativeLocation(FVector(0, 0, 100)); 33 | MovementCollisionCapsule->SetCapsuleHalfHeight(50); 34 | MovementCollisionCapsule->SetCapsuleRadius(30); 35 | MovementCollisionCapsule->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); 36 | 37 | SelectedHighlight = CreateDefaultSubobject("SelectedHighlight"); 38 | SelectedHighlight->SetupAttachment(SceneRoot); 39 | UStaticMesh *Selected = ConstructorHelpers::FObjectFinder( 40 | TEXT("StaticMesh'/NavGrid/SMesh/NavGrid_Cursor.NavGrid_Cursor'")).Object; 41 | SelectedHighlight->SetStaticMesh(Selected); 42 | SelectedHighlight->SetCollisionEnabled(ECollisionEnabled::NoCollision); 43 | SelectedHighlight->SetVisibility(false); 44 | 45 | Arrow = CreateDefaultSubobject("Arrow"); 46 | Arrow->SetupAttachment(MovementCollisionCapsule); 47 | Arrow->SetCollisionEnabled(ECollisionEnabled::NoCollision); 48 | 49 | bHumanControlled = true; 50 | 51 | /* bind mouse events*/ 52 | OnClicked.AddDynamic(this, &AGridPawn::Clicked); 53 | #if WITH_EDITORONLY_DATA 54 | USelection::SelectObjectEvent.AddUObject(this, &AGridPawn::OnObjectSelectedInEditor); 55 | #endif 56 | 57 | // dont place tiles on top of pawns 58 | Tags.AddUnique(ANavGrid::DisableVirtualTilesTag); 59 | } 60 | 61 | void AGridPawn::BeginPlay() 62 | { 63 | Super::BeginPlay(); 64 | 65 | ATurnManager *TurnManager =TurnComponent->GetTurnManager(); 66 | if (IsValid(TurnManager)) 67 | { 68 | TurnManager->OnRoundStart().AddDynamic(this, &AGridPawn::OnRoundStart); 69 | TurnManager->OnTurnStart().AddDynamic(this, &AGridPawn::OnAnyTurnStart); 70 | TurnManager->OnTurnEnd().AddDynamic(this, &AGridPawn::OnAnyTurnEnd); 71 | TurnManager->OnTeamTurnStart().AddDynamic(this, &AGridPawn::OnAnyTeamTurnStart); 72 | TurnManager->OnTeamTurnEnd().AddDynamic(this, &AGridPawn::OnAnyTeamTurnEnd); 73 | TurnManager->OnReadyForInput().AddDynamic(this, &AGridPawn::OnAnyPawnReadyForInput); 74 | } 75 | 76 | SetGenericTeamId(TeamId); 77 | 78 | #if WITH_EDITORONLY_DATA 79 | GEditor->GetTimerManager()->ClearTimer(PreviewTimerHandle); 80 | #endif //WITH_EDITORONLY_DATA 81 | } 82 | 83 | void AGridPawn::OnConstruction(const FTransform & Transform) 84 | { 85 | Super::OnConstruction(Transform); 86 | 87 | #if WITH_EDITORONLY_DATA 88 | if (bPreviewTiles && IsSelectedInEditor()) 89 | { 90 | GEditor->GetTimerManager()->SetTimer(PreviewTimerHandle, this, &AGridPawn::UpdatePreviewTiles, 1, true); 91 | } 92 | else 93 | { 94 | GEditor->GetTimerManager()->ClearTimer(PreviewTimerHandle); 95 | } 96 | #endif //WITH_EDITORONLY_DATA 97 | } 98 | 99 | void AGridPawn::SetGenericTeamId(const FGenericTeamId & InTeamId) 100 | { 101 | // we must unregister before we change the team id 102 | TurnComponent->UnregisterWithTurnManager(); 103 | TeamId = InTeamId; 104 | TurnComponent->RegisterWithTurnManager(); 105 | } 106 | 107 | void AGridPawn::OnAnyTurnStart(UTurnComponent *InTurnComponent) 108 | { 109 | if (InTurnComponent == TurnComponent) 110 | { 111 | OnTurnStart(); 112 | } 113 | } 114 | 115 | void AGridPawn::OnTurnStart() 116 | { 117 | if (SnapToGrid) 118 | { 119 | MovementComponent->SnapToGrid(); 120 | } 121 | 122 | SelectedHighlight->SetVisibility(true); 123 | 124 | TurnComponent->OwnerReadyForInput(); 125 | if (!bHumanControlled) 126 | { 127 | PlayAITurn(); 128 | } 129 | } 130 | 131 | void AGridPawn::OnAnyTurnEnd(UTurnComponent *InTurnComponent) 132 | { 133 | if (InTurnComponent == TurnComponent) 134 | { 135 | OnTurnEnd(); 136 | } 137 | } 138 | 139 | void AGridPawn::OnTurnEnd() 140 | { 141 | SelectedHighlight->SetVisibility(false); 142 | MovementComponent->HidePath(); 143 | } 144 | 145 | void AGridPawn::OnAnyTeamTurnStart(const FGenericTeamId & InTeamId) 146 | { 147 | if (InTeamId == GetGenericTeamId()) 148 | { 149 | OnTeamTurnStart(); 150 | } 151 | } 152 | 153 | void AGridPawn::OnAnyTeamTurnEnd(const FGenericTeamId & InTeamId) 154 | { 155 | if (InTeamId == GetGenericTeamId()) 156 | { 157 | OnTeamTurnEnd(); 158 | } 159 | } 160 | 161 | void AGridPawn::OnMoveEnd() 162 | { 163 | //Moving costs one action point 164 | TurnComponent->RemainingActionPoints--; 165 | TurnComponent->EndTurn(); 166 | } 167 | 168 | void AGridPawn::OnAnyPawnReadyForInput(UTurnComponent * InTurnComponent) 169 | { 170 | if (InTurnComponent == TurnComponent) 171 | { 172 | OnPawnReadyForInput(); 173 | } 174 | } 175 | 176 | void AGridPawn::PlayAITurn() 177 | { 178 | //default implementation is simply to end turn 179 | TurnComponent->RemainingActionPoints = 0; 180 | TurnComponent->EndTurn(); 181 | } 182 | 183 | EGridPawnState AGridPawn::GetState() const 184 | { 185 | if (!TurnComponent->MyTurn()) 186 | { 187 | return EGridPawnState::WaitingForTurn; 188 | } 189 | else if (MovementComponent->Velocity.Size() > 0) 190 | { 191 | return EGridPawnState::Busy; 192 | } 193 | else 194 | { 195 | return EGridPawnState::Ready; 196 | } 197 | } 198 | 199 | /** Can this pawn start its turn right now? 200 | * 1) It must be the pawns teams turn 201 | * 2) It must be in the WaitingForTurn state 202 | * 3) The pawn currently in its turn must be idle 203 | * 4) It must not have used all its action points 204 | */ 205 | bool AGridPawn::CanBeSelected() 206 | { 207 | ANavGridGameState *GameState = Cast(GetWorld()->GetGameState()); 208 | if (IsValid(GameState)) 209 | { 210 | ATurnManager *TurnManager = GameState->GetTurnManager(); 211 | if (IsValid(TurnManager)) 212 | { 213 | // 1) It must be the pawns teams turn 214 | if (TurnManager->GetCurrentTeam() != TeamId) 215 | { 216 | return false; 217 | } 218 | // 2) It must be in the WaitingForTurn state 219 | if (GetState() != EGridPawnState::WaitingForTurn) 220 | { 221 | return false; 222 | } 223 | // 3) The pawn currently in its turn must be idle 224 | AGridPawn* CurrentPawn = Cast(TurnManager->GetCurrentComponent()->GetOwner()); 225 | if (!IsValid(CurrentPawn) || CurrentPawn->GetState() == EGridPawnState::Busy) 226 | { 227 | return false; 228 | } 229 | // 4) It must action points remaining 230 | return TurnComponent->RemainingActionPoints > 0; 231 | } 232 | } 233 | return false; 234 | } 235 | 236 | bool AGridPawn::CanMoveTo(const UNavTileComponent & Tile) 237 | { 238 | if (MovementComponent->GetTile() != &Tile && 239 | Tile.LegalPositionAtEndOfTurn(MovementComponent->AvailableMovementModes)) 240 | { 241 | TArray InRange; 242 | MovementComponent->GetNavGrid()->GetTilesInRange(this, InRange); 243 | if (Tile.Distance <= MovementComponent->MovementRange) 244 | { 245 | return true; 246 | } 247 | } 248 | return false; 249 | } 250 | 251 | void AGridPawn::MoveTo(const UNavTileComponent & Tile) 252 | { 253 | MovementComponent->MoveTo(Tile); 254 | MovementComponent->HidePath(); 255 | } 256 | 257 | void AGridPawn::Clicked(AActor *ClickedActor, FKey PressedKey) 258 | { 259 | if (CanBeSelected()) 260 | { 261 | TurnComponent->RequestStartTurn(); 262 | } 263 | } 264 | 265 | #if WITH_EDITORONLY_DATA 266 | void AGridPawn::OnObjectSelectedInEditor(UObject * SelectedObject) 267 | { 268 | AGridPawn *SelectedPawn = Cast(SelectedObject); 269 | if (SelectedPawn && SelectedPawn->bPreviewTiles) 270 | { 271 | if (SelectedPawn == this) 272 | { 273 | GEditor->GetTimerManager()->SetTimer(PreviewTimerHandle, this, &AGridPawn::UpdatePreviewTiles, 1, true); 274 | } 275 | else 276 | { 277 | GEditor->GetTimerManager()->ClearTimer(PreviewTimerHandle); 278 | } 279 | } 280 | } 281 | 282 | void AGridPawn::UpdatePreviewTiles() 283 | { 284 | // check if a previewgrid already exist 285 | if (!IsValid(PreviewGrid)) 286 | { 287 | for (TActorIterator Itr(GetWorld()); Itr; ++Itr) 288 | { 289 | if (Itr->Tags.Contains(FName("PreviewGrid"))) 290 | { 291 | PreviewGrid = *Itr; 292 | break; 293 | } 294 | } 295 | } 296 | 297 | // create a preview grid if no grid already exists in the level 298 | if (!IsValid(PreviewGrid)) 299 | { 300 | FActorSpawnParameters SpawnParams; 301 | SpawnParams.bAllowDuringConstructionScript = true; 302 | SpawnParams.bTemporaryEditorActor = true; 303 | SpawnParams.Name = FName(*FString::Printf(TEXT("PreviewNavGrid_%s"), *GetName())); 304 | PreviewGrid = GetWorld()->SpawnActor(SpawnParams); 305 | PreviewGrid->TileSize = PreviewTileSize; 306 | PreviewGrid->Tags.Add(FName("PreviewGrid")); 307 | 308 | TArray Tiles; 309 | ANavGrid::GetEveryTile(Tiles, GetWorld()); 310 | for (UNavTileComponent *Tile : Tiles) 311 | { 312 | Tile->SetGrid(PreviewGrid); 313 | } 314 | } 315 | 316 | TArray Tiles; 317 | PreviewGrid->GetTilesInRange(this, Tiles); 318 | for (UNavTileComponent *Tile : Tiles) 319 | { 320 | Tile->SetHighlight("Movable"); 321 | } 322 | } 323 | #endif //WITH_EDITORONLY_DATA 324 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavGrid.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | #include "AssetRegistryModule.h" 5 | 6 | #include 7 | 8 | DEFINE_LOG_CATEGORY(NavGrid); 9 | 10 | TEnumAsByte ANavGrid::ECC_NavGridWalkable = ECollisionChannel::ECC_GameTraceChannel1; 11 | FName ANavGrid::DisableVirtualTilesTag = "NavGrid:DisableVirtualTiles"; 12 | 13 | // Sets default values 14 | ANavGrid::ANavGrid() 15 | { 16 | // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. 17 | PrimaryActorTick.bCanEverTick = false; 18 | 19 | TileClass = UNavTileComponent::StaticClass(); 20 | 21 | SceneComponent = CreateDefaultSubobject("RootComponent"); 22 | RootComponent = SceneComponent; 23 | 24 | Cursor = CreateDefaultSubobject(FName("Cursor")); 25 | Cursor->SetupAttachment(GetRootComponent()); 26 | Cursor->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); 27 | Cursor->ToggleVisibility(false); 28 | auto HCRef = TEXT("StaticMesh'/NavGrid/SMesh/NavGrid_Cursor.NavGrid_Cursor'"); 29 | auto HCFinder = ConstructorHelpers::FObjectFinder(HCRef); 30 | if (HCFinder.Succeeded()) 31 | { 32 | Cursor->SetStaticMesh(HCFinder.Object); 33 | } 34 | else 35 | { 36 | UE_LOG(NavGrid, Error, TEXT("Error loading %s"), HCRef); 37 | } 38 | 39 | AddHighlightType("Movable", TEXT("Material'/NavGrid/Materials/Movable_Mat.Movable_Mat'")); 40 | AddHighlightType("Dangerous", TEXT("Material'/NavGrid/Materials/Dangerous_Mat.Dangerous_Mat'")); 41 | AddHighlightType("Special", TEXT("Material'/NavGrid/Materials/Special_Mat.Special_Mat'")); 42 | 43 | CurrentPawn = NULL; 44 | CurrentTile = NULL; 45 | } 46 | 47 | void ANavGrid::SetTileHighlight(UNavTileComponent & Tile, FName Type) 48 | { 49 | Tile.SetHighlight(Type); 50 | } 51 | 52 | void ANavGrid::ClearTileHighlights() 53 | { 54 | for (auto &H : TileHighlights) 55 | { 56 | H.Value->ClearInstances(); 57 | } 58 | } 59 | 60 | void ANavGrid::AddHighlightType(const FName &Type, const TCHAR *FileName) 61 | { 62 | TileHighLightPaths.Add(Type, FileName); 63 | } 64 | 65 | UInstancedStaticMeshComponent * ANavGrid::GetHighlightComponent(FName Type) 66 | { 67 | /* build the instanced mesh component if we have not already done so */ 68 | if (!TileHighlights.Contains(Type) && TileHighLightPaths.Contains(Type)) 69 | { 70 | FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); 71 | UStaticMesh *Mesh = LoadObject(this, TEXT("StaticMesh'/NavGrid/SMesh/NavGrid_TileHighlight.NavGrid_TileHighlight'")); 72 | check(Mesh); 73 | UMaterial *Material = LoadObject(this, TileHighLightPaths[Type]); 74 | check(Material); 75 | auto *Comp = NewObject(this); 76 | Comp->SetupAttachment(GetRootComponent()); 77 | Comp->SetStaticMesh(Mesh); 78 | Comp->SetMaterial(0, Material); 79 | Comp->SetCollisionEnabled(ECollisionEnabled::NoCollision); 80 | Comp->RegisterComponent(); 81 | Comp->bOnlyOwnerSee = true; 82 | TileHighlights.Add(Type, Comp); 83 | } 84 | /* we *should* now have the object we need*/ 85 | if (TileHighlights.Contains(Type)) 86 | { 87 | return TileHighlights[Type]; 88 | } 89 | else 90 | { 91 | return NULL; 92 | } 93 | } 94 | 95 | ANavGrid *ANavGrid::GetNavGrid(AActor *ActorInWorld) 96 | { 97 | return GetNavGrid(ActorInWorld->GetWorld()); 98 | } 99 | 100 | ANavGrid * ANavGrid::GetNavGrid(UWorld *World) 101 | { 102 | ANavGridGameState* GameState = World->GetGameState(); 103 | if (IsValid(GameState)) 104 | { 105 | return GameState->GetNavGrid(); 106 | } 107 | else 108 | { 109 | return nullptr; 110 | } 111 | } 112 | 113 | UNavTileComponent *ANavGrid::GetTile(const FVector &WorldLocation, bool FindFloor/*= true*/, float UpwardTraceLength/* = 100*/, float DownwardTraceLength/* = 100*/) 114 | { 115 | return LineTraceTile(WorldLocation, FindFloor, UpwardTraceLength, DownwardTraceLength); 116 | } 117 | 118 | UNavTileComponent * ANavGrid::LineTraceTile(const FVector & WorldLocation, bool FindFloor, float UpwardTraceLength, float DownwardTraceLength) 119 | { 120 | UNavTileComponent *Result = nullptr; 121 | 122 | if (FindFloor) 123 | { 124 | Result = LineTraceTile(WorldLocation + FVector(0, 0, UpwardTraceLength), WorldLocation - FVector(0, 0, DownwardTraceLength)); 125 | } 126 | else 127 | { 128 | /* Do a bunch of horizontal line traces and pick the closest tile component*/ 129 | UNavTileComponent *Closest = nullptr; 130 | static FVector EndPoints[8] = { 131 | FVector(0, 200, 0), 132 | FVector(200, 200, 0), 133 | FVector(200, 0, 0), 134 | FVector(200, -200, 0), 135 | FVector(0, -200, 0), 136 | FVector(-200, -200, 0), 137 | FVector(-200, 0, 0), 138 | FVector(-200, 200, 0) 139 | }; 140 | for (FVector EndPoint : EndPoints) 141 | { 142 | UNavTileComponent *Candidate = LineTraceTile(WorldLocation - EndPoint, WorldLocation + EndPoint); 143 | if (Candidate) 144 | { 145 | if (!Closest) 146 | { 147 | Closest = Candidate; 148 | } 149 | else if (FVector::Dist(Candidate->GetComponentLocation(), WorldLocation) < FVector::Dist(Closest->GetComponentLocation(), WorldLocation)) 150 | { 151 | Closest = Candidate; 152 | } 153 | } 154 | } 155 | Result = Closest; 156 | } 157 | 158 | return Result; 159 | } 160 | 161 | UNavTileComponent *ANavGrid::LineTraceTile(const FVector &Start, const FVector &End) 162 | { 163 | TArray HitResults; 164 | FCollisionQueryParams CQP; 165 | CQP.TraceTag = "NavGridTile"; 166 | 167 | GetWorld()->LineTraceMultiByChannel(HitResults, Start, End, ECC_NavGridWalkable, CQP); 168 | if (HitResults.Num()) 169 | { 170 | UPrimitiveComponent *Comp = HitResults[0].GetComponent(); 171 | return Cast(Comp); 172 | } 173 | else 174 | { 175 | return nullptr; 176 | } 177 | } 178 | 179 | void ANavGrid::TileClicked(const UNavTileComponent *Tile) 180 | { 181 | OnTileClicked.Broadcast(Tile); 182 | } 183 | 184 | void ANavGrid::TileCursorOver(const UNavTileComponent *Tile) 185 | { 186 | OnTileCursorOver.Broadcast(Tile); 187 | } 188 | 189 | void ANavGrid::EndTileCursorOver(const UNavTileComponent *Tile) 190 | { 191 | OnEndTileCursorOver.Broadcast(Tile); 192 | } 193 | 194 | void ANavGrid::CalculateTilesInRange(AGridPawn *Pawn) 195 | { 196 | QUICK_SCOPE_CYCLE_COUNTER(STAT_ANavGrid_CalculateTilesInRange); 197 | 198 | ClearTiles(); 199 | if (EnableVirtualTiles) 200 | { 201 | GenerateVirtualTiles(Pawn); 202 | } 203 | UNavTileComponent *Current = Pawn->GetTile(); 204 | /* if we're not on the grid, the number of tiles in range is zero */ 205 | if (!Current) 206 | { 207 | return; 208 | } 209 | 210 | Current->Distance = 0; 211 | TArray NeighbouringTiles; 212 | Current->GetUnobstructedNeighbours(*Pawn->MovementCollisionCapsule, NeighbouringTiles); 213 | TArray TentativeSet(NeighbouringTiles); 214 | 215 | while (Current) 216 | { 217 | Current->GetUnobstructedNeighbours(*Pawn->MovementCollisionCapsule, NeighbouringTiles); 218 | for (UNavTileComponent *N : NeighbouringTiles) 219 | { 220 | if (!N->Traversable(Pawn->MovementComponent->AvailableMovementModes)) 221 | { 222 | continue; 223 | } 224 | 225 | if (!N->Visited) 226 | { 227 | float TentativeDistance = N->Cost + Current->Distance; 228 | if (TentativeDistance <= N->Distance) 229 | { 230 | 231 | // Prioritize straight paths by using the world distance as a tiebreaker 232 | // when TentativeDistance is equal N->Dinstance 233 | float OldDistance = std::numeric_limits::infinity(); 234 | float NewDistance = 0; 235 | if (TentativeDistance == N->Distance) 236 | { 237 | NewDistance = (Current->GetComponentLocation() - N->GetComponentLocation()).Size(); 238 | if (N->Backpointer) 239 | { 240 | OldDistance = (N->Backpointer->GetComponentLocation() - N->GetComponentLocation()).Size(); 241 | } 242 | } 243 | 244 | if (NewDistance < OldDistance) // Always true if TentativeDistance < N->Distance 245 | { 246 | N->Distance = TentativeDistance; 247 | N->Backpointer = Current; 248 | 249 | if (TentativeDistance <= Pawn->MovementComponent->MovementRange) 250 | { 251 | TentativeSet.AddUnique(N); 252 | } 253 | } 254 | } 255 | } 256 | } 257 | Current->Visited = true; 258 | TentativeSet.Remove(Current); 259 | if (Current != Pawn->GetTile()) { TilesInRange.Add(Current); } // dont include the starting tile 260 | if (TentativeSet.Num()) 261 | { 262 | Current = TentativeSet[0]; 263 | } 264 | else 265 | { 266 | Current = NULL; 267 | } 268 | } 269 | } 270 | 271 | void ANavGrid::GetTilesInRange(AGridPawn *Pawn, TArray& OutTiles) 272 | { 273 | if (Pawn != CurrentPawn || Pawn->GetTile() != CurrentTile) 274 | { 275 | CalculateTilesInRange(Pawn); 276 | CurrentPawn = Pawn; 277 | CurrentTile = Pawn->GetTile(); 278 | } 279 | OutTiles = TilesInRange; 280 | } 281 | 282 | void ANavGrid::ClearTiles() 283 | { 284 | TilesInRange.Empty(); 285 | TArray AllTiles; 286 | GetEveryTile(AllTiles, GetWorld()); 287 | for (auto *T : AllTiles) 288 | { 289 | T->Reset(); 290 | } 291 | 292 | ClearTileHighlights(); 293 | NumPersistentTiles = AllTiles.Num() - VirtualTiles.Num(); 294 | } 295 | 296 | bool ANavGrid::TraceTileLocation(const FVector & TraceStart, const FVector & TraceEnd, FVector & OutTilePos) 297 | { 298 | FCollisionQueryParams CQP; 299 | CQP.bFindInitialOverlaps = true; 300 | CQP.TraceTag = "NavGridTilePlacement"; 301 | FHitResult HitResult; 302 | 303 | GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECollisionChannel::ECC_Pawn, CQP); 304 | bool bHasDisableTileTag = false; 305 | if (HitResult.Actor.IsValid()) 306 | { 307 | bHasDisableTileTag = HitResult.Actor->ActorHasTag(DisableVirtualTilesTag); 308 | } 309 | 310 | OutTilePos = HitResult.ImpactPoint; 311 | // return true if we hit the 'outside' of something that does not have the disabletile-tag 312 | return HitResult.bBlockingHit && !HitResult.bStartPenetrating && !bHasDisableTileTag; 313 | } 314 | 315 | UNavTileComponent * ANavGrid::PlaceTile(const FVector & Location, AActor * TileOwner) 316 | { 317 | if (!TileOwner) 318 | { 319 | TileOwner = this; 320 | } 321 | 322 | UNavTileComponent *TileComp = NewObject(TileOwner, TileClass); 323 | TileComp->SetupAttachment(TileOwner->GetRootComponent()); 324 | TileComp->SetWorldTransform(FTransform::Identity); 325 | TileComp->SetWorldLocation(Location); 326 | TileComp->SetBoxExtent(FVector(TileSize / 2, TileSize / 2, 5)); 327 | TileComp->RegisterComponentWithWorld(TileOwner->GetWorld()); 328 | TileComp->SetGrid(this); 329 | 330 | return TileComp; 331 | } 332 | 333 | UNavTileComponent * ANavGrid::ConsiderPlaceTile(const FVector &TraceStart, const FVector &TraceEnd, AActor * TileOwner /*= NULL*/) 334 | { 335 | if (!TileOwner) 336 | { 337 | TileOwner = this; 338 | } 339 | 340 | FVector TileLocation; 341 | bool FoundGoodLocation = TraceTileLocation(TraceStart, TraceEnd, TileLocation); 342 | if (FoundGoodLocation) 343 | { 344 | // check if we a new tile will overlap any existing tiles 345 | // use a mutlisweep as tiles returs overlap responses to this channel 346 | TArray HitResults; 347 | FCollisionShape TileShape = FCollisionShape::MakeBox(FVector(TileSize / 3, TileSize / 3, 25)); 348 | GetWorld()->SweepMultiByChannel(HitResults, TileLocation, TileLocation - FVector(0, 0, 1), FQuat::Identity, ECC_NavGridWalkable, TileShape); 349 | 350 | UNavTileComponent* ExistingTile = nullptr; 351 | for (FHitResult& HitResult : HitResults) 352 | { 353 | if (IsValid(ExistingTile = Cast(HitResult.Component.Get()))) 354 | { 355 | break; 356 | } 357 | } 358 | 359 | if (!IsValid(ExistingTile)) 360 | { 361 | return PlaceTile(TileLocation, TileOwner); 362 | } 363 | } 364 | 365 | 366 | return nullptr; 367 | } 368 | 369 | FVector ANavGrid::AdjustToTileLocation(const FVector &Location) 370 | { 371 | UNavTileComponent *SnapTile = LineTraceTile(Location, true, 100, 100); 372 | if (SnapTile) 373 | { 374 | return SnapTile->GetComponentLocation(); 375 | } 376 | 377 | // try to position the pawn so that it matches a regular grid 378 | // we do not change the vertical location 379 | FVector Offset = Location - GetActorLocation(); 380 | int32 XRest = (int32)Offset.X % (int32)TileSize; 381 | int32 YRest = (int32)Offset.Y % (int32)TileSize; 382 | FVector AdjustedLocation = Location; 383 | AdjustedLocation.X += (TileSize / 2) - XRest; 384 | AdjustedLocation.Y += (TileSize / 2) - YRest; 385 | return AdjustedLocation; 386 | } 387 | 388 | void ANavGrid::GenerateVirtualTiles(const AGridPawn *Pawn) 389 | { 390 | QUICK_SCOPE_CYCLE_COUNTER(STAT_ANavGrid_GenerateVirtualTiles); 391 | 392 | // only keep a reasonable number 393 | if (VirtualTiles.Num() > MaxVirtualTiles) 394 | { 395 | UE_LOG(NavGrid, Log, TEXT("Limit reached (%i), removing all virtual tiles"), MaxVirtualTiles); 396 | DestroyVirtualTiles(); 397 | } 398 | 399 | GenerateVirtualTile(Pawn); 400 | 401 | FVector Center = AdjustToTileLocation(Pawn->GetActorLocation()); 402 | 403 | FVector Min = Center - FVector(Pawn->MovementComponent->MovementRange * TileSize); 404 | FVector Max = Center + FVector(Pawn->MovementComponent->MovementRange * TileSize); 405 | for (float X = Min.X; X <= Max.X; X += TileSize) 406 | { 407 | for (float Y = Min.Y; Y <= Max.Y; Y += TileSize) 408 | { 409 | for (float Z = Max.Z; Z >= Min.Z; Z -= TileSize) 410 | { 411 | UNavTileComponent *TileComp = ConsiderPlaceTile(FVector(X, Y, Z + TileSize), FVector(X, Y, Z - 0.1)); 412 | if (TileComp) 413 | { 414 | VirtualTiles.Add(TileComp); 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | void ANavGrid::GenerateVirtualTile(const AGridPawn * Pawn) 422 | { 423 | FVector Location = AdjustToTileLocation(Pawn->GetActorLocation()); 424 | UNavTileComponent *TileComp = ConsiderPlaceTile(Location + FVector(0, 0, TileSize), Location - FVector(0, 0, 0.1)); 425 | if (TileComp) 426 | { 427 | VirtualTiles.Add(TileComp); 428 | } 429 | } 430 | 431 | void ANavGrid::DestroyVirtualTiles() 432 | { 433 | for (UNavTileComponent *T : VirtualTiles) 434 | { 435 | if (IsValid(T)) 436 | { 437 | T->DestroyComponent(); 438 | } 439 | } 440 | VirtualTiles.Empty(); 441 | } 442 | 443 | void ANavGrid::Destroyed() 444 | { 445 | Super::Destroyed(); 446 | DestroyVirtualTiles(); 447 | } 448 | 449 | void ANavGrid::GetEveryTile(TArray &OutTiles, UWorld * World) 450 | { 451 | for (TObjectIterator Itr; Itr; ++Itr) 452 | { 453 | if (Itr->GetWorld() == World) 454 | { 455 | OutTiles.Add(*Itr); 456 | } 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavGridGameMode.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | 5 | ANavGridGameMode::ANavGridGameMode() 6 | :Super() 7 | { 8 | PlayerControllerClass = ANavGridPC::StaticClass(); 9 | GameStateClass = ANavGridGameState::StaticClass(); 10 | } 11 | 12 | void ANavGridGameMode::BeginPlay() 13 | { 14 | Super::BeginPlay(); 15 | 16 | // Uncomment for trace debug lines 17 | //GetWorld()->DebugDrawTraceTag = "NavGridMovement"; 18 | //GetWorld()->DebugDrawTraceTag = "NavGridTile"; 19 | //GetWorld()->DebugDrawTraceTag = "NavGridTilePlacement"; 20 | } 21 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavGridGameState.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | 5 | ANavGrid* ANavGridGameState::GetNavGrid() 6 | { 7 | if (!IsValid(Grid)) 8 | { 9 | // if a navgrid exists in the game world, grab it 10 | TActorIterator GridItr(GetWorld()); 11 | if (GridItr) 12 | { 13 | Grid = *GridItr; 14 | } 15 | else 16 | { 17 | Grid = SpawnNavGrid(); 18 | } 19 | 20 | // make sure that every tile belongs to a grid 21 | TArray AllTiles; 22 | Grid->GetEveryTile(AllTiles, GetWorld()); 23 | for (UNavTileComponent* Tile : AllTiles) 24 | { 25 | if (!Tile->GetGrid()) 26 | { 27 | Tile->SetGrid(Grid); 28 | } 29 | } 30 | } 31 | return Grid; 32 | } 33 | 34 | ATurnManager* ANavGridGameState::GetTurnManager() 35 | { 36 | if (!IsValid(TurnManager)) 37 | { 38 | TurnManager = SpawnTurnManager(); 39 | } 40 | return TurnManager; 41 | } 42 | 43 | ATurnManager * ANavGridGameState::SpawnTurnManager() 44 | { 45 | ATurnManager *Manager = GetWorld()->SpawnActor(); 46 | Manager->SetOwner(this); 47 | return Manager; 48 | } 49 | 50 | ANavGrid * ANavGridGameState::SpawnNavGrid() 51 | { 52 | ANavGrid *NewGrid = GetWorld()->SpawnActor(); 53 | NewGrid->SetOwner(this); 54 | return NewGrid; 55 | } 56 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavGridPC.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | 5 | ANavGridPC::ANavGridPC(const FObjectInitializer& ObjectInitializer) 6 | :Super(ObjectInitializer) 7 | { 8 | bShowMouseCursor = true; 9 | /* Enable mouse events */ 10 | bEnableClickEvents = true; 11 | bEnableMouseOverEvents = true; 12 | bEnableTouchEvents = true; 13 | bEnableTouchOverEvents = true; 14 | } 15 | 16 | void ANavGridPC::BeginPlay() 17 | { 18 | // grab turn manager and grid from the game state 19 | auto *State = GetWorld()->GetGameState(); 20 | SetTurnManager(State->GetTurnManager()); 21 | SetGrid(State->GetNavGrid()); 22 | } 23 | 24 | void ANavGridPC::OnTileClicked(const UNavTileComponent *Tile) 25 | { 26 | /* Try to move the current pawn to the clicked tile */ 27 | if (GridPawn && GridPawn->GetState() == EGridPawnState::Ready) 28 | { 29 | if (GridPawn->CanMoveTo(*Tile)) 30 | { 31 | GridPawn->MoveTo(*Tile); 32 | } 33 | } 34 | } 35 | 36 | void ANavGridPC::OnTileCursorOver(const UNavTileComponent *Tile) 37 | { 38 | /* If the pawn is not moving, try to create a path to the hovered tile and show it */ 39 | if (GridPawn && GridPawn->GetState() == EGridPawnState::Ready) 40 | { 41 | Grid->Cursor->SetWorldLocation(Tile->GetPawnLocation() + FVector(0, 0, Grid->UIOffset)); 42 | Grid->Cursor->SetVisibility(true); 43 | 44 | UGridMovementComponent *MovementComponent = GridPawn->MovementComponent; 45 | if (GridPawn->CanMoveTo(*Tile)) 46 | { 47 | MovementComponent->CreatePath(*Tile); 48 | MovementComponent->ShowPath(); 49 | } 50 | } 51 | } 52 | 53 | void ANavGridPC::OnEndTileCursorOver(const UNavTileComponent *Tile) 54 | { 55 | Grid->Cursor->SetVisibility(false); 56 | /* Hide the previously shown path */ 57 | if (GridPawn) 58 | { 59 | UGridMovementComponent *MovementComponent = GridPawn->MovementComponent; 60 | MovementComponent->HidePath(); 61 | } 62 | } 63 | 64 | void ANavGridPC::OnTurnStart(UTurnComponent *Component) 65 | { 66 | if (Component->GetOwner()->IsA()) 67 | { 68 | GridPawn = Cast(Component->GetOwner()); 69 | } 70 | } 71 | 72 | void ANavGridPC::OnTurnEnd(UTurnComponent * Component) 73 | { 74 | GridPawn = NULL; 75 | } 76 | 77 | void ANavGridPC::SetTurnManager(ATurnManager * InTurnManager) 78 | { 79 | check(InTurnManager); 80 | 81 | // unregister any delegates from the previous manager 82 | if (TurnManager) 83 | { 84 | TurnManager->OnRoundStart().RemoveDynamic(this, &ANavGridPC::OnRoundStart); 85 | TurnManager->OnTurnStart().RemoveDynamic(this, &ANavGridPC::OnTurnStart); 86 | TurnManager->OnTurnEnd().RemoveDynamic(this, &ANavGridPC::OnTurnEnd); 87 | TurnManager->OnTeamTurnStart().RemoveDynamic(this, &ANavGridPC::OnTeamTurnStart); 88 | } 89 | 90 | TurnManager = InTurnManager; 91 | TurnManager->OnRoundStart().AddDynamic(this, &ANavGridPC::OnRoundStart); 92 | TurnManager->OnTurnStart().AddDynamic(this, &ANavGridPC::OnTurnStart); 93 | TurnManager->OnTurnEnd().AddDynamic(this, &ANavGridPC::OnTurnEnd); 94 | TurnManager->OnTeamTurnStart().AddDynamic(this, &ANavGridPC::OnTeamTurnStart); 95 | } 96 | 97 | void ANavGridPC::SetGrid(ANavGrid * InGrid) 98 | { 99 | check(InGrid); 100 | if (Grid) 101 | { 102 | Grid->OnTileClicked.RemoveDynamic(this, &ANavGridPC::OnTileClicked); 103 | Grid->OnTileCursorOver.RemoveDynamic(this, &ANavGridPC::OnTileCursorOver); 104 | Grid->OnEndTileCursorOver.RemoveDynamic(this, &ANavGridPC::OnEndTileCursorOver); 105 | } 106 | 107 | Grid = InGrid; 108 | Grid->OnTileClicked.AddDynamic(this, &ANavGridPC::OnTileClicked); 109 | Grid->OnTileCursorOver.AddDynamic(this, &ANavGridPC::OnTileCursorOver); 110 | Grid->OnEndTileCursorOver.AddDynamic(this, &ANavGridPC::OnEndTileCursorOver); 111 | } 112 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavGridPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "NavGridPrivatePCH.h" 2 | #include "NavGridPlugin.h" 3 | 4 | 5 | void NavGridPluginImpl::StartupModule() 6 | { 7 | UE_LOG(NavGrid, Log, TEXT("Starting")); 8 | } 9 | 10 | void NavGridPluginImpl::ShutdownModule() 11 | { 12 | UE_LOG(NavGrid, Log, TEXT("Shutting down")); 13 | } 14 | 15 | IMPLEMENT_MODULE(NavGridPluginImpl, NavGrid) 16 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavGridPlugin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ModuleManager.h" 4 | 5 | class NavGridPluginImpl : public IModuleInterface 6 | { 7 | public: 8 | /** IModuleInterface implementation */ 9 | void StartupModule(); 10 | void ShutdownModule(); 11 | }; -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavGridPrivatePCH.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Engine.h" 4 | #include "NavGrid.h" 5 | 6 | #include "../Classes/NavGrid.h" 7 | #include "../Classes/NavTileComponent.h" 8 | #include "../Classes/NavLadderComponent.h" 9 | #include "../Classes/GridPawn.h" 10 | #include "../Classes/GridMovementComponent.h" 11 | #include "../Classes/TurnComponent.h" 12 | #include "../Classes/TurnManager.h" 13 | #include "../Classes/NavTileActor.h" 14 | #include "../Classes/NavLadderActor.h" 15 | #include "../Classes/NavGridGameMode.h" 16 | #include "../Classes/NavGridGameState.h" 17 | #include "../Classes/NavGridPC.h" 18 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavLadderActor.cpp: -------------------------------------------------------------------------------- 1 | #include "NavGridPrivatePCH.h" 2 | #include "NavLadderActor.h" 3 | 4 | ANavLadderActor::ANavLadderActor(const FObjectInitializer &ObjectInitializer) 5 | : Super(ObjectInitializer) 6 | { 7 | SceneComponent = CreateDefaultSubobject("RootComponent"); 8 | RootComponent = SceneComponent; 9 | 10 | NavLadderComponent = CreateDefaultSubobject("NavLadderComponent"); 11 | NavLadderComponent->SetRelativeLocation(FVector(0, 0, 150)); 12 | NavLadderComponent->SetBoxExtent(FVector(5, 100, 150)); 13 | NavLadderComponent->SetupAttachment(SceneComponent); 14 | 15 | Mesh = CreateDefaultSubobject("StaticMesh"); 16 | Mesh->SetupAttachment(SceneComponent); 17 | 18 | const TCHAR* AssRef = TEXT("StaticMesh'/NavGrid/SMesh/NavGrid_Ladder.NavGrid_Ladder'"); 19 | auto OF = ConstructorHelpers::FObjectFinder(AssRef); 20 | if (OF.Succeeded()) 21 | { 22 | Mesh->SetStaticMesh(OF.Object); 23 | } 24 | else 25 | { 26 | UE_LOG(NavGrid, Error, TEXT("Error loading %s"), AssRef); 27 | } 28 | } -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavLadderComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | 5 | UNavLadderComponent::UNavLadderComponent() 6 | :Super() 7 | { 8 | MovementModes.Empty(); 9 | MovementModes.Add(EGridMovementMode::ClimbingUp); 10 | MovementModes.Add(EGridMovementMode::ClimbingDown); 11 | } 12 | 13 | void UNavLadderComponent::SetGrid(ANavGrid *InGrid) 14 | { 15 | Super::SetGrid(InGrid); 16 | TileSize = InGrid->TileSize; 17 | } 18 | 19 | void UNavLadderComponent::GetNeighbours(const UCapsuleComponent &CollisionCapsule, TArray &OutUnObstructed, TArray &OutObstructed) 20 | { 21 | OutUnObstructed.Empty(); 22 | OutObstructed.Empty(); 23 | if (IsValid(Grid)) 24 | { 25 | FCollisionShape Shape = FCollisionShape::MakeBox(BoxExtent + FVector(Grid->TileSize / 2)); 26 | 27 | TArray HitResults; 28 | TArray AllNeighbours; 29 | Grid->GetWorld()->SweepMultiByChannel(HitResults, GetComponentLocation(), GetComponentLocation() + FVector(0, 0, 1), GetComponentQuat(), Grid->ECC_NavGridWalkable, Shape); 30 | for (FHitResult &Hit : HitResults) 31 | { 32 | UNavTileComponent *HitTile = Cast(Hit.GetComponent()); 33 | if (IsValid(HitTile) && HitTile != this) 34 | { 35 | AllNeighbours.AddUnique(HitTile); 36 | } 37 | } 38 | 39 | for (UNavTileComponent *N : AllNeighbours) 40 | { 41 | //Determine if we should trace from the top or bottom point 42 | float TopDistance = (GetTopPathPoint() - N->GetPawnLocation()).Size(); 43 | float BottomDistance = (GetBottomPathPoint() - N->GetPawnLocation()).Size(); 44 | FVector TracePoint = TopDistance < BottomDistance ? GetTopPathPoint() : GetBottomPathPoint(); 45 | if (N->Obstructed(TracePoint, CollisionCapsule)) 46 | { 47 | OutObstructed.Add(N); 48 | } 49 | else 50 | { 51 | OutUnObstructed.Add(N); 52 | } 53 | } 54 | } 55 | } 56 | 57 | bool UNavLadderComponent::Obstructed(const FVector & FromPos, const UCapsuleComponent & CollisionCapsule) const 58 | { 59 | //Determine if we should trace to the top or bottom point 60 | float TopDistance = (GetTopPathPoint() - FromPos).Size(); 61 | float BottomDistance = (GetBottomPathPoint() - FromPos).Size(); 62 | FVector TracePoint = TopDistance < BottomDistance ? GetTopPathPoint() : GetBottomPathPoint(); 63 | 64 | FHitResult OutHit; 65 | FCollisionShape CollisionShape = CollisionCapsule.GetCollisionShape(); 66 | FCollisionQueryParams CQP; 67 | CQP.AddIgnoredActor(CollisionCapsule.GetOwner()); 68 | CQP.TraceTag = "NavGridMovement"; 69 | return CollisionCapsule.GetWorld()->SweepSingleByChannel(OutHit, FromPos + CollisionCapsule.GetRelativeLocation(), TracePoint + CollisionCapsule.GetRelativeLocation(), 70 | GetComponentQuat(), ECollisionChannel::ECC_Pawn, CollisionShape, CQP); 71 | } 72 | 73 | void UNavLadderComponent::AddPathSegments(USplineComponent &OutSpline, TArray &OutPathSegments, bool EndTile) const 74 | { 75 | FVector EntryPoint = OutSpline.GetLocationAtSplinePoint(OutSpline.GetNumberOfSplinePoints() - 1, ESplineCoordinateSpace::Local); 76 | float TopDistance = (GetTopPathPoint() - EntryPoint).Size(); 77 | float BottomDistance = (GetBottomPathPoint() - EntryPoint).Size(); 78 | 79 | FPathSegment NewSegment; 80 | NewSegment.MovementModes = MovementModes; 81 | NewSegment.PawnRotationHint = GetComponentRotation(); 82 | NewSegment.PawnRotationHint.Yaw -= 180; 83 | 84 | // add spline points and segments 85 | if (TopDistance > BottomDistance) 86 | { 87 | OutSpline.AddSplinePoint(GetBottomPathPoint(), ESplineCoordinateSpace::Local); 88 | NewSegment.Start = OutSpline.GetSplineLength(); 89 | OutSpline.AddSplinePoint(GetTopPathPoint(), ESplineCoordinateSpace::Local); 90 | NewSegment.End = OutSpline.GetSplineLength(); 91 | } 92 | else 93 | { 94 | OutSpline.AddSplinePoint(GetTopPathPoint(), ESplineCoordinateSpace::Local); 95 | NewSegment.Start = OutSpline.GetSplineLength(); 96 | OutSpline.AddSplinePoint(GetBottomPathPoint(), ESplineCoordinateSpace::Local); 97 | NewSegment.End = OutSpline.GetSplineLength(); 98 | } 99 | 100 | // unlike regular tiles, we do not want the pawn to change movement mode untill it reaches the first path point 101 | // we therefore extend the previous segment to that point 102 | if (OutPathSegments.Num()) 103 | { 104 | OutPathSegments.Last().End = NewSegment.Start; 105 | } 106 | 107 | // add the new segment 108 | OutPathSegments.Add(NewSegment); 109 | 110 | if (EndTile) 111 | { 112 | OutSpline.RemoveSplinePoint(OutSpline.GetNumberOfSplinePoints() - 1); 113 | OutSpline.AddSplinePoint(PawnLocationOffset + GetComponentLocation(), ESplineCoordinateSpace::Local); 114 | } 115 | } 116 | 117 | FVector UNavLadderComponent::GetSplineMeshUpVector() 118 | { 119 | FRotator Rot = GetComponentRotation(); 120 | FVector UpVector = Rot.RotateVector(FVector(0, -1, 0)); 121 | return UpVector; 122 | } 123 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavTileActor.cpp: -------------------------------------------------------------------------------- 1 | #include "NavGridPrivatePCH.h" 2 | #include "NavTileActor.h" 3 | 4 | ANavTileActor::ANavTileActor(const FObjectInitializer &ObjectInitializer) 5 | :Super(ObjectInitializer) 6 | { 7 | SceneComponent = CreateDefaultSubobject("RootComponent"); 8 | RootComponent = SceneComponent; 9 | Mesh = CreateDefaultSubobject("StaticMesh"); 10 | Mesh->SetupAttachment(SceneComponent); 11 | NavTileComponent = CreateDefaultSubobject("NavTileComponent"); 12 | NavTileComponent->SetupAttachment(SceneComponent); 13 | NavTileComponent->SetBoxExtent(FVector(100, 100, 5)); 14 | 15 | const TCHAR* AssRef = TEXT("StaticMesh'/NavGrid/SMesh/NavGrid_Tile.NavGrid_Tile'"); 16 | auto OF = ConstructorHelpers::FObjectFinder(AssRef); 17 | if (OF.Succeeded()) 18 | { 19 | Mesh->SetStaticMesh(OF.Object); 20 | } 21 | else 22 | { 23 | UE_LOG(NavGrid, Error, TEXT("Error loading %s"), AssRef); 24 | } 25 | 26 | SetActorTickEnabled(false); 27 | } 28 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/NavTileComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | #include 5 | #include "Components/CapsuleComponent.h" 6 | #include "DrawDebugHelpers.h" 7 | 8 | UNavTileComponent::UNavTileComponent() 9 | :Super() 10 | { 11 | PawnLocationOffset = FVector::ZeroVector; 12 | SetComponentTickEnabled(false); 13 | 14 | /* Bind mouse events */ 15 | OnBeginCursorOver.AddDynamic(this, &UNavTileComponent::CursorOver); 16 | OnEndCursorOver.AddDynamic(this, &UNavTileComponent::EndCursorOver); 17 | OnClicked.AddDynamic(this, &UNavTileComponent::Clicked); 18 | /* Bind touch events */ 19 | OnInputTouchEnter.AddDynamic(this, &UNavTileComponent::TouchEnter); 20 | OnInputTouchLeave.AddDynamic(this, &UNavTileComponent::TouchLeave); 21 | OnInputTouchEnd.AddDynamic(this, &UNavTileComponent::TouchEnd); 22 | 23 | SetCollisionEnabled(ECollisionEnabled::QueryOnly); 24 | SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore); 25 | SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block); // So we get mouse over events 26 | SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Block); // So we get mouse over events 27 | SetCollisionResponseToChannel(ANavGrid::ECC_NavGridWalkable, ECollisionResponse::ECR_Overlap); // So we can find the floor with a line trace 28 | 29 | MovementModes.Add(EGridMovementMode::Stationary); 30 | MovementModes.Add(EGridMovementMode::Walking); 31 | MovementModes.Add(EGridMovementMode::InPlaceTurn); 32 | 33 | ShapeColor = FColor::Magenta; 34 | 35 | Reset(); 36 | } 37 | 38 | bool UNavTileComponent::Traversable(const TSet& PawnMovementModes) const 39 | { 40 | return MovementModes.Intersect(PawnMovementModes).Num() > 0; 41 | } 42 | 43 | bool UNavTileComponent::LegalPositionAtEndOfTurn(const TSet &PawnMovementModes) const 44 | { 45 | return MovementModes.Contains(EGridMovementMode::Stationary); 46 | } 47 | 48 | FVector UNavTileComponent::GetPawnLocation() const 49 | { 50 | return GetComponentLocation() + GetComponentRotation().RotateVector(PawnLocationOffset); 51 | } 52 | 53 | void UNavTileComponent::SetPawnLocationOffset(const FVector &Offset) 54 | { 55 | PawnLocationOffset = Offset; 56 | } 57 | 58 | void UNavTileComponent::SetGrid(ANavGrid * InGrid) 59 | { 60 | Grid = InGrid; 61 | } 62 | 63 | ANavGrid * UNavTileComponent::GetGrid() const 64 | { 65 | return Grid; 66 | } 67 | 68 | void UNavTileComponent::Reset() 69 | { 70 | Distance = std::numeric_limits::infinity(); 71 | Backpointer = NULL; 72 | Visited = false; 73 | } 74 | 75 | bool UNavTileComponent::Obstructed(const FVector &FromPos, const UCapsuleComponent &CollisionCapsule) const 76 | { 77 | return Obstructed(FromPos + CollisionCapsule.GetRelativeLocation(), GetPawnLocation() + CollisionCapsule.GetRelativeLocation(), CollisionCapsule); 78 | } 79 | 80 | bool UNavTileComponent::Obstructed(const FVector &From, const FVector &To, const UCapsuleComponent &CollisionCapsule) const 81 | { 82 | FHitResult OutHit; 83 | FQuat Rot = FQuat::Identity; 84 | FCollisionShape CollisionShape = CollisionCapsule.GetCollisionShape(); 85 | FCollisionQueryParams CQP; 86 | CQP.AddIgnoredActor(CollisionCapsule.GetOwner()); 87 | CQP.TraceTag = "NavGridMovement"; 88 | return CollisionCapsule.GetWorld()->SweepSingleByChannel(OutHit, From, To, Rot, ECollisionChannel::ECC_Pawn, CollisionShape, CQP); 89 | } 90 | 91 | void UNavTileComponent::GetNeighbours(const UCapsuleComponent & CollisionCapsule, TArray& OutUnObstructed, TArray& OutObstructed) 92 | { 93 | QUICK_SCOPE_CYCLE_COUNTER(STAT_UNavTileComponent_GetNeighbours); 94 | 95 | OutUnObstructed.Empty(); 96 | OutObstructed.Empty(); 97 | 98 | if (IsValid(Grid)) 99 | { 100 | FVector MyExtent = BoxExtent + FVector(Grid->TileSize * 0.75); 101 | TArray HitResults; 102 | Grid->GetWorld()->SweepMultiByChannel(HitResults, GetComponentLocation(), GetComponentLocation() + FVector(0, 0, 1), GetComponentQuat(), Grid->ECC_NavGridWalkable, FCollisionShape::MakeBox(MyExtent)); 103 | for (FHitResult &Hit : HitResults) 104 | { 105 | UNavTileComponent *HitTile = Cast(Hit.GetComponent()); 106 | if (IsValid(HitTile)) 107 | { 108 | if (HitTile != this && !HitTile->Obstructed(GetPawnLocation(), CollisionCapsule)) 109 | { 110 | OutUnObstructed.AddUnique(HitTile); 111 | } 112 | else 113 | { 114 | OutObstructed.AddUnique(HitTile); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | void UNavTileComponent::GetUnobstructedNeighbours(const UCapsuleComponent &CollisionCapsule, TArray &OutNeighbours) 122 | { 123 | TArray Dummy; 124 | GetNeighbours(CollisionCapsule, OutNeighbours, Dummy); 125 | } 126 | 127 | void UNavTileComponent::Clicked(UPrimitiveComponent* TouchedComponent, FKey Key) 128 | { 129 | Grid->TileClicked(this); 130 | } 131 | 132 | void UNavTileComponent::CursorOver(UPrimitiveComponent* TouchedComponent) 133 | { 134 | Grid->TileCursorOver(this); 135 | } 136 | 137 | void UNavTileComponent::EndCursorOver(UPrimitiveComponent* TouchedComponent) 138 | { 139 | Grid->EndTileCursorOver(this); 140 | } 141 | 142 | void UNavTileComponent::TouchEnter(ETouchIndex::Type Type, UPrimitiveComponent* TouchedComponent) 143 | { 144 | CursorOver(TouchedComponent); 145 | } 146 | 147 | void UNavTileComponent::TouchLeave(ETouchIndex::Type Type, UPrimitiveComponent* TouchedComponent) 148 | { 149 | EndCursorOver(TouchedComponent); 150 | } 151 | 152 | void UNavTileComponent::TouchEnd(ETouchIndex::Type Type, UPrimitiveComponent* TouchedComponent) 153 | { 154 | Grid->TileClicked(this); 155 | } 156 | 157 | void UNavTileComponent::AddPathSegments(USplineComponent &OutSpline, TArray &OutPathSegments, bool EndTile) const 158 | { 159 | FVector EntryPoint = OutSpline.GetLocationAtSplinePoint(OutSpline.GetNumberOfSplinePoints() - 1, ESplineCoordinateSpace::Local); 160 | float SegmentStart = OutSpline.GetSplineLength(); 161 | OutSpline.AddSplinePoint(GetComponentLocation() + PawnLocationOffset, ESplineCoordinateSpace::Local); 162 | OutPathSegments.Add(FPathSegment(MovementModes, SegmentStart, OutSpline.GetSplineLength())); 163 | } 164 | 165 | FVector UNavTileComponent::GetSplineMeshUpVector() 166 | { 167 | return FVector(0, 0, 1); 168 | } 169 | 170 | void UNavTileComponent::SetHighlight(FName NewHighlightType) 171 | { 172 | auto *HighlightComponent = Grid->GetHighlightComponent(NewHighlightType); 173 | if (HighlightComponent) 174 | { 175 | FVector MeshSize = HighlightComponent->GetStaticMesh()->GetBoundingBox().GetSize(); 176 | FVector TileSize = GetScaledBoxExtent() * 2; 177 | FTransform Transform = GetComponentTransform(); 178 | Transform.SetScale3D(FVector(TileSize.X / MeshSize.X, TileSize.Y / MeshSize.Y, 1)); 179 | HighlightComponent->AddInstanceWorldSpace(Transform); 180 | } 181 | } 182 | 183 | void UNavTileComponent::DrawDebug(UCapsuleComponent *CollisionCapsule, bool bPersistentLines, float LifeTime, float Thickness) 184 | { 185 | DrawDebugCapsule(GetWorld(), GetPawnLocation() + CollisionCapsule->GetRelativeLocation(), CollisionCapsule->GetScaledCapsuleHalfHeight(), CollisionCapsule->GetScaledCapsuleRadius(), 186 | CollisionCapsule->GetComponentQuat(), FColor::Cyan, bPersistentLines, LifeTime, 0, Thickness); 187 | DrawDebugBox(GetWorld(), GetComponentLocation(), BoxExtent, GetComponentQuat(), FColor::Cyan, bPersistentLines, LifeTime, 0, Thickness); 188 | if (IsValid(Grid)) 189 | { 190 | DrawDebugBox(GetWorld(), GetComponentLocation(), BoxExtent + FVector(Grid->TileSize * 0.75), GetComponentQuat(), FColor::Blue, bPersistentLines, LifeTime, 0, Thickness); 191 | } 192 | TArray UnObstructed, Obstructed; 193 | GetNeighbours(*CollisionCapsule, UnObstructed, Obstructed); 194 | for (UNavTileComponent *Tile : UnObstructed) 195 | { 196 | DrawDebugLine(GetWorld(), GetPawnLocation() + CollisionCapsule->GetRelativeLocation(), CollisionCapsule->GetRelativeLocation() + ((GetPawnLocation() + Tile->GetPawnLocation()) / 2), FColor::Green, bPersistentLines, LifeTime, 0, Thickness); 197 | } 198 | for (UNavTileComponent *Tile : Obstructed) 199 | { 200 | DrawDebugLine(GetWorld(), GetPawnLocation() + CollisionCapsule->GetRelativeLocation(), CollisionCapsule->GetRelativeLocation() + ((GetPawnLocation() + Tile->GetPawnLocation()) / 2), FColor::Red, bPersistentLines, LifeTime, 0, Thickness); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/TurnComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "NavGridPrivatePCH.h" 4 | 5 | UTurnComponent::UTurnComponent() 6 | :Super(), 7 | TurnManager(nullptr), 8 | StartingActionPoints(1), 9 | RemainingActionPoints(1), 10 | TurnTimeout(30) 11 | { 12 | } 13 | 14 | void UTurnComponent::BeginPlay() 15 | { 16 | Super::BeginPlay(); 17 | RegisterWithTurnManager(); 18 | } 19 | 20 | void UTurnComponent::OnComponentDestroyed(bool bDestroyingHierarchy) 21 | { 22 | Super::OnComponentDestroyed(bDestroyingHierarchy); 23 | UnregisterWithTurnManager(); 24 | } 25 | 26 | ATurnManager * UTurnComponent::GetTurnManager() 27 | { 28 | if (!IsValid(TurnManager)) 29 | { 30 | RegisterWithTurnManager(); 31 | } 32 | return TurnManager; 33 | } 34 | 35 | void UTurnComponent::EndTurn() 36 | { 37 | if (IsValid(TurnManager)) 38 | { 39 | TurnManager->EndTurn(this); 40 | } 41 | } 42 | 43 | void UTurnComponent::EndTeamTurn() 44 | { 45 | if (IsValid(TurnManager)) 46 | { 47 | TurnManager->EndTeamTurn(FGenericTeamId::GetTeamIdentifier(GetOwner())); 48 | } 49 | } 50 | 51 | void UTurnComponent::RequestStartTurn() 52 | { 53 | if (IsValid(TurnManager)) 54 | { 55 | TurnManager->RequestStartTurn(this); 56 | 57 | } 58 | } 59 | 60 | void UTurnComponent::RequestStartNextComponent() 61 | { 62 | if (IsValid(TurnManager)) 63 | { 64 | TurnManager->RequestStartNextComponent(this); 65 | } 66 | } 67 | 68 | void UTurnComponent::OnTurnTimeout() 69 | { 70 | if (MyTurn()) 71 | { 72 | UE_LOG(NavGrid, Warning, TEXT("Turn timeout (%f sec) reached for %s"), TurnTimeout, *GetOwner()->GetName()); 73 | RemainingActionPoints = 0; 74 | EndTurn(); 75 | } 76 | } 77 | 78 | void UTurnComponent::OwnerReadyForInput() 79 | { 80 | if (IsValid(TurnManager) && TurnManager->GetCurrentComponent() == this) 81 | { 82 | TurnManager->OnReadyForInput().Broadcast(this); 83 | } 84 | } 85 | 86 | AActor *UTurnComponent::GetCurrentActor() const 87 | { 88 | if (IsValid(TurnManager)) 89 | { 90 | return TurnManager->GetCurrentActor(); 91 | } 92 | return nullptr; 93 | } 94 | 95 | void UTurnComponent::RegisterWithTurnManager() 96 | { 97 | UnregisterWithTurnManager(); 98 | ANavGridGameState *GameState = GetWorld()->GetGameState(); 99 | if (IsValid(GameState)) 100 | { 101 | TurnManager = GameState->GetTurnManager(); 102 | TurnManager->RegisterTurnComponent(this); 103 | } 104 | } 105 | 106 | void UTurnComponent::UnregisterWithTurnManager() 107 | { 108 | if (IsValid(TurnManager)) 109 | { 110 | TurnManager->UnregisterTurnComponent(this); 111 | } 112 | TurnManager = nullptr; 113 | } 114 | 115 | void UTurnComponent::OnTurnStart() 116 | { 117 | GetWorld()->GetTimerManager().SetTimer(TurnTimeoutHandle, this, &UTurnComponent::OnTurnTimeout, TurnTimeout); 118 | } 119 | 120 | void UTurnComponent::OnTurnEnd() 121 | { 122 | GetWorld()->GetTimerManager().ClearTimer(TurnTimeoutHandle); 123 | } 124 | -------------------------------------------------------------------------------- /Source/Navgrid/Private/TurnManager.cpp: -------------------------------------------------------------------------------- 1 | #include "NavGridPrivatePCH.h" 2 | 3 | ATurnManager::ATurnManager() : 4 | MinNumberOfTeams(1), 5 | CurrentComponent(nullptr), 6 | NextComponent(nullptr), 7 | Round(0), 8 | bStartNewTurn(true) 9 | { 10 | PrimaryActorTick.bCanEverTick = true; 11 | } 12 | 13 | void ATurnManager::Tick(float DeltaTime) 14 | { 15 | Super::Tick(DeltaTime); 16 | 17 | TArray Keys; 18 | Teams.GetKeys(Keys); 19 | if (bStartNewTurn && Keys.Num() >= MinNumberOfTeams) 20 | { 21 | // broadcast TurnEnd and TeamTurnEnd 22 | if (IsValid(CurrentComponent)) 23 | { 24 | CurrentComponent->OnTurnEnd(); 25 | OnTurnEnd().Broadcast(CurrentComponent); 26 | if (!IsValid(FindNextTeamMember(CurrentComponent->TeamId()))) 27 | { 28 | OnTeamTurnEnd().Broadcast(CurrentComponent->TeamId()); 29 | } 30 | } 31 | 32 | // start a new round if no more components can act this turn 33 | if (Round == 0 || !HasComponentsThatCanAct()) 34 | { 35 | if (Round > 0) 36 | { 37 | OnRoundEnd().Broadcast(); 38 | } 39 | 40 | for (TPair &Pair : Teams) 41 | { 42 | Pair.Value->RemainingActionPoints = Pair.Value->StartingActionPoints; 43 | } 44 | 45 | CurrentComponent = nullptr; 46 | NextComponent = nullptr; 47 | 48 | Round++; 49 | UE_LOG(NavGrid, Log, TEXT("Starting round %i"), Round); 50 | OnRoundStart().Broadcast(); 51 | } 52 | 53 | // figure out which component that has the next turn 54 | if (!IsValid(NextComponent) || NextComponent->RemainingActionPoints <= 0) 55 | { 56 | if (IsValid(CurrentComponent) && CurrentComponent->RemainingActionPoints > 0) 57 | { 58 | NextComponent = CurrentComponent; 59 | } 60 | else 61 | { 62 | NextComponent = FindNextComponent(); 63 | } 64 | } 65 | 66 | // broadcast TurnStart and TeamTurnStart 67 | check(IsValid(NextComponent)) 68 | UTurnComponent *PreviousComponent = CurrentComponent; 69 | CurrentComponent = NextComponent; 70 | if (!IsValid(PreviousComponent) || CurrentComponent->TeamId() != PreviousComponent->TeamId()) 71 | { 72 | UE_LOG(NavGrid, Log, TEXT("Starting team turn for team %i"), CurrentComponent->TeamId().GetId()); 73 | OnTeamTurnStart().Broadcast(CurrentComponent->TeamId()); 74 | } 75 | CurrentComponent->OnTurnStart(); 76 | OnTurnStart().Broadcast(CurrentComponent); 77 | 78 | NextComponent = nullptr; 79 | bStartNewTurn = false; 80 | } 81 | } 82 | 83 | void ATurnManager::RegisterTurnComponent(UTurnComponent *TurnComponent) 84 | { 85 | UE_LOG(NavGrid, Verbose, TEXT("%s (team %i) registering"), *TurnComponent->GetName(), TurnComponent->TeamId().GetId()); 86 | Teams.AddUnique(TurnComponent->TeamId(), TurnComponent); 87 | } 88 | 89 | void ATurnManager::UnregisterTurnComponent(UTurnComponent * TurnComponent) 90 | { 91 | UE_LOG(NavGrid, Verbose, TEXT("%s (team %i) unregistering"), *TurnComponent->GetName(), TurnComponent->TeamId().GetId()); 92 | Teams.RemoveSingle(TurnComponent->TeamId(), TurnComponent); 93 | if (CurrentComponent == TurnComponent) 94 | { 95 | CurrentComponent = nullptr; 96 | bStartNewTurn = true; 97 | } 98 | if (NextComponent == TurnComponent) 99 | { 100 | NextComponent = nullptr; 101 | } 102 | } 103 | 104 | void ATurnManager::EndTurn(UTurnComponent *Ender) 105 | { 106 | if (Ender == CurrentComponent) 107 | { 108 | bStartNewTurn = true; 109 | } 110 | else 111 | { 112 | if (IsValid(CurrentComponent)) 113 | { 114 | UE_LOG(NavGrid, Warning, TEXT("ATurnManager::EndTurn(%s): CurrentComponent: %s"), *Ender->GetOwner()->GetName(), *CurrentComponent->GetOwner()->GetName()); 115 | } 116 | else 117 | { 118 | UE_LOG(NavGrid, Warning, TEXT("ATurnManager::EndTurn(%s): CurrentComponent: null"), *Ender->GetOwner()->GetName()); 119 | } 120 | } 121 | } 122 | 123 | void ATurnManager::EndTeamTurn(FGenericTeamId InTeamId) 124 | { 125 | if (CurrentComponent->TeamId() == InTeamId) 126 | { 127 | TArray TeamMemebers; 128 | Teams.MultiFind(InTeamId, TeamMemebers); 129 | for (UTurnComponent *Member : TeamMemebers) 130 | { 131 | Member->RemainingActionPoints = 0; 132 | } 133 | 134 | bStartNewTurn = true; 135 | } 136 | } 137 | 138 | void ATurnManager::RequestStartTurn(UTurnComponent * CallingComponent) 139 | { 140 | if (!IsValid(CurrentComponent) || CurrentComponent->TeamId() == CallingComponent->TeamId()) 141 | { 142 | NextComponent = CallingComponent; 143 | bStartNewTurn = true; 144 | } 145 | } 146 | 147 | void ATurnManager::RequestStartNextComponent(UTurnComponent *CallingComponent) 148 | { 149 | if (IsValid(CurrentComponent) && CurrentComponent->TeamId() == CallingComponent->TeamId()) 150 | { 151 | UTurnComponent *Candidate = FindNextTeamMember(CallingComponent->TeamId()); 152 | if (IsValid(Candidate)) 153 | { 154 | NextComponent = Candidate; 155 | bStartNewTurn = true; 156 | } 157 | } 158 | } 159 | 160 | FGenericTeamId ATurnManager::GetCurrentTeam() const 161 | { 162 | return CurrentComponent ? CurrentComponent->TeamId() : FGenericTeamId::NoTeam; 163 | } 164 | 165 | AActor *ATurnManager::GetCurrentActor() const 166 | { 167 | return IsValid(CurrentComponent) ? CurrentComponent->GetOwner() : nullptr; 168 | } 169 | 170 | UTurnComponent * ATurnManager::FindNextTeamMember(const FGenericTeamId & TeamId) 171 | { 172 | TArray TeamMembers; 173 | Teams.MultiFind(TeamId, TeamMembers, true); 174 | int32 StartIndex; 175 | TeamMembers.Find(CurrentComponent, StartIndex); 176 | for (int32 Idx = 1; Idx <= TeamMembers.Num(); Idx++) 177 | { 178 | UTurnComponent *Candidate = TeamMembers[(StartIndex + Idx) % TeamMembers.Num()]; 179 | if (Candidate->RemainingActionPoints > 0) 180 | { 181 | return Candidate; 182 | } 183 | } 184 | return nullptr; 185 | } 186 | 187 | UTurnComponent * ATurnManager::FindNextComponent() 188 | { 189 | TArray TeamIds; 190 | Teams.GenerateKeyArray(TeamIds); 191 | TeamIds.Sort(); 192 | for (FGenericTeamId &TeamId : TeamIds) 193 | { 194 | UTurnComponent *Candidate = FindNextTeamMember(TeamId); 195 | if (IsValid(Candidate)) 196 | { 197 | return Candidate; 198 | } 199 | } 200 | 201 | return nullptr; 202 | } 203 | 204 | bool ATurnManager::HasComponentsThatCanAct() 205 | { 206 | for (TPair &Pair : Teams) 207 | { 208 | if (Pair.Value->RemainingActionPoints > 0) 209 | { 210 | return true; 211 | } 212 | } 213 | 214 | return false; 215 | } 216 | 217 | -------------------------------------------------------------------------------- /Source/Navgrid/Public/INavGrid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ModuleManager.h" 4 | 5 | /** 6 | * The public interface to this module. In most cases, this interface is only public to sibling modules 7 | * within this plugin. 8 | */ 9 | class INavGrid: public IModuleInterface 10 | { 11 | 12 | public: 13 | 14 | /** 15 | * Singleton-like access to this module's interface. This is just for convenience! 16 | * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. 17 | * 18 | * @return Returns singleton instance, loading the module on demand if needed 19 | */ 20 | static inline INavGrid& Get() 21 | { 22 | return FModuleManager::LoadModuleChecked("NavGrid"); 23 | } 24 | 25 | /** 26 | * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. 27 | * 28 | * @return True if the module is loaded and ready to use 29 | */ 30 | static inline bool IsAvailable() 31 | { 32 | return FModuleManager::Get().IsModuleLoaded("NavGrid"); 33 | } 34 | }; --------------------------------------------------------------------------------