Download

Looking for tutorials to help build a turn base map

At my university, I have a project where my team and I have decided to build a turn base games (Similar to CIV V, but nowhere near as complex), but we are having issues with building the tile map/vector map in the unreal engine 4. We already built a mock-up of the game in visual studios and it worked perfectly. We know that we can build the game, but we can’t start until we find out how to build a tile map/vector map. So, I was hoping that someone could post a tutorial that could help my team and me with this issue.

Thank you in advance.

Not too sure if this will help you, but a while back I tried to make a tactical rpg but gave up because i decided it was too big a project to handle by myself. But I got most of a tile map working-ish converting from a Unity tutorial. This is what I had (though a lot of it I’d do differently now that I’m more experienced). I’m pretty I had it using hexes instead of squares, but you’d just have to change the way we define neighbours to have it work with squares.

This is the actor I placed in the world, and I set up some other actor called Tile or something, and I spawned a tile for each tile in the tilemap, and I used the Tile to interact with the TileMap

.h



// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Actor.h"
#include "TileMap.generated.h"


UENUM(BlueprintType)
enum class ETileTypes : uint8 {
	TT_Plains	UMETA(DisplayName = "Plains"),
	TT_Roads	UMETA(DisplayName = "Roads"),
	TT_Forest	UMETA(DisplayName = "Forest"),
	TT_Swamp	UMETA(DisplayName = "Swamp"),
	TT_Desert	UMETA(DisplayName = "Desert"),
	TT_Mountain UMETA(DisplayName = "Mountain"),
	TT_Air		UMETA(DisplayName = "Air")
};



// Struct that holds data about a location required for pathfinding.
USTRUCT(BlueprintType) 
struct FTile {
	GENERATED_USTRUCT_BODY()

public:
	UPROPERTY(BlueprintReadOnly)
	int32 height;
	UPROPERTY(BlueprintReadOnly)
	ETileTypes tileType;
	UPROPERTY(BlueprintReadOnly)
	TArray<int32> neighbours;

	FTile() {
		height = 0;
		tileType = ETileTypes::TT_Plains;
	}

};



// A simple structure used to store 2 int32's
USTRUCT(BlueprintType)
struct FInt2 {
	GENERATED_USTRUCT_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int32 x;
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	int32 y;

	FInt2() {
		x = 0;
		y = 0;
	}

	FInt2(int32 _x, int32 _y) {
		x = _x;
		y = _y;
	};

};



/* Actor responsible for holding all the data about the terrain being fought on and 
all the required functions for interacting with the terrain, such as pathfinding.
Holds a 1D array of FTile that is treated like a 2D array via the Conversion Functions.
*/
UCLASS()
class TRPG_API ATileMap : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ATileMap();

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void Tick( float DeltaSeconds ) override;
	


	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Dimensions")
	bool resetMap = false;
	// How high 1 unit of height represents in the world
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Dimensions")
	float tileHeight = 50.0f;
	// The size of the meshes used to represent the tiles in the graph
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Dimensions")
	float tileSize = 200.0f;
	// The width of the graph, number of tiles along the X-axis
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Dimensions")
	int32 mapWidth = 10;
	// The height of the graph, number of tiles along the Y-axis
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Dimensions")
	int32 mapHeight = 10;
	// The array of tiles used in gameplay. Treated like a 2D array using the conversion functions
	UPROPERTY(BlueprintReadOnly)
	TArray<FTile> graph;



	// Returns what would be the X and Y of a tile if the graph was a 2D array
	UFUNCTION(BlueprintPure, Category = "Conversions")
	FInt2 IndexToXY(const int32 index);
	// Returns the index of a tile using what would be it's X and Y if the graph was a 2D array
	UFUNCTION(BlueprintPure, Category = "Conversions")
	int32 XYToIndex(const FInt2 XY);
	// Returns the tile's transform.position based on the tileSize
	UFUNCTION(BlueprintPure, Category = "Conversions")
	FVector IndexToTransform(const int32 index);



	// Clears the graph and resize it to the MapWidth x the MapHeight
	UFUNCTION(BlueprintCallable, Category = "Map Generation")
	void InitializeMap();
	// Assigns all tiles their neighbouring tiles indexes
	UFUNCTION(BlueprintCallable, Category = "Map Generation")
	void AssignNeighbours();



	// Returns the shortest path from 1 tile to another, if a path exist
	// If no path exist, returns an empty array of int32
	UFUNCTION(BlueprintPure, Category = "Pathfinding")
	TArray<int32> GeneratePathTo(const int32 start, const int32 end);
	// Sets the height of the tile at the index given
	UFUNCTION(BlueprintCallable, Category = "Map Generation")
	void SetHeightAt(const int32 index, float height);
	// Sets the tile type of the tile at the index given
	UFUNCTION(BlueprintCallable, Category = "Map Generation")
	void SetTileTypeAt(const int32 index, const ETileTypes tileType);
};



.cpp



// Fill out your copyright notice in the Description page of Project Settings.

#include "TRPG.h"
#include "TileMap.h"

// Sets default values
ATileMap::ATileMap()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;

}



// Called when the game starts or when spawned
void ATileMap::BeginPlay()
{
	Super::BeginPlay();

}



// Called every frame
void ATileMap::Tick( float DeltaTime )
{
	Super::Tick( DeltaTime );

}



// Returns what would be the X and Y of a tile if the graph was a 2D array
FInt2 ATileMap::IndexToXY(const int32 index) {
	return FInt2(index % mapWidth, index / mapWidth);
}



// Returns the index of a tile using what would be it's X and Y if the graph was a 2D array
int32 ATileMap::XYToIndex(const FInt2 XY) {
	return XY.y * mapWidth + XY.x;
}



