본문 바로가기

UNITY

UNITY_20231004[팀 과제-로그라이크 RPG - 완료]

추석을 걸치고 일주일 만의 TIL이다.

일주일 간의 과정으로 2D 로그라이크 게임 프로젝트가 완료됐고, 아래의 깃헙 링크는 그 결과물이다.

https://github.com/GYALLERHORN/A07_RoguelikeProject

 

GitHub - GYALLERHORN/A07_RoguelikeProject

Contribute to GYALLERHORN/A07_RoguelikeProject development by creating an account on GitHub.

github.com

짧게 이진 공간 분한(BSP)알고리즘으로 작성한 맵 생성 기능에 대해 쓰자면, 하나의 공간을 2^n + @의 공간으로 나누어 여러 개의 분할된 공간을 생성하고, 각각의 공간에 적절한 크기의 맵을 구현하는 알고리즘이다.

아래는 BSP를 이용한 맵의 예시다.

이전의 RandomWalk 알고리즘으로 생성된 맵과 비교하면 하나의 방이 불규칙하게 생성되는 것을 볼 수 있다.

아래는 RandomWalk 알고리즘을 구현한 스크립트다. 이전의 코드와 호환하지 않는다.

더보기
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class RoomFirstDungeonGenerator : SimpleWalkDungeonGenerator // BSP 맵 생성 스크립트
{
    [SerializeField]
    private int minRoomWidth = 4, minRoomHeight = 4;
    [SerializeField]
    private int dungeonWidth = 20, dungeonHeight = 20;
    [SerializeField]
    [Range(0,10)]
    private int offset = 1;
    [SerializeField]
    private bool randomWalkRooms = false;

    protected override void RunProceduralGeneration()
    {
        CreateRooms();
    }

    private void CreateRooms() // BSP 방 생성 스크립트
    {
        var roomsList = ProceduralGenerationAlgorithms.BinarySpacePartitioning(new BoundsInt((Vector3Int)startPosition,
            new Vector3Int(dungeonWidth, dungeonHeight, 0)), minRoomWidth, minRoomHeight);

        var floor = new HashSet<Vector2Int>();

        if (randomWalkRooms)
        {
            floor = CeateRoomsRandomly(roomsList);
        }
        else
        {
            floor = CreateSimpleRooms(roomsList);
        }

        var roomCenters = new List<Vector2Int>();
        foreach (var room in roomsList)
        {
            roomCenters.Add(Vector2Int.RoundToInt(room.center));
        }

        HashSet<Vector2Int> corridors = ConnectRooms(roomCenters);
        floor.UnionWith(corridors);

        tilemapVisualizer.PaintFloorTile(floor);
        WallGenerator.CreateWalls(floor, tilemapVisualizer);
    }

    private HashSet<Vector2Int> CeateRoomsRandomly(List<BoundsInt> roomsList)
    {
        HashSet<Vector2Int> floor = new HashSet<Vector2Int>();
        for (int i = 0; i < roomsList.Count; i++)
        {
            var roomBounds = roomsList[i];
            var roomCenter = new Vector2Int(Mathf.RoundToInt(roomBounds.center.x), Mathf.RoundToInt(roomBounds.center.y));
            var roomFloor = RunRandomWalk(randomWalkParameters, roomCenter);
            foreach (var position in roomFloor)
            {
                if (position.x >= (roomBounds.xMin + offset) && position.x <= (roomBounds.xMax - offset)
                    && position.y >= (roomBounds.yMin + offset) && position.y <= (roomBounds.yMax - offset))
                {
                    floor.Add(position);
                }
            }
        }
        return floor;
    }

    private HashSet<Vector2Int> ConnectRooms(List<Vector2Int> roomCenters) // BSP 복도 생성 스크립트
    {
        var corridors = new HashSet<Vector2Int>();
        var currentRoomCenter = roomCenters[Random.Range(0, roomCenters.Count)];
        roomCenters.Remove(currentRoomCenter);

        while (roomCenters.Count > 0)
        {
            Vector2Int closest = FindClosestPointTo(currentRoomCenter, roomCenters);
            roomCenters.Remove(closest);
            HashSet<Vector2Int> newCorridor = CreateCorridor(currentRoomCenter, closest);
            currentRoomCenter = closest;
            corridors.UnionWith(newCorridor);
        }
        return corridors;
    }
    private Vector2Int FindClosestPointTo(Vector2Int currentRoomCenter, List<Vector2Int> roomCenters)
    {
        Vector2Int closest = Vector2Int.zero;
        float distance = float.MaxValue;
        foreach (var position in roomCenters)
        {
            float currentDistance = Vector2.Distance(position, currentRoomCenter);
            if (currentDistance < distance)
            {
                distance = currentDistance;
                closest = position;
            }
        }
        return closest;
    }

    private HashSet<Vector2Int> CreateCorridor(Vector2Int currentRoomCenter, Vector2Int destination)
    {
        var corridor = new HashSet<Vector2Int>();
        var position = currentRoomCenter;
        corridor.Add(position);
        while (position.y != destination.y)
        {
            if (destination.y > position.y)
            {
                position += Vector2Int.up;
            }
            else if (destination.y < position.y)
            {
                position += Vector2Int.down;
            }
            corridor.Add(position);
        }
        while (position.x != destination.x)
        {
            if (destination.x > position.x)
            {
                position += Vector2Int.right;
            }
            else if (destination.x < position.x)
            {
                position += Vector2Int.left;
            }
            corridor.Add(position);
        }
        return corridor;
    }


    private HashSet<Vector2Int> CreateSimpleRooms(List<BoundsInt> roomList)
    {
        var floor = new HashSet<Vector2Int>();
        foreach (var room in roomList)
        {
            for (int col = offset; col < room.size.x - offset; col++)
            {
                for (int row = offset; row < room.size.y - offset; row++)
                {
                    Vector2Int position = (Vector2Int)room.min + new Vector2Int(col, row);
                    floor.Add(position);
                }
            }
        }
        return floor;
    }
}

시간이 없어서 세부 설명 주석을 적지 못했다.

아래는 프로젝트 맵 작성 내내 참고한 유튜브 링크다. 맵 작성~벽 작성까지 전부 참고했다.

풀 영어이긴 하지만, 재밌는거 많다. 나중에 도움될 내용이 있으면 지속적으로 참고하지 않을까 생각한다.

https://www.youtube.com/@SunnyValleyStudio

 

Sunny Valley Studio

Hi! I'm Peter 👋 I am a game developer / programmer. Also Unity insider 😁 If you want to learn how to make games in Unity I do my best to share my knowledge about coding in C# and creating game mechanics in my video tutorials and in my courses at (htt

www.youtube.com