[Unity3D]Unity3D遊戲開發之(zhī)塔防遊戲項目講解(上(shàng)) - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 技術分享 >

[Unity3D]Unity3D遊戲開發之(zhī)塔防遊戲項目講解(上(shàng))

發表時(shí)間:2021-1-4

發布人(rén):融晨科技

浏覽次數:60

  
喜歡我的(de)博客請記住我的(de)名字:秦元培,我的(de)博客地(dì / de)址是(shì)blog.csdn.net/qinyuanpei
轉載請注明出(chū)處,本文作者:秦元培, 本文出(chū)處:http://blog.csdn.net/qinyuanpei/article/details/42394949
??
      大(dà)家好,我是(shì)秦元培。我參加了(le/liǎo)CSDN2014博客之(zhī)星的(de)評選,歡迎大(dà)家爲(wéi / wèi)我投票,同時(shí)希望在(zài)新的(de)一年裏大(dà)家能繼續支持我的(de)博客!
      作爲(wéi / wèi)2015年的(de)第一篇博客,博主首先想要(yào / yāo)感謝各位朋友的(de)鼓勵和(hé / huò)支持,在(zài)新的(de)一年裏,博主将努力爲(wéi / wèi)大(dà)家分享更多、更好的(de)遊戲開發方面的(de)原創技術文章,希望大(dà)家能繼續關注和(hé / huò)支持博主的(de)博客。那麽,今天博主想和(hé / huò)大(dà)家分享的(de)是(shì)一個(gè)塔防遊戲的(de)項目案例。通常意義上(shàng)講,塔防遊戲是(shì)指一類在(zài)地(dì / de)圖上(shàng)建造炮台或者類似建築物來(lái)阻止敵人(rén)進攻的(de)策略類遊戲。從這(zhè)個(gè)概念中,我們可以(yǐ)快速地(dì / de)抽離出(chū)來(lái)三個(gè)元素,即地(dì / de)圖(場景)、敵人(rén)、炮台(防守單位)。當我們抽離出(chū)來(lái)這(zhè)樣三個(gè)元素後,現在(zài)塔防遊戲就(jiù)變成了(le/liǎo)這(zhè)樣的(de)一種描述,即敵人(rén)按照地(dì / de)圖中設計的(de)路徑進攻,玩家利用防守單位進行防守的(de)一類策略遊戲。經典的(de)塔防遊戲有哪些呢?比如我們最爲(wéi / wèi)熟悉的(de)《植物大(dà)戰僵屍》、《保衛蘿蔔》都是(shì)塔防類遊戲的(de)經典遊戲。如果我們将塔防遊戲中的(de)防守單位的(de)範圍擴大(dà)到(dào)玩家,那麽像《英雄聯盟》這(zhè)樣的(de)遊戲同樣是(shì)可以(yǐ)稱之(zhī)爲(wéi / wèi)塔防遊戲的(de),因爲(wéi / wèi)敵我陣營的(de)最終目的(de)都是(shì)要(yào / yāo)摧毀敵方的(de)防禦塔,隻是(shì)敵我雙方都從炮台或者怪物變成了(le/liǎo)有血有肉的(de)人(rén)物,加之(zhī)角色扮演(RPG)和(hé / huò)即時(shí)戰略(RTS)等元素的(de)混合滲透,使得這(zhè)樣的(de)遊戲從單純的(de)塔防遊戲變成了(le/liǎo)一款可玩度極高的(de)遊戲(天啊,我居然在(zài)誇這(zhè)個(gè)遊戲.....)。好了(le/liǎo),那麽我們就(jiù)來(lái)嘗試着做出(chū)一個(gè)簡單的(de)塔防遊戲吧,注意是(shì)簡單的(de)塔防遊戲哦,既然塔防遊戲的(de)三個(gè)要(yào / yāo)素是(shì)地(dì / de)圖、敵人(rén)和(hé / huò)防守單位,那麽我們就(jiù)從這(zhè)三個(gè)方面來(lái)着手設計這(zhè)個(gè)遊戲吧!在(zài)本篇文章中,我們将用到(dào)下面的(de)知識:
  •  Unity2D中的(de)Sprite動畫
  •  Unity3D中的(de)可視化輔助類Gizmos
  •  塔防遊戲中敵人(rén)按路徑尋路的(de)實現
  •  Unity3D uGUI的(de)初步探索
  •  簡單的(de)AI算法

      一、地(dì / de)圖篇
    地(dì / de)圖是(shì)一個(gè)塔防遊戲中玩家最爲(wéi / wèi)關注的(de)地(dì / de)方,因爲(wéi / wèi)地(dì / de)圖和(hé / huò)敵人(rén)将直接影響到(dào)玩家的(de)策略。如圖是(shì)博主從《保衛蘿蔔》遊戲中提取的(de)一張遊戲地(dì / de)圖。在(zài)這(zhè)張地(dì / de)圖中我們可以(yǐ)清楚看到(dào)怪物進攻的(de)路徑,怪物将沿着地(dì / de)圖中的(de)路徑向我方防守單位發起攻擊。那麽,在(zài)遊戲中,我們該怎樣确定怪物的(de)攻擊路徑呢?首先我們可以(yǐ)對地(dì / de)圖進行下分析,在(zài)地(dì / de)圖中基本上(shàng)基本上(shàng)隻有兩種類型的(de)區域,即可以(yǐ)放置防守單位的(de)區域和(hé / huò)不(bù)可放置防守單位的(de)區域兩種。由此我們可以(yǐ)設計出(chū)下面的(de)結構:
 [img]http://img.blog.csdn.net/20150105095547785?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlueXVhbnBlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
