• unity3d随机地牢生成代码


    现在也是处于失业状态,碰巧看到个面试题是要用unity生成个随机地牢,就把做题过程中的思路和代码记录一下吧。

    做完了以后我又想了一下,发现其实根本不需要这么麻烦,果然demo里的代码对我的思路影响还是有点大。demo里的c++代码为了展示地牢的墙壁,在二维数组中加上了wall这个东西表示墙壁。事实上用unity来做的话,只需要考虑地板的位置,然后根据邻接的地板有没有东西来判断是否生成墙壁即可。

    Demo 使用素材以及题目地址:http://pan.baidu.com/s/1c2l3RFE 密码:aseh

    首先用一个枚举类型代表地牢迷宫中的各个元素:

    public enum Tile
    {
    Default,
    DirtFloor,// 房间地板
    Wall_w,//上方的墙
    Wall_s,//下方的墙
    Wall_a,//左方的墙
    Wall_d,//右方的墙
    Corridor_ad,//横向走廊
    Corridor_ws,//纵向走廊
    Door,// 房门
    UpStairs,// 入口
    DownStairs// 出口
    }

    然后考虑使用二维数组来保存地牢元素的信息。既然是用unity来做,先不考虑随机地牢的逻辑数组要怎么生成,先把二维数组转化为实体的方法写出来:

    建立一个test脚本,用于测试生成用的creat_dungeon方法是否好用,并在里面定义一个测试用的二维数组:

    using UnityEngine;
    using System.Collections;
    using System;
    public class test : MonoBehaviour {
    void Start () {
    Action _Instantiate=(t,v)=>{Instantiate(t,v,t.rotation);};//这里考虑不当,理论上不应该在test脚本中写Instantiate方法,而是应该在creat_dungeon中用。不过既然写出来了也能显示我懂一些委托的知识...将错就错好了
    Tile[,] _map=new Tile[4,4];
    _map [0, 1] = Tile.Wall_s;
    _map [0, 2] = Tile.Wall_s;
    _map [1, 1] = Tile.DirtFloor;
    _map [1, 2] = Tile.DirtFloor;
    _map [2, 1] = Tile.DirtFloor;
    _map [2, 2] = Tile.DirtFloor;
    _map [3, 1] = Tile.Corridor_ws;
    _map [3, 2] = Tile.Wall_w;
    _map [1, 3] = Tile.Corridor_ad;
    _map [2, 3] = Tile.Wall_d;
    _map [1, 0] = Tile.Corridor_ad;
    _map [2, 0] = Tile.Wall_a;//自定义一个地图
    creat_dungeon.Instance.creat (_map,_Instantiate);//调用生成函数
    }

    接下去就是考虑怎么写creat_dungeon这个类了,首先这个类只用于生成实体,所以不应该存在多份,所以设计成单例:

    public class creat_dungeon{
    private static creat_dungeon _instance;
    private creat_dungeon(){}
    public static creat_dungeon Instance{
    get 
    {
    if (_instance == null) 
    {
    _instance = new creat_dungeon ();
    }
    return _instance;
    }
    }
    }

    然后这个类中肯定需要动态加载预设的资源,这里用resources.load方法加载,在初始化函数中加上加载资源的代码:

    private Transform floor,pillar,wall_ws,wall_ad;
    private creat_dungeon(){
    floor = Resources.Load ("Floor_1_Prefab",typeof (Transform)) as Transform;
    pillar = Resources.Load ("Pillar_1_Prefab",typeof (Transform))as Transform;
    wall_ws = Resources.Load ("Wall_w", typeof(Transform))as Transform;
    wall_ad = Resources.Load ("Wall_a", typeof(Transform))as Transform;
     
    }

    然后根据资源模型的大小,定义一个常量:

    private float floor_length=1.518111f;

    最后就是其中生成用的代码:

    /// 根据二维数组实例化地牢
    public void creat(Tile[,] map,Action Instantiate){
    for (int i = 0; i < map.GetLength (0); i++)
    for (int j = 0; j < map.GetLength (1); j++) {
    switch (map [i, j]) {
    case (Tile.DirtFloor)://地板的场合
    Instantiate (floor,new Vector3(i*floor_length,0,j*floor_length));
    break;
    case (Tile.Wall_s)://墙壁的场合
    Instantiate (wall_ws,new Vector3((i+0.5f)*floor_length,0,j*floor_length));
    Instantiate (pillar,new Vector3((i+0.5f)*floor_length,0,(j+0.5f)*floor_length));//在墙壁对应的位置生成柱子,由于最终生成的是一个封闭的空间,所以墙壁和柱子的数目必定是相等的。
    break;
    case (Tile.Wall_w):
    Instantiate (wall_ws,new Vector3((i-0.5f)*floor_length,0,j*floor_length));
    Instantiate (pillar,new Vector3((i-0.5f)*floor_length,0,(j-0.5f)*floor_length));
    break;
    case (Tile.Wall_a):
    Instantiate (wall_ad,new Vector3((i)*floor_length,0,(j+0.5f)*floor_length));
    Instantiate (pillar,new Vector3((i-0.5f)*floor_length,0,(j+0.5f)*floor_length));
    break;
    case (Tile.Wall_d):
    Instantiate (wall_ad,new Vector3((i)*floor_length,0,(j-0.5f)*floor_length));
    Instantiate (pillar,new Vector3((i+0.5f)*floor_length,0,(j-0.5f)*floor_length));
    break;
    case (Tile.Corridor_ad)://走廊的话,则需要额外生成两面墙和两根柱子,不妨想想为什么要这么做
    Instantiate (floor,new Vector3(i*floor_length,0,j*floor_length));
    Instantiate (wall_ws,new Vector3((i+0.5f)*floor_length,0,j*floor_length));
    Instantiate (wall_ws,new Vector3((i-0.5f)*floor_length,0,j*floor_length));
    Instantiate (pillar,new Vector3((i-0.5f)*floor_length,0,(j+0.5f)*floor_length));
    Instantiate (pillar,new Vector3((i+0.5f)*floor_length,0,(j-0.5f)*floor_length));
    break;
    case (Tile.Corridor_ws):
    Instantiate (floor,new Vector3(i*floor_length,0,j*floor_length));
    Instantiate (wall_ad,new Vector3(i*floor_length,0,(j+0.5f)*floor_length));
    Instantiate (wall_ad,new Vector3(i*floor_length,0,(j-0.5f)*floor_length));
    Instantiate (pillar,new Vector3((i+0.5f)*floor_length,0,(j+0.5f)*floor_length));
    Instantiate (pillar,new Vector3((i-0.5f)*floor_length,0,(j-0.5f)*floor_length));
    break;
    default:
    break;
    }
    }
    }

    最后运行效果如下:

    unity3d随机地牢生成代码

    可以看到三个走廊没有封上口,因为这里预定走廊两端必须要连接房间,不能出现死胡同的情况,这里可以看到左侧和右侧走廊的柱子是可以对上的。

    好,这样的话转化为实体的方法就写好了,现在考虑怎么生成逻辑地形。

    思路是这样的:第一次生成房间时,直接在地图最中间生成,之后每次生成一个房间和一个走廊,把走廊和之前造出来的东西连起来。考虑到不想让走廊拐弯,所以每次先生成走廊再生成房间。

    最终map类的代码如下:

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    
    public class map
    {
    private Tile[,] full_map;//地图
    private int room_max_length, room_max_width, room_min_length, room_min_width, map_max_length, map_max_width, room_num,min_corridor_len,max_corridor_len;//房间的最大最小宽度,地图的最大长宽,房间的个数
    private bool _first;
    private static map _instance;
    
    private map ()
    {
    }
    
    /// 地图的单例
    public static map Instance {
    get {
    if (null == _instance) {
    _instance = new map ();
    }
    return _instance;
    }
    }
    
    /// 初始化函数,全部赋予default
    public void Init (int room_max_length,int room_max_width,int room_min_length,int room_min_width,int map_max_length,int map_max_width,int room_num,int min_corridor_len,int max_corridor_len)
    {
    full_map = new Tile[map_max_width, map_max_length];
    _first = true;
    this.room_max_length = room_max_length; 
    this.room_max_width = room_max_width; 
    this.room_min_length = room_min_length;
    this.room_min_width = room_min_width;
    this.map_max_length = map_max_length;
    this.map_max_width = map_max_width;
    this.room_num = room_num;
    this.min_corridor_len = min_corridor_len;
    this.max_corridor_len = max_corridor_len;
    }
    
    /// 判断某一块是否在地图区域中
    private bool IsInBounds_x (int x)
    {
    if ((x < 0) || (x > map_max_width - 1))
    return false;
    else 
    return true;
    }
    
    /// 判断某一块是否在地图区域中
    private bool IsInBounds_y (int y)
    {
    if ((y < 0) || (y > map_max_length - 1))
    return false;
    else 
    return true;
    }
    
    /// 将一块区域设置为指定类型块
    private void SetCells (int xStart, int yStart, int xEnd, int yEnd, Tile cellType)
    {
    for (int i = xStart; i <= xEnd; i++)
    for (int j = yStart; j <= yEnd; j++) {
    full_map [i, j] = cellType;
    }
    }
    
    /// 判断一个区域是否被使用过
    private bool IsAreaUnused (int xStart, int yStart, int xEnd, int yEnd)
    {
    for (int i = xStart; i <= xEnd; i++)
    for (int j = yStart; j <= yEnd; j++)
    if (full_map [i, j] != Tile.Default)
    return false;
    return true;
    }
    
    /// 创建单个房间
    private void Creat_room(int xStart, int yStart, int xEnd, int yEnd){
    for (int i = xStart + 1; i < xEnd ; i++)
    for (int j = yStart + 1; j < yEnd; j++)
    full_map [i, j] = Tile.DirtFloor;
    for (int i = xStart + 1; i < xEnd ; i++) {
    full_map [i, yStart] = Tile.Wall_a;
    full_map [i, yEnd] = Tile.Wall_d;
    }
    for (int j = yStart + 1; j < yEnd; j++) {
    full_map [xStart, j] = Tile.Wall_s;
    full_map [xEnd, j] = Tile.Wall_w;
    }
    }
    
    /// 创建单个走廊
    private void Creat_Corridor(int xStart, int yStart, int xEnd, int yEnd,Tile t){
    for (int i = xStart; i <= xEnd ; i++)
    for (int j = yStart; j <= yEnd; j++)
    full_map [i, j] = t;
    }
    
    private Tile[] tiles = System.Enum.GetValues (typeof(Tile)) as Tile[];
    /// 创建走廊与房间
    private bool MakeRoomAndCorridor(int x,int y){
    int xStart = -1, xEnd = -1, yStart = -1, yEnd = -1, width, length;
    width = Random.Range (room_min_width, room_max_width );
    length = Random.Range (room_min_length, room_min_length );//随机长宽
    if (_first) {
    xStart = map_max_width / 2 - width / 2;
    yStart = map_max_length / 2 - length / 2;
    xEnd = xStart + width;
    yEnd = yStart + length;
    
    if (IsInBounds_x (xStart) && IsInBounds_x (xEnd) && IsInBounds_y (yStart) && (IsInBounds_y (yEnd))) {
    if (IsAreaUnused (xStart, yStart, xEnd, yEnd)) {
    _first = false;//如果是第一次创建房间的话,就在地图中间生成一个
    Creat_room (xStart, yStart, xEnd, yEnd);
    return true;
    } else
    return false;
    }
    } else {
    if ((full_map [x, y] == Tile.Wall_a) || (full_map [x, y] == Tile.Wall_w) || (full_map [x, y] == Tile.Wall_s) || (full_map [x, y] == Tile.Wall_d)) {
    //生成走廊与房间
    int corridor_length = Random.Range (min_corridor_len - 2, max_corridor_len - 1);
    int c_xStart = -1, c_xEnd = -1, c_yStart = -1, c_yEnd = -1;
    int away = Random.Range (1, length - 1);//偏移量
    Tile type=Tile.Default;
    //根据打穿的墙壁类型决定房间和走廊的位置
    switch (full_map [x, y]) {
    case(Tile.Wall_a):
    xStart = x - away;
    xEnd = x + width;
    yEnd = y - corridor_length - 1;
    yStart = yEnd - length;
    c_yEnd = y;
    c_yStart = y - corridor_length - 1;
    c_xEnd = x;
    c_xStart = x;
    type = Tile.Corridor_ad;
    break;
    case(Tile.Wall_d):
    xStart = x - away;
    xEnd = x + width;
    yStart = y + corridor_length + 1;
    yEnd = yStart + length;
    c_yStart = y;
    c_yEnd = y + corridor_length + 1;
    c_xEnd = x;
    c_xStart = x;
    type = Tile.Corridor_ad;
    break;
    case(Tile.Wall_w):
    yStart = y - away;
    yEnd = yStart + length;
    xStart = x + corridor_length + 1;
    xEnd = xStart + width;
    c_xStart = x;
    c_xEnd = x + corridor_length + 1;
    c_yStart = y;
    c_yEnd = y;
    type = Tile.Corridor_ws;
    break;
    case(Tile.Wall_s):
    yStart = y - away;
    yEnd = yStart + length;
    xEnd = x - corridor_length - 1;
    xStart = xEnd - width;
    c_xEnd = x;
    c_xStart = x - corridor_length - 1;
    c_yStart = y;
    c_yEnd = y;
    type = Tile.Corridor_ws;
    break;
    default:
    break;
    }
    if (IsAreaUnused (xStart, yStart, xEnd, yEnd)) {
    Creat_room (xStart, yStart, xEnd, yEnd);
    Creat_Corridor (c_xStart, c_yStart, c_xEnd, c_yEnd,type);//判断是否在地图内,然后生成
    return true;
    } else
    return false;
    } else
    return false;
    }
    
    return true;
    }
    public void make_dungeon(int step){
    int x, y;
    int num=0;
    for (int i = 0; i < step; i++) {
    x = Random.Range (0,map_max_width);
    y = Random.Range (0,map_max_length);
    if (MakeRoomAndCorridor(x,y)){
    num++;
    }
    if (num==room_num){
    break;
    }
    }
    if (num
    Debug.Log ("无法生成指定个数的房间!请确认数据的合法性或加大步数");
    }
    }
    public Tile[,] getmap(){
    return(full_map);
    }
    }
    然后是用于控制生成的类,绑在摄像机上就能运行:
    using UnityEngine;
    using System.Collections;
    using System;
    
    public class gameDriver : MonoBehaviour {
    private Transform dungeon;
    public int random_seed=1;
    public int room_max_length=5,room_max_width=5,room_min_length=3,room_min_width=3,map_max_length=100,map_max_width=100,room_num=5,min_corridor_len=1,max_corridor_len=3,step=10000;//房间的最大最小宽度,地图的最大长宽,房间的个数
    // Use this for initialization
    void Start () {
    dungeon = GameObject.Find ("dungeon").transform;//在地图上事先创建了一个放实体的空物体
    Action _Instantiate=(t,v)=>{object g=Instantiate(t,v,t.rotation);((Transform)g).SetParent(dungeon);};
    UnityEngine.Random.InitState (random_seed);//初始化随机种子
    map.Instance.Init (room_max_length, room_max_width, room_min_length, room_min_width, map_max_length, map_max_width, room_num,min_corridor_len,max_corridor_len);//初始化参数
    map.Instance.make_dungeon(step);//生成地牢
    creat_dungeon.Instance.creat (map.Instance.getmap (), _Instantiate);//实体化地牢
    }
    // Update is called once per frame
    void Update () {
    }
    }

    最终的效果预览:

    unity3d随机地牢生成代码

    当然这个地牢还有很多东西没加上去,比如我在枚举类中定义的门(可以加在走廊的两端),出入口。还有装饰用的灯啊,小物品等,暂时就先这样吧,也没想到用这个后续搞点事情。

    补发工程源码:

    链接:https://pan.baidu.com/s/1jIuh36m 密码:hx9b

  • 相关阅读:
    C#第八节课
    C#第七节课
    C#第六节课
    supervisor进程管理的使用
    oracle分区表
    Zabbix配置邮件监控
    python连接oracle数据库
    json内存级非关系数据库
    Oracle 12c CDB PDB 安装/配置/管理
    Let's Encrypt免费泛域名证书申请
  • 原文地址:https://www.cnblogs.com/Swallowtail/p/6181441.html
Copyright © 2020-2023  润新知