// Returns the tile's transform.position based on the tileSize
FVector ATileMap::IndexToTransform(const int32 index) {

	int x = IndexToXY(index).x;
	int y = IndexToXY(index).y;
	float sin60 = FMath::Sin(FMath::DegreesToRadians(60));

	if (y % 2 != 0) { return FVector(x - 0.5f, -y * sin60, 0) * tileSize; }
	else { return FVector(x, -y * sin60, 0) * tileSize; }

}



// Clears the graph and resize it to the MapWidth x the MapHeight
void ATileMap::InitializeMap() {
	
	graph.Empty();
	graph.Init(FTile(), mapWidth * mapHeight);

}



// Assigns all tiles their neighbouring tiles indexes
void ATileMap::AssignNeighbours() {

	// Foreach FTile in the graph
	for (int i = 0; i < mapWidth * mapHeight; i++) {
		int32 x = IndexToXY(i).x;
		int32 y = IndexToXY(i).y;
		// Add tile on the left
		if (x > 0) {
			graph*.neighbours.Add(XYToIndex(FInt2(x - 1, y)));
		}
		// Add tile on the right
		if (x < mapWidth - 1) {
			graph*.neighbours.Add(XYToIndex(FInt2(x + 1, y)));
		}
		// If the Y is even
		if (y % 2 == 0) {
			if (y > 0) {
				// Add tile on the bottom left
				graph*.neighbours.Add(XYToIndex(FInt2(x, y - 1)));
				if (x < mapWidth - 1) {
					// Add tile on the bottom right
					graph*.neighbours.Add(XYToIndex(FInt2(x + 1, y - 1)));
				}
			}
			if (y < mapHeight - 1) {
				// Add tile on the top left
				graph*.neighbours.Add(XYToIndex(FInt2(x, y + 1)));
				if (x < mapWidth - 1) {
					// Add tile on the top right
					graph*.neighbours.Add(XYToIndex(FInt2(x + 1, y + 1)));
				}
			}
		}
		// If the Y is odd
		else {
			// Add tile on the bottom right
			graph*.neighbours.Add(XYToIndex(FInt2(x, y - 1)));
			if (x > 0) {
				// Add tile on the bottom left
				graph*.neighbours.Add(XYToIndex(FInt2(x - 1, y - 1)));
			}
			if (y < mapHeight - 1) {
				// Add tile on the top right
				graph*.neighbours.Add(XYToIndex(FInt2(x, y + 1)));
				if (x > 0) {
					// Add tile on the top left
					graph*.neighbours.Add(XYToIndex(FInt2(x - 1, y + 1)));
				}
			}
		}
	}

}



// Returns the shortest path from start to end, if a path exist
// If no path exist, returns an empty array of int32
// Uses -1 to represent a NULL tile
TArray<int32> ATileMap::GeneratePathTo(const int32 start, const int32 end) {
	
	// Keeps track of which FTile comes before this one on the way to the end
	TMap<int32, int32> prev = TMap<int32, int32>();
	// Keeps track of how much it cost to enter the FTile from the prev FTile
	TMap<int32, float> dist = TMap<int32, float>();
	// Keeps track of which FTiles we haven't checked yet
	TArray<int32> unvisited = TArray<int32>();

	// Sets the default value for all FTiles
	for (int32 i = 0; i < mapWidth * mapHeight; i++) {
		// If it is not where we started from, tell it we don't know anything about it
		if (i != start) {
			dist.Add(i, 1000);
			prev.Add(i, -1);
		}
		// Otherwise, it cost nothing to enter and there is no FTile before it
		else {
			dist.Add(i, 0);
			prev.Add(i, -1);
		}
		// TODO Change to: If is walkable == true, then add to unvisited
		unvisited.Add(i);
	}

	// Checks all the FTiles until we find the end FTile
	while (unvisited.Num() > 0) {
		int32 u = -1;
		// TODO Add comment
		for (auto tile : unvisited) {
			if (u == -1 || dist[tile] < dist) {
				u = tile;
			}
		}
		// Move on if we found the the end FTile
		if (u == end) {
			break;
		}
		unvisited.Remove(u);
		// TODO Add comment
		for (auto tile : graph.neighbours) {
			// TODO Change +1 to +Cost to enter tile
			float alt = dist + 1;
			if (alt < dist[tile]) {
				dist[tile] = alt;
				prev[tile] = u;
			}
		}
	}

	// Break out of function if no path to the end was found
	if (prev[end] == -1) {
		return TArray<int32>();
	}

	// Keep going back from the end to the start, adding the FTiles on the way to a 
	// list of FTiles. We know that we arrived at the beginning because it will be the 
	// only one on the path that has NULL for it's prev
	TArray<int32> reversedPath = TArray<int32>();
	int32 curr = end;
	while (curr != -1) {
		reversedPath.Add(curr);
		curr = prev[curr];
	}
	// TODO Change to: path.Reverse();
	TArray<int32> path = TArray<int32>();
	for (int32 i = reversedPath.Num() - 1; i > -1; i--) {
		path.Add(reversedPath*);
	}

	return path;

}



// Sets the height of the tile at the index given
void ATileMap::SetHeightAt(const int32 index, float height) {
	graph[index].height = height / tileHeight;
}



// Sets the tile type of the tile at the index given
void ATileMap::SetTileTypeAt(const int32 index, const ETileTypes tileType) {
	graph[index].tileType = tileType;
}


Take a look at my first map generator thread. That is where I basically went through the process of creating the most basic map.

You basically just need to establish a data structure on which to base your map arrays and ensure they are all aligned. I show some pics of spreadsheets showing how my arrays align with the physical placement of the map, as well as how I determine the vectors and fill the initial vector array.

Good luck!