基于 Unity 的 Roguelike 随机房间生成的方法

作者:sTarrr_/A/
2017-08-11
33 36 4

本文主要是为了实现各种随机大小不一样的方块基础房间,便于 Roguelike 游戏地图使用,希望对各位独立游戏的新手有所帮助,同时也希望各位大牛可以给出宝贵意见。

由于我的思路是地图=基础房间+特殊房间(宝箱房、挑战房、任务房、Boss房),本文暂不涉及特殊房间以及个性化的多边形房间。

房间要求

  1. 房间大小基本符合一定的长宽比;
  2. 单个房间不会有重叠;
  3. 房间间保留一定间隙,方便后续填充围墙与走廊;
  4. 整体房间分布不是线性的,方便后续连通的多样性;

核心思路

  1. 根据游戏的操作,定下一个房间的长宽比;(本文的房间长宽比为1.6-2.0)
  2. 根据长宽比,取得1号房间的长与宽X1,Y1;
  3. 取得1号房间的中心点O1;
  4. 对O1进行一个四向的随机偏移,得到O2;
  5. 取得2号房间的长于宽X2,Y2;
  6. 利用射线检测,若无重叠则可生成,若有,重复4、5、6步骤直到可以生成;
  7. 铺设地毯、围墙等基本装饰物,完成。

效果图

17215-1502247342

房间概览图

17215-1502247354

房间细节图

代码

