본문 바로가기

UNITY

UNITY_20230926[팀 과제-로그라이크 RPG - RandomWalk 알고리즘]

프로젝트에 사용하기 적합한 절차적 맵 생성 알고리즘을 찾는 와중에 기가 막힌 유튭 강의를 찾았다.

아래는 그 링크다.

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에 색칠된 모든 타일 삭제
    }
}

 

위 세 스크립트로 생성되는 컴포넌트와 맵 예시는 아래와 같다.

우하단의 버튼 컴포넌트는 아래의 Generate버튼이다.
SimpleWalkDungeonGenerator의 Iterations, WalkLength, StartRandomlyEachIteration 값을 변경하면서 생성한 타일맵이다. 아주 다양한 맵을 생성할 수 있다.

 

앞으로 해야 할 과정은-

  1. 생성된 타일맵을 던전으로 사용할 수 있도록 고정된 맵으로 만들기. 던전에 입장할 때 마다 맵이 바뀌는 게임은 아니다. 던전 내 지정된 위치에 캐릭터, 아이템, 몬스터를 배치하려면 불변의 맵이 필요하다.
  2. BSP를 이용해 위처럼 생성된 맵을 연결하기. 위 사진의 맵이 던전이 되는 방식이 아니라, 저런 맵이 다수 있고, 그 맵을 연결해서 하나의 큰 맵을 만들 생각이다.

C# 단축키 

Ctrl + F, F : 어셈블리 내에서 사용된 특정 키워드(단어)의 위치를 검색