把场景模型文件拖拽到层级视图中,创建游戏所需要的场景,同时把场景中所需要的元素补充完整。具 体实现步骤如下:
把Assets/MOBA and Tower Defense/Demo文件夹里面的Demo2_towers拖拽到层级视图中,transform组件属性设置如下:
1、首先创建怪物数据脚本,并通过游戏控制脚本在指定地点生成怪物:
在场景中创建出生位置游戏对象startPositon,transform组件属性设置如下:
2、在场景中创建目标点游戏对象endPositon,transform组件属性设置如下:
3、场景中创建GameController游戏对象,并添加CreateMonster和MonsterData脚本组件。
/// <summary>
/// 生成怪物
/// </summary>
public class CreateMonster : MonoBehaviour
{
//怪物预制件
private GameObject monsterPrefab;
//怪物出生地
[SerializeField]
private Transform startPosition;
//怪物每一波生成计时器
private float time;
//定义一个变量存储正在生成第几波
private int index = 0;
//定义一个bool类型变量来存储是否正在生成怪物
private bool isBorning = false;
//定义一个结构体类型变量来缓存当前需要生成的波次怪物信息
private MonsterDataStruct dataStruct;
//定义一个小计时器用来记录每一个小怪的生成
private float smallTime;
//定义每一个小怪的生成时间间隔
private float smallTimeInterval = 1;
//定义一个变量记录当前波生成到第几个小怪
private int monsterCount = 0;
// Use this for initialization
void Start()
{
//得到第一波敌人数据
dataStruct = MonsterData.Instance.Monsters[index];
//加载怪物预制件
monsterPrefab = Resources.Load<GameObject>("mon_orcWarrior");
}
// Update is called once per frame
void Update()
{
//判断计数变量的值是否和怪物链表长度相等,如果相等证明所有波次的怪物生成完毕,直接返回
if (index == MonsterData.Instance.Monsters.Count)
{
return;
}
//isBorning=false
if (isBorning==false)
{
//如果正在生成怪物标志位为false
//开始启动计时器
//计时器不断累加时间
time += Time.deltaTime;
//如果累加的时间大于等于当前波次的等待时间
if (time >= dataStruct.waitTime)
{
//将正在生成怪物标志位设置为true
isBorning = true;
//将计时器清0;
time = 0;
}
}
else
{
//如果正在生成怪物标志位为true
//调用生成怪物方法即可
CreatMethod();
}
}
//生成一波怪物的方法
private void CreatMethod()
{
//开始生成第一波怪物
//重新定义一个小的定时器来控制每一个小怪的生成间隔
smallTime += Time.deltaTime;
if (smallTime >= smallTimeInterval)
{
smallTime = 0;
//满足时间生成一个怪物
GameObject monster = Instantiate(monsterPrefab, startPosition.position,Quaternion.identity);
monster.name = "monster"+index+ monsterCount;
//每生成一个怪物记录一次,
monsterCount++;
//判断记录的个数和当前波次最大怪物数量相等,
if (monsterCount == dataStruct.monsterCount)
{
//将小怪物计数也清零
monsterCount = 0;
//如果相等那么当前波怪物生成完毕
//正在生成第几波计数变量自增
index++;
//将正在生成标志位设置为false
isBorning = false;
//更新下一波波敌人数据
if (index < MonsterData.Instance.Monsters.Count)
{
dataStruct = MonsterData.Instance.Monsters[index];
}
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//定义一个结构体,来存储每一波怪物信息
public struct MonsterDataStruct
{
//当前波怪物的移动速度
public float speed;
//当前波怪物的数量
public int monsterCount;
//上一波生成结束之后的等待时间
public float waitTime;
}
/// <summary>
/// 怪物数据管理类
/// </summary>
public class MonsterData : MonoBehaviour {
//将脚本组件设置为单例的方式
//首先定义一个静态变量
private static MonsterData instance;
//定义一个公有的静态属性值
public static MonsterData Instance
{
get { return instance; }
}
//第一波怪物的移动速度初始值
private float initSpeed=4;
//第一波怪物的数量初始值
private int initCount = 6;
//第一波怪物生成等待时间
private float initWaitTime = 2;
//定义一个链表存储每一波的数据
private List<MonsterDataStruct> monsters;
public List<MonsterDataStruct> Monsters
{
get { return monsters; }
}
private void Awake()
{
//由于Awake总是被执行而且只执行一次,所以开始就对instance赋值
instance = this;
//初始化链表对象
monsters = new List<MonsterDataStruct>();
//定义一个循环来生成5波怪物数据
for (int i = 0; i < 5; i++)
{
//创建一个结构体对象
MonsterDataStruct data = new MonsterDataStruct();
//设置速度值
data.speed = initSpeed + i * 0.5f;
//设置数量
data.monsterCount = initCount + i;
//设置等待时间
data.waitTime = initWaitTime + i * 3f;
//将数据添加到链表中
monsters.Add(data);
}
}
// Use this for initialization
void Start () {
}
}
Navgation烘焙场景导航路径:
怪物添加如下组件:
public class MonsterControl : MonoBehaviour {
//定义一个委托来广播怪物死亡事件
public delegate void DeadDelegate(GameObject monster);
//用事件修饰委托变量
public event DeadDelegate deadEvent;
//获取导航组件
private NavMeshAgent agent;
//获取寻路目标点
private Transform endPosition;
//怪物血量
private float hp = 100;
//得到Animation组件
private Animation ani;
// Use this for initialization
void Start () {
ani = GetComponent<Animation>();
endPosition = GameObject.Find("endPosition").transform;
agent = GetComponent<NavMeshAgent>();
//调用导航方法设置目标点
agent.SetDestination(endPosition.position);
}
// Update is called once per frame
void Update () {
}
/// <summary>
/// 受到伤害逻辑处理
/// </summary>
/// <param name="damage"></param>
public void Damage(float damage)
{
if (hp <= 0)
{
return;
}
hp -= damage;
if (hp <= 0)
{
//怪物死亡
//禁用导航组件
//setActive方法是隐藏和显示游戏对象
//enabled属性是禁用和启用游戏对象身上的组件,false禁用,true启用
agent.enabled = false;
//播放死亡动作
ani.CrossFade("Dead");
//死亡之后禁用碰撞器组件
GetComponent<CapsuleCollider>().enabled = false;
//广播自己死亡事件
if (deadEvent != null)
{
Debug.Log("dead:"+transform.name);
deadEvent(gameObject);
}
//等待死亡动作播完延迟销毁当前怪物对象
Destroy(gameObject,1);
}
}
}
GameController游戏对象添加InitTowerFire组件即可,如下:
public class InitTowerFire : MonoBehaviour {
//定义字段存储大炮预制件
private GameObject firePrefab;
private int index;
// Use this for initialization
void Start () {
//通过Resources加载大炮预制件
firePrefab = Resources.Load<GameObject>("Tow_Machinegun1");
}
// Update is called once per frame
void Update () {
//实时校验是否摁下鼠标左键
if (Input.GetMouseButtonDown(0))
{
//在鼠标点击位置生成一条射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
//获取拦截射线的所有碰撞体信息
RaycastHit[] hits = Physics.RaycastAll(ray);
//遍历数组
foreach (RaycastHit hit in hits)
{
//校验拦截对象的标签是否是tower并且该炮塔基座下没有子物体时生成一门大炮
if (hit.transform.tag == "tower" && hit.transform.childCount == 0)
{
//创建一门大炮
//实例化需要大炮预制件,父物体
GameObject fire = Instantiate(firePrefab, hit.transform);
index++;
fire.name = "tower" + index;
//得到基座的碰撞体组件
BoxCollider collider = hit.transform.GetComponent<BoxCollider>();
//通过碰撞体组件得到y轴的值也就是对应大炮基座的高度
float height = collider.size.y - 0.2f;
//设置大炮的本地坐标为:沿着y轴上移以上得到的高度值
fire.transform.localPosition = Vector3.up * height;
//结束循环
break;
}
}
}
}
}
Tow_Machinegun1预制件展开在Base游戏对象添加以下组件TowerFireControl:
/// <summary>
/// 主要控制炮塔转向,开火,处理进入攻击范围的怪物等逻辑
/// </summary>
public class TowerFireControl : MonoBehaviour {
//炮管对象
[SerializeField]
private GameObject turret;
//子弹预制件
private GameObject firePrefab;
//炮塔转向目标的速度
private float turnSpeed=5;
//设置一个变量存储正在攻击的目标
private GameObject currentAttackTarget;
//定义生成炮弹的计时器
private float time;
//定义发射间隔
private float timeInterval = 0.5f;
//开火点
[SerializeField]
private Transform firePoint;
//定义一个链表存储正在攻击范围内的所有怪物
private List<GameObject> attackList = new List<GameObject>();
// Use this for initialization
void Start () {
//加载子弹预设体
firePrefab = Resources.Load<GameObject>("Fire");
}
// Update is called once per frame
void Update() {
//如果攻击目标为空
if (currentAttackTarget == null)
{
//查询链表是否为空,如果链表不为空,那么获取第一个怪物作为攻击目标
if (attackList.Count != 0)
{
currentAttackTarget = attackList[0];
}
}
else
{
//如果攻击目标不为空那么开火
//开炮
Fire();
}
}
private void OnTriggerEnter(Collider other)
{
//怪物进入攻击范围时将其加入攻击链表
if (other.tag == "monster")
{
attackList.Add(other.gameObject);
//订阅怪物死亡事件
other.GetComponent<MonsterControl>().deadEvent += MosterDead;
}
}
private void OnTriggerExit(Collider other)
{
//怪物离开攻击范围时将其从攻击链表中移除
if (other.tag == "monster")
{
attackList.Remove(other.gameObject);
if (other.gameObject == currentAttackTarget)
{
//将攻击目标清空
currentAttackTarget = null;
}
//怪物走出攻击范围取消订阅死亡事件
other.GetComponent<MonsterControl>().deadEvent -= MosterDead;
}
}
/// <summary>
/// 当怪物死亡之后的逻辑处理
/// </summary>
private void MosterDead(GameObject monster)
{
//判断死亡的怪物是否在攻击链表中,如果在的话移除
if (attackList.Contains(monster))
{
attackList.Remove(monster);
}
//如果死亡的怪物是正在攻击的目标
if (monster == currentAttackTarget)
{
//清空正在攻击的目标对象
currentAttackTarget = null;
}
}
/// <summary>
/// 当攻击目标不为空时调用该方法开炮
/// </summary>
private void Fire()
{
//首先得到由大炮指向攻击目标的方向向量
Vector3 dir = currentAttackTarget.transform.position - turret.transform.position;
//将该方向转为四元数
Quaternion dirQua = Quaternion.LookRotation(dir);
//使用四元数插值控制炮管由快到慢看向攻击目标
turret.transform.rotation = Quaternion.Lerp(turret.transform.rotation,dirQua,Time.deltaTime*turnSpeed);
//计算炮管的朝向和目标方向的夹角,如果小于5度,那么直接将炮管的方向设置为目标方向
float angle = Vector3.Angle(turret.transform.forward,dir);
if (angle <= 5)
{
turret.transform.rotation = dirQua;
//生成子弹
time += Time.deltaTime;
if (time >= timeInterval)
{
time = 0;
//间隔一段时间生成一发子弹
GameObject fire = Instantiate(firePrefab, firePoint.position, firePoint.rotation);
//设置子弹目标点为当前正在攻击的目标
fire.GetComponent<FireControl>().Target = currentAttackTarget;
}
}
}
}
_MainCamer游戏对象添加CameraControl脚本组件:
/// <summary>
/// 相机控制脚本,相机根据水平和垂直轴的输入沿着世界坐标进行移动
/// </summary>
public class CameraControl : MonoBehaviour {
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
//得到水平轴输入
float h = Input.GetAxis("Horizontal");
//得到垂直虚拟轴输入
float v = Input.GetAxis("Vertical");
//h的值来控制相机沿着z轴进行移动,v的值控制相机沿着x轴前后移动
//摁下D=(0,0,1),摁下A=(0,0,-1)
//摁下W =(-1,0,0),摁下s=(1,0,0)
transform.Translate(new Vector3(h, 0, v) * Time.deltaTime * 6,Space.World);
}
}