using UnityEngine;
using System.Collections;

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

	public NodeType GridNodeType=NodeType.CanPlace;
}

可以(yǐ)看出(chū),我們在(zài)GridNode類中定義了(le/liǎo)一個(gè)稱爲(wéi / wèi)NodeType的(de)枚舉類型,這(zhè)個(gè)枚舉類型有兩個(gè)值,CanPlace表示可以(yǐ)放置防守單位,CantPlace表示不(bù)可以(yǐ)放置防守單位。在(zài)GridNode類中隻有一個(gè)NodeType類型的(de)成員變量GridNodeType,該成員變量的(de)默認值是(shì)CanPlace,即可以(yǐ)放置防守單位。那麽,現在(zài)問題來(lái)了(le/liǎo),我們找到(dào)了(le/liǎo)一種可以(yǐ)用來(lái)描述地(dì / de)圖中不(bù)同區域的(de)方法,可是(shì)這(zhè)些區域在(zài)哪裏呢?所以(yǐ)我們需要(yào / yāo)一種方法來(lái)生成這(zhè)些區域。這(zhè)裏隆重向大(dà)家介紹Gizoms類,Gizmo是(shì)Unity中一個(gè)用于(yú)在(zài)場景視圖可視化調試或輔助設置的(de)工具類。簡單的(de)說(shuō),當我們需要(yào / yāo)在(zài)編輯器環境中實現某種可視化調試的(de)時(shí)候,我們就(jiù)可以(yǐ)使用Gizmo類。所以(yǐ)的(de)Gizmo繪制都需要(yào / yāo)在(zài)OnDrawGizmos或OnDrawGizmosSelected函數裏完成。從這(zhè)兩個(gè)函數的(de)名稱我們就(jiù)可以(yǐ)看出(chū)它們的(de)區别,OnDrawGizmos在(zài)每一幀都調用,所有在(zài)Gizmos裏渲染的(de)Gizmo都将被渲染,而(ér)OnDrawGizmosSelected僅在(zài)腳本附加的(de)物體被選中時(shí)渲染。好了(le/liǎo),在(zài)了(le/liǎo)解了(le/liǎo)Gizmos的(de)基本概念和(hé / huò)用法後,我們回到(dào)我們的(de)遊戲中。我們剛剛提到(dào),我們需要(yào / yāo)一種方法來(lái)生成區域以(yǐ)便于(yú)我們可以(yǐ)使用GridNode類來(lái)描述每個(gè)區域的(de)屬性,那麽具體怎麽做呢?其實思路就(jiù)是(shì)在(zài)地(dì / de)圖上(shàng)畫出(chū)網格,這(zhè)樣網格便可以(yǐ)将整個(gè)地(dì / de)圖分割成不(bù)同的(de)區域,然後我們就(jiù)可以(yǐ)使用GridNode來(lái)描述每個(gè)區域的(de)屬性啦。好了(le/liǎo),下面我們來(lái)看具體的(de)腳本:
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();
	}


}

