前言
TD(炮台防守)类游戏是比较经典的游戏类型,当年在 War3 中有很多 TD 类地图,还有类似《保卫萝卜》这种 2D 炮台防守游戏也曾火爆一时。
本次实例将使用 Unity 实现此类游戏,功能包含地图创建、敌人创建、敌人导航、炮台创建与升级、炮台攻击、敌人攻击等功能。
本章实现效果
场景和敌人
场景的创建使用 MTE 插件,该插件的使用方法可以参考另两篇文章:
【Unity】Unity插件:地形编辑器MTE(Mesh Terrain Editor)
【Unity】Unity寻路系统讲解及Navigation实际应用
敌人的模型及动画可以在 Unity Assets Store 中下载。后续章节我也会讲解如何使用模型和动画来生成预制件。
导航的功能可以参考另外几篇文章:
【Unity】Unity寻路系统讲解及Navigation实际应用
【Unity】导航基本组件:Nav Mesh Agent、Off Mesh Link、Nav Mesh Obstacle
【Unity】导航 Navigation 设置:窄桥、隧道、斜坡、台阶、坠落、跳跃
【Unity】Navigation 选择性寻路:Area Mask
动画播放可以参考另一篇文章:
【Unity】U3D ARPG游戏制作实例(二)人物基本动作切换
文件目录结构
如上图所示,目录结构基本按照以下原则:
- Data:用于存放所有的模型、材质、纹理、动画控制器,其目录结构按照防御塔、敌人、基地、地形划分,每个目录下有相应的文件夹,以第一个敌人为例,在Enemy目录下创建一个名为Enemy_0001的目录,该目录下包含3D(模型及动画文件)、AnimatorController(动画控制器)、Materials、Textures四个目录。注意,敌人的Prefab预制体不应被存放到此文件夹中,因为预制体需要放到Resources文件夹中,以便后续动态加载。
- Plugins:存放所有插件,例如EasyTouch、MTE等。
- Resources:该文件夹中的内容可以被动态加载,所以我们做好的Prefab预制件要放到此文件夹中。注意,不要把所有的文件都放到此文件夹中,这会影响性能。
- Secenes:场景文件。
- Scripts:存放脚本文件。目前脚本暂时分为 Control(控制)、Defense(防御塔)、Enemy(敌人)、Game(游戏主程序)、Level(关卡)、Nav(导航),相应的脚本放入相应的文件夹下。
场景层次结构(Hierarchy)
如上图,场景 Hierarchy 结构大致如下:
- Directional Light:直接光源,其后两个为测试光源,请忽略。
- Main Camera:主相机。目前只提供一个主相机,后续可能会提供地图相机。
- Easytouch:Easytouch、InputManager、EasyTouchControlsCanvas、EventSystem等都是用于处理输入事件的,在此不做过多处理。
- Canvas:UI界面画布。
- EnemyGeneratePoint:敌人出生点,所有敌人都要以出生点为父节点创建出来。一个场景内可能会有多个出生点,后续在加载 Level 时统一做处理。
- TerrainParent:地图父节点,通过预制件加载的地图就放在此节点下。
- GameScript:游戏脚本挂载处,用于挂载游戏核心脚本。
目前文件和场景结构设定如下,后续可能会有小范围更改。
处理模型
先将模型放到场景中,给模型创建好动画控制器,具体操作方式请见:【Unity】U3D ARPG游戏制作实例(二)人物基本动作切换。
给模型指定导航代理以及自定义脚本:
将模型从场景中拖拽到文件夹中:
指定GameScript脚本
给 GameScript 节点指定两个脚本,代码如下:
using TDGameDemo.Nav;
using UnityEngine;
namespace TDGameDemo.Enemy
{
/// <summary>
/// 敌人创建器
/// </summary>
public class EnemyGenerator : MonoBehaviour
{
/// <summary>
/// 生成敌人
/// </summary>
/// <param name="parent">父对象</param>
/// <param name="enemy">敌人配置对象</param>
/// <param name="target">导航目标</param>
/// <returns></returns>
public bool GenerateEnemy(Transform parent, string enemy, Transform target)
{
try
{
GameObject enemyPrefab = Resources.Load<GameObject>(enemy);
GameObject o = Instantiate(enemyPrefab, parent, true);
o.GetComponent<NavTest>().target = target;
}
catch (System.Exception)
{
return false;
throw;
}
return true;
}
}
}
using TDGameDemo.Enemy;
using UnityEngine;
namespace TDGameDemo.Game
{
/// <summary>
/// 游戏主程序
/// 加载关卡、游戏的开始、暂停、通关、失败
/// </summary>
public class GameMain : MonoBehaviour
{
private EnemyGenerator _enemyGenerator;
public Transform _generatePoint;
public Transform _target;
void Start()
{
_enemyGenerator = GetComponent<EnemyGenerator>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
_enemyGenerator.GenerateEnemy(_generatePoint, "Enemy/Prefab/Prefab_Enemy_10001", _target);
}
}
}
}
此处路径暂时写死,后续要根据配置文件读取。
将出生点和基地的节点给到 GameMain 脚本:
运行游戏
使用 A 键来生成敌人。效果如下:
更多内容请查看总目录【Unity】Unity学习笔记目录整理