C#代码如下,包含三个主体方法:取得房间半径、取得房间中心点和生成房间。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MapManager : MonoBehaviour
{

  // Use this for initialization
  private float roomRadii;//房间的长度半径
  private float roomRatio;//由宽决定长的比值
  public float roomX;//房间的长
  public float roomY;//房间的宽
  public float roomXi;//房间i的长
  public float roomYi;//房间i的宽

  public int roomXMin = 10;
  public int roomXMax = 16;

  public Vector2 posO;//房间中心点

  int roomIndex = 0;

  public List roomPos = new List();//创建储存房间有效位置的二维表
  public List offset = new List();//创建储存房间中心点的二维表

  public GameObject[] floorArray;

  public Transform roomHolder;

  void Start()
  {
    roomPos.Clear();//后续房间内的所有可用位置都会添加到这个二维表里
    SetRoomXY();
    posO = new Vector2(0, 0);

    //生成12个房间进行检验
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
    CreateRoom(posO);
  }

  void SetRoomXY()//取得房间长宽半径
  {
    roomRadii = Random.Range(roomXMin, roomXMax);//标准值取随机值
    roomRatio = Random.Range(16, 20);
    roomRatio = roomRatio / 10;
    roomX = 2 * roomRadii;//房间的长是标准值的两倍,因此就是偶数
    roomY = roomX / roomRatio;//房间的宽由长来决定
    roomY = Mathf.Round(roomY);//对Y取整数
    if (roomY % 2 != 0)//对Y进行判断,取偶数 {
      roomY += 1;
    }
  }

  Vector2 CreateCenter(Vector2 pos)//对中心点进行偏移
  {
    int x = Random.Range(2 * roomXMin + 1, 2 * roomXMax + 1);
    offset.Clear();
    for (int i = 2 * roomXMin + 1; i <= 2 * roomXMax + 1; i++) {
      for (int j = 30; j <= 50; j++) {
        offset.Add(new Vector2(i, j));
      }
    }
    //现在取出一个符合基本规则的中心点偏移量
    int index = Random.Range(0, offset.Count);
    float offsetX = offset[index].x;
    float offsetY = offset[index].y;

    Vector2 pos2 = new Vector2(0, 0);

    int o = Random.Range(0, 4);
    if (o == 0) {
      pos2 = pos + new Vector2(offsetX, offsetY);
    }
    if (o == 1) {
      pos2 = pos + new Vector2(offsetX, -offsetY);
    }
    if (o == 2) {
      pos2 = pos + new Vector2(-offsetX, offsetY);
    }
    if (o == 3) {
      pos2 = pos + new Vector2(-offsetX, -offsetY);
    }

    //现在取得了下一个房间中心
    posO = pos2;
    return posO;
  }

  void CreateRoom(Vector2 pos)//生成基础房间(铺设地板)
  {
    CreateCenter(pos);//对上一个房间的中心点进行偏移,取得本次房间的中心点
    pos = posO;
    SetRoomXY();//取得当前要生成房间的长宽半径

    roomXi = roomX;
    roomYi = roomY;

    int isRoom = 0;

    //对预生成的房间先进行是否有房间的检测(基于房间九点:左上,中上,右上,左中……)
    Vector2 checkPos1 = new Vector2(pos.x - roomXi, pos.y - roomYi) + new Vector2(-4, -4);
    Vector2 checkPos2 = new Vector2(pos.x - roomXi, pos.y + roomYi) + new Vector2(-4, 4);
    Vector2 checkPos3 = new Vector2(pos.x + roomXi, pos.y - roomYi) + new Vector2(-4, 4);
    Vector2 checkPos4 = new Vector2(pos.x + roomXi, pos.y + roomYi) + new Vector2(4, 4);
    Vector2 checkPos6 = new Vector2(pos.x, pos.y - roomYi) + new Vector2(0, -4);
    Vector2 checkPos7 = new Vector2(pos.x, pos.y + roomYi) + new Vector2(0, 4);
    Vector2 checkPos8 = new Vector2(pos.x + roomXi, pos.y) + new Vector2(4, 0);
    Vector2 checkPos9 = new Vector2(pos.x - roomXi, pos.y) + new Vector2(-4, 0);

    RaycastHit2D hit1 = Physics2D.Linecast(checkPos1, checkPos1, 1 << LayerMask.NameToLayer("floor"));
    RaycastHit2D hit2 = Physics2D.Linecast(checkPos2, checkPos2, 1 << LayerMask.NameToLayer("floor"));
    RaycastHit2D hit3 = Physics2D.Linecast(checkPos3, checkPos3, 1 << LayerMask.NameToLayer("floor"));
    RaycastHit2D hit4 = Physics2D.Linecast(checkPos4, checkPos4, 1 << LayerMask.NameToLayer("floor"));
    RaycastHit2D hit5 = Physics2D.Linecast(pos, pos, 1 << LayerMask.NameToLayer("floor"));
    RaycastHit2D hit6 = Physics2D.Linecast(checkPos6, checkPos6, 1 << LayerMask.NameToLayer("floor"));
    RaycastHit2D hit7 = Physics2D.Linecast(checkPos7, checkPos7, 1 << LayerMask.NameToLayer("floor"));
    RaycastHit2D hit8 = Physics2D.Linecast(checkPos8, checkPos8, 1 << LayerMask.NameToLayer("floor"));
    RaycastHit2D hit9 = Physics2D.Linecast(checkPos9, checkPos9, 1 << LayerMask.NameToLayer("floor"));
    if (hit1.collider == null && hit2.collider == null && hit3.collider == null && hit4.collider == null && hit5.collider == null && hit6.collider == null && hit7.collider == null && hit8.collider == null && hit9.collider == null)
    {
      isRoom += 1;
    }
    if (isRoom >= 0)
    {
      if (isRoom == 0) {
        CreateRoom(pos);
      } else {
        roomIndex += 1;//可以生成房间的时候,给房间记上标记
        roomHolder = new GameObject("Room" + roomIndex).transform;//给父类添加名字

        //为地板进行二维数组赋值
        for (float x = pos.x - roomXi; x <= pos.x + roomXi; x++) {
          for (float y = pos.y - roomYi; y <= pos.y + roomYi; y++) {
            Vector2 posFloor = new Vector2(x, y);
            roomPos.Add(new Vector2(x, y));
            int i = Random.Range(0, floorArray.Length);
            GameObject go = Instantiate(floorArray[i], posFloor, Quaternion.identity);
            go.transform.SetParent(roomHolder);
          }
        }
      }
    }
  }
}

关于连通与特殊房,我的想法是因为生成的时候给房间以及进行序号标记了(1-12),先依次对中心点进行走廊生成,以及门生成,确保主干道连通,再取随机房间进行连通丰富路线(如2和4,6和8),在这个基础上加入预设的特殊房。(特殊房应该有足够的设计量,因此不适宜用随机。)

近期点赞的会员

 分享这篇文章

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. sTarrr_/A/ 2017-08-11

    嗨呀,没想到被推荐了,这种分享的成就感和喜悦还是很大的,在此谢谢编辑大大。
    写这篇文章主要的目的是当时为了想要实现房间生成的时候,网上找了大半天的都没有合适使用的,由于自己目前还是新手上路,网上能找到的对我来讲又晦涩难懂……自己完成后就分享出来,希望可以给有需要的人带来帮助。
    另外希望各位有经验的大大可以分享下关于 [实现联通] 的一些经验,同时希望IndieNova的讨论氛围可以越来越活跃。

    • eastecho 2017-08-11

      @sTarrr_/A/:感谢您的投稿!咱们慢慢讨论!

  2. resty 2017-08-11

    题图赞,Nethack啊

  3. 晒死js 2017-10-07

    题图是nethack的城堡层吗..

您需要登录或者注册后才能发表评论

登录/注册