在(zài)這(zhè)段腳本中,我們首先定義了(le/liǎo)兩個(gè)int類型的(de)變量MapSizeX,MapSizeZ,這(zhè)兩個(gè)變量分别用來(lái)表示需要(yào / yāo)繪制網格的(de)大(dà)小。下面我們來(lái)重點關注OnDrawGizmos方法,在(zài)這(zhè)個(gè)方法中我們定義了(le/liǎo)3個(gè)方法DrawGrid、DrawColor和(hé / huò)DrawPath。其中DrawGrid方法負責繪制地(dì / de)圖網格,DrawColor方法負責繪制地(dì / de)圖區域、DrawPath方法負責繪制敵人(rén)尋路路徑。我們首先來(lái)說(shuō)DrawGrid,DrawGrid負責繪制地(dì / de)圖網格,默認從原點開始繪制,要(yào / yāo)繪制網格隻需要(yào / yāo)繪制交錯的(de)橫線和(hé / huò)豎線即可,這(zhè)裏我們使用的(de)Gizmos類下的(de)DrawLine方法我們首先在(zài)場景中創建一個(gè)MeshRoot的(de)空物體,将GridMap腳本附加到(dào)該物體上(shàng),我們下面來(lái)看看繪制的(de)效果:
[img]http://img.blog.csdn.net/20150106225424442?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlueXVhbnBlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
因爲(wéi / wèi)Gizmos爲(wéi / wèi)我們提供了(le/liǎo)可視化的(de)調試功能,因此我們可以(yǐ)直接在(zài)編輯器窗口中看到(dào)實際的(de)效果,這(zhè)樣我們就(jiù)利用Unity繪制出(chū)了(le/liǎo)地(dì / de)圖的(de)網格。爲(wéi / wèi)了(le/liǎo)讓地(dì / de)圖的(de)左下角和(hé / huò)場景原點能夠完全匹配,博主這(zhè)裏寫了(le/liǎo)一個(gè)簡單的(de)工具類AutoPlace來(lái)實現地(dì / de)圖的(de)位置計算和(hé / huò)調整:
using UnityEngine;
using System.Collections;

public class AutoPlace : MonoBehaviour {

	//精靈渲染器
	private SpriteRenderer mRenderer;
	//精靈寬度
	private float mSpriteWidth;
	//精靈高度
	private float mSpriteHeight;


