/
LevelLoader.h
174 lines (134 loc) · 4.75 KB
/
LevelLoader.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#pragma once
#include <array>
#include <unordered_set>
#include <vector>
#include "CoreMinimal.h"
#include "ILevelLoader.h"
#include "Math/IntPoint.h"
#include "AStar/Graph.h"
#include "AStar/GridLocation.h"
#include "LevelLoader.generated.h"
/**
* ULevelLoader loads the contents of a level file (.txt file extension) into memory,
* then also serves as an instance of the loaded level afterward.
*
* TODO: This class has too many responsibilities.
* If time permits, I'd refactor into ULevel (UDataAsset) and ULoadedLevel (UObject),
* though that takes lower priority than implementing game mechanics and polishing the UX.
*/
UCLASS(Blueprintable)
class ULevelLoader : public UObject, public IGraph, public ILevelLoader
{
GENERATED_BODY()
public:
/**
* The path of the file to load from our Content/ directory.
*
* Example: "Levels/level2.txt"
*/
UPROPERTY(EditDefaultsOnly, Category = "Level Filename Customization")
FString LevelFilename = TEXT("Levels/level2.txt");
/**
* Grab a reference to a Blueprint asset's default instance of ULevelLoader.
*/
static ULevelLoader* GetInstance(const TSubclassOf<ULevelLoader>& BlueprintClass);
/**
* Load the contents of a level file into this instance of ULevelLoader.
*/
void LoadLevel();
/**
* Clear any loaded level content within this instance of ULevelLoader.
*/
void Clear();
/**
* Get the width (horizontal dimension) of the level.
*/
virtual int GetLevelWidth() const override;
/**
* Get the height (vertical dimension) of the level.
*/
virtual int GetLevelHeight() const override;
/**
* Given grid coordinates, convert them to world coordinates.
*
* Note that the grid origin (0,0) is at the bottom-left of the map.
*/
virtual FVector2D GridToWorld(const FGridLocation& GridPosition) const override;
// More convenient version of GridToWorld().
FVector GridToWorld3D(const FGridLocation& GridPosition) const;
/**
* Given world coordinates, convert them to grid coordinates.
*
* Note that the grid origin (0,0) is at the bottom-left of the map.
*/
FGridLocation WorldToGrid(FVector2D WorldPosition) const;
/**
* Given world coordinates, compute the direction required to snap the world coordinates to the closest grid position.
*
* Note that the grid origin (0,0) is at the bottom-left of the map.
*/
static FGridLocation SnapToGridDirection(FVector2D WorldPosition);
/**
* Check whether ToLocation is passable, given that we're coming from FromLocation.
*/
bool Passable(const FGridLocation& FromLocation, const FGridLocation& ToLocation) const;
bool Passable(const FGridLocation& TestLocation) const;
bool Passable(const FVector& WorldTestLocation) const;
virtual bool IsWall(const FGridLocation& Location) const override;
bool IsGhostHouse(const FGridLocation& Location) const;
bool IsWrapAround(const FGridLocation& Location) const;
bool IsGateTile(const FGridLocation& Location) const;
/**
* Check whether a grid position is within the map boundaries.
*/
bool InBounds(const FGridLocation& GridPosition) const;
/**
* Get the passable neighbor nodes of a node.
*/
virtual std::vector<FGridLocation> Neighbors(FGridLocation GridPosition) const override;
/**
* Return the unitless cost of going from FromNode to ToNode.
*/
virtual double Cost(FGridLocation FromNode, FGridLocation ToNode) const override;
/**
* A list of strings that describe the loaded level.
*
* The first string is at x=0, and the last string is at x=number_of_lines-1.
*
* Note that x is the vertical dimension in Unreal.
*/
TArray<FString> StringList;
bool IsIntersectionTile(const FGridLocation& TileToTest) const;
FGridLocation GetGateTile() const;
FGridLocation GetRightOutsideGhostHouseTile() const;
FGridLocation GetBonusSymbolTile() const;
private:
/**
* The number of rows in the level.
*/
int NumberOfRows = 0;
/**
* The number of columns in the level.
*/
int NumberOfColumns = 0;
/**
* A set of FGridLocations that describe all the wall tiles in the level.
*/
std::unordered_set<FGridLocation> Walls;
std::optional<FGridLocation> GateTile;
std::optional<FGridLocation> BonusSymbolTile;
/**
* A set of FGridLocations that describe all the tiles within the ghost house in the level.
*/
std::unordered_set<FGridLocation> GhostHouseTiles;
std::unordered_set<FGridLocation> WrapAroundTiles;
std::optional<FGridLocation> RightOutsideGhostHouseTile;
/**
* The cardinal directions.
*/
static std::array<FGridLocation, 4> CardinalDirections;
bool IsLoaded = false;
static FGridLocation GetTile(std::optional<FGridLocation> MaybeTile, const ULevelLoader* LevelInstance);
bool AreWithinOneUnit(const FGridLocation& A, const FGridLocation& B) const;
bool AreOnOppositeEnds(const FGridLocation& A, const FGridLocation& B) const;
};