This game was a solo school project. It’s made in C# and Unity. Models and art are taken from the Unity Store, Mixamo and Google.
This was the very first game I made in Unity.
The game is a clone of Temple Run and as such, was made for Android.
Gameplay
You begin by dropping down on the street.
Controlling your character is done by tilting your phone.
Your goal is to run as far as you can without hitting obstacles. Touching the many pickups might help you out.
If you bump into two obstacles within a short time a monster will get you and it’s a game over.
Code
One thing I’m quite proud of is how the world is created. It uses a very basic procedural generation script which chooses random tiles from a list and places attaches those to a collider placed on the previous tile. Instead of creating a new tile every time a player reaches a certain point, the old tiles are reused. This is done to increase performance.
public class WorldComponent : MonoBehaviour {
public int MaxVisible = 10;
private AreaPoolComponent pool;
private List areas;
private int tileCounter = 0;
void Start() {
pool = GetComponent();
areas = new List();
pool.Initialize();
GenerateAreas();
}
private void GenerateAreas() {
List list = pool.GetAreas(MaxVisible);
list[list.Count - 1] = pool.GetUnusedTurn();
AreaPrefabComponent prefab = list[0];
prefab.SetPosition(new Vector3(0, 0, 0));
areas.Add(prefab);
for (var i = 1; i < list.Count; i++) {
AreaPrefabComponent previous = areas[i - 1];
var area = list[i];
SetNextPosition(area, previous);
areas.Add(area);
}
}
public void ReplaceAreaFromPool() {
if (tileCounter >= MaxVisible) {
ReplaceWithTurn();
}
else {
if (Random.Range(0, 5) <= 2) {
ReplaceWithTurn();
}
ReplaceWithArea();
}
ShiftList();
}
private void ReplaceWithArea() {
areas[0] = pool.ReplaceWithArea(areas[0]);
SetNewArea(areas[0]);
tileCounter++;
}
private void ReplaceWithTurn() {
areas[0] = pool.ReplaceWithTurn(areas[0]);
SetNewArea(areas[0]);
tileCounter = 0;
}
private void SetNewArea(AreaPrefabComponent area) {
AreaPrefabComponent last = areas[areas.Count - 1];
SetNextPosition(area, last);
}
private void SetNextPosition(AreaPrefabComponent area, AreaPrefabComponent other) {
area.SetPosition(other.GetExitPos());
area.SetRotation(other.GetExitRot());
pool.FillPickups(area);
}
private void ShiftList() {
AreaPrefabComponent first = areas[0];
AreaPrefabComponent[] newList = new AreaPrefabComponent[areas.Count];
Array.Copy(areas.ToArray(), 1, newList, 0, areas.Count - 1);
newList[areas.Count - 1] = first;
areas = new List(newList);
}
}
public class AreaPoolComponent : PoolComponent {
public GameObject[] TurnPrefabs;
public GameObject[] AreaPrefabs;
private List areaPool;
private List turnPool;
private PickupPoolComponent PickupPool;
public override void Initialize() {
Debug.Log("Initialized Area Pool");
areaPool = new List();
turnPool = new List();
PickupPool = GetComponent();
FillPool();
PickupPool.Initialize();
}
protected override void FillPool() {
for (int i = 0; i < AreaPrefabs.Length; i++) {
GameObject area = Instantiate(AreaPrefabs[i], new Vector3(-100, -100, 0), new Quaternion());
var prefab = area.GetComponent();
prefab.Initialize();
areaPool.Add(prefab);
}
int remaining = Max - AreaPrefabs.Length;
for (int i = 0; i < remaining; i++) {
GameObject area = Instantiate(AreaPrefabs[GetRandom(AreaPrefabs.Length)], new Vector3(-100, -100, 0),
new Quaternion());
var prefab = area.GetComponent();
prefab.Initialize();
areaPool.Add(prefab);
}
for (int i = 0; i < TurnPrefabs.Length; i++) {
for (int j = 0; j < 2; j++) {
GameObject turn = Instantiate(TurnPrefabs[i], new Vector3(-100, -100, 0), new Quaternion());
var prefab = turn.GetComponent();
prefab.Initialize();
turnPool.Add(prefab);
}
}
}
private List GetUnusedAreas() {
List list = new List();
foreach (var prefab in areaPool) {
if (!prefab.IsInUse()) {
list.Add(prefab);
}
}
return list;
}
private List GetUnusedTurns() {
List list = new List();
foreach (var prefab in turnPool) {
if (!prefab.IsInUse()) {
list.Add(prefab);
}
}
return list;
}
public AreaPrefabComponent GetUnused() {
if (areaPool.Count == 0) return null;
List unused = GetUnusedAreas();
AreaPrefabComponent area = unused[GetRandom(unused.Count)];
area.SetInUse(true);
return area;
}
public AreaPrefabComponent GetUnusedTurn() {
if (turnPool.Count == 0) return null;
List unused = GetUnusedTurns();
AreaPrefabComponent turn = unused[GetRandom(unused.Count)];
turn.SetInUse(true);
return turn;
}
public void FillPickups(AreaPrefabComponent area) {
int amount = Random.Range(1, 3);
List pickups = PickupPool.GetPickups(amount);
foreach (var pickup in pickups) {
area.AddToPickupPos(pickup);
}
}
private void StorePickups(AreaPrefabComponent area) {
foreach (var pickup in area.GetPickups()) {
PickupPool.StoreUsed(pickup);
}
area.ResetPickups();
}
public void StoreUsed(AreaPrefabComponent area) {
ResetArea(area);
StorePickups(area);
areaPool.Add(area);
}
public List GetAreas(int count) {
List list = new List();
for (int i = 0; i < count; i++) {
list.Add(GetUnused());
}
return list;
}
private void ResetArea(AreaPrefabComponent area) {
area.SetInUse(false);
area.SetPosition(new Vector3(-100, -100, 0));
area.SetRotation(new Quaternion());
foreach (var o in area.GetObstacles()) {
o.SetActive(true);
}
foreach (var p in area.GetPickupPositions()) {
p.SetActive(true);
}
}
public AreaPrefabComponent ReplaceWithTurn(AreaPrefabComponent area) {
AreaPrefabComponent turn = GetUnusedTurn();
StoreUsed(area);
return turn;
}
public AreaPrefabComponent ReplaceWithArea(AreaPrefabComponent area) {
AreaPrefabComponent newArea = GetUnused();
StoreUsed(area);
return newArea;
}
}
Along the way of this project a teacher told me to use a script to manage all the stuff I put into a single Tile prefab. This helped me a lot in making my code a bit cleaner and easier to understand
public class AreaPrefabComponent : MonoBehaviour {
private GameObject area;
private List pickups;
private Dictionary pickupPositions;
private List obstacles;
private GameObject exit;
private bool currentlyUsed;
public void Initialize() {
pickupPositions = new Dictionary();
pickups = new List();
obstacles = new List();
area = transform.gameObject;
foreach (Transform t in transform) {
if (t.CompareTag("Obstacle")) {
obstacles.Add(t.gameObject);
}
else if (t.name == "PickupLocations") {
foreach (Transform child in t.transform) {
pickupPositions.Add(child.gameObject, false);
}
}
else if (t.name == "Exit") {
exit = t.gameObject;
}
}
}
public bool IsInUse() {
return currentlyUsed;
}
public void SetInUse(bool inUse) {
currentlyUsed = inUse;
}
public void SetPosition(Vector3 pos) {
transform.position = pos;
}
public void SetRotation(Quaternion rot) {
transform.rotation = rot;
}
public List GetObstacles() {
return obstacles;
}
public List GetPickupPositions() {
return new List(pickupPositions.Keys);
}
private GameObject GetRandomPickupPosition() {
var list = GetUnusedPickupPositions();
GameObject r = list[Random.Range(0, list.Count)];
pickupPositions[r] = true;
return r;
}
private List GetUnusedPickupPositions() {
Dictionary collection = pickupPositions.Where(x => x.Value == false).ToDictionary(x => x.Key, x => x.Value);
return new List(collection.Keys);
}
public void AddToPickupPos(PickupComponent pickup) {
GameObject pos = GetRandomPickupPosition();
pickup.SetPosition(pos.transform.position);
pickup.SetRotation(pos.transform.rotation);
pickups.Add(pickup);
}
public void ResetPickups() {
pickups = new List();
foreach (GameObject key in pickupPositions.Keys.ToList()) {
pickupPositions[key] = false;
}
}
public List GetPickups() {
return pickups;
}
public Vector3 GetExitPos() {
return exit.transform.position;
}
public Quaternion GetExitRot() {
return exit.transform.rotation;
}
}