	void Start () 
	{
		mRenderer=GetComponent<SpriteRenderer>();
		//計算精靈的(de)實際大(dà)小
		mSpriteWidth=mRenderer.sprite.bounds.size.x * transform.localScale.x;
		mSpriteHeight=mRenderer.sprite.bounds.size.y * transform.localScale.y;
		//自動調整精靈的(de)位置
		transform.position=new Vector3(mSpriteWidth/2,mSpriteHeight/2,0);
	}
}

      好了(le/liǎo),下面我們來(lái)繼續講解地(dì / de)圖中區域的(de)生成。什麽是(shì)地(dì / de)圖中的(de)區域呢?在(zài)塔防遊戲中玩家通常情況下都隻能在(zài)可以(yǐ)放置防守單位的(de)區域放置防守的(de)單位,那麽可以(yǐ)放置防守單位的(de)這(zhè)些地(dì / de)方就(jiù)是(shì)我們接下來(lái)要(yào / yāo)來(lái)研究的(de)區域。我們首先需要(yào / yāo)根據第一步繪制的(de)網格,爲(wéi / wèi)每一個(gè)網格單元創建一個(gè)空物體NodeObject,并爲(wéi / wèi)該物體附加GridNode腳本,如果該物體所在(zài)的(de)位置在(zài)地(dì / de)圖上(shàng)是(shì)可以(yǐ)放置防守單位,那麽我們就(jiù)将其GridNodeType設爲(wéi / wèi)CanPlace,否則就(jiù)設爲(wéi / wèi)CantPlace。其實博主在(zài)這(zhè)裏是(shì)更喜歡用動态生成的(de)方式來(lái)爲(wéi / wèi)每個(gè)網格單元添加區域屬性的(de),不(bù)過這(zhè)裏我們爲(wéi / wèi)了(le/liǎo)将過程講明白,索性就(jiù)手動創建吧!哈哈,可是(shì)博主居然手動創建了(le/liǎo)96個(gè)空物體,想想都覺得醉了(le/liǎo)啊。好了(le/liǎo),我們這(zhè)裏需要(yào / yāo)給每個(gè)NodeObject設置一個(gè)GridNode的(de)Tag,這(zhè)樣我們可以(yǐ)在(zài)程序中通過Tag來(lái)獲取所有的(de)NodeObject。最後,我們将這(zhè)些NodeObject全部放到(dào)MeshRoot這(zhè)個(gè)節點下面,使其成爲(wéi / wèi)MeshRoot的(de)子(zǐ)節點。下面呢,我們繼續回到(dào)GridMap腳本中的(de)DrawColor方法中,我們在(zài)腳本的(de)Awake方法中首先獲取所有的(de)NodeObject,然後根據每一個(gè)NodeObject對象附加的(de)GridNode腳本,來(lái)判斷這(zhè)個(gè)網格單元是(shì)可以(yǐ)放置防守單位還是(shì)不(bù)可以(yǐ)放置防守單位,如果可以(yǐ)放置防守單位就(jiù)用綠色繪制一個(gè)Cube,如果不(bù)可以(yǐ)放置防守單位就(jiù)用紅色繪制一個(gè)Cube,這(zhè)樣我們編輯器中就(jiù)可以(yǐ)根據顔色來(lái)區分不(bù)同的(de)區域了(le/liǎo)。好了(le/liǎo),我們下面來(lái)看看實際的(de)效果:
[img]http://img.blog.csdn.net/20150106233924796?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlueXVhbnBlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
好了(le/liǎo),現在(zài)大(dà)家可以(yǐ)很明确的(de)看到(dào)整個(gè)地(dì / de)圖中區域的(de)分布,紅色的(de)部分爲(wéi / wèi)不(bù)可放置防守單位的(de)區域,綠色的(de)部分爲(wéi / wèi)可以(yǐ)放置防守單位區域。大(dà)家應該注意到(dào)紅色的(de)區域中有條白色的(de)線,這(zhè)條線呢其實就(jiù)是(shì)敵人(rén)的(de)尋路路徑。那麽好下面我們就(jiù)來(lái)講述敵人(rén)尋路路徑的(de)生成。相比網格和(hé / huò)區域的(de)生成,路徑的(de)生成要(yào / yāo)簡單許多。因爲(wéi / wèi)路徑隻需要(yào / yāo)關注起點、終點和(hé / huò)節點即可。具體怎麽做呢,首先我們在(zài)場景中新建一個(gè)空物體命名爲(wéi / wèi)PathRoot,接下來(lái)我們在(zài)紅色區域中分别爲(wéi / wèi)起點、終點和(hé / huò)節點建立一個(gè)空物體,命名爲(wéi / wèi)PathNode,并設置其Tag爲(wéi / wèi)PathNode。
       好了(le/liǎo),接下來(lái),我們再來(lái)一起看一個(gè)叫做PathNode的(de)腳本,這(zhè)個(gè)腳本的(de)作用是(shì)描述各個(gè)路徑節點的(de)關系,類似于(yú)鏈表的(de)結構:
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;
		}

	}

}

