[Unity3D]Unity3D游戏开发之塔防游戏项目讲解(上)

喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei

转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/42394949

??

大家好,我是秦元培。我参加了CSDN2014博客之星的评选,欢迎大家为我投票,同时希望在新的一年里大家能继续支持我的博客!

作为2015年的第一篇博客,博主首先想要感谢各位朋友的鼓励和支持,在新的一年里,博主将努力为大家分享更多、更好的游戏开发方面的原创技术文章,希望大家能继续关注和支持博主的博客。那么,今天博主想和大家分享的是一个塔防游戏的项目案例。通常意义上讲,塔防游戏是指一类在地图上建造炮台或者类似建筑物来阻止敌人进攻的策略类游戏。从这个概念中,我们可以快速地抽离出来三个元素,即地图(场景)、敌人、炮台(防守单位)。当我们抽离出来这样三个元素后,现在塔防游戏就变成了这样的一种描述,即敌人按照地图中设计的路径进攻,玩家利用防守单位进行防守的一类策略游戏。经典的塔防游戏有哪些呢?比如我们最为熟悉的《植物大战僵尸》、《保卫萝卜》都是塔防类游戏的经典游戏。如果我们将塔防游戏中的防守单位的范围扩大到玩家,那么像《英雄联盟》这样的游戏同样是可以称之为塔防游戏的,因为敌我阵营的最终目的都是要摧毁敌方的防御塔,只是敌我双方都从炮台或者怪物变成了有血有肉的人物,加之角色扮演(RPG)和即时战略(RTS)等元素的混合渗透,使得这样的游戏从单纯的塔防游戏变成了一款可玩度极高的游戏(天啊,我居然在夸这个游戏.....)。好了,那么我们就来尝试着做出一个简单的塔防游戏吧,注意是简单的塔防游戏哦,既然塔防游戏的三个要素是地图、敌人和防守单位,那么我们就从这三个方面来着手设计这个游戏吧!在本篇文章中,我们将用到下面的知识:

  • Unity2D中的Sprite动画
  • Unity3D中的可视化辅助类Gizmos
  • 塔防游戏中敌人按路径寻路的实现
  • Unity3D uGUI的初步探索
  • 简单的AI算法

一、地图篇

地图是一个塔防游戏中玩家最为关注的地方,因为地图和敌人将直接影响到玩家的策略。如图是博主从《保卫萝卜》游戏中提取的一张游戏地图。在这张地图中我们可以清楚看到怪物进攻的路径,怪物将沿着地图中的路径向我方防守单位发起攻击。那么,在游戏中,我们该怎样确定怪物的攻击路径呢?首先我们可以对地图进行下分析,在地图中基本上基本上只有两种类型的区域,即可以放置防守单位的区域和不可放置防守单位的区域两种。由此我们可以设计出下面的结构:

using UnityEngine;
using System.Collections;

[SerializeField]
public class GridNode : MonoBehaviour
{
	public enum NodeType
	{
		CanPlace,
		CantPlace
	}

	public NodeType GridNodeType=NodeType.CanPlace;
}

可以看出,我们在GridNode类中定义了一个称为NodeType的枚举类型,这个枚举类型有两个值,CanPlace表示可以放置防守单位,CantPlace表示不可以放置防守单位。在GridNode类中只有一个NodeType类型的成员变量GridNodeType,该成员变量的默认值是CanPlace,即可以放置防守单位。那么,现在问题来了,我们找到了一种可以用来描述地图中不同区域的方法,可是这些区域在哪里呢?所以我们需要一种方法来生成这些区域。这里隆重向大家介绍Gizoms类,Gizmo是Unity中一个用于在场景视图可视化调试或辅助设置的工具类。简单的说,当我们需要在编辑器环境中实现某种可视化调试的时候,我们就可以使用Gizmo类。所以的Gizmo绘制都需要在OnDrawGizmos或OnDrawGizmosSelected函数里完成。从这两个函数的名称我们就可以看出它们的区别,OnDrawGizmos在每一帧都调用,所有在Gizmos里渲染的Gizmo都将被渲染,而OnDrawGizmosSelected仅在脚本附加的物体被选中时渲染。好了,在了解了Gizmos的基本概念和用法后,我们回到我们的游戏中。我们刚刚提到,我们需要一种方法来生成区域以便于我们可以使用GridNode类来描述每个区域的属性,那么具体怎么做呢?其实思路就是在地图上画出网格,这样网格便可以将整个地图分割成不同的区域,然后我们就可以使用GridNode来描述每个区域的属性啦。好了,下面我们来看具体的脚本:

