따로 씬을 만들어서 맵 크리에이터를 생성했다.
레벨이 무한대라서, 매 레벨마다 랜덤으로 맵을 생성하기 위해 MapCreator 객체를 만들었다
랜덤 요소
고정 요소
(Map 객체에 붙어있는 스크립트)
(Map 객체에 붙어있는 콜라이더)
using UnityEngine;
using System.Collections.Generic;
public class MapGenerator : MonoBehaviour
{
public Map[] maps;
public int mapindex;
GameManager gm;
public Transform tilePrefab;
//public Vector2 mapSize;
public Transform navmeshFloor;
public Vector2 maxMapSize;
//맵에 생성할 prefabs
public Transform obstaclePrefab1;
public Transform obstaclePrefab2;
public Transform clearPrefab;
public Transform wallPrefab;
public Transform clearcheckPrefab;
//장애물 밀도
//[Range(0.1f,0.2f)]
//public float obstaclePercent;
List<Coord> allTileCoords; //전체 타일 위치 리스트
Queue<Coord> shuffledTileCoords; //셔플한 타일 위치 리스트
Queue<Coord> shuffledOpenTileCoords;
Map currentMap;
Transform[,] tileMap;
private void Start()
{
gm = FindObjectOfType<GameManager>();
//mapindex = gm.gameInfo.GetIndex();
mapindex = 0;
maps[mapindex].mapSize.x = 30;
maps[mapindex].mapSize.y = 30;
maps[mapindex].seed = Random.Range(-10, 200);
maps[mapindex].obstaclePercent = Random.Range(0.1f, 0.3f);
GenerateMap();
gm.gameInfo.IncreaseLevel();
}
public void GenerateMap()
{
Debug.Log("Generate map");
currentMap = maps[mapindex];
tileMap = new Transform[currentMap.mapSize.x, currentMap.mapSize.y];
GetComponent<BoxCollider>().size = new Vector3(currentMap.mapSize.x, 0, currentMap.mapSize.y);
allTileCoords = new List<Coord>();
//모든 타일 좌표 리스트에
for (int x = 2; x < currentMap.mapSize.x-2; x++)
{
for (int y = 2; y < currentMap.mapSize.y-2; y++)
{
allTileCoords.Add(new Coord(x, y));
}
}
shuffledTileCoords = new Queue<Coord>(Utility.ShuffleArray(allTileCoords.ToArray(),currentMap.seed));
string holderName = "Generated Map";
//editor 관리용
if (transform.Find(holderName))
{
DestroyImmediate(transform.Find(holderName).gameObject);
//editor에서 호출할거라 destroy가 아닌 destroyImmediate 사용
}
//한 객체에 다 집어넣을려고
Transform mapHolder = new GameObject(holderName).transform;
mapHolder.parent = transform; //map generator의 자식으로 들어가도록
//맵 크기만큼 타일 생성
for(int x = 0; x < currentMap.mapSize.x; x++)
{
for (int y = 0; y < currentMap.mapSize.y; y++)
{
//일반 타일
if (x != currentMap.mapSize.x - 2 || y != currentMap.mapSize.y - 2)
{
Vector3 tilePosition = CoordToPosition(x, y);
Transform newTile = Instantiate(tilePrefab, tilePosition, Quaternion.Euler(Vector3.right * 90)) as Transform;
newTile.parent = mapHolder; // 새로 생성된 타일은 모두 mapHolder로 들어감
tileMap[x, y] = newTile;
}
//clear point 지점
else
{
Vector3 clearPosition = CoordToPosition(x, y);
Transform clearTile = Instantiate(clearPrefab, clearPosition, Quaternion.Euler(Vector3.right * 90)) as Transform;
Transform clearCheck = Instantiate(clearcheckPrefab, clearPosition, Quaternion.identity) as Transform;
clearCheck.parent = mapHolder;
clearTile.parent = mapHolder;
tileMap[x, y] = clearTile;
}
}
}
//flood fill 알고리즘 이용해 전체 맵 접근 가능한지 확인
//
int obstacleCount = (int)(currentMap.mapSize.x * currentMap.mapSize.y * currentMap.obstaclePercent);
List<Coord> allOpenCoords = new List<Coord>(allTileCoords);
//랜덤한 장애물 위치
for(int i = 0; i < obstacleCount; i++)
{
Coord randomCoord = GetRandmCoord();
Vector3 obstaclePosition = CoordToPosition(randomCoord.x, randomCoord.y);
if (i % 2 == 0)
{
Transform obstacle1 = Instantiate(obstaclePrefab1, obstaclePosition, Quaternion.identity) as Transform;
obstacle1.parent = mapHolder;
}
else if (i % 5 == 0)
{
Transform obstacle2 = Instantiate(obstaclePrefab2, obstaclePosition, Quaternion.identity) as Transform;
obstacle2.parent = mapHolder;
//겹치는 주변 8개 타일 빼버리기
RemoveUseTile(randomCoord.x - 1, randomCoord.y - 1, allOpenCoords);
RemoveUseTile(randomCoord.x - 1, randomCoord.y , allOpenCoords);
RemoveUseTile(randomCoord.x - 1, randomCoord.y + 1, allOpenCoords);
RemoveUseTile(randomCoord.x, randomCoord.y - 1, allOpenCoords);
RemoveUseTile(randomCoord.x, randomCoord.y + 1, allOpenCoords);
RemoveUseTile(randomCoord.x + 1, randomCoord.y - 1, allOpenCoords);
RemoveUseTile(randomCoord.x + 1, randomCoord.y, allOpenCoords);
RemoveUseTile(randomCoord.x + 1, randomCoord.y + 1, allOpenCoords);
}
allOpenCoords.Remove(randomCoord);
}
shuffledOpenTileCoords = new Queue<Coord>(Utility.ShuffleArray(allOpenCoords.ToArray(), currentMap.seed));
//벽 생성
//세로축 벽
for(int j = 0; j < currentMap.mapSize.y; j++)
{
GenerateWall(mapHolder, 0, j);
GenerateWall(mapHolder,(int)currentMap.mapSize.x - 1, j);
}
//가로축 벽
for (int i = 1; i < currentMap.mapSize.x-1; i++)
{
GenerateWall(mapHolder, i, 0);
GenerateWall(mapHolder, i, (int)currentMap.mapSize.y - 1);
}
navmeshFloor.localScale = new Vector3(maxMapSize.x, maxMapSize.y);
}
void RemoveUseTile(int x, int y,List<Coord> allOpenCoords)
{
Coord extraCoord = new Coord(x, y);
allOpenCoords.Remove(extraCoord);
}
void GenerateWall(Transform mapHolder, int x, int y)
{
Vector3 wallPosition = CoordToPosition(x, y);
Transform wall = Instantiate(wallPrefab, wallPosition, Quaternion.identity) as Transform;
wall.parent = mapHolder;
}
//좌표를 벡터로 전환
Vector3 CoordToPosition(int x, int y)
{
return new Vector3(-currentMap.mapSize.x / 2 + 0.5f + x, 0, -currentMap.mapSize.y / 2 + 0.5f + y);
}
//큐에서 다음 아이템 얻어 랜덤 좌표 반환
public Coord GetRandmCoord()
{
Coord C = new Coord(-1,-1);
Coord randomCoord = shuffledTileCoords.Dequeue();
shuffledTileCoords.Enqueue(randomCoord);
//입구&출구 근처에서 생성되지 못하게
if ((randomCoord.x < 4 && randomCoord.y < 4) || (randomCoord.x >currentMap.mapSize.x-4 && randomCoord.y >currentMap.mapSize.y-4))
{
C = GetRandmCoord();
}
if(C.x != -1)
{
randomCoord = C;
}
return randomCoord;
}
//몬스터 랜덤 소환 위치
public Transform GetRandomOpenTile()
{
Coord C = new Coord(-1, -1);
if (shuffledOpenTileCoords.Equals(null))
{
//Debug.Log("no opentile");
return null;
}
else
{
Coord randomCoord = shuffledOpenTileCoords.Dequeue();
shuffledOpenTileCoords.Enqueue(randomCoord);
if ((randomCoord.x < 4 && randomCoord.y < 4) || (randomCoord.x > currentMap.mapSize.x - 4 && randomCoord.y > currentMap.mapSize.y - 4))
{
return GetRandomOpenTile();
}
return tileMap[randomCoord.x, randomCoord.y];
}
}
//맵의 모든 타일 위치 좌표 저장할 구조체 (리스트로 저장)
[System.Serializable]
public struct Coord {
public int x;
public int y;
public Coord(int _x, int _y)
{
x = _x;
y = _y;
}
}
[System.Serializable]
public class Map
{
public Coord mapSize;
public float obstaclePercent;
public int seed;
}
}
코드 수정할 때 내가 에디터로 편하게 보기 위해 작성.
맵을 구성하는 요소 값이 바뀌거나, Generate Map 버튼을 누를 때 에디터 상에 뜨는 맵이 갱신된다.
using System.Collections;
using UnityEditor;
using UnityEngine;
//editor에서 맵 설정하게
[CustomEditor (typeof(MapGenerator))]
public class MapEditor : Editor
{
public override void OnInspectorGUI()
{
//base.OnInspectorGUI();
MapGenerator map = target as MapGenerator;
if (DrawDefaultInspector())
{
map.GenerateMap();
}
if(GUILayout.Button("Generate Map"))
{
map.GenerateMap();
}
}
}
Map에서 랜덤으로 위치를 뽑아내기 위해 사용한 함수. 다른 곳에 쓸 일이 있을지도 몰라 Utility 클래스로 따로 만들어두었다.
using System.Collections;
public static class Utility
{
//seed값을 기준으로 배열 내부 요소 섞기
public static T[] ShuffleArray<T>(T[] array, int seed)
{
System.Random prng = new System.Random(seed);
//the fisher-yates shuffle
//마지막 루프 생략해도 돼서
for(int i = 0; i < array.Length - 1; i++)
{
//i와 배열 끝 사이 중 하나 렌덤으로 선택
int randomIndex = prng.Next(i,array.Length);
//i와 랜덤 선택 원소 바꿈
T temp = array[randomIndex];
array[randomIndex] = array[i];
array[i] = temp;
}
return array;
}
}
모두 Box Collider를 가지고 있다.
맵과 플레이어 모두에 콜라이더가 있음에도 불구하고 벽에 플레이어를 비비면 플레이어가 맵 밖으로 떨어지는 문제가 생겼다.
원인은 플레이어의 자식들에 콜라이더가 없어서 해당 물체와 벽이 부딪혔을 때 플레이어가 밖으로 떨어지는 것이었다.
플레이어의 자식들에 콜라이더를 붙여 문제를 해결했다.
좌표값이랑 건물 크기가 일치하지 않아서 발생하는 문제다.
근데 건물이 겹치는게 화면 상으로 더 다채로워 보여서 굳이 수정하지 않았다.