unity实现循环地图组件

169 阅读7分钟

前言

在梦日记及其相关派生中,总会用到循环的地图,这些循环的地图都有一个能让玩家能一直走下去不会停止的功能,今天,我们就要实现它。

方法

首先我们就要实现移动的功能,再在这个移动功能的基础上进行循环地图的效果。

要循环地图,有以下几点:

第一,你得准备两张地图,一张是物理上的地图,一张是抽象上的地图。

物理意义上的地图准备好了,抽象的地图在你的键盘揉搓一下,也是可以做好的。主要设置的是地图长和宽及内容,要循环就要获取地图的左右边界x值与上下边界y值,求格子边长要用到地图宽度。

设置地图的内容,就只用空格填充就行。

为防止你被莫名其妙的来到了地图的另一边,并且你毫无意义的像橡皮掉下去一样找不到你自己了,就还要有一点改动——给地图周围添加其他与之一样的地图(等你添加完了地图,玩梦日记及其派生的玩家想:这就是东还是西北方?)

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

public class map : MonoBehaviour
{
    public static int x = 10;
    public static int y = 10;
    public int a = 100;
    public int b = 100;
    public GameObject o;
    public static int minX = -50;
    public static int minY = -50;
    public static int maxX = 50;
    public static int maxY = 50;
    public static int heightX { 
        get
        {
            return Mathf.Abs(maxX - minX);
        }
    }
    public static int widthY
    {
        get
        {
            return Mathf.Abs(maxY - minY);
        }
    }
    public static char[,] wmap;
    void Awake()
    {
        wmap = new char[x, y];
        for (int i = 0; i < x; i++)
        {
            for (int j = 0; j < y; j++)
            {
                wmap[i, j] = ' ';
            }
        }
    }

    private void Start()
    {
        for (int i = 0; i < 9; i++)
        {
            if (4 == i)
            {
                continue;
            }
            Instantiate(o, new Vector3(transform.position.x + (i % 3 - 1) * a, 0, transform.position.z + (i / 3 - 1) * b), transform.rotation);
        }
    }
    void Update()
    {
        
    }
}

倒数第一,你得准备一个玩家,还有一个抽象化的玩家;

抽象化的玩家,我们把它压得像烧饼一样由又香又扁,就是很简单的一个指针在移动,拆分一下也就是x和y两个轴移动,通过这两个轴,可以在抽象出来的地图上引出无数条明亮的思路。

其中,在抽象化的玩家上,唯一难的是你得将地图上的x轴转为游戏上的x轴,并把地图上的y轴转为游戏上的z轴。解决问题时,我们要先知道已知的信息。

从该问题可知,抽象出来的地图的原点就是在最左上角。然后通过这些抽象地图上的数据,我们可以得出在地图上物体在游戏中的实际位置,就是pos=((x最小+((x+12)xx),y,y最大((y+12)yy)))p_{os}=((x_{最小}+(\frac{(x_物 + \frac{1}{2})x_长}{x_图}),y_实, y_{最大}-(\frac{(y_物 + \frac{1}{2})y_宽}{y_图})))

在这个式子中,xx\frac{x_长}{x_图}yy\frac{y_宽}{y_图}求出了一个格子的长与宽,两个12\frac{1}{2}分别表示一个格子长与宽的一半,由于x2x\frac{x_长}{2x_图}y2y\frac{y_宽}{2y_图}表示为在长方形里的数对的话,就恰好在长方形中间。因此,这两个分数就成了关键的一点。

之后通过x轴和y轴分别乘以xx_物yy_物,就得出了地图上的物体在游戏中的实际位置。(在刚才这个例子中,如果你将地块拆分成了像屎一样的形状,那么这个式子不成立,可能连你自己都要笑死)

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

