프로젝트에 사용하기 적합한 절차적 맵 생성 알고리즘을 찾는 와중에 기가 막힌 유튭 강의를 찾았다.
아래는 그 링크다.
https://www.youtube.com/watch?v=-QOCX6SVFsk&list=PLcRSafycjWFenI87z7uZHFv6cUG2Tzu9v
위 영상은 RandomWalk와 BSP 두 절차적 맵 생성 알고리즘을 조합해 2D 맵을 생성하는 방법을 설명하는 강의다.
오늘은 RandomWalk을 이용해 맵을 생성하는 단계까지 진행했다.
아래는 그 주요 스크립트다. 총 3개. 맵은 Tliemap을 깔아서 생성했다. 맵 = 타일맵, 맵 위치 = 좌표 라는 개념으로 설명한다.
중요한 코드에는 전부 주석을 추가했다.
ProceduralGenerationAlgorithm , 맵 좌표의 집합을 생성하는 알고리즘 클래스
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class ProceduralGenerationAlgorithms
{
public static HashSet<Vector2Int> SimpleRandomWalk(Vector2Int startPosition, int walkLength)
{
HashSet<Vector2Int> path = new HashSet<Vector2Int>(); // HashSet<T> : 인덱스가 없는 제레릭 배열. 인덱스 없이 Hash로 관리된다.
// 여기서 만드는 path가 SimpleWalkDungeonGenerator의 floorPositions, 즉 타일이 생성될 좌표의 집합이 된다.
path.Add(startPosition); // 첫 시작은 startPosition부터. 물론 SimpleWalkDungeonGenerator.startRandomlyEachIteration = true일 경우에는 이마저도 0,0이 아닌 생성된 타일 좌표 중에서 랜덤으로 정해진다.
var previousposition = startPosition;
for (int i = 0; i < walkLength; i++)
{
var newPosition = previousposition + Direction2D.GetRandomCardinalDirection(); // newPosition : 기존의 좌표에 상하좌우 택1 한칸이동 값을 더한다.
path.Add(newPosition); // 새로 만들어진 좌표를 path에 추가
previousposition = newPosition; // 새로 만들어진 좌표는 다시 기존의 좌표가 된다.
}
return path; // path => floorPositions
}
public static class Direction2D // newPosition이 이동할 좌표(상/하/좌/우 1칸)을 나타내는 클래스
{
public static List<Vector2Int> cardinalDirectionsList = new List<Vector2Int>() // cardinal : 기본적인, 가장 중요한
{
new Vector2Int(0,1), // UP
new Vector2Int(0,-1), // DOWN
new Vector2Int(1,0), // RIGHT
new Vector2Int(-1,0), // LEFT
};
public static Vector2Int GetRandomCardinalDirection()
{
return cardinalDirectionsList[Random.Range(0, cardinalDirectionsList.Count)]; // cardinalDirectionList리스트의 랜덤 인덱스에 따른 값
}
}
}
SimpleWalkDungeonGenerator, 좌표 생성 시작점, 좌표 생성 반복 횟수, 좌표 생성 길이, 반복에 따른 시작점 무작위 설정 여부를 설정하고, 이 설정을 ProceduralGenerationAlgorithm의 메서드를 호출하여 좌표 집합을 생성하는 클래스
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Random = UnityEngine.Random;
public class SimpleWalkDungeonGenerator : AbstractDungeonGenerator
{
//[SerializeField]
//protected Vector2Int startPosition = Vector2Int.zero; 시작 위치 0,0
[SerializeField]
private int iterations = 10; // iteration : 반복
// walkLength에 따라 맵 만들기를 반복할 횟수
[SerializeField]
public int walkLength = 10;
// 맵(타일)이 뻗어나갈 길이
[SerializeField]
public bool startRandomlyEachIteration = true;
// iternation에 따라 맵 만들기를 반복할 때, 맵(타일) 작성 시작 위치를 0,0이 아닌 다른 곳에서 시작할 것인가?
//[SerializeField]
//private TilemapVisualizer tilemapVisualizer;
protected override void RunProceduralGeneration() // Generate버튼 클릭 시 이벤트
{
HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 타일맵 좌표 집합을 생성하는 명령
tilemapVisualizer.Clear(); // 기존에 생성됐던 타일맵 삭제 명령
tilemapVisualizer.PaintFloorTile(floorPositions); // floorPositions의 좌표 집합에 따라 타일맵 생성 명령
}
protected HashSet<Vector2Int> RunRandomWalk()
{
var currentPosition = startPosition;
HashSet<Vector2Int> floorPositions = new HashSet<Vector2Int>(); // n회차 반복으로 만들어진 모든 타일맵 좌표의 집합
for (int i = 0; i < iterations; i++)
{
var path = ProceduralGenerationAlgorithms.SimpleRandomWalk(currentPosition, walkLength);
floorPositions.UnionWith(path); // UnionWith : HashSet제네릭에서만 사용 가능. A제네릭.UnionWith(B제네릭) => A에 B의 내용을 합친다. A는 B의 값을 포함한다(두 제네릭의 중복된 요소는 하나로 생략된다). B는 변경되지 않는다.
if (startRandomlyEachIteration)
{
currentPosition = floorPositions.ElementAt(Random.Range(0, floorPositions.Count));
}
}
return floorPositions;
}
}
TilpmapVisualizer, 생성된 좌표 집합에 Tilemap을 붙이는 클래스(맵 가시화 클래스)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class TilemapVisualizer : MonoBehaviour
{
[SerializeField]
private Tilemap floorTilemap; // Tilemap게임오브젝트, 씬 뷰에서 바둑판 격자 그거
[SerializeField]
private TileBase floorTile; // Tile로 생성(색칠)할 TilePallete의 파일.
public void PaintFloorTile(IEnumerable<Vector2Int> floorPositions)
{
PaintTiles(floorPositions, floorTilemap, floorTile);
}
private void PaintTiles(IEnumerable<Vector2Int> positions, Tilemap tilemap, TileBase tile)
{
foreach (var position in positions)
{
PaintSingleTile(tilemap, tile, position);
}
}
private void PaintSingleTile(Tilemap tilemap, TileBase tile, Vector2Int position)
{
var tilePosition = tilemap.WorldToCell((Vector3Int)position);
tilemap.SetTile(tilePosition, tile);
}
public void Clear()
{
floorTilemap.ClearAllTiles(); // floorTilemap에 색칠된 모든 타일 삭제
}
}
위 세 스크립트로 생성되는 컴포넌트와 맵 예시는 아래와 같다.