using UnityEngine;
using System.Collections;

public class GridMap : MonoBehaviour {

	public static GridMap Instance=null;

	public int MapSizeX;
	public int MapSizeZ;

	[HideInInspector]
	public GameObject[] mNodes;
	[HideInInspector]
	public GameObject[] mPaths;

	void Awake()
	{
		Instance=this;
		mNodes=GameObject.FindGameObjectsWithTag("GridNode");
		mPaths=GameObject.FindGameObjectsWithTag("PathNode");
	}

	void DrawGrid()
	{
		Gizmos.color=Color.blue;
		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) return;
		foreach(GameObject go in mNodes)
		{
			Vector3 mPos=go.transform.position;
			if(go.GetComponent<GridNode>()!=null){
				if(go.GetComponent<GridNode>().GridNodeType==GridNode.NodeType.CanPlace){
					Gizmos.color=Color.green;
				}else if(go.GetComponent<GridNode>().GridNodeType==GridNode.NodeType.CantPlace){
					Gizmos.color=Color.red;
				}
				Gizmos.DrawCube(mPos,new Vector3(1,1,1));
			}
		}
	}

	void DrawPath()
	{
		Gizmos.color=Color.white;
		if(mPaths==null) return;
		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 UnityEngine;
using System.Collections;

public class AutoPlace : MonoBehaviour {

	//精灵渲染器
	private SpriteRenderer mRenderer;
	//精灵宽度
	private float mSpriteWidth;
	//精灵高度
	private float mSpriteHeight;

	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 UnityEngine;
using System.Collections;

public class PathNode : MonoBehaviour {

	public PathNode ThisNode;
	public PathNode ThatNode;

	public void SetNode(PathNode _node)
	{
		if(ThatNode!=null){
			ThatNode.ThisNode=null;
			ThatNode=_node;
			_node.ThisNode=this;
		}

	}

}

在这段脚本中,我们让ThisNode指向节点自身,ThatNode指向下一个节点,并提供了一个设置下一个节点的方法SetNode。现在,我们将这个脚本附加到各个PathNode上,通过编辑器可以快速地为每个节点指定ThisNode和ThatNode。那么,现在各个路径节点的关系我们已经很清楚了,接下来要做的是事情就是利用Gizmos将路径画出来,怎么画呢?从当前节点指向下一个节点就可以了。现在我们来看看DrawPath方法具体都做了什么:

void DrawPath()
	{
		Gizmos.color=Color.white;
		if(mPaths==null) return;
		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这两个方法。

好了,这个项目的内容比较多啦,因此博主决定将敌人篇、防守单位篇放在下一篇文章中来为大家讲解,因为在一篇文章中写完的话,不仅博主写起来会比较累,大家读起来会更累啊,所以今天的内容就是这样啦,希望大家喜欢啊!最后为大家送上今天的项目演示:

每日箴言:痛苦也好,错误也罢,正是背负着这些,我们才能一步一步到达未知的前方,不是吗?

喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei

转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/42394949??

??

?

??

时间: 2024-12-24 12:11:05

[Unity3D]Unity3D游戏开发之塔防游戏项目讲解(上)的相关文章

关于《Unity3D/2D游戏开发从0到1》书籍再版说明

关于<Unity3D/2D游戏开发从0到1>第一版本在2015年7月1日全国发行,累计得到不少国内高校教师.培训机构的好评.但是由于Unity官方对于技术不断的升级与版本的快速迭代,基于Unity4.6版本的教学知识体系,则略显滞后.    随着2017年7月份,Unity2017.1正式版本的推出,<Unity3D/2D游戏开发从0到1>书籍的再版,则正式提上日程. 目前2017年8月份再版工作已经进行过半,现在给各位小伙伴汇报一下工作,希望得到更好的建议.现在说说第二版本的总体

《Unity3D/2D游戏开发从0到1》正式出版发行啦

书籍信息:   书籍的名称: <Unity3D/2D 游戏开发从0到1>   书号(ISBN): 978-7-121-26239-5    出版社: 电子工业出版社   发行时间:2015年7月1日 写作背景:    2015年6月30日我收到电子工业出版社张迪老师寄来,正式发行的<Unity3D/2D游戏开发从0到1>书籍.这本凝结着大半年心血的作品让我感慨万千.   本人从事游戏.软件与教学十多年,一直梦想可以进一步服务于全国广大的游戏与软件从业开发人员.14年下半年电子工业出

《Unity3D/2D游戏开发从0到1》正式出版发行

去年个人编写的Unity书籍正式在2015年7月正式发行,现在补充介绍一下个人著作.书籍信息: 书籍的名称: <Unity3D/2D 游戏开发从0到1> 书号(ISBN): 978-7-121-26239-5 出版社: 电子工业出版社 发行时间:2015年7月1日 写作背景: 2015年6月30日我收到电子工业出版社张迪老师寄来,正式发行的<Unity3D/2D游戏开发从0到1>书籍.这本凝结着大半年心血的作品让我感慨万千. 本人从事游戏.软件与教学十多年,一直梦想可以进一步服务于

Unity3D 入门 游戏开发 Unity3D portal game development

Unity3D 入门 游戏开发 Unity3D portal game development 作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:[email protected] E-mail: 313134555 @qq.com 视频学习链接:Video learning link: [教学视频]深入浅出Unity3D--第一篇-何韬-CSDN学院-在线学习教程 u3d 4.6 c#  性能上 比 java脚本 好一些 C # is better than a Java

Unity3D ARPG游戏开发《最初的幻想》之第一张地图与跳跃的改善详解

前面我们终于实现了人物动画.相机控制及昼夜系统,现在继续吧,为了方便继续编写的敌人之类的脚本,所以打算今天先把第一张地图画个大概.结果,画了我好久的地图….. ≡(▔﹏▔)≡ 我对美术方面的天赋简直是惨不忍睹了…..加之地图中途画的差不多的时候Unity又崩溃了一次….忘了保存(ㄒoㄒ).于是又忙活半天….这个地图完全靠临时发挥了,第一张地图我画了好几次了,基本上每次画出来的地形之类的都完全不一样,所以就不多解释了,就上一张大概的场景图吧: 看起来好简单吧?唉……开启编辑器,这时侯如果在场景中转

Unity3D ARPG游戏开发《最初的幻想》之强化敌人AI,折腾的GUI

 昨天搞了死亡的完善与提示栏的工作.今天的话,首先再升级一下敌人的Ai吧.毕竟在玩家在敌人身后使劲攻击的话,这敌人也都不会有反应的,太不合理了. 打开Enemy脚本,在最前面"变量"标签中,先定义一个计数器,因为必须考虑到被同类"误伤"的情况: private int m_attackMeCount=0;//被一个同类对象攻击的次数 然后再在最下面的WaitForAttack函数中将自己的对象传入自己的攻击对象,以作判断: m_damageTarget.m_ch

Unity3D/2D游戏开发从0到1

这篇是计算机中Oracle类的优质预售推荐>>>><Unity3D/2D游戏开发从0到1> Unity3D/2D游戏开发从0到1(含DVD光盘1张) 采用 "案例化"教学思路, 以个人长期线下培训讲义为蓝本:讲解透彻.循序渐进.突出与优化游戏开发实战技巧:附有全国Unity游戏研发职位笔试面试真题集锦.Unity开发常见错误与分析.游戏开发职位简历模板 编辑推荐 按照"案例化"教学特点,全书贯穿两个重量级游戏的开发全过程讲解,让学

Unity3D ARPG游戏开发《最初的幻想》之GUI背包系统实现

昨天做了角色暴击…GUI浮动提示栏.现在的话,终于该继续前天被残忍打断的物品栏之类的GUI界面了! 首先还是把“属性”.“物品”.“技能”.“系统设置”几个按钮放上去罢. 我是这样设计的: 美工方面确实不是俺的长处,资源也都是用的DaikonGUI自带的. 新建一个Panel,用于盛放这几个按钮,Anchor设置为Right和Bottom即可. 然后再将这个Panel命名为“MyBottomBar”,在这个Panel中新建四个按钮,放上相应的图标即可.如果为了好看点,还可以设置Havor状态的显

Unity3D独立游戏开发日记(一):动态生成树木

目前写的独立游戏是一个沙盒类型的游戏.游戏DEMO视频如下: 提到沙盒类型的游戏,就有人给出了这样的定义: 游戏世界离现实世界越近,自由度.随机度越高才叫沙盒游戏.所谓自由度,就是你在游戏里想干啥就干啥,想开车就开车,想走路就走路.想盖房子就盖房子,没有城管来找你麻烦.那么随机度,就是每天发生的事情不能一样,做的任务也不会就一条线路可走. 在我的沙盒游戏里,地形上的树木,岩石等都是随机生成的,这样不同的人玩的地图都会不一样.当然如果最后能做到地形也随机生成那就更完美了. 下面我就讲下树木随机生成