为什么按照金玺曾做的塔防游戏不能放好玩的魔兽rpg防守图单位

君,已阅读到文档的结尾了呢~~
基于Unity3D的塔防游戏关键技术的研究与实现,unityd,塔防三国志,梦塔防,天天来塔防,塔防游戏,塔防三国志论坛,塔防单机游戏,塔防海贼王,三国塔防
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
基于Unity3D的塔防游戏关键技术的研究与实现
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer--144.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口4146人阅读
[Unity引擎](86)
[移动互联](50)
[游戏开发](79)
各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是。
在上一篇文章中,我们从塔防游戏的三个组成要素地图、敌人、防守单位对塔防游戏进行了较为全面的阐述,并通过实例完成了塔防游戏的第一部分:地图篇。那么我们今天来继续完成这个塔防游戏的开发,首先我们来看第一部分:敌人篇。
敌人在游戏中有一个基本的行为,即沿着寻路路径向我方阵地移动并发起攻击。在地图篇中,我们详细地介绍了敌人寻路路径的生成原理。既然有了敌人寻路的路线,那么怎么让敌人沿着路线移动呢?其实只要指定敌人寻路的起点,因为在寻路路径的设计中,我们使用的是一个类似于链表的结构,这样我们就能根据每个结点获取它的目标结点,从而实现敌人沿着寻路路径移动的效果了。因为敌人寻路的路线是定义在PathNode类中的,因此我们可以写出下面这样的代码:
void Move()
Vector3 mPos1=this.transform.position
Vector3 mPos2=this.StartNode.transform.position
//计算敌人与路径节点间的距离
float mDis=Vector2.Distance(new Vector2(mPos1.x,mPos1.y),new Vector2(mPos2.x,mPos2.y))
if(mDis&0.1F){
if(StartNode.ThatNode==null){
//对防守阵地进行摧毁
GameManager.Instance.PlayerHP-=20
//从敌人列表中移除自身
GameManager.Instance.Enemys.Remove(this)
//销毁自身
Destroy(this.gameObject)
//销毁血条
Destroy(mHPBar.gameObject)
StartNode=StartNode.ThatNode
//计算敌人的移动方向
Vector3 mDir=new Vector3(mPos2.x-mPos1.x,mPos2.y-mPos1.y,0).normalized
transform.Translate(mDir * MoveSpeed * Time.deltaTime)
好了,现在我们来一起分析这段代码。首先,我们计算了敌人与路径结点间的距离,这里我们用0.1来近似地表示敌人已经到了路径结点上,此时如果该结点的目标结点为null则表示此时敌人已经到了最后一个结点处,所以敌人会对我方的阵地造成20点的伤害并销毁敌人。在GameManager我们使用了一个列表来管理和维护当前场景中的所有敌人,因此当当前敌人销毁时需要从列表中移除,GameManager类是一个静态类,负责对游戏的全局维护,这个类我们放到稍后来讲啊。那么如果敌人没有走到最后一个结点怎么办呢?我们只需要将StartNode指向StartNode的目标节点,这样我们就可以对整个路径结点实现遍历。这里是不是有种数据结构的感觉呢?哈哈,数据结构和算法是编程中最基础、最重要的内容,这些内容到了游戏开发领域同样是适用的。那么,好了,既然知道敌人是怎么移动的,现在我们就来对敌人进行移动吧,这里是采用计算移动方向的方式来实现,这个很简单啦。
好了,现在我们来说说敌人的血条吧,我们知道当怪物沿着寻路路径向我方阵地发起攻击的时候,我方防守单位会自动地对敌人进行防御性攻击,那么此时血条就可以显示敌人的实时血量,从而方便玩家根据战场的情况来调整兵力部署情况。我们知道从Unity4.6以后Unity开始使用全新的GUI系统UGUI,因为博主在之前的项目中一直使用NGUI,加上博主不是很喜欢做UI,所以每次用NGUI的时候整个人的心情都是不好的,有段时间被NGUI虐得体无完肤,感觉整个人都不好了。好了,既然现在我们有了新的选择UGUI,那么就让我们先睹为快吧!如图,全新的NGUI位于GameObect-&UI菜单下,基本覆盖了常用的如Button、Image、Slider、ScrollBar等控件,因为UGUI刚推出不久,所以博主希望大家还是能客观、公正的对待UGUI和NGUI,博主认为在短期内这两个GUI系统将处于共存的状态,不存在相互替代的可能性。
好了,UGUI所有的控件都是放到一个叫做Canvas的父控件下的,这一点和NGUI的UIRoot有些类似吧!Canvas提供了三种模式的UI系统,即Screen Space-Overlay、Screen Space-Camera、World Space。第一种Screen Space-Overlay它是完全以当前屏幕的像素大小创建的一个矩形范围,即控件是以屏幕坐标来绘制的;第二种Screen Space-Camera它是根据相机的视线范围来确定的一个矩形范围,其控件是根据Camera的ViewPortPoint坐标来绘制的;第三种从名称我们就可以知道,它是完全3D化的UI,使用的是常用的世界坐标。博主是刚开始研究UGUI,如果有不对的地方还希望大家能够原谅啊。好了,下面我们正式来做血条吧,在这里我们使用的是默认的Slider控件,用Slider控件来制作血条需要将Slider控件自带的滑块删除,然后我们通过改变value就可以实现一个简单的血条了。在UGUI中所有的图片素材都是以Sprite的形式来出现的,所以UGUI可以自己生成图集,不需要像NGUI在做UI前首先要生成图集。这是博主做的一个简单的血条。现在血条做好了,可是问题来了:这UGUI的所有控件都必须要放到Canvas下面啊,所以我们没法像NGUI一样直接把做好的血条放到怪物下面。怎么办呢?既然不能放到怪物下面,那我们就放到Canvas下面吧,不过我们需要自己计算血条的位置。好了,下面来看代码:
public class Enemy : MonoBehaviour
public float MaxHP;
public float HP;
public PathNode StartN
public float MoveSpeed=0.15F;
public float RotateSpeed=0.3F;
public GameObject HPB
private Slider mHPB
void Awake()
GameManager.Instance.Enemys.Add(this.GetComponent&Enemy&());
Transform mUiRoot=GameObject.Find("UIManager").
Vector3 mPos=this.transform.FindChild("EnemyHP").transform.
mPos.z=-5;
GameObject go=(GameObject)Instantiate(HPBar,mPos,Quaternion.identity);
go.transform.parent=mUiR
go.GetComponent&RectTransform&().localScale=new Vector3(0.5F,0.30F,1);
mHPBar=go.transform.GetComponent&Slider&();
void Move()
Vector3 mPos1=this.transform.
Vector3 mPos2=this.StartNode.transform.
float mDis=Vector2.Distance(new Vector2(mPos1.x,mPos1.y),new Vector2(mPos2.x,mPos2.y));
if(mDis&0.1F){
if(StartNode.ThatNode==null){
GameManager.Instance.PlayerHP-=20;
GameManager.Instance.Enemys.Remove(this);
Destroy(this.gameObject);
Destroy(mHPBar.gameObject);
StartNode=StartNode.ThatN
Vector3 mDir=new Vector3(mPos2.x-mPos1.x,mPos2.y-mPos1.y,0).
transform.Translate(mDir * MoveSpeed * Time.deltaTime);
void Rotate()
float mStartAngle=this.transform.eulerAngles.z;
transform.LookAt(StartNode.transform);
float mTargetAngle=this.transform.eulerAngles.z;
float mAngle=Mathf.MoveTowardsAngle(mStartAngle,mTargetAngle,RotateSpeed *Time.deltaTime);
this.transform.eulerAngles = new Vector3(0,0,mAngle);
void Update ()
UpdateHPBar();
private void UpdateHPBar()
Vector3 mPos=this.transform.FindChild("EnemyHP").transform.
mPos.z=-5;
mHPBar.transform.position=mP
mHPBar.value=(float)HP/MaxHP;
public void SetDamage(int mValue)
if(HP&=0){
Destroy(this.gameObject);
Destroy(mHPBar.gameObject);
GameManager.Instance.Enemys.Remove(this.GetCopmonent&Enemy&());
在这里我们做了三件事情:
第一,在Awake方法中我们首先计算出血条的位置然后在这个位置生成血条,并取得相关的变量备用。
第二,在Update方法中增加一个UpdateHPBar方法以实现对血条血量的更新。
第三,增加了一个SetDamage方法,当敌人血量为0时销毁自身、销毁血条、从敌人列表中移除敌人
好了,到现在为止,对于敌人的逻辑我们就全部实现了。可是我们知道在塔防游戏中敌人通常是一波一波出现的,所以我们需要一个敌人生成器EnemySpawn。那么,怎么来生成敌人呢,这里我们使用Xml文件来配置要生成的敌人列表,首先我们来构建一个Xml文件:
&?xml version="1.0" encoding="utf-8" standalone="yes"?&
Wave="1" EnemyName="Enemy" Level="1" Wait="0.5"/&
Wave="2" EnemyName="Enemy" Level="2" Wait="0.45"/&
Wave="2" EnemyName="Enemy" Level="2" Wait="0.45"/&
Wave="3" EnemyName="Enemy" Level="3" Wait="0.4"/&
Wave="3" EnemyName="Enemy" Level="3" Wait="0.4"/&
Wave="3" EnemyName="Enemy" Level="3" Wait="0.4"/&
Wave="4" EnemyName="Enemy" Level="4" Wait="0.35"/&
Wave="4" EnemyName="Enemy" Level="4" Wait="0.35"/&
Wave="4" EnemyName="Enemy" Level="4" Wait="0.35"/&
Wave="4" EnemyName="Enemy" Level="4" Wait="0.35"/&
Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/&
Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/&
Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/&
Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/&
Wave="5" EnemyName="Enemy" Level="5" Wait="0.3"/&
Wave="6" EnemyName="Enemy" Level="99" Wait="0.15"/&
从这个Xml文件中我们可以看到这样一个结构:
using UnityE
using System.C
using System.Collections.G
using System.X
public class SpawnData
public int W
敌人名称,我们将根据这个名称来生成不同的敌人
public string EnemyN
public int L
public float W
在SpawnData这个结构中,我们可以得到敌人攻击的波数、敌人的名称、敌人等级、敌人生成需要等待的时间,因为博主在游戏中只有一种敌人,所以敌人的名称都是一样的。好了,现在我们可以开始解析Xml了:
//解析Xml文件
void ReadXml()
//创建一个字典以存储敌人列表
mEnemyDatas=new List&SpawnData&()
//加载Xml文档
XmlDocument mDocument=new XmlDocument()
mDocument.LoadXml(ConfigFile.text)
XmlElement mRoot=mDocument.DocumentElement
//解析Xml文档
XmlNodeList mNodes=mRoot.SelectNodes("/Enemies/Enemy")
foreach(XmlNode mNode in mNodes)
//为每一个SpawnData赋值
SpawnData mData=new SpawnData()
mData.Wave=int.Parse(mNode.Attributes[0].Value)
mData.EnemyName=mNode.Attributes[1].Value
mData.Level=int.Parse(mNode.Attributes[2].Value)
mData.Wait=float.Parse(mNode.Attributes[3].Value)
mEnemyDatas.Add(mData)
那么好了,在解析完Xml后我们得到了所有的敌人数据,接下来我们只需要按照顺序生成敌人就可以了。具体怎么做呢,我们知道在塔防游戏中生成敌人有两种情况:
一个是要生成的敌人和当前敌人是同一波的,这种情况只要继续生成就好了。
一个是要生成的敌人的波数大于当前波数,这种情况需要等待这一波敌人被消灭完。
好了,现在来写代码:
using UnityE
using System.C
using System.Collections.G
using System.X
public class EnemySpawn : MonoBehaviour {
public PathNode SpawnP
public GameObject E
public TextAsset ConfigF
private List&SpawnData& mEnemyD
private int mWave=0;
private int mIndex=0;
private float mW
void Start()
ReadXml();
Debug.Log(mEnemyDatas.Count);
SpawnData mData=mEnemyDatas[mIndex];
mWave=mData.W
mWait=mData.W
GameManager.Instance.AttackWave=mW
CreateEnemy(mData);
mIndex+=1;
void CreateEnemy(SpawnData mData)
GameObject go=(GameObject)Instantiate(Enemy,SpawnPath.transform.position,Quaternion.identity);
Enemy _Enemy=go.GetComponent&Enemy&();
_Enemy.MaxHP= (float)mData.Level*0.25F * 100;
_Enemy.HP= (float)mData.Level*0.25F * 100;
go.GetComponent&Enemy&().MoveSpeed=(float)mData.Level * 0.15F;
go.GetComponent&Enemy&().StartNode=SpawnP
void Update ()
if(mIndex&=mEnemyDatas.Count-1){
SpawnEnemy();
if(GameManager.Instance.Enemys.Count==0){
GameManager.Instance.IsWin=true;
Debug.Log("玩家胜");
private void SpawnEnemy()
SpawnData mData=mEnemyDatas[mIndex];
mWait-=Time.deltaT
if(mWait&=0 ){
if(mWave==mData.Wave){
mWait=mEnemyDatas[mIndex].W
mWave=mEnemyDatas[mIndex].W
GameManager.Instance.AttackWave=mW
if(mData!=null){
CreateEnemy(mData);
mIndex+=1;
else if(mWave&mData.Wave && GameManager.Instance.Enemys.Count==0){
mWait=mData.W
mWave=mData.W
GameManager.Instance.AttackWave=mW
CreateEnemy(mData);
mIndex+=1;
void ReadXml()
mEnemyDatas=new List&SpawnData&();
XmlDocument mDocument=new XmlDocument();
mDocument.LoadXml(ConfigFile.text);
XmlElement mRoot=mDocument.DocumentE
XmlNodeList mNodes=mRoot.SelectNodes("/Enemies/Enemy");
foreach(XmlNode mNode in mNodes)
SpawnData mData=new SpawnData();
mData.Wave=int.Parse(mNode.Attributes[0].Value);
mData.EnemyName=mNode.Attributes[1].V
mData.Level=int.Parse(mNode.Attributes[2].Value);
mData.Wait=float.Parse(mNode.Attributes[3].Value);
mEnemyDatas.Add(mData);
我们可以注意到,到现在为止敌人相关的内容博主都已经为大家讲解完了,这里博主和大家开了一个小玩笑,不知道大家有没有发现,在敌人的Xml配置文件中博主最后设计了一个等级为99级的敌人,哈哈,这个敌人在游戏中的特点大家要自己从代码中来探索了,大家可以按照博主的思路做出这个塔防游戏然后自己去试试看,相信大家会更加深刻地理解数值平衡的重要性吧!
防守单位篇
防守单位是塔防游戏中玩家可以支配和控制的一种资源,玩家通过合理地分布防守单位的位置来对玩家的防守阵地进行防御,当玩家的防守阵地被摧毁时玩家将无法继续部署防守单位。这就是防守单位在游戏中的主要作用。通常为了增加游戏的可玩性,游戏设计者往往会设计多种防守单位,在博主的这个小游戏中,我们只设计了一种防守单位,更多的防守单位的设计大家可以参考《保卫萝卜》和《植物大战僵尸》这两个游戏。好了,说了这么多,那么防守单位在整个塔防游戏中主要的作用是什么呢?答案就是防守,哈哈,这是一句不折不扣的废话。可是就是这样一句废话,却足以让我们知道防守单位需要对敌人进行自动攻击,这就要涉及到简单的AI算法了。好了,我们来看下面的脚本:
using UnityE
using System.C
public class Defender : MonoBehaviour {
private Enemy mT
public float AttackArea=2.5F;
private float mDistance=0;
public float RotateSpeed=1.5F;
public float AttakTime=2.5F;
private float mTime=0.0F;
public GameObject BulletO
void Start ()
mTime=AttakT
void FindEnemy()
mTarget=null;
ArrayList mEnemys=GameManager.Instance.E
foreach(Enemy _enemy in mEnemys)
if(_enemy.HP==0) continue;
Vector3 mPos1=transform.
Vector3 mPos2=_enemy.transform.
float mDis=Vector2.Distance(new Vector2(mPos1.x,mPos1.y),new Vector2(mPos2.x,mPos2.y));
if(mDis&AttackArea){
if(mDistance==0 || mDistance & mDis){
mDistance=mD
mDistance=0;
void RotateTo()
if(mTarget==null) return;
Vector3 mPos1=this.transform.
Vector3 mPos2=mTarget.transform.
Vector3 mDir=(mPos2-mPos1).
float mAngle=getAngle(Vector3.up,mDir);
this.transform.eulerAngles=new Vector3(0,0,mAngle) * RotateS
private float getAngle(Vector3 v1,Vector3 v2)
float mDot=Vector3.Dot(v1,v2);
float mv1=Mathf.Sqrt(v1.x*v1.x+v1.y*v1.y+v1.z*v1.z);
float mv2=Mathf.Sqrt(v2.x*v2.x+v2.y*v2.y+v2.z*v2.z);
if(v2.x&v1.x){
return -Mathf.Acos(mDot/(mv1*mv2))* Mathf.Rad2D
return Mathf.Acos(mDot/(mv1*mv2))* Mathf.Rad2D
void Attack()
RotateTo();
if(mTarget==null) return;
mTime-=Time.deltaT
if(mTime&0){
Vector3 _angle=transform.Find("Bullet").eulerA
Vector3 _pos=new Vector3(this.transform.position.x,this.transform.position.y,-2);
Instantiate(BulletObject,_pos,Quaternion.Euler(_angle));
mTime=AttakT
void Update ()
FindEnemy();
防守单位的脚本定义在Defender这个类中,主要的行为有两个,即发现敌人后转向敌人、向敌人发射炮弹,这块的代码较为简单,大家自己去领会就好啦。我们知道在塔防游戏中玩家可以通过点击屏幕来自由地增加或移动防守单位,这部分的内容主要是和GUI相关的,因为目前博主对UGUI掌握地还不是很熟,所以就等以后博主有时间了再来补充吧!好了,这个塔防游戏的讲解教程就是这样了,希望大家能够喜欢,我知道大家等这篇下篇已经好久了,哈哈!
最后想说的是,博主的独立博客正式开始使用了,以后发表的文章会在独立博客和CSDN同时更新,希望大家能继续关注博主的博客!谢谢大家
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:800160次
积分:10348
积分:10348
排名:第1164名
原创:185篇
评论:1099条
秦元培,程序员、游戏开发者
如果觉得我的我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
(2)(5)(1)(4)(2)(5)(4)(1)(5)(4)(2)(1)(5)(4)(1)(7)(13)(1)(2)(1)(9)(8)(2)(5)(9)(23)(8)(13)(18)(7)(1)(1)(2)(1)(1)(1)(2)(1)(3)(1)京 东 价:
[定价:¥]
PLUS会员专享价
支  持:
搭配赠品:
所 在 地:北京 海淀区
服务支持:
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
Unity3D手机游戏开发(配光盘) 0 清华大学出版社 金玺曾著
加载中,请稍候...
商品介绍加载中...
扫一扫,精彩好书免费看
服务承诺:
京东平台卖家销售并发货的商品,由平台卖家提供发票和相应的售后服务。请您放心购买!
注:因厂家会在没有任何提前通知的情况下更改产品包装、产地或者一些附件,本司不能确保客户收到的货物与商城图片、产地、附件说明完全一致。只能确保为原厂正货!并且保证与当时市场上同样主流新品一致。若本商城没有及时更新,请大家谅解!
权利声明:京东上的所有商品信息、客户评价、商品咨询、网友讨论等内容,是京东重要的经营资源,未经许可,禁止非法转载使用。
注:本站商品信息均来自于合作方,其真实性、准确性和合法性由信息拥有者(合作方)负责。本站不提供任何保证,并不承担任何法律责任。
印刷版次不同,印刷时间和版次以实物为准。
价格说明:
京东价:京东价为商品的销售价,是您最终决定是否购买商品的依据。
划线价:商品展示的划横线价格为参考价,该价格可能是品牌专柜标价、商品吊牌价或由品牌供应商提供的正品零售价(如厂商指导价、建议零售价等)或该商品在京东平台上曾经展示过的销售价;由于地区、时间的差异性和市场行情波动,品牌专柜标价、商品吊牌价等可能会与您购物时展示的不一致,该价格仅供您参考。
折扣:如无特殊说明,折扣指销售商在原价、或划线价(如品牌专柜标价、商品吊牌价、厂商指导价、厂商建议零售价)等某一价格基础上计算出的优惠比例或优惠金额;如有疑问,您可在购买前联系销售商进行咨询。
异常问题:商品促销信息以商品详情页“促销”栏中的信息为准;商品的具体售价以订单结算页价格为准;如您发现活动商品售价或促销信息有异常,建议购买前先联系销售商咨询。
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
加载中,请稍候...
浏览了该商品的用户还浏览了
加载中,请稍候...
价 格: 到
   
iframe(src='///ns.html?id=GTM-T947SH', height='0', width='0', style='display: visibility:')7943人阅读
[游戏开发](79)
[编程语言](118)
[移动互联](50)
[Unity引擎](86)
&& 喜欢我的博客请记住我的名字:,我的博客地址是转载请注明出处,本文作者:, 本文出处:&&&&& 大家好,我是秦元培。我参加了CSDN2014博客之星的评选,欢迎大家为我,同时希望在新的一年里大家能继续支持我的博客! &&&& &作为2015年的第一篇博客,博主首先想要感谢各位朋友的鼓励和支持,在新的一年里,博主将努力为大家分享更多、更好的游戏开发方面的原创技术文章,希望大家能继续关注和支持博主的博客。那么,今天博主想和大家分享的是一个塔防游戏的项目案例。通常意义上讲,塔防游戏是指一类在地图上建造炮台或者类似建筑物来阻止敌人进攻的策略类游戏。从这个概念中,我们可以快速地抽离出来三个元素,即地图(场景)、敌人、炮台(防守单位)。当我们抽离出来这样三个元素后,现在塔防游戏就变成了这样的一种描述,即敌人按照地图中设计的路径进攻,玩家利用防守单位进行防守的一类策略游戏。经典的塔防游戏有哪些呢?比如我们最为熟悉的《植物大战僵尸》、《保卫萝卜》都是塔防类游戏的经典游戏。如果我们将塔防游戏中的防守单位的范围扩大到玩家,那么像《英雄联盟》这样的游戏同样是可以称之为塔防游戏的,因为敌我阵营的最终目的都是要摧毁敌方的防御塔,只是敌我双方都从炮台或者怪物变成了有血有肉的人物,加之角色扮演(RPG)和即时战略(RTS)等元素的混合渗透,使得这样的游戏从单纯的塔防游戏变成了一款可玩度极高的游戏(天啊,我居然在夸这个游戏.....)。好了,那么我们就来尝试着做出一个简单的塔防游戏吧,注意是简单的塔防游戏哦,既然塔防游戏的三个要素是地图、敌人和防守单位,那么我们就从这三个方面来着手设计这个游戏吧!在本篇文章中,我们将用到下面的知识:&Unity2D中的Sprite动画&Unity3D中的可视化辅助类Gizmos&塔防游戏中敌人按路径寻路的实现&Unity3D uGUI的初步探索&简单的AI算法&&&&& 一、地图篇&&&&地图是一个塔防游戏中玩家最为关注的地方,因为地图和敌人将直接影响到玩家的策略。如图是博主从《保卫萝卜》游戏中提取的一张游戏地图。在这张地图中我们可以清楚看到怪物进攻的路径,怪物将沿着地图中的路径向我方防守单位发起攻击。那么,在游戏中,我们该怎样确定怪物的攻击路径呢?首先我们可以对地图进行下分析,在地图中基本上基本上只有两种类型的区域,即可以放置防守单位的区域和不可放置防守单位的区域两种。由此我们可以设计出下面的结构:&using UnityE
using System.C
[SerializeField]
public class GridNode : MonoBehaviour
public enum NodeType
public NodeType GridNodeType=NodeType.CanP
可以看出,我们在GridNode类中定义了一个称为NodeType的枚举类型,这个枚举类型有两个值,CanPlace表示可以放置防守单位,CantPlace表示不可以放置防守单位。在GridNode类中只有一个NodeType类型的成员变量GridNodeType,该成员变量的默认值是CanPlace,即可以放置防守单位。那么,现在问题来了,我们找到了一种可以用来描述地图中不同区域的方法,可是这些区域在哪里呢?所以我们需要一种方法来生成这些区域。这里隆重向大家介绍Gizoms类,Gizmo是Unity中一个用于在场景视图可视化调试或辅助设置的工具类。简单的说,当我们需要在编辑器环境中实现某种可视化调试的时候,我们就可以使用Gizmo类。所以的Gizmo绘制都需要在OnDrawGizmos或OnDrawGizmosSelected函数里完成。从这两个函数的名称我们就可以看出它们的区别,OnDrawGizmos在每一帧都调用,所有在Gizmos里渲染的Gizmo都将被渲染,而OnDrawGizmosSelected仅在脚本附加的物体被选中时渲染。好了,在了解了Gizmos的基本概念和用法后,我们回到我们的游戏中。我们刚刚提到,我们需要一种方法来生成区域以便于我们可以使用GridNode类来描述每个区域的属性,那么具体怎么做呢?其实思路就是在地图上画出网格,这样网格便可以将整个地图分割成不同的区域,然后我们就可以使用GridNode来描述每个区域的属性啦。好了,下面我们来看具体的脚本:using UnityE
using System.C
public class GridMap : MonoBehaviour {
public static GridMap Instance=
public int MapSizeX;
public int MapSizeZ;
[HideInInspector]
public GameObject[] mN
[HideInInspector]
public GameObject[] mP
void Awake()
mNodes=GameObject.FindGameObjectsWithTag(&GridNode&);
mPaths=GameObject.FindGameObjectsWithTag(&PathNode&);
void DrawGrid()
Gizmos.color=Color.
for(int i=0;i&=MapSizeX;i++)
Gizmos.DrawLine(new Vector3(i,0,0),new Vector3(i,MapSizeZ,0));
for(int j=0;j&=MapSizeZ;j++)
Gizmos.DrawLine(new Vector3(0,j,0),new Vector3(MapSizeX,j,0));
void DrawColor()
if(mNodes==null)
foreach(GameObject go in mNodes)
Vector3 mPos=go.transform.
if(go.GetComponent&GridNode&()!=null){
if(go.GetComponent&GridNode&().GridNodeType==GridNode.NodeType.CanPlace){
Gizmos.color=Color.
}else if(go.GetComponent&GridNode&().GridNodeType==GridNode.NodeType.CantPlace){
Gizmos.color=Color.
Gizmos.DrawCube(mPos,new Vector3(1,1,1));
void DrawPath()
Gizmos.color=Color.
if(mPaths==null)
foreach(GameObject go in mPaths)
if(go.GetComponent&PathNode&()!=null){
PathNode node=go.GetComponent&PathNode&();
if(node.ThatNode!=null){
Gizmos.DrawLine(node.transform.position,node.ThatNode.transform.position);
void OnDrawGizmos()
DrawGrid();
DrawColor();
DrawPath();
}在这段脚本中,我们首先定义了两个int类型的变量MapSizeX,MapSizeZ,这两个变量分别用来表示需要绘制网格的大小。下面我们来重点关注OnDrawGizmos方法,在这个方法中我们定义了3个方法DrawGrid、DrawColor和DrawPath。其中DrawGrid方法负责绘制地图网格,DrawColor方法负责绘制地图区域、DrawPath方法负责绘制敌人寻路路径。我们首先来说DrawGrid,DrawGrid负责绘制地图网格,默认从原点开始绘制,要绘制网格只需要绘制交错的横线和竖线即可,这里我们使用的Gizmos类下的DrawLine方法我们首先在场景中创建一个MeshRoot的空物体,将GridMap脚本附加到该物体上,我们下面来看看绘制的效果:因为Gizmos为我们提供了可视化的调试功能,因此我们可以直接在编辑器窗口中看到实际的效果,这样我们就利用Unity绘制出了地图的网格。为了让地图的左下角和场景原点能够完全匹配,博主这里写了一个简单的工具类AutoPlace来实现地图的位置计算和调整:using UnityE
using System.C
public class AutoPlace : MonoBehaviour {
//精灵渲染器
private SpriteRenderer mR
//精灵宽度
private float mSpriteW
//精灵高度
private float mSpriteH
void Start ()
mRenderer=GetComponent&SpriteRenderer&();
//计算精灵的实际大小
mSpriteWidth=mRenderer.sprite.bounds.size.x * transform.localScale.x;
mSpriteHeight=mRenderer.sprite.bounds.size.y * transform.localScale.y;
//自动调整精灵的位置
transform.position=new Vector3(mSpriteWidth/2,mSpriteHeight/2,0);
&&&&& 好了,下面我们来继续讲解地图中区域的生成。什么是地图中的区域呢?在塔防游戏中玩家通常情况下都只能在可以放置防守单位的区域放置防守的单位,那么可以放置防守单位的这些地方就是我们接下来要来研究的区域。我们首先需要根据第一步绘制的网格,为每一个网格单元创建一个空物体NodeObject,并为该物体附加GridNode脚本,如果该物体所在的位置在地图上是可以放置防守单位,那么我们就将其GridNodeType设为CanPlace,否则就设为CantPlace。其实博主在这里是更喜欢用动态生成的方式来为每个网格单元添加区域属性的,不过这里我们为了将过程讲明白,索性就手动创建吧!哈哈,可是博主居然手动创建了96个空物体,想想都觉得醉了啊。好了,我们这里需要给每个NodeObject设置一个GridNode的Tag,这样我们可以在程序中通过Tag来获取所有的NodeObject。最后,我们将这些NodeObject全部放到MeshRoot这个节点下面,使其成为MeshRoot的子节点。下面呢,我们继续回到GridMap脚本中的DrawColor方法中,我们在脚本的Awake方法中首先获取所有的NodeObject,然后根据每一个NodeObject对象附加的GridNode脚本,来判断这个网格单元是可以放置防守单位还是不可以放置防守单位,如果可以放置防守单位就用绿色绘制一个Cube,如果不可以放置防守单位就用红色绘制一个Cube,这样我们编辑器中就可以根据颜色来区分不同的区域了。好了,我们下面来看看实际的效果:好了,现在大家可以很明确的看到整个地图中区域的分布,红色的部分为不可放置防守单位的区域,绿色的部分为可以放置防守单位区域。大家应该注意到红色的区域中有条白色的线,这条线呢其实就是敌人的寻路路径。那么好下面我们就来讲述敌人寻路路径的生成。相比网格和区域的生成,路径的生成要简单许多。因为路径只需要关注起点、终点和节点即可。具体怎么做呢,首先我们在场景中新建一个空物体命名为PathRoot,接下来我们在红色区域中分别为起点、终点和节点建立一个空物体,命名为PathNode,并设置其Tag为PathNode。&&&&&& 好了,接下来,我们再来一起看一个叫做PathNode的脚本,这个脚本的作用是描述各个路径节点的关系,类似于链表的结构:using UnityE
using System.C
public class PathNode : MonoBehaviour {
public PathNode ThisN
public PathNode ThatN
public void SetNode(PathNode _node)
if(ThatNode!=null){
ThatNode.ThisNode=
ThatNode=_
_node.ThisNode=
在这段脚本中,我们让ThisNode指向节点自身,ThatNode指向下一个节点,并提供了一个设置下一个节点的方法SetNode。现在,我们将这个脚本附加到各个PathNode上,通过编辑器可以快速地为每个节点指定ThisNode和ThatNode。那么,现在各个路径节点的关系我们已经很清楚了,接下来要做的是事情就是利用Gizmos将路径画出来,怎么画呢?从当前节点指向下一个节点就可以了。现在我们来看看DrawPath方法具体都做了什么:void DrawPath()
Gizmos.color=Color.
if(mPaths==null)
foreach(GameObject go in mPaths)
if(go.GetComponent&PathNode&()!=null){
PathNode node=go.GetComponent&PathNode&();
if(node.ThatNode!=null){
Gizmos.DrawLine(node.transform.position,node.ThatNode.transform.position);
Gizmos.DrawCube(node.transform.position,new Vector3(0.25F,0.25F,0.25F));
相信大家都明白了吧,我们首先根据Tag获取了全部的PathNode对象,然后根据PathNode脚本绘制了每个节点指向下一个节点的线段,同时为该节点绘制一个小Cube。好了,我们来看看最终的效果:&&&&&& 到现在为止,所有的关于地图的内容都讲解完了,我们来简单总结下,在这一部分,我们主要学习了可视化辅助类Gizmos在绘制网格、区域、路径等方面的应用,主要利用了DrawLine和DrawCube这两个方法。&&&&&&&好了,这个项目的内容比较多啦,因此博主决定将敌人篇、防守单位篇放在下一篇文章中来为大家讲解,因为在一篇文章中写完的话,不仅博主写起来会比较累,大家读起来会更累啊,所以今天的内容就是这样啦,希望大家喜欢啊!最后为大家送上今天的项目演示:每日箴言:痛苦也好,错误也罢,正是背负着这些,我们才能一步一步到达未知的前方,不是吗?喜欢我的博客请记住我的名字:,我的博客地址是转载请注明出处,本文作者:, 本文出处:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:800163次
积分:10348
积分:10348
排名:第1164名
原创:185篇
评论:1099条
秦元培,程序员、游戏开发者
如果觉得我的我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
(2)(5)(1)(4)(2)(5)(4)(1)(5)(4)(2)(1)(5)(4)(1)(7)(13)(1)(2)(1)(9)(8)(2)(5)(9)(23)(8)(13)(18)(7)(1)(1)(2)(1)(1)(1)(2)(1)(3)(1)}

我要回帖

更多关于 魔兽防守地图 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信