public class move : MonoBehaviour
{
    enum wasd
    {
        n,
        w,
        a,
        s,
        d
    };
    private bool isEnd = true;
    public float moveTime = 0.01f;
    public float waitTime = 0.2f;
    public int x = 5;
    public int y = 5;
    private float high = 5;
    wasd getwasd()
    {
        if (Input.GetKey("w") && ' ' == map.wmap[x, y - 1 >= 0 ? y - 1 : map.y - 1])
        {
            map.wmap[x, y--] = ' ';
            if (y < 0)
            {
                transform.position = new Vector3(transform.position.x, high, map.minY - map.widthY / map.y / 2.0f);
                y = map.y - 1;
            }
            map.wmap[x, y] = 'I';
            return wasd.w;
        }
        else if (Input.GetKey("a") && ' ' == map.wmap[x - 1 >= 0 ? x - 1 : map.x - 1, y])
        {
            map.wmap[x--, y] = ' ';
            if (x < 0)
            {
                transform.position = new Vector3(map.maxX + map.heightX / map.x / 2.0f, high, transform.position.z);
                x = map.x - 1;
            }
            map.wmap[x, y] = 'I';
            return wasd.a;
        }
        else if (Input.GetKey("s") && ' ' == map.wmap[x, (y + 1) % map.y])
        {
            map.wmap[x, y++] = ' ';
            if (y >= map.y)
            {
                transform.position = new Vector3(transform.position.x, high, map.maxY + map.widthY / map.y / 2.0f);
            }
            y %= map.y;
            map.wmap[x, y] = 'I';
            return wasd.s;
        }
        else if (Input.GetKey("d") && ' ' == map.wmap[(x + 1) % map.x, y])
        {
            map.wmap[x++, y] = ' ';
            if (x >= map.x)
            {
                transform.position = new Vector3(map.minX - map.heightX / map.x / 2.0f, high, transform.position.z);
            }
            x %= map.x;
            map.wmap[x, y] = 'I';
            return wasd.d;
        }
        else 
        {
            return wasd.n;
        }
    }

    IEnumerator pmove()
    {
        //初始
        isEnd = false;
        wasd i = getwasd();
        for (int j = 0; j < 100; j++)
        {
            switch (i)//移动
            {
                case wasd.w:
                    transform.position += new Vector3(0, 0, map.widthY / map.y / 100.0f);
                    yield return new WaitForSeconds(moveTime);
                    break;
                case wasd.a:
                    transform.position += new Vector3(-map.heightX / map.x / 100.0f, 0, 0);
                    yield return new WaitForSeconds(moveTime);
                    break;
                case wasd.s:
                    transform.position += new Vector3(0, 0, -map.widthY / map.y / 100.0f);
                    yield return new WaitForSeconds(moveTime);
                    break;
                case wasd.d:
                    transform.position += new Vector3(map.heightX / map.x / 100.0f, 0, 0);
                    yield return new WaitForSeconds(moveTime);//移动间隔时间
                    break;
                default://wasd.n时无
                    goto nowait;
            }
        }
        yield return new WaitForSeconds(waitTime);//移动等待时间
    nowait:
        isEnd = true;
        yield return null;
    }

    void Start()
    {
        //根据地图z轴进行高度计算
        high = transform.position.y;
        //根据地图xy轴进行位置计算
        transform.position = new Vector3(map.minX + map.heightX / map.x * (0.5f + x), high, map.maxY - map.widthY / map.y * (0.5f + y));
        map.wmap[x, y] = 'I';
        StartCoroutine(pmove());
    }

    void Update()
    {
        if (isEnd)
        {
            StartCoroutine(pmove());
        }
    }
}

做到这了,你可以添加一些NPC,那么,实质的我们不讲,简单到没什么讲的,主要就是讲抽象方面的。(抽象)

抽象方面,NPC这个任务主要就是随便的乱动,但移动的逻辑还是跟玩家一样。因此,我们就复制玩家移动的逻辑,之后将用按键判断的条件改为用随机值判断的条件,就可以。

