版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gohuge/article/details/80272848
地宫说明
我们看到的游戏地宫有如暗黑的地下城风格,泰瑞利亚的地道风格,苍之纪元和冒险岛的走格子风格。
东方故事的深渊探险风格,这里了结合苍之纪元+深渊探险实现一套地宫副本的基础结构;
与其走格子不同的是,地宫是触屏点击行走,当然ARPG的寻路实现走格子便不屑一谈了。
我们简单的拆解一下地宫的基础程序结构,大概有:
1、地图编辑器:可自动随机生成地宫,可编辑地表和景观,可编辑事件对象,可设置地图呈现效果;
2、地宫场景:第三人称单位控制,视角控制,网格寻路实现,物件碰撞和交互处理;
3、地宫事件(持续开发):事件呈现和触发(碰撞或近位点击触发),事件编辑和放置。
地图制作
地图声明为三层
即地表层,景观
层,和事件层,分三层在于,后续地表的起伏表现,景观层编辑优化,运行中事件管理,等... ...
我们可以通过地牢算法,生成地牢随机网格,在通过网格路径优化,优化至项目中可行的地图网格布局;
不过只有算法是不行的,利用算法生成的二维坐标网格,我们可以在网格中布局地表样式(可以随机或选择性布局
,景观雷同)。
完成生成后,策划可以通过编辑器改善地图,和添加事件,下面为编辑器的部分思路代码:
//*****************************************************************************
//Created By [email protected] on 2018/5/8.
//
//@Description 地图制作工具
//*****************************************************************************
using UnityEngine;
using UnityEditor;
using Boo.Lang;
namespace Assets.Game.Editor.Scene
{
public class TravelMapTools : EditorWindow
{
// 地表,景观,事件 资源
public static TravelMapRes surfaceRes, barrierRes, eventsRes;
[MenuItem("Tools/地宫地图", false, 111)]
static void window()
{
TravelMapTools win = (TravelMapTools)EditorWindow.GetWindow(typeof(TravelMapTools), false, "TravelMap", false);
surfaceRes = new TravelMapRes("Assets/GameRes/Model/Map/TravelSurface/",".FBX");
barrierRes = new TravelMapRes("Assets/GameRes/Model/Map/TravelBarrier/",".FBX");
eventsRes = new TravelMapRes("Assets/GameRes/Model/Map/TravelEvents/",".prefab");
win.Show();
}
private int row = 32, col = 32;
// 景观几率
private int scape = 10;
private bool Wall;
// 对象集合
private bool[,] mapArray;
// 生成次数
private int times;
// 地表,高空,事件
private GameObject map;
private GameObject surface, barrier, events;
private GameObject block;
private BoxCollider bc;
private Vector2 sp1, sp2, sp3;
void OnGUI()
{
int x = 10;
GUI.Label(new Rect(x, 30, 150, 220), "输入地图名称");
GUI.TextField(new Rect(x, 60, 150, 20), "MapName");
// 选择地表资源
GUI.Label(new Rect(x, 100, 150, 220), "选择地表资源");
sp1 = GUI.BeginScrollView(new Rect(x, 130, 150, 200), sp1, new Rect(0, 0, 150, 400));
for (int i = 0; i < surfaceRes.resNames.Count; i++)
{
surfaceRes.selected[i] = GUI.Toggle(new Rect(5, i * 25, 150, 20), surfaceRes.selected[i], surfaceRes.resNames[i]);
}
GUI.EndScrollView();
// 选择高空资源
GUI.Label(new Rect(x + 180, 100, 150, 220), "选择高空资源");
sp2 = GUI.BeginScrollView(new Rect(x + 180, 130, 150, 200), sp2, new Rect(0, 0, 150, 400));
for (int i = 0; i < barrierRes.resNames.Count; i++)
{
barrierRes.selected[i] = GUI.Toggle(new Rect(5, i * 25, 150, 20), barrierRes.selected[i], barrierRes.resNames[i]);
}
GUI.EndScrollView();
// 生成地图
if (GUI.Button(new Rect(x, 350, 130, 25), "生成地图"))
{
Creat();
}
// 优化地图
if (GUI.Button(new Rect(x, 390, 130, 25), "优化地图"))
{
Next();
}
// 添加物件
GUI.Label(new Rect(x + 20, 430, 200, 25), "点击添加物件");
// 上方添加物件
if (GUI.Button(new Rect(x + 40, 450, 50, 25), "上"))
{
CopyBlock(0);
}
// 左方添加物件
if (GUI.Button(new Rect(x + 10, 480, 50, 25), "左"))
{
CopyBlock(2);
}
// 右方添加物件
if (GUI.Button(new Rect(x + 70, 480, 50, 25), "右"))
{
CopyBlock(3);
}
// 选择高空资源
if (GUI.Button(new Rect(x , 520, 130, 25), "添加事件"))
{
CopyBlock(-1);
}
sp3 = GUI.BeginScrollView(new Rect(x, 560, 150, 150), sp2, new Rect(0, 0, 150, 300));
for (int i = 0; i < eventsRes.resNames.Count; i++)
{
eventsRes.selected[i] = GUI.Toggle(new Rect(5, i * 25, 150, 20), eventsRes.selected[i], eventsRes.resNames[i]);
}
GUI.EndScrollView();
}
public void Creat()
{
// 创建地图
block = GameObject.Find("Block");
bc = block.GetComponent<BoxCollider>();
// 资源选择
surfaceRes.ReSelected();
barrierRes.ReSelected();
if (map)
{
Undo.DestroyObjectImmediate(map);
}
map = NewMap();
map.transform.localPosition = Vector3.zero;
mapArray = InitMapArray();
CreateMap(mapArray);
}
public void ResSelected(string path, List<GameObject> list, List<string> resList, bool[] selected)
{
list.Clear();
for (int i = 0; i < selected.Length; i++)
{
if (selected[i])
{
GameObject obj = AssetDatabase.LoadAssetAtPath(path + resList[i], typeof(GameObject)) as GameObject;
list.Add(obj);
}
}
}
public void Next()
{
if (times < 7)
{
times++;
Undo.DestroyObjectImmediate(map);
map = NewMap();
mapArray = SmoothMapArray(mapArray);
CreateMap(mapArray);
}
}
GameObject NewMap()
{
GameObject obj = new GameObject("mapName");
obj.AddComponent<BlockMap>();
obj.AddComponent<Terrain>();
obj.AddComponent<TerrainCollider>();
BlockMap bm = obj.GetComponent<BlockMap>();
bm.row = row;
bm.col = col;
surface = new GameObject("surface");
surface.transform.SetParent(obj.transform);
barrier = new GameObject("barrier");
barrier.transform.SetParent(obj.transform);
events = new GameObject("events");
events.transform.SetParent(obj.transform);
return obj;
}
// 随机地块
GameObject RandomBlock(GameObject layer, List<GameObject> list, int x, int lay, int z)
{
string name = GetBlockName(x, lay, z);
GameObject blockobj = GameObject.Find(name);
// 预防重复添加
if (blockobj != null) return blockobj;
blockobj = Instantiate(block,
new Vector3(x * bc.size.x, (lay == 1 ? 0 : (lay - 1) * bc.size.y), z * bc.size.z),
Quaternion.identity, layer.transform) as GameObject;
blockobj.name = name;
Block b = blockobj.GetComponent<Block>();
b.rowIndex = x;
b.colIndex = z;
b.layerIndex = lay;
int index = Random.Range(0, list.Count);
GameObject res = Instantiate(list[index], blockobj.transform);
res.transform.localPosition = Vector3.zero;
return blockobj;
}
public void CopyBlock(int way)
{
GameObject obj = (GameObject)Selection.activeObject;
if (!obj.transform.parent.gameObject.name.StartsWith("Block"))
return;
obj = obj.transform.parent.gameObject;
Block b = obj.GetComponent<Block>();
switch (way)
{
case 0: //上
RandomBlock(barrier, barrierRes.objs, b.rowIndex, b.layerIndex + 1, b.colIndex);
break;
case 2: //左
RandomBlock(surface, surfaceRes.objs, b.rowIndex - 1, b.layerIndex, b.colIndex);
break;
case 3: //右
RandomBlock(surface, surfaceRes.objs, b.rowIndex + 1, b.layerIndex, b.colIndex);
break;
case -1: //事件格子
eventsRes.ReSelected();
RandomBlock(events, eventsRes.objs, b.rowIndex, b.layerIndex + 1, b.colIndex);
break;
}
}
string GetBlockName(int x, int y, int z)
{
return "Block_" + x + "_" + y + "_" + z;
}
bool[,] InitMapArray()
{
bool[,] array = new bool[row, col];
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
array[i, j] = Random.Range(0, 100) < 60;
if (i == 0 || i == row - 1 || j == 0 || j == col - 1)
{
array[i, j] = false;
}
}
}
return array;
}
bool[,] SmoothMapArray(bool[,] array)
{
bool[,] newArray = new bool[row, col];
int count1, count2;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
count1 = CheckNeighborWalls(array, i, j, 1);
count2 = CheckNeighborWalls(array, i, j, 2);
if (count1 >= 5 || count2 <= 2)
{
newArray[i, j] = false;
}
else
{
newArray[i, j] = true;
}
if (i == 0 || i == row - 1 || j == 0 || j == col - 1)
{
newArray[i, j] = false;
}
}
}
return newArray;
}
int CheckNeighborWalls(bool[,] array, int i, int j, int t)
{
int count = 0;
for (int i2 = i - t; i2 < i + t + 1; i2++)
{
for (int j2 = j - t; j2 < j + t + 1; j2++)
{
if (i2 > 0 && i2 < row && j2 >= 0 && j2 < col)
{
if (!array[i2, j2])
{
count++;
}
}
}
}
if (!array[i, j])
count--;
return count;
}
void CreateMap(bool[,] array)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (Wall)
{
if (!array[i, j])
{
GameObject go = RandomBlock(surface, surfaceRes.objs, i, 1, j);
}
else
{
GameObject go = RandomBlock(barrier, barrierRes.objs, i, 2, j);
}
}
else
{
if (array[i, j])
{
GameObject go = RandomBlock(surface, surfaceRes.objs, i, 1, j);
if (Random.Range(1, 100) < scape)
{
go = RandomBlock(barrier, barrierRes.objs, i, 2, j);
}
}
}
}
}
}
}
}
//*****************************************************************************
//Created By [email protected] on 2018/5/8.
//
//@Description 地图制作工具
//*****************************************************************************
using Boo.Lang;
using System.IO;
using UnityEngine;
using UnityEditor;
namespace Assets.Game.Editor.Scene
{
// 资源管理类
public class TravelMapRes
{
// 资源路径
public string path;
// 资源名称
public List<string> resNames = new List<string>();
// 选中位置
public bool[] selected;
// 选中资源
public List<GameObject> objs = new List<GameObject>();
// 许可资源类型
public string resPrex = null;
public TravelMapRes(string p)
{
this.path = p;
InitResList(this.path);
}
public TravelMapRes(string p,string prex)
{
this.path = p;
resPrex = prex;
InitResList(this.path);
}
public void InitResList(string path)
{
resNames.Clear();
if (Directory.Exists(path))
{
DirectoryInfo direction = new DirectoryInfo(path);
FileInfo[] files = direction.GetFiles("*", SearchOption.AllDirectories);
for (int i = 0; i < files.Length; i++)
{
if (files[i].Name.EndsWith(".meta")) continue;
if (resPrex != null && !files[i].Name.EndsWith(resPrex))
{
continue;
}
resNames.Add(files[i].Name);
}
this.selected = new bool[resNames.Count];
}
else {
this.selected = new bool[0];
}
}
public void ReSelected()
{
objs.Clear();
for (int i = 0; i < selected.Length; i++)
{
if (selected[i])
{
GameObject obj = AssetDatabase.LoadAssetAtPath(path + resNames[i], typeof(GameObject)) as GameObject;
objs.Add(obj);
}
}
}
public GameObject RandomSelectedObj()
{
ReSelected();
return objs[Random.Range(0, objs.Count)];
}
}
}
角色控制
地图中角色控制和相机差不多和ARPG一样了,视角可上下,左右移动,相机跟随角色和调整视角。
//*****************************************************************************
//Created By Gouhj on 2018/5/3.
//
//@Description 移动控制器
//*****************************************************************************
using UnityEngine;
using UnityEngine.AI;
public class PlayerMove : MonoBehaviour
{
[Header("是否点击移动")]
public bool isClickMove = false;
[Header("是否采用网格导航")]
public bool IsNavAgent;
[Header("移动速度")]
public float m_speed = 5f;
[Header("跑动动作")]
public string RUN = "is_run";
[Header("待机动作")]
public string IDLE = "is_idle";
float ogrY = 0;
// 动作 0 待机,1 跑动
int currentState = 0;
// 寻路者
NavMeshAgent agent;
// 玩家
GameObject player;
// 目标位置
Vector3 targetPos;
void Start()
{
agent = this.GetComponent<NavMeshAgent>();
targetPos = this.transform.position;
ogrY = targetPos.y;
}
void LateUpdate()
{
if (isClickMove)
{
MoveControlByMouse();
}
else
{
MoveControlByTranslate();
}
}
//Translate移动控制函数
void MoveControlByTranslate()
{
if (Input.GetKey(KeyCode.W) | Input.GetKey(KeyCode.UpArrow)) //前
{
this.transform.Translate(Vector3.forward * m_speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.S) | Input.GetKey(KeyCode.DownArrow)) //后
{
this.transform.Translate(Vector3.forward * -m_speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.A) | Input.GetKey(KeyCode.LeftArrow)) //左
{
this.transform.Translate(Vector3.right * -m_speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.D) | Input.GetKey(KeyCode.RightArrow)) //右
{
this.transform.Translate(Vector3.right * m_speed * Time.deltaTime);
}
}
// 鼠标点击移动
void MoveControlByMouse()
{
if (Input.GetMouseButton(0))
{
//获得鼠标屏幕位置
Vector3 mousePos = Input.mousePosition;
//将屏幕位置转为射线
Ray ray = Camera.main.ScreenPointToRay(mousePos);
//用来记录射线碰撞记录
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
// 点中地板砖
//if (!hit.collider.name.Equals("Terrain"))
//{
// return;
//}
targetPos = new Vector3(hit.point.x, ogrY, hit.point.z);
if (!player) {
player = this.transform.GetChild(0).gameObject;
}
// 差值
transform.rotation = Quaternion.LookRotation(targetPos - transform.position);
if (IsNavAgent)
{
agent.SetDestination(targetPos);
}
}
}
if (IsNavAgent)
{
if (agent.remainingDistance == 0)
{
AnimatorChange(0);
}
else
{
AnimatorChange(1);
}
}
else {
if (Mathf.Abs(transform.position.x - targetPos.x) > 0.1 || Mathf.Abs(transform.position.z - targetPos.z) > 0.1)
{
AnimatorChange(1);
transform.position = Vector3.MoveTowards(transform.position, targetPos, m_speed * Time.deltaTime);
}
else
{
AnimatorChange(0);
}
}
}
// 动作切换
void AnimatorChange(int state)
{
if (!player) return;
if (currentState == state) return;
currentState = state;
switch (state)
{
case 0:
Animator ani = player.GetComponent<Animator>();
ani.SetTrigger(IDLE);
break;
case 1:
ani = player.GetComponent<Animator>();
ani.SetTrigger(RUN);
break;
default:
break;
}
}
}
//*****************************************************************************
//Created By [email protected] on 2018/5/3.
//
//@Description 第三人称相机
//*****************************************************************************
using UnityEngine;
namespace Game
{
public class PlayerCamera : MonoBehaviour
{
private Transform player;
private Vector3 offsetPosition;
private float distance;
private float scrollSpeed = 10; //鼠标滚轮速度
private bool isRotating; //开启摄像机旋转
private float rotateSpeed = 2; //摄像机旋转速度
// Use this for initialization
void Start()
{
player = GameObject.Find("player").transform;
//摄像机朝向player
transform.LookAt(player.position);
transform.SetParent(transform);
//获取摄像机与player的位置偏移
offsetPosition = transform.position - player.position;
}
// Update is called once per frame
void Update()
{
//摄像机跟随player与player保持相对位置偏移
transform.position = offsetPosition + player.position;
//摄像机的旋转
RotateView();
//摄像机的摄影控制
ScrollView();
}
void ScrollView()
{
//返回位置偏移的向量长度
distance = offsetPosition.magnitude;
distance -= Input.GetAxis("Mouse ScrollWheel") * scrollSpeed;
//限制变化长度的范围在最小为4最大为22之间
distance = Mathf.Clamp(distance, 4, 22);
//新的偏移值为偏移值的单位向量*变换长度
offsetPosition = offsetPosition.normalized * distance;
}
void RotateView()
{
//按下鼠标右键开启旋转摄像机
if (Input.GetMouseButtonDown(1))
{
isRotating = true;
}
//抬起鼠标右键关闭旋转摄像机
if (Input.GetMouseButtonUp(1))
{
isRotating = false;
}
if (isRotating)
{
//获取摄像机初始位置
Vector3 pos = transform.position;
//获取摄像机初始角度
Quaternion rot = transform.rotation;
//摄像机围绕player的位置延player的Y轴旋转,旋转的速度为鼠标水平滑动的速度
transform.RotateAround(player.position, player.up, Input.GetAxis("Mouse X") * rotateSpeed);
//摄像机围绕player的位置延自身的X轴旋转,旋转的速度为鼠标垂直滑动的速度
transform.RotateAround(player.position, transform.right, Input.GetAxis("Mouse Y") * rotateSpeed);
//获取摄像机x轴向的欧拉角
float x = transform.eulerAngles.x;
//如果摄像机的x轴旋转角度超出范围,恢复初始位置和角度
if (x < 10 || x > 80)
{
transform.position = pos;
transform.rotation = rot;
}
}
//更新摄像机与player的位置偏移
offsetPosition = transform.position - player.position;
}
}
}
事件相关
地图生成显然都是用地块填充的,所以为了控制地块,添加地块事件,以及设置地块属性等因素,一定会给地块绑定一个脚本。
而事件的触发,可通过配置决定是碰撞还是点击触发,在把消息派发到事件系统就好了。
关于事件的触发,可和这篇文章关联起来:寻路
地图起伏效果实现
//*****************************************************************************
//Created buy Gouhj on 2018/5/7.
//
//@Description 地块特效
//*****************************************************************************
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BlockMap : MonoBehaviour {
[Header("地块是否起落")]
public bool isDownUp = false;
[Header("是否遮罩探索")]
public bool isUnknow = false;
[Header("设置玩家")]
public GameObject player;
[Header("上升位置起点")]
public int downPosY = 10;
[Header("上升速度")]
public float upSpeed = 0.5f;
// 地表,高空,事件
GameObject surface, barrier, events;
Transform tr;
// 地表二维坐标系
GameObject[,] blocks;
// 函数和咧数
public int row, col;
// 地块大小;
public float bsizeX = 2, bsizeZ = 2;
private int startRow, startCol;
// 附近的格子
List<GameObject> nearList = new List<GameObject>();
// 上升列表
List<GameObject> upList = new List<GameObject>();
// 地表默认平面在世界坐标中的高度
int defaultSurfaceY = 0;
// 默认圈数
int defaultRoundCount = 2;
// Use this for initialization
void Start () {
if (player != null)
{
tr = player.transform;
}
else
{
tr = GameObject.Find("player").transform;
}
surface = GameObject.Find("surface");
barrier = GameObject.Find("barrier");
events = GameObject.Find("events");
blocks = new GameObject[row,col];
GameObject obj;
Block block;
for (int i = 0; i < surface.transform.childCount; i++)
{
obj = surface.transform.GetChild(i).gameObject;
block = obj.GetComponent<Block>();
blocks[block.rowIndex, block.colIndex] = obj;
if (block.isStartPoint)
{
startRow = block.rowIndex;
startCol = block.colIndex;
}
if (isDownUp) {
obj.SetActive(false);
}
}
}
// Update is called once per frame
void Update () {
UpAndDown();
}
// 显示周围
void show(int r,int c)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (blocks[i, j] == null) continue;
}
}
}
// 判断是否是周围的格子
bool IsNear(int r,int c,GameObject obj)
{
Block b = obj.GetComponent<Block>();
//if(b.rowIndex-r)
return true;
}
// 上下起伏效果实现
void UpAndDown()
{
if (isDownUp) {
ShowNear();
UpListRun();
}
}
void UpListRun() {
if (upList.Count == 0) return;
GameObject obj = null;
for (int i = upList.Count - 1 ; i >= 0 ;i --) {
obj = upList[i];
// 地表默认平面
if (obj.transform.position.y >= defaultSurfaceY) {
upList.RemoveAt(i);
obj.transform.position = new Vector3(obj.transform.position.x,
defaultSurfaceY, obj.transform.position.z);
continue;
}
obj.transform.position = new Vector3(obj.transform.position.x,
obj.transform.position.y + upSpeed, obj.transform.position.z);
}
}
void ShowNear() {
if (tr == null) return;
SelectNear(nearList);
GameObject obj = null;
for (int i = 0; i < nearList.Count; i++) {
obj = nearList[i];
if (! obj.activeSelf) {
// 下降到起伏位置
obj.transform.position = new Vector3(obj.transform.position.x,
obj.transform.position.y - downPosY ,obj.transform.position.z);
obj.SetActive(true);
upList.Add(obj);
}
}
}
void SelectNear(List<GameObject> list) {
list.Clear();
float x = tr.position.x;
float y = tr.position.y;
float z = tr.position.z;
int r = Mathf.CeilToInt(x/bsizeX);
int c = Mathf.CeilToInt(z/bsizeZ);
// 玩家位置
list.Add(blocks[r, c]);
SelectRound(list,r,c, defaultRoundCount);
}
void SelectRound(List<GameObject> list,int r,int c,int round) {
for (int i = 1; i < round+1; i++) {
//上,下,左,右
CheckIn(list, r, c + i);
CheckIn(list, r, c - i);
CheckIn(list, r - i, c);
CheckIn(list, r + i, c);
//左上,左下,右上,右下
CheckIn(list, r - i, c + i);
CheckIn(list, r - i, c - i);
CheckIn(list, r + i, c + i);
CheckIn(list, r + i, c - i);
}
}
bool CheckIn(List<GameObject> list,int r,int c) {
if (r < 0) return false;
if (r >= row) return false;
if (c < 0) return false;
if (c >= col) return false;
GameObject obj = blocks[r, c];
if (obj == null) return false;
list.Add(obj);
return true;
}
}