Unity 实现简单的关卡管理系统
今天我们尝试实现一个简单的关卡管理系统。先想想关卡都需要什么功能?在我基本的考虑下主要有:1.投放物体(主角,道具,怪物)2.控制关卡流程(比如,小关卡顺序,怪物波次)3.触发各种事件(比如触发剧情,激活技能,激活碰撞墙等等)OK,知道我们需要什么了,那我们就开始考虑要怎么制作了。在一个大关卡中,往往会划分多个小的战斗区,玩家要控制角色依次通过战斗区域,获得胜利,所以我
今天我们尝试实现一个简单的关卡管理系统。
先想想关卡都需要什么功能?
在我基本的考虑下主要有:
1.投放物体(主角,道具,怪物)
2.控制关卡流程(比如,小关卡顺序,怪物波次)
3.触发各种事件(比如触发剧情,激活技能,激活碰撞墙等等)
OK,知道我们需要什么了,那我们就开始考虑要怎么制作了。
在一个大关卡中,往往会划分多个小的战斗区,玩家要控制角色依次通过战斗区域,获得胜利,所以我们首要目标是要实现这些小的战斗区。
目标确定,那需要考虑一下战斗区一般的形状:
1.矩形
2.圆形
3.不规则多边形(这个实现较为复杂,暂且不考虑)
4.扇形(没考虑到使用的地方,先绘制放一边吧)
主要的还是矩形,和圆形。不规则多边形,实现较为复杂,尚未研究。但是我们可以通过矩形实现不规则多变型的范围管理。
那我们开始绘制战斗区
脚本CheckPoint
//此脚本为小战斗区控制脚本
//首先实现战斗区范围控制,我们采用 Gizmos.DrawMesh() 的方式来绘制战斗区范围
public enum CheckRange
{
Round, //圆形
Fan, //扇形
Rect //矩形
}
public class CheckPoint : MonoBehaviour {
//战斗区形状
[Space(20)]
public CheckRange CheckRange = CheckRange.Round;
//圆形半径
[Header("Round:")]
public float RoundRadio = 0;
//扇形参数
[Header("Fan")]
//角度
public float FanEnagle = 0;
//最小距离
[Range(0,1)]
public float FanMinRange = 0;
//最大剧情
public float FanMaxRange = 0;
//获取最小距离
public float GetFanMinRange {
get {
return FanMaxRange * FanMinRange;
}
}
//矩形范围
[Header("Rect")]
//长度
public float RectWidth = 0;
//宽度
public float RectHeight = 0;
//是否绘制Gizmos
public bool IsGzimos = false;
void OnDrawGizmos()
{
if (!IsGzimos) return;
DrawGizmos();
}
//绘制mesh
void DrawGizmos()
{
switch (CheckRange)
{
case CheckRange.Round:
Mesh round = MeshTools.MeshRound(RoundRadio,60);
Gizmos.color = new Color(1,0,0,0.5f);
Gizmos.DrawMesh(round,transform.position);
break;
case CheckRange.Fan:
Mesh fan = MeshTools.MeshFan(FanEnagle, FanMaxRange, GetFanMinRange);
Gizmos.color = new Color(1, 0, 0, 0.5f);
Gizmos.DrawMesh(fan, transform.position);
break;
case CheckRange.Rect:
Mesh rect = MeshTools.MeshRect(transform.position, RectWidth, RectHeight);
Gizmos.color = new Color(1, 0, 0, 0.5f);
Gizmos.DrawMesh(rect, transform.position);
break;
}
}
}
脚本:MeshTools 此脚本主要是绘制mesh
//此脚本主要是绘制mesh
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MeshTools {
/// <summary>
/// 圆形
/// </summary>
/// <param name="radio"></param>
/// <param name="pointAmount"></param>
/// <returns></returns>
public static Mesh MeshRound(float radio,int pointAmount)
{
float eachAngle = 360f / pointAmount;
List<Vector3> vertices = new List<Vector3>();
for (int i = 0; i <= pointAmount; i++)
{
Vector3 pos = Quaternion.Euler(0f, eachAngle * i, 0f) * Vector3.forward * radio;
vertices.Add(pos);
}
int[] triangles;
Mesh mesh = new Mesh();
int trangleAmount = vertices.Count - 2;
triangles = new int[3 * trangleAmount];
for (int i = 0; i < trangleAmount; i++)
{
triangles[3 * i] = 0;
triangles[3 * i + 1] = i + 1;
triangles[3 * i + 2] = i + 2;
}
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles;
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
/// <summary>
/// 矩形
/// </summary>
/// <param name="target"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public static Mesh MeshRect(Transform target,float width, float height)
{
List<Vector3> vertices = new List<Vector3>();
vertices.Add(new Vector3(0, 0, 0) - target.right * (width / 2));
vertices.Add(new Vector3(0, 0, 0) - target.right * (width / 2) + target.forward * height);
vertices.Add(new Vector3(0, 0, 0) + target.right * (width / 2) + target.forward * height);
vertices.Add(new Vector3(0, 0, 0) + target.right * (width / 2));
int[] triangles;
Mesh mesh = new Mesh();
int trangleAmount = vertices.Count - 2;
triangles = new int[3 * trangleAmount];
for (int i = 0; i < trangleAmount; i++)
{
triangles[3 * i] = 0;
triangles[3 * i + 1] = i + 1;
triangles[3 * i + 2] = i + 2;
}
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles;
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
/// <summary>
/// 矩形
/// </summary>
/// <param name="pos"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
public static Mesh MeshRect(Vector3 pos, float width, float height)
{
List<Vector3> vertices = new List<Vector3>();
vertices.Add(new Vector3(width / 2, pos.y,height / 2));
vertices.Add(new Vector3( width / 2, pos.y, -height / 2));
vertices.Add(new Vector3(-width / 2, pos.y, -height / 2));
vertices.Add(new Vector3(-width / 2, pos.y, height / 2));
Mesh mesh = new Mesh();
int[] triangles = new int[3 * 2];
triangles[0] = 0;
triangles[1] = 1;
triangles[2] = 2;
triangles[3] = 0;
triangles[4] = 2;
triangles[5] = 3;
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles;
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
/// <summary>
/// 扇形
/// </summary>
/// <param name="angle"></param>
/// <param name="maxRadio"></param>
/// <param name="minRadio"></param>
/// <returns></returns>
public static Mesh MeshFan(float angle,float maxRadio,float minRadio)
{
//因为vertices(顶点)的个数与triangles(索引三角形顶点数)必须匹配
int vertices_count = 60 * 2 + 2;
Vector3[] vertices = new Vector3[vertices_count];
float angleRad = Mathf.Deg2Rad * angle;
float angleCur = angleRad;
float angledelta = angleRad / 60;
for (int i = 0; i < vertices_count; i += 2)
{
float cosA = Mathf.Cos(angleCur);
float sinA = Mathf.Sin(angleCur);
vertices[i] = new Vector3(maxRadio * cosA, 0, maxRadio * sinA);
vertices[i + 1] = new Vector3(minRadio * cosA, 0, minRadio * sinA);
angleCur -= angledelta;
}
//triangles:
int triangle_count = 60 * 6;
int[] triangles = new int[triangle_count];
for (int i = 0, vi = 0; i < triangle_count; i += 6, vi += 2)
{
triangles[i] = vi;
triangles[i + 1] = vi + 3;
triangles[i + 2] = vi + 1;
triangles[i + 3] = vi + 2;
triangles[i + 4] = vi + 3;
triangles[i + 5] = vi;
}
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
}
如此在Unity的Scene窗口中可以看见我们绘制的范围了。
显示效果:
好吧,范围画好了,我们接来需要考虑什么呢?
战斗区,战斗区得需要有怪物吧,应该考虑投放怪物了,然而在一个战斗区中,往往会存在好几波的怪物。这个要怎么控制呢?
不然我们在战斗区得下面在创建一个脚本 CheckPointWave,用来控制管理怪物或道具的投放。
那我们就需要考虑一下怪物的投放了
1.随机投放(这个怎么感觉用的应该都很少吧)
2.指定位置投放
还要考虑一下,我们怎么控制着一波是否结束了呢?
我选择最简单的方式,那就没死一只怪物就会与当前投放的怪物数量进行比较,如果相等,那就代表着结束了。
脚本:CheckPointWave 此脚本为波数管理控制脚本
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class CheckPointWave : MonoBehaviour {
/// <summary>
/// 波数
/// </summary>
public int Index = 0;
/// <summary>
/// 当波数结束时
/// </summary>
public Action OnWaveFinsh;
/// <summary>
/// 所属战斗区
/// </summary>
[SerializeField]
private CheckPoint CheckPoint;
/// <summary>
/// 随机创建
/// </summary>
public WaveRandomCreate RandomCreate;
/// <summary>
/// 指定位置创建
/// </summary>
public List<WavePosCreate> PosCreate = new List<WavePosCreate>();
/// <summary>
/// 下一波
/// </summary>
[SerializeField]
private CheckPointWave NextWave;
/// <summary>
/// 怪物列表
/// </summary>
private List<Actor> MonsterActor = new List<Actor>();
/// <summary>
/// 怪物数量
/// </summary>
public int MonsterNum { get { return RandomCreate.CreateNum + PosCreate.Count; } }
/// <summary>
/// 死亡数量
/// </summary>
public int DeathNum = 0;
/// <summary>
/// 开始事件
/// </summary>
public List<CpAction> OnStart = new List<CpAction>();
/// <summary>
/// 结束事件
/// </summary>
public List<CpAction> OnEnd = new List<CpAction>();
/// <summary>
/// 设置所属战斗区
/// </summary>
/// <param name="cp"></param>
public void SetCheckPoint(CheckPoint cp)
{
CheckPoint = cp;
}
/// <summary>
/// 设置下一波
/// </summary>
/// <param name="nextWave"></param>
public void SetNextWave(CheckPointWave nextWave)
{
NextWave = nextWave;
}
/// <summary>
/// 当波数激活
/// </summary>
public void OnActive()
{
//开始事件
if (OnStart.Count > 0)
{
for (int i = 0; i < OnStart.Count; i++)
CheckPoint.CpManager.DoCpAction(OnStart[i].ActionType, OnStart[i].ActionParam);
}
StartCoroutine(CreateActor());
//设置当前正在进行的是那一拨
CheckPoint.CpManager.SetCurWave(this);
}
/// <summary>
/// 创建怪物
/// </summary>
/// <returns></returns>
IEnumerator CreateActor()
{
if (RandomCreate.CreateNum != 0)
{
for (int i = 0; i < RandomCreate.CreateNum; i++)
{
yield return new WaitForSeconds(0.02f);
int index = UnityEngine.Random.Range(0, RandomCreate.MonsterID.Count);
CheckPoint.CpManager.CreateActor(RandomCreate.MonsterID[index], CheckPoint.GetRandomPos(), delegate(Actor actor) {
DeathNum++;
OnFinsh();
},delegate(Actor actor) {
MonsterActor.Add(actor);
});
}
}
if (PosCreate.Count > 0)
{
for (int i = 0; i < PosCreate.Count; i++)
{
yield return new WaitForSeconds(0.02f);
CheckPoint.CpManager.CreateActor(PosCreate[i].MonsterID, PosCreate[i].Postion, delegate(Actor actor) {
DeathNum++;
OnFinsh();
}, delegate (Actor actor) {
MonsterActor.Add(actor);
});
DeathNum++;
OnFinsh();
}
}
}
/// <summary>
/// 杀死所有怪物
/// </summary>
/// <param name="isPoint"></param>
public void kill(bool isPoint = false)
{
if (isPoint) NextWave = null;
for (int i = 0; i < MonsterActor.Count; i++)
{
if (!MonsterActor[i].ActorAttr.IsDeath)
MonsterActor[i].Kill();
}
}
/// <summary>
/// 拍段当前这波是否结束
/// </summary>
public void OnFinsh()
{
if (DeathNum != MonsterNum) return;
MonsterActor.Clear();
//结束事件
if (OnEnd.Count > 0)
{
for (int i = 0; i < OnEnd.Count; i++)
CheckPoint.CpManager.DoCpAction(OnEnd[i].ActionType,OnEnd[i].ActionParam);
}
//拍段是否进行下一波
if (NextWave != null)
{
NextWave.OnActive();
return;
}
//如果是最后一波控制当前战斗区结束
CheckPoint.OnFinsh();
}
}
/// <summary>
/// 随机创建
/// </summary>
[System.Serializable]
public class WaveRandomCreate
{
public int CreateNum;
public float MinDis = 1;
public List<string> MonsterID = new List<string>();
}
/// <summary>
/// 固定位置创建
/// </summary>
[System.Serializable]
public class WavePosCreate
{
public string MonsterID;
public Vector3 Postion;
}
主要功能:
1.波数管理器激活
/// <summary>
/// 当波数激活
/// </summary>
public void OnActive()
{
//开始事件
if (OnStart.Count > 0)
{
for (int i = 0; i < OnStart.Count; i++)
CheckPoint.CpManager.DoCpAction(OnStart[i].ActionType, OnStart[i].ActionParam);
}
StartCoroutine(CreateActor());
//设置当前正在进行的是那一拨
CheckPoint.CpManager.SetCurWave(this);
}
2.创建怪物或道具
/// <summary>
/// 创建怪物
/// </summary>
/// <returns></returns>
IEnumerator CreateActor()
{
if (RandomCreate.CreateNum != 0)
{
for (int i = 0; i < RandomCreate.CreateNum; i++)
{
yield return new WaitForSeconds(0.02f);
int index = UnityEngine.Random.Range(0, RandomCreate.MonsterID.Count);
CheckPoint.CpManager.CreateActor(RandomCreate.MonsterID[index], CheckPoint.GetRandomPos(), delegate(Actor actor) {
DeathNum++;
OnFinsh();
},delegate(Actor actor) {
MonsterActor.Add(actor);
});
}
}
if (PosCreate.Count > 0)
{
for (int i = 0; i < PosCreate.Count; i++)
{
yield return new WaitForSeconds(0.02f);
CheckPoint.CpManager.CreateActor(PosCreate[i].MonsterID, PosCreate[i].Postion, delegate(Actor actor) {
DeathNum++;
OnFinsh();
}, delegate (Actor actor) {
MonsterActor.Add(actor);
});
DeathNum++;
OnFinsh();
}
}
}
3.结束,开始下一波,还是结束战斗区
/// <summary>
/// 拍段当前这波是否结束
/// </summary>
public void OnFinsh()
{
if (DeathNum != MonsterNum) return;
MonsterActor.Clear();
//结束事件
if (OnEnd.Count > 0)
{
for (int i = 0; i < OnEnd.Count; i++)
CheckPoint.CpManager.DoCpAction(OnEnd[i].ActionType,OnEnd[i].ActionParam);
}
//拍段是否进行下一波
if (NextWave != null)
{
NextWave.OnActive();
return;
}
//如果是最后一波控制当前战斗区结束
CheckPoint.OnFinsh();
}
恩恩,注意了,在代码中的Actor 类,为我想项目的演员对象,这个地方应该替换为你们自己演员对象
恩波数也有了,注意了,我们在代码中加入了两个事件列表
/// <summary>
/// 开始事件
/// </summary>
public List<CpAction> OnStart = new List<CpAction>();
/// <summary>
/// 结束事件
/// </summary>
public List<CpAction> OnEnd = new List<CpAction>();
分别控制着当这波激活触发的事件和当这波结束后触发的事件。这个咋们下面再说,你只要注意到了就好…
现在好了,战斗区有了,波数控制也有了。那接下来呢?
现在我们来补充一下控制区部分的代码,因为我们需要获取到波数控制器啊,并且还要管理波数控制器的先后顺序呢?在这里我放出完整的CheckPoint 脚本:
脚本:CheckPoint
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public enum CheckRange
{
Round,
Fan,
Rect
}
public class CheckPoint : MonoBehaviour {
/// <summary>
/// 战斗区ID
/// </summary>
public int Index = 0;
/// <summary>
/// 战斗区下一个ID
/// </summary>
public int NextIndex = -1;
/// <summary>
/// 是否激活
/// </summary>
public bool IsActive = false;
/// <summary>
/// 是否检测玩家
/// </summary>
public bool IsDetActor = false;
/// <summary>
/// 关卡管理器
/// </summary>
[SerializeField]
public CheckPointManager CpManager { get; private set; }
/// <summary>
/// 下一个战斗区
/// </summary>
[SerializeField]
private CheckPoint NextCheckPoint;
/// <summary>
/// 当前战斗区所有的波数管理器
/// </summary>
[SerializeField]
private List<CheckPointWave> Waves = new List<CheckPointWave>();
/// <summary>
/// 当战斗区结束事件
/// </summary>
public Action OnCheckFinsh;
[Header("当进入战斗开始时:")]
public List<CpAction> OnStartAction = new List<CpAction>();
[Header("当在战斗区时:")]
public List<CpAction> OnInsertAction = new List<CpAction>();
[Header("当战斗区结束时:")]
public List<CpAction> OnEndAction = new List<CpAction>();
//战斗区形状
[Space(20)]
public CheckRange CheckRange = CheckRange.Round;
//圆形半径
[Header("Round:")]
public float RoundRadio = 0;
//扇形参数
[Header("Fan")]
//角度
public float FanEnagle = 0;
//最小距离
[Range(0, 1)]
public float FanMinRange = 0;
//最大剧情
public float FanMaxRange = 0;
//获取最小距离
public float GetFanMinRange
{
get
{
return FanMaxRange * FanMinRange;
}
}
//矩形范围
[Header("Rect")]
//长度
public float RectWidth = 0;
//宽度
public float RectHeight = 0;
//是否绘制Gizmos
public bool IsGzimos = false;
/// <summary>
/// 设置关卡管理器
/// </summary>
/// <param name="cpManager"></param>
public void SetCPManager(CheckPointManager cpManager)
{
CpManager = cpManager;
}
/// <summary>
/// 设置下一个战斗区
/// </summary>
/// <param name="cp"></param>
public void SetNextCp(CheckPoint cp)
{
NextCheckPoint = cp;
}
void Start()
{
//获取所有的波数管理器
CheckPointWave[] wave = transform.GetComponentsInChildren<CheckPointWave>();
for (int i = 0; i < wave.Length; i++)
{
wave[i].SetCheckPoint(this);
Waves.Add(wave[i]);
}
//根据波数管理器id进行排序
Waves.Sort((r1,r2)=> { if (r1.Index > r2.Index) return 1; else if (r1.Index == r2.Index) return 0;return -1; });
//设置波数
for (int i = 0; i < Waves.Count; i++)
{
if (i != Waves.Count - 1)
Waves[i].SetNextWave(Waves[i + 1]);
else
Waves[i].SetNextWave(null);
}
}
void Update()
{
if (CpManager == null) return;
if (Waves.Count < 1) return;
if (!IsDetActor) return;
///检测玩家是否进入管战斗区
for (int i = 0; i < CpManager.DetActor.Count; i++)
{
if (DetActor(CpManager.DetActor[i]))
{
OnActive();
IsDetActor = false;
}
}
}
/// <summary>
/// 激活战斗区
/// </summary>
public void OnActive()
{
IsActive = true;
///设置当前战斗区
CpManager.SetCurCheckPoint(this);
//第一波激活
Waves[0].OnActive();
if (OnStartAction.Count > 0)
{
for (int i = 0; i < OnStartAction.Count; i++)
CpManager.DoCpAction(OnStartAction[i].ActionType, OnStartAction[i].ActionParam);
}
}
/// <summary>
/// 战斗区结束
/// </summary>
public void OnFinsh()
{
IsActive = false;
if (OnEndAction.Count > 0)
{
for (int i = 0; i < OnEndAction.Count; i++)
CpManager.DoCpAction(OnEndAction[i].ActionType,OnEndAction[i].ActionParam);
}
if (NextCheckPoint != null)
{
NextCheckPoint.IsDetActor = true;
return;
}
CpManager.OnFinsh();
}
/// <summary>
/// 检测玩家是否进入战斗区
/// </summary>
/// <param name="actor"></param>
/// <returns></returns>
bool DetActor(Actor actor)
{
bool IsActive = false;
switch (CheckRange)
{
case CheckRange.Round:
IsActive = Vector3.Distance(actor.position,transform.position)<= RoundRadio;
break;
case CheckRange.Fan:
break;
case CheckRange.Rect:
Vector2 pos1 = new Vector2(transform.position.x+ RectWidth/2,transform.position.z+RectHeight/2);
Vector2 pos2 = new Vector2(transform.position.x - RectWidth / 2, transform.position.z - RectHeight / 2);
IsActive = actor.position.x <= pos1.x && actor.position.x >= pos2.x && actor.position.z <= pos1.y && actor.position.z >= pos2.y;
break;
}
return IsActive;
}
/// <summary>
/// 获取随机位置
/// </summary>
/// <returns></returns>
public Vector3 GetRandomPos()
{
Vector3 pos = Vector3.zero;
switch (CheckRange)
{
case CheckRange.Round:
Vector2 pos3 = new Vector2(transform.position.x + RoundRadio / 2, transform.position.z + RoundRadio / 2);
Vector2 pos4 = new Vector2(transform.position.x - RoundRadio / 2, transform.position.z - RoundRadio / 2);
float rx = UnityEngine.Random.Range(pos4.x, pos3.x);
float rz = UnityEngine.Random.Range(pos4.y, pos3.y);
Vector3 dir = new Vector3(rx, transform.position.y, rz);
float range = UnityEngine.Random.Range(0, RoundRadio);
pos = dir.normalized * range + transform.position;
break;
case CheckRange.Rect:
Vector2 pos1 = new Vector2(transform.position.x + RectWidth / 2, transform.position.z + RectHeight / 2);
Vector2 pos2 = new Vector2(transform.position.x - RectWidth / 2, transform.position.z - RectHeight / 2);
float x = UnityEngine.Random.Range(pos2.x, pos1.x);
float z = UnityEngine.Random.Range(pos2.y, pos1.y);
pos = new Vector3(x, 10, z);
break;
}
return pos;
}
void OnDrawGizmos()
{
if (!IsGzimos) return;
DrawGizmos();
}
/// <summary>
/// 绘制战斗区
/// </summary>
void DrawGizmos()
{
switch (CheckRange)
{
case CheckRange.Round:
Mesh round = MeshTools.MeshRound(RoundRadio,60);
Gizmos.color = new Color(1,0,0,0.5f);
Gizmos.DrawMesh(round,transform.position);
break;
case CheckRange.Fan:
Mesh fan = MeshTools.MeshFan(FanEnagle, FanMaxRange, GetFanMinRange);
Gizmos.color = new Color(1, 0, 0, 0.5f);
Gizmos.DrawMesh(fan, transform.position);
break;
case CheckRange.Rect:
Mesh rect = MeshTools.MeshRect(transform.position, RectWidth, RectHeight);
Gizmos.color = new Color(1, 0, 0, 0.5f);
Gizmos.DrawMesh(rect, transform.position);
break;
}
}
}
现在这个脚本主要实现的功能有:
1.获取当前战斗区下的所有波数管理器,并进行排序,控制波数的先后顺序
void Start()
{
//获取所有的波数管理器
CheckPointWave[] wave = transform.GetComponentsInChildren<CheckPointWave>();
for (int i = 0; i < wave.Length; i++)
{
wave[i].SetCheckPoint(this);
Waves.Add(wave[i]);
}
//根据波数管理器id进行排序
Waves.Sort((r1,r2)=> { if (r1.Index > r2.Index) return 1; else if (r1.Index == r2.Index) return 0;return -1; });
//设置波数
for (int i = 0; i < Waves.Count; i++)
{
if (i != Waves.Count - 1)
Waves[i].SetNextWave(Waves[i + 1]);
else
Waves[i].SetNextWave(null);
}
}
2.检测玩家列表(为什么列表,考虑到会有多个玩家情况)是否进入当前战斗
void Update()
{
if (CpManager == null) return;
if (Waves.Count < 1) return;
if (!IsDetActor) return;
///检测玩家是否进入管战斗区
for (int i = 0; i < CpManager.DetActor.Count; i++)
{
if (DetActor(CpManager.DetActor[i]))
{
OnActive();
IsDetActor = false;
}
}
}
/// <summary>
/// 检测玩家是否进入战斗区
/// </summary>
/// <param name="actor"></param>
/// <returns></returns>
bool DetActor(Actor actor)
{
bool IsActive = false;
switch (CheckRange)
{
case CheckRange.Round:
IsActive = Vector3.Distance(actor.position,transform.position)<= RoundRadio;
break;
case CheckRange.Fan:
//懒癌犯了,这里无非就是距离和角度判定
break;
case CheckRange.Rect:
Vector2 pos1 = new Vector2(transform.position.x+ RectWidth/2,transform.position.z+RectHeight/2);
Vector2 pos2 = new Vector2(transform.position.x - RectWidth / 2, transform.position.z - RectHeight / 2);
IsActive = actor.position.x <= pos1.x && actor.position.x >= pos2.x && actor.position.z <= pos1.y && actor.position.z >= pos2.y;
break;
}
return IsActive;
}
注:我考虑的情况是,只要有一个玩家进入战斗区,则激活战斗区,其他玩家强制拉到战斗区中,当然你也可以让所有玩家进入战斗区才激活战斗区,这里也可以用碰撞体控制,我感觉都差不多,看心情吧
3.激活当前战斗区,并激活第一个波数管理器
/// <summary>
/// 激活战斗区
/// </summary>
public void OnActive()
{
IsActive = true;
///设置当前战斗区
CpManager.SetCurCheckPoint(this);
//第一波激活
Waves[0].OnActive();
if (OnStartAction.Count > 0)
{
for (int i = 0; i < OnStartAction.Count; i++)
CpManager.DoCpAction(OnStartAction[i].ActionType, OnStartAction[i].ActionParam);
}
}
注:战斗区激活后,检测变会关闭
IsDetActor = false;
4.获取随机创建位置,GetRandomPos()这个方法是波数管理器调用的,随机创建只会在指定范围创建,而指定位置创建,是根据配置进行创建的。
/// <summary>
/// 获取随机位置
/// </summary>
/// <returns></returns>
public Vector3 GetRandomPos()
{
Vector3 pos = Vector3.zero;
switch (CheckRange)
{
case CheckRange.Round:
Vector2 pos3 = new Vector2(transform.position.x + RoundRadio / 2, transform.position.z + RoundRadio / 2);
Vector2 pos4 = new Vector2(transform.position.x - RoundRadio / 2, transform.position.z - RoundRadio / 2);
float rx = UnityEngine.Random.Range(pos4.x, pos3.x);
float rz = UnityEngine.Random.Range(pos4.y, pos3.y);
Vector3 dir = new Vector3(rx, transform.position.y, rz);
float range = UnityEngine.Random.Range(0, RoundRadio);
pos = dir.normalized * range + transform.position;
break;
case CheckRange.Rect:
Vector2 pos1 = new Vector2(transform.position.x + RectWidth / 2, transform.position.z + RectHeight / 2);
Vector2 pos2 = new Vector2(transform.position.x - RectWidth / 2, transform.position.z - RectHeight / 2);
float x = UnityEngine.Random.Range(pos2.x, pos1.x);
float z = UnityEngine.Random.Range(pos2.y, pos1.y);
pos = new Vector3(x, 10, z);
break;
}
return pos;
}
这里经测试 圆形范围计算有问题,快过年了,我也不想去整了,你们有精力的自己写一下吧
5.战斗区结束控制,有始也有终,主要是结束事件能激活下一个战斗区得检测功能
/// <summary>
/// 战斗区结束
/// </summary>
public void OnFinsh()
{
IsActive = false;
if (OnEndAction.Count > 0)
{
for (int i = 0; i < OnEndAction.Count; i++)
CpManager.DoCpAction(OnEndAction[i].ActionType,OnEndAction[i].ActionParam);
}
if (NextCheckPoint != null)
{
NextCheckPoint.IsDetActor = true;
return;
}
CpManager.OnFinsh();
}
5.当然这里也有两个事件列表
[Header("当进入战斗开始时:")]
public List<CpAction> OnStartAction = new List<CpAction>();
[Header("当战斗区结束时:")]
public List<CpAction> OnEndAction = new List<CpAction>();
恩,到这里战斗区,和波数控制我们基本上都完成了,那还需要什么呢?
我感觉我们还需要一个关卡管理器,用来控制战斗区的激活和战斗区得顺序,以便在后面我们要实现自动寻路时,能获取下一个战斗区或者当前战斗区等等功能。
OK,我们先看所有的代码
using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
public class CheckPointManager : MonoBehaviour {
/// <summary>
/// 当角色创建
/// </summary>
public Action<Actor> OnActorCreate;
/// <summary>
/// 场景中所有动态元素
/// </summary>
[SerializeField]
private List<Actor> ScenesActors = new List<Actor>();
/// <summary>
/// 编辑器
/// </summary>
private GUIAction FindGUI = null;
private GUIAction ManagerGui = null;
/// <summary>
/// 关卡
/// </summary>
[SerializeField]
private List<CheckPoint> CheckPoint = new List<global::CheckPoint>();
/// <summary>
/// 当前执行的管卡
/// </summary>
public CheckPoint CurPoint { get; private set; }
/// <summary>
/// 当执行的波数
/// </summary>
public CheckPointWave CurWave { get; private set; }
/// <summary>
/// 怪物检测列表
/// </summary>
public List<Actor> DetActor { get; private set; }
/// <summary>
/// 当关卡开始
/// </summary>
public List<CpAction> OnStartAction = new List<CpAction>();
/// <summary>
/// 当怪物结束
/// </summary>
public List<CpAction> OnEndAction = new List<CpAction>();
void Awake()
{
//辅助UI
FindGUI = GUITools.ResterWindowAction(new Rect(Screen.width - 500, 150, 200, Screen.height), delegate (GUIAction action1)
{
action1.Rect = GUI.Window(action1.Id, action1.Rect, delegate
{
List<PlayerTable> pts = PlayerTable.TryGet();
for (int i = 0; i < pts.Count; i++)
{
if (GUI.Button(new Rect(5, i * 20 + 20, 190, 20), pts[i].PlayerID + "-" + pts[i].PlayerName))
{
CreateActor(pts[i].PlayerID, new Vector3(float.Parse(ManagerGui.Param[2].Split(',')[0]), 10, float.Parse(ManagerGui.Param[2].Split(',')[1])));
}
}
}, "Find");
});
ManagerGui = GUITools.ResterWindowAction(new Rect(Screen.width-300,150,300,300),delegate(GUIAction action) {
action.Rect = GUI.Window(action.Id,action.Rect,delegate {
action.Param[0] = GUI.TextField(new Rect(5, 20, 150, 20), action.Param[0]);
if (GUI.Button(new Rect(160, 20, 150, 20), "Find"))
{
if (action.Param[1].Equals("1"))
action.Param[1] = "0";
else action.Param[1] = "1";
}
FindGUI.IsShow = action.Param[1].Equals("1");
action.Param[2] = GUI.TextField(new Rect(5, 45, 150, 20), action.Param[2]);
if (GUI.Button(new Rect(160,45,100,20),"Create"))
CreateActor(action.Param[0], new Vector3(float.Parse(action.Param[2].Split(',')[0]),10,float.Parse(action.Param[2].Split(',')[1])));
if (GUI.Button(new Rect(5, 70, 80, 20), "Kill Wave"))
{
CurWave.kill();
}
if (GUI.Button(new Rect(90, 70, 80, 20), "Kill Point"))
{
CurWave.kill(true);
CurPoint.OnFinsh();
}
if (GUI.Button(new Rect(180, 70, 80, 20), "Kill All"))
{
OnFinsh();
}
GUI.DragWindow();
},"关卡管理器");
},"0","0","69,44");
}
void Start()
{
///初始化关卡
InitCheckPoint();
}
/// <summary>
/// 关卡结束时
/// </summary>
public void OnFinsh()
{
if (OnEndAction.Count > 0)
{
for (int i = 0; i < OnEndAction.Count; i++)
DoCpAction(OnEndAction[i].ActionType, OnEndAction[i].ActionParam);
}
///接下来需要显示评分,和网络通信等等功能
}
/// <summary>
/// 初始化关卡
/// </summary>
public void InitCheckPoint()
{
//设置检测列表
DetActor = new List<Actor>();
//添加主角
if (MainActor.Instance != null)
InsertActor(MainActor.Instance, 0);
//获取所有的战斗区
CheckPoint[] cps = transform.GetComponentsInChildren<CheckPoint>();
if (cps.Length < 1) return;
for (int i = 0; i < cps.Length; i++)
{
cps[i].SetCPManager(this);
CheckPoint.Add(cps[i]);
}
///战斗区排序
CheckPoint.Sort((a1, a2) => { if (a1.Index > a2.Index) return 1; else if (a1.Index == a2.Index) return 0; return -1; });
//设置下一关
for (int i = 0; i < CheckPoint.Count; i++)
{
if (i != CheckPoint.Count - 1)
{
//没有手动指定关卡
if (CheckPoint[i].NextIndex != -1)
{
CheckPoint[i].SetNextCp(GetCheckPoint(CheckPoint[i].NextIndex));
continue;
}
CheckPoint[i].SetNextCp(CheckPoint[i + 1]);
}
else
CheckPoint[i].SetNextCp(null);
}
///第一个关卡开始检测玩家
CheckPoint[0].IsDetActor = true;
if (OnStartAction.Count > 0)
{
for (int i = 0; i < OnStartAction.Count; i++)
DoCpAction(OnStartAction[i].ActionType, OnStartAction[i].ActionParam);
}
}
/// <summary>
/// 获取关卡
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public CheckPoint GetCheckPoint(int index)
{
for (int i = 0; i < CheckPoint.Count; i++)
{
if (CheckPoint[i].Index == index)
return CheckPoint[i];
}
return null;
}
/// <summary>
/// 设置当前那个关卡
/// </summary>
/// <param name="point"></param>
public void SetCurCheckPoint(CheckPoint point)
{
CurPoint = point;
}
/// <summary>
/// 设置当前第几波
/// </summary>
/// <param name="wave"></param>
public void SetCurWave(CheckPointWave wave)
{
CurWave = wave;
}
/// <summary>
/// 创建actor
/// </summary>
public void CreateActor(string heroID, Vector3 Pos, Action<Actor> OnDeath = null,Action<Actor> OnCreate =null)
{
PlayerTable pt = PlayerTable.TryGet(heroID);
Actor actor = null;
///加载资源
ResourcesManager.Instance.AsnyLoadResources<GameObject>(GeneralTable.TryGet(pt.PlayerActor).GeneralValue,delegate(GameObject obj) {
actor = obj.GetComponent<Actor>();
actor.OnCreateEvent = OnCreate;
ProfilerTools.BeginSample("InitActor",delegate {
//创建模型
actor.Init(pt.PlayerModelID, delegate {
actor.CharacterController.radius = pt.PlayerActorRadioAndHeight.x;
actor.CharacterController.height = pt.PlayerActorRadioAndHeight.y;
actor.CharacterController.center = pt.PlayerActorOffset;
actor.ActorAttr.Speed = pt.PlayerSpeed;
actor.ActorAttr.UpSpeed = pt.PlayerUpSpeed;
actor.ActorAttr.JumpAbility = pt.PlayerJumpAbility;
actor.ActorAttr.FlyAbility = pt.PlayerFlyAbility;
actor.ActorAttr.BackAbility = pt.PlayerBackAbility;
actor.ActorAttr.ForwardAbility = pt.PlayerForwardAbility;
actor.ActorAttr.MaxHp = pt.PlayerMaxHp;
actor.OnDeathEvent = OnDeath;
});
});
GeneralTools.SetTransfrom(actor.transform,Pos,Vector3.zero,Vector3.one);
if (actor is MainActor && actor.ActorType == ActorType.Player)
InsertActor(actor, 0);
else
AddActor(actor);
if (OnActorCreate != null)
OnActorCreate(actor);
});
}
/// <summary>
/// 插入actor 主角放在0的位置
/// </summary>
/// <param name="actor"></param>
/// <param name="index"></param>
void InsertActor(Actor actor,int index)
{
//玩家如家类型为玩家或友方则需要将其放入怪物检测列表中
if (actor.ActorType == ActorType.Player)
DetActor.Add(actor);
ScenesActors.Insert(index,actor);
}
/// <summary>
/// 添加Actor
/// </summary>
/// <param name="actor"></param>
void AddActor(Actor actor)
{
ScenesActors.Add(actor);
}
/// <summary>
/// 获取主角
/// </summary>
public Actor GetMainActor
{
get {
if (ScenesActors.Count < 1)
return null;
return ScenesActors[0];
}
}
/// <summary>
/// 执行 动作
/// </summary>
public void DoCpAction(string command)
{
string[] data = command.Split(':');
if (data.Length < 2) return;
DoCpAction(data[0],data[1]);
}
/// <summary>
/// 执行时间
/// </summary>
/// <param name="command"></param>
/// <param name="comParams"></param>
public void DoCpAction(string command,string comParams)
{
CheckPointAction cpa = CheckPointActionFactory.Create(command);
if (cpa == null)
{
Debug.Log(string.Format("加载关卡事件 {0} 失败...", command));
return;
}
cpa.ActionType = command;
if (cpa.OnParse(comParams))
cpa.DoAction(this);
}
/// <summary>
/// 编辑器方法 添加关卡
/// </summary>
/// <returns></returns>
public GameObject AddCheckPoint()
{
GameObject go = new GameObject("CheckPoint");
go.AddComponent<CheckPoint>();
GeneralTools.SetParent(transform,go.transform);
GameObject waveGo = new GameObject("Wave");
waveGo.AddComponent<CheckPointWave>();
GeneralTools.SetParent(go.transform,waveGo.transform);
return go;
}
}
解析一下主要功能:
1.初始化关卡,并且获取检测列表(这个里只添加主角),并且获取所有的战斗区进行排序,并且激活第一个战斗进行玩家检测
/// <summary>
/// 初始化关卡
/// </summary>
public void InitCheckPoint()
{
//设置检测列表
DetActor = new List<Actor>();
//添加主角
if (MainActor.Instance != null)
InsertActor(MainActor.Instance, 0);
//获取所有的战斗区
CheckPoint[] cps = transform.GetComponentsInChildren<CheckPoint>();
if (cps.Length < 1) return;
for (int i = 0; i < cps.Length; i++)
{
cps[i].SetCPManager(this);
CheckPoint.Add(cps[i]);
}
///战斗区排序
CheckPoint.Sort((a1, a2) => { if (a1.Index > a2.Index) return 1; else if (a1.Index == a2.Index) return 0; return -1; });
//设置下一关
for (int i = 0; i < CheckPoint.Count; i++)
{
if (i != CheckPoint.Count - 1)
{
//没有手动指定关卡
if (CheckPoint[i].NextIndex != -1)
{
CheckPoint[i].SetNextCp(GetCheckPoint(CheckPoint[i].NextIndex));
continue;
}
CheckPoint[i].SetNextCp(CheckPoint[i + 1]);
}
else
CheckPoint[i].SetNextCp(null);
}
///第一个关卡开始检测玩家
CheckPoint[0].IsDetActor = true;
if (OnStartAction.Count > 0)
{
for (int i = 0; i < OnStartAction.Count; i++)
DoCpAction(OnStartAction[i].ActionType, OnStartAction[i].ActionParam);
}
}
2.我们把角色创建的代码也放置在这里了,并且我们将主角始终放置在第一位
/// <summary>
/// 创建actor
/// </summary>
public void CreateActor(string heroID, Vector3 Pos, Action<Actor> OnDeath = null,Action<Actor> OnCreate =null)
{
PlayerTable pt = PlayerTable.TryGet(heroID);
Actor actor = null;
///加载资源
ResourcesManager.Instance.AsnyLoadResources<GameObject>(GeneralTable.TryGet(pt.PlayerActor).GeneralValue,delegate(GameObject obj) {
actor = obj.GetComponent<Actor>();
actor.OnCreateEvent = OnCreate;
ProfilerTools.BeginSample("InitActor",delegate {
//创建模型
actor.Init(pt.PlayerModelID, delegate {
actor.CharacterController.radius = pt.PlayerActorRadioAndHeight.x;
actor.CharacterController.height = pt.PlayerActorRadioAndHeight.y;
actor.CharacterController.center = pt.PlayerActorOffset;
actor.ActorAttr.Speed = pt.PlayerSpeed;
actor.ActorAttr.UpSpeed = pt.PlayerUpSpeed;
actor.ActorAttr.JumpAbility = pt.PlayerJumpAbility;
actor.ActorAttr.FlyAbility = pt.PlayerFlyAbility;
actor.ActorAttr.BackAbility = pt.PlayerBackAbility;
actor.ActorAttr.ForwardAbility = pt.PlayerForwardAbility;
actor.ActorAttr.MaxHp = pt.PlayerMaxHp;
actor.OnDeathEvent = OnDeath;
});
});
GeneralTools.SetTransfrom(actor.transform,Pos,Vector3.zero,Vector3.one);
if (actor is MainActor && actor.ActorType == ActorType.Player)
InsertActor(actor, 0);
else
AddActor(actor);
if (OnActorCreate != null)
OnActorCreate(actor);
});
}
3.另外这里还有事件的解析部分
/// <summary>
/// 执行 动作
/// </summary>
public void DoCpAction(string command)
{
string[] data = command.Split(':');
if (data.Length < 2) return;
DoCpAction(data[0],data[1]);
}
/// <summary>
/// 执行时间
/// </summary>
/// <param name="command"></param>
/// <param name="comParams"></param>
public void DoCpAction(string command,string comParams)
{
CheckPointAction cpa = CheckPointActionFactory.Create(command);
if (cpa == null)
{
Debug.Log(string.Format("加载关卡事件 {0} 失败...", command));
return;
}
cpa.ActionType = command;
if (cpa.OnParse(comParams))
cpa.DoAction(this);
}
4.当然还有结束的部分
/// <summary>
/// 关卡结束时
/// </summary>
public void OnFinsh()
{
if (OnEndAction.Count > 0)
{
for (int i = 0; i < OnEndAction.Count; i++)
DoCpAction(OnEndAction[i].ActionType, OnEndAction[i].ActionParam);
}
///接下来需要显示评分,和网络通信等等功能
}
OK,大体上完成了,接下来,我们主要来看一下事件的部分。
主题思想还是,所有事件继承一个积累,再用一个工厂类来创建事件的实例,调用事件,解析参数,执行事件
这个跟我前面几篇文章的事件大体上相似,直接看一下代码吧!
工程类:CheckPointActionFactory
public class CheckPointActionFactory
{
private static Hashtable lookUpType = new Hashtable();
public static CheckPointAction Create(string name)
{
CheckPointAction c = null;
try
{
var type = (Type)lookUpType[name];
lock (lookUpType)
{
if (type == null)
{
//Assembly curAssembly = Assembly.GetEntryAssembly();
type = Type.GetType(name);
//type = Type.GetType();
lookUpType[name] = type;
}
}
if (type != null)
c = Activator.CreateInstance(type) as CheckPointAction;
}
catch (Exception ex)
{
Debug.Log(string.Format("创建关卡事件,失败!!", ex.Message));
}
return c;
}
}
基类:CheckPointAction
/// <summary>
/// 关卡事件
/// </summary>
public abstract class CheckPointAction
{
public string ActionType;
public abstract bool OnParse(string command);
public abstract bool DoAction(CheckPointManager cm);
}
实体类:CpActive
现在只实现了一个,其他类型根据自己需要来进行创建
/// <summary>
/// 激活或显示物体 CpActive:UI,TipPanel,true
/// </summary>
public class CpActive : CheckPointAction
{
/// <summary>
/// UI,Cp,
/// </summary>
public string targetType;
public string targetName;
public bool IsActive;
public override bool OnParse(string command)
{
try
{
string[] data = command.Split(',');
targetType = data[0];
targetName = data[1];
IsActive = bool.Parse(data[2]);
return true;
}
catch { return false; }
}
public override bool DoAction(CheckPointManager cm)
{
switch (targetType.ToLower())
{
case "ui":
break;
case "cp":
Transform target = cm.transform.Find(targetName);
if (target != null)
target.gameObject.SetActive(IsActive);
break;
}
return true;
}
}
这个类主要实现的功能是,隐藏或显示物体,有三个参数,隐藏物体类型(ui或者关卡下的内容,这个可以分开写,我只是测试随意了),物体名称,状态(显示或隐藏)
具体配置方法:
好,基本的都介绍完了,具体需要实现的一些功能,还得需要在项目需要的进行添加,当然这些数据你都可以配置到配置表中,然后根据配置进行创建,也可以将关卡直接存储为一个预制体,在需要的时候进行加载。
哦对了,主要注意啊,在CheckPointManager 的Awake位置,我创建了ui创建方便进行测试,类似于这样:
写了一个window的管理类:
using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
/// <summary>
/// GUI工具
/// </summary>
public class GUITools : MonoBehaviour {
public static GUITools _instance;
public static GUITools Instance
{
get { return _instance; }
}
/// <summary>
/// 是否显示GUI
/// </summary>
public bool IsShowGUI = false;
public bool IsShowSystemInfo = false;
/// <summary>
/// GUI
/// </summary>
private static List<GUIAction> GUIActions = new List<global::GUIAction>();
/// <summary>
/// 注册GUI Window
/// </summary>
/// <param name="rect"></param>
/// <param name="func"></param>
/// <param name="param"></param>
/// <returns></returns>
public static GUIAction ResterWindowAction(Rect rect, Action<GUIAction> func,params string[] param)
{
GUIAction ga = new global::GUIAction(func);
ga.Rect = rect;
ga.Param = param;
GUIActions.Add(ga);
return ga;
}
/// <summary>
/// 绘制GUI
/// </summary>
void OnGUI()
{
if (IsShowGUI)
{
GUI.color = Color.yellow;
for (int i = 0; i < GUIActions.Count; i++)
if(GUIActions[i].IsShow)
GUIActions[i].DoAction();
}
}
}
/// <summary>
/// GUI功能
/// </summary>
public class GUIAction
{
public static int NextID = 0;
public int Id { get; private set; }
public Rect Rect;
public string[] Param;
public Action<GUIAction> Func { get; private set; }
public bool IsShow = true;
public GUIAction(Action<GUIAction> func)
{
Id = NextID++;
Func = func;
}
public void DoAction()
{
if (Func != null)
Func(this);
}
}
方便对GUI进行管理,不过现在只有一个window。使用时,挂载到一个预制体就行了。
最终结果
快过年,给种无动力奔袭而来,最后一个周,让我混混日子吧…
更多推荐
所有评论(0)