光是完成这一点还不够,还要给NPC在周围复制8个,让移动的位置在其它地图上与物体刚才地图上的相对位置一样。我们想想,想到可以给NPC移动地图的长或宽步。这样NPC就会觉得:这回是鬼打墙吗,有点意思。

移动之后,我们就要让这些克隆体的父级设为刚才复制这些克隆体的对象,那如何在全局位置不变的情况下能够在克隆的同时,设置克隆物体的父级呢?答案就是只需要一个克隆物体函数Instantiate的重载版本:

这个重载版本,重点就在要重看的那个bool类参数上,如果该参为真,那么物体的全局位置不变;反之,物体的全局位置就变,相当于

之后,就可以写了。

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

public class npcMove : MonoBehaviour
{
    enum wasd
    {
        w,
        a,
        s,
        d,
        n
    };
    private bool isEnd = true;
    public float moveTime = 0.01f;
    public float waitTime = 0.2f;
    public int x = 5;
    public int y = 5;
    private float high = 5;
    public GameObject clone;
    wasd getwasd()
    {
        wasd w = (wasd)Random.Range(0, 4);
        if (wasd.w == w && ' ' == map.wmap[x, y - 1 >= 0 ? y - 1 : map.y - 1])
        {
            map.wmap[x, y--] = ' ';
            if (y < 0)
            {
                transform.position = new Vector3(transform.position.x, high, map.minY - map.widthY / map.y / 2.0f);
                y = map.y - 1;
            }
            map.wmap[x, y] = 'N';
            return wasd.w;
        }
        else if (wasd.a == w && ' ' == map.wmap[x - 1 >= 0 ? x - 1 : map.x - 1, y])
        {
            map.wmap[x--, y] = ' ';
            if (x < 0)
            {
                transform.position = new Vector3(map.maxX + map.heightX / map.x / 2.0f, high, transform.position.z);
                x = map.x - 1;
            }
            map.wmap[x, y] = 'N';
            return wasd.a;
        }
        else if (wasd.s == w && ' ' == map.wmap[x, (y + 1) % map.y])
        {
            map.wmap[x, y++] = ' ';
            if (y >= map.y)
            {
                transform.position = new Vector3(transform.position.x, high, map.maxY + map.widthY / map.y / 2.0f);
            }
            y %= map.y;
            map.wmap[x, y] = 'N';
            return wasd.s;
        }
        else if (wasd.d == w && ' ' == map.wmap[(x + 1) % map.x, y])
        {
            map.wmap[x++, y] = ' ';
            if (x >= map.x)
            {
                transform.position = new Vector3(map.minX - map.heightX / map.x / 2.0f, high, transform.position.z);
            }
            x %= map.x;
            map.wmap[x, y] = 'N';
            return wasd.d;
        }
        else
        {
            return wasd.n;
        }
    }

    IEnumerator pmove()
    {
        //初始
        isEnd = false;
        wasd i = getwasd();
        for (int j = 0; j < 100; j++)
        {
            switch (i)//移动
            {
                case wasd.w:
                    transform.rotation = Quaternion.Euler(-90, 0, 180);
                    transform.position += new Vector3(0, 0, map.widthY / map.y / 100.0f);
                    yield return new WaitForSeconds(moveTime);
                    break;
                case wasd.a:
                    transform.rotation = Quaternion.Euler(-90, 0, 90);
                    transform.position += new Vector3(-map.heightX / map.x / 100.0f, 0, 0);
                    yield return new WaitForSeconds(moveTime);
                    break;
                case wasd.s:
                    transform.rotation = Quaternion.Euler(-90, 0, 0);
                    transform.position += new Vector3(0, 0, -map.widthY / map.y / 100.0f);
                    yield return new WaitForSeconds(moveTime);
                    break;
                case wasd.d:
                    transform.rotation = Quaternion.Euler(-90, 0, -90);
                    transform.position += new Vector3(map.heightX / map.x / 100.0f, 0, 0);
                    yield return new WaitForSeconds(moveTime);//移动间隔时间
                    break;
                default://wasd.n时无
                    goto nowait;
            }
        }
        yield return new WaitForSeconds(waitTime);//移动等待时间
    nowait:
        isEnd = true;
        yield return null;
    }