在(zài)這(zhè)段腳本中,我們讓ThisNode指向節點自身,ThatNode指向下一個(gè)節點,并提供了(le/liǎo)一個(gè)設置下一個(gè)節點的(de)方法SetNode。現在(zài),我們将這(zhè)個(gè)腳本附加到(dào)各個(gè)PathNode上(shàng),通過編輯器可以(yǐ)快速地(dì / de)爲(wéi / wèi)每個(gè)節點指定ThisNode和(hé / huò)ThatNode。那麽,現在(zài)各個(gè)路徑節點的(de)關系我們已經很清楚了(le/liǎo),接下來(lái)要(yào / yāo)做的(de)是(shì)事情就(jiù)是(shì)利用Gizmos将路徑畫出(chū)來(lái),怎麽畫呢?從當前節點指向下一個(gè)節點就(jiù)可以(yǐ)了(le/liǎo)。現在(zài)我們來(lái)看看DrawPath方法具體都做了(le/liǎo)什麽:
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));
			}
		}
	}

相信大(dà)家都明白了(le/liǎo)吧,我們首先根據Tag獲取了(le/liǎo)全部的(de)PathNode對象,然後根據PathNode腳本繪制了(le/liǎo)每個(gè)節點指向下一個(gè)節點的(de)線段,同時(shí)爲(wéi / wèi)該節點繪制一個(gè)小Cube。好了(le/liǎo),我們來(lái)看看最終的(de)效果:
[img]http://img.blog.csdn.net/20150107001554894?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlueXVhbnBlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
       到(dào)現在(zài)爲(wéi / wèi)止,所有的(de)關于(yú)地(dì / de)圖的(de)内容都講解完了(le/liǎo),我們來(lái)簡單總結下,在(zài)這(zhè)一部分,我們主要(yào / yāo)學習了(le/liǎo)可視化輔助類Gizmos在(zài)繪制網格、區域、路徑等方面的(de)應用,主要(yào / yāo)利用了(le/liǎo)DrawLine和(hé / huò)DrawCube這(zhè)兩個(gè)方法。
       好了(le/liǎo),這(zhè)個(gè)項目的(de)内容比較多啦,因此博主決定将敵人(rén)篇、防守單位篇放在(zài)下一篇文章中來(lái)爲(wéi / wèi)大(dà)家講解,因爲(wéi / wèi)在(zài)一篇文章中寫完的(de)話,不(bù)僅博主寫起來(lái)會比較累,大(dà)家讀起來(lái)會更累啊,所以(yǐ)今天的(de)内容就(jiù)是(shì)這(zhè)樣啦,希望大(dà)家喜歡啊!最後爲(wéi / wèi)大(dà)家送上(shàng)今天的(de)項目演示:
[img]http://img.blog.csdn.net/20150107003715142
每日箴言:痛苦也(yě)好,錯誤也(yě)罷,正是(shì)背負着這(zhè)些,我們才能一步一步到(dào)達未知的(de)前方,不(bù)是(shì)嗎?


[img]http://img.blog.csdn.net/20150107005423776?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWlueXVhbnBlaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

喜歡我的(de)博客請記住我的(de)名字:秦元培,我的(de)博客地(dì / de)址是(shì)blog.csdn.net/qinyuanpei
轉載請注明出(chū)處,本文作者:秦元培, 本文出(chū)處:http://blog.csdn.net/qinyuanpei/article/details/42394949??
??
?
??

相關案例查看更多