앞으로 해야 할 과정은-
- 생성된 타일맵을 던전으로 사용할 수 있도록 고정된 맵으로 만들기. 던전에 입장할 때 마다 맵이 바뀌는 게임은 아니다. 던전 내 지정된 위치에 캐릭터, 아이템, 몬스터를 배치하려면 불변의 맵이 필요하다.
- BSP를 이용해 위처럼 생성된 맵을 연결하기. 위 사진의 맵이 던전이 되는 방식이 아니라, 저런 맵이 다수 있고, 그 맵을 연결해서 하나의 큰 맵을 만들 생각이다.
C# 단축키
Ctrl + F, F : 어셈블리 내에서 사용된 특정 키워드(단어)의 위치를 검색
'UNITY' 카테고리의 다른 글
| UNITY_20231004[팀 과제-로그라이크 RPG - 완료] (0) | 2023.10.04 |
|---|---|
| UNITY_20231002[팀 과제-로그라이크 RPG - 벽, 방, 복도 만들기] (0) | 2023.09.27 |
| UNITY_20230925[팀 과제-로그라이크 RPG, 디자인 패턴-전략 패턴] (1) | 2023.09.25 |
| UNITY_20230921[개인과제] (0) | 2023.09.22 |
| UNITY_20230920[개인과제] (0) | 2023.09.20 |