    void Start()
    {
        //根据地图z轴进行高度计算
        high = transform.position.y;
        //根据地图xy轴进行位置计算
        transform.position = new Vector3(map.minX + map.heightX / map.x * (0.5f + x), high, map.maxY - map.widthY / map.y * (0.5f + y));
        map.wmap[x, y] = 'N';
        Vector3[] clones = { new Vector3(-map.heightX, 0, map.widthY), new Vector3(0, 0, map.widthY), new Vector3(map.heightX, 0, map.widthY), new Vector3(-map.heightX, 0, 0), new Vector3(map.heightX, 0, 0), new Vector3(-map.heightX, 0, -map.widthY), new Vector3(0, 0, -map.widthY), new Vector3(map.heightX, 0, -map.widthY) };
        foreach (Vector3 v in clones){
            clone.transform.localPosition = v + transform.position;
            clone.transform.rotation = transform.rotation;
            Instantiate(clone, transform, true);
        }
    }

    void Update()
    {
        if (isEnd)
        {
            StartCoroutine(pmove());
        }
    }
}

最后,你也可以添加一些墙,墙的逻辑比NPC更加简单,不用关于移动的功能了,但是,关于克隆的,也是不可少的。

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

public class wall : MonoBehaviour
{
    public int x = 0;
    public int y = 0;
    public bool ChangeTransform = true;
    public GameObject wallPrefab;
    void Start()
    {
        if (ChangeTransform)
        {
            //根据地图xyz轴进行transform计算
            transform.position = new Vector3(map.minX + map.heightX / map.x * (0.5f + x), transform.position.y, map.maxY - map.widthY / map.y * (0.5f + y));
        }
        map.wmap[x, y] = 'X';
        if (null != wallPrefab)
        {
            Vector3[] clones = { new Vector3(-map.heightX, 0, map.widthY), new Vector3(0, 0, map.widthY), new Vector3(map.heightX, 0, map.widthY), new Vector3(-map.heightX, 0, 0), new Vector3(map.heightX, 0, 0), new Vector3(-map.heightX, 0, -map.widthY), new Vector3(0, 0, -map.widthY), new Vector3(map.heightX, 0, -map.widthY) };
            foreach (Vector3 v in clones)
            {
                wallPrefab.transform.localPosition = v + transform.position;
                wallPrefab.transform.rotation = transform.rotation;
                Instantiate(wallPrefab, transform, true);
            }
        }
    }

    void Update()
    {
        
    }
}

而且,你也可以生成墙。只需用一个Vector2数组即可。

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

public class summonWall : MonoBehaviour
{
    public Vector2[] wallXY;
    public Vector2 xyOffSet = Vector2.zero;//偏移量,可以创建出比1*1还大的墙,也可以对生成出来的墙进行整体移动
    public bool ChangeTransform = true;
    public wall wallPreFab;
    void Start() {
        foreach (Vector2 xy in wallXY)
        {
            if (null == wallPreFab)
            {
                GameObject emptyWall = new GameObject("EmptyWall");
                wallPreFab = emptyWall.AddComponent<wall>();
            }
            wallPreFab.x = (int)xy.x + (int)xyOffSet.x;
            wallPreFab.y = (int)xy.y + (int)xyOffSet.y;
            wallPreFab.ChangeTransform = ChangeTransform;
            Instantiate(wallPreFab);
        }
    }

    void Update()
    {
        
    }
}

之后,你可以测试一下你的代码里还有什么“打不死的小强”——bug。或是你最近你想去试一下这个游戏怎样,也可以先试着玩一玩,再调整调整。这是我最终调整的游戏。

后言

在梦日记及其派生中,可以通过门来传送,下篇博客就把这个功能给实现好。

示例代码