├── .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 | };
--------------------------------------------------------------------------------