3D游戏制作

                                                          结对编程--使用unity3D制作一个3D游戏

                    组员:胡梦琪  2012211486     周丽丽  2012211558

       软件工程老师布置了一个结对编程任务,两人合作编写一个3D游戏。我们两人对这方面都不是很擅长,并且没有什么头绪。我咨询了我们以前班上的同学,了解了可以使用unity3D来进行3D游戏的制作,于是决定使用这个软件来进行编写。其中看视频学习如何使用这个软件和查找资料花费了我们很多时间,并且,我们也没有去独立做过一个游戏,过程对我们来说挺痛苦的,但最终还是完成了,可能这个游戏不是很好,也挺简单,但是,我们确实尽力去做了,并且在截止时间之前才弄好,写了这篇博客。



一、任务分配

1.两人首先确定好要做什么游戏,要用什么软件

2.根据确定下来的结果,一个人着重准备游戏的算法和相应的设计,一个人着重学习使用unity3D

3.开始编程,一个人敲代码,另外一人口述,并能及时发现程序的错误和不足,及时改正


二、准备阶段:

    控件的安放与编码
    
    平台及使用材质包:
    Unity3d  ,Tower Defense Toolkit
    
    
    图形绘制:
    https://www.gliffy.com/




三、 设计分析


       结构图如下





四、 具体的步骤
    1.首先建立基础平面,并在上面铺设小型平面以做到放置防御塔。(使用 )。小技巧:可以使用父元素来存放子元素以达到一次铺设多个平面的目的。
                                              
平面铺设好的样子
    2.路径()的设置,首先要做的是先设置起始点和终点,中间的转弯点也需要设定,重要的是,要标清楚转弯方向。弄好管道以后再在上面加一个箭头特效。(实质上是一张贴图,根据时间的变化,他会改变坐标。)



                                              
                                                                                      选中的管道就是路径
     3.左边ui的设置,使用(),这个可以直接在旁边添加文字,然后使用自带的脚本动态修改里面的值。这里也要做塔的生成,一起集成在了ui里面。
                                                        
           UI界面
脚本代码:
using UnityEngine;
using System.Collections;

public class GameMessage : MonoBehaviour {

	public GUIText displayText;
	[HideInInspector] public GUIText guiText2;
	
	public static GameMessage gameMessage;
	
	private bool displayed=false;
	private float timeDisplay;
	
	private bool displayed2=false;
	private float timeDisplay2;
	
	private bool init=false;
	private GameObject messageObj;
	
	void Awake () {
		gameMessage=this;
		messageObj=gameObject;
		Init();
	}
	
	// Update is called once per frame
	void Update () {
	
	}
	
	static public void Init(){
		//Debug.Log("init");
		
		if(gameMessage==null){
			GameObject obj=new GameObject();
			obj.name="GameMessage";
			gameMessage=obj.AddComponent<GameMessage>();
			
			gameMessage.messageObj=obj;
		}
		
		gameMessage.init=true;
		
		if(gameMessage.displayText==null){
			GameObject obj=new GameObject();
			obj.name="guiText1";
			
			Transform t=obj.transform;
			t.parent=gameMessage.messageObj.transform;
			t.position=new Vector3(1-(10f/Screen.width), 0, 1);
			
			gameMessage.displayText=obj.AddComponent<GUIText>();
			
			gameMessage.displayText.alignment = TextAlignment.Right;
			gameMessage.displayText.anchor = TextAnchor.LowerRight;
		}
		
		if(gameMessage.guiText2==null){
//			GameObject obj=new GameObject();
//			obj.name="guiText2";
//			
//			Transform t=obj.transform;
//			t.parent=gameMessage.messageObj.transform;
//			t.position=new Vector3(1-(10f/Screen.width), 0, 1);
//			
//			gameMessage.guiText2=obj.AddComponent<GUIText>();
//			
//			gameMessage.guiText2.alignment = TextAlignment.Right;
//			gameMessage.guiText2.anchor = TextAnchor.LowerRight;
		}
	}
	
	static public void DisplayMessage(string str){
		if(!gameMessage.init){
			GameObject obj=new GameObject();
			obj.name="GameMessage";
			gameMessage=obj.AddComponent<GameMessage>();
			gameMessage.messageObj=obj;
		
			Init();
		}
		
		gameMessage.DisplayMsg(str);
	}
	
	void DisplayMsg(string str){
		timeDisplay=Time.time;
		displayText.text=displayText.text+str+"\n";
		if(!displayed){
			displayed=true;
			StartCoroutine(DisplayRoutine());
		}
	}
	
	IEnumerator DisplayRoutine(){
		while(Time.time-timeDisplay<3){
			yield return null;
		}
		displayed=false;
		displayText.text="";
	}
	
	static public void DisplayMessage2(string str){
		//if(gameMessage==null) Init();
		gameMessage.DisplayMsg2(str);
	}
	
	void DisplayMsg2(string str){
		timeDisplay2=Time.time;
		guiText2.text=str;
		if(!displayed2){
			displayed2=true;
			StartCoroutine(DisplayRoutine2());
		}
	}
	
	IEnumerator DisplayRoutine2(){
		while(Time.time-timeDisplay2<2){
			yield return null;
		}
		displayed2=false;
		guiText2.text="";
	}
	
}

4.敌人生产是通过一个叫生产器的东西制作的,这个脚本在项目的脚本库里面。由于我设置两个路径,所以要添加两个生产器。
脚本代码:
using UnityEngine;
using System.Collections;

public class BuildManager : MonoBehaviour {

	public UnitTower[] towers;
	
	static private float _gridSize=0;
	public float gridSize=2;
	public Transform[] platforms;
	private Platform[] buildPlatforms;
	
	public bool AutoAdjstTextureToGrid=true;
	
	static public BuildManager buildManager;
	
	static private BuildableInfo currentBuildInfo;
	
	static private int towerCount=0;
	
	public static int PrePlaceTower(){
		return towerCount+=1;
	}
	public static int GetTowerCount(){
		return towerCount;
	}
	
	void Awake(){
		buildManager=this;
		
		foreach(UnitTower tower in towers){
			tower.thisObj=tower.gameObject;
		}
		
		towerCount=0;
		
		gridSize=Mathf.Clamp(gridSize, 0.5f, 3.0f);
		_gridSize=gridSize;
		
		InitPlatform();
	}
	

	// Use this for initialization
	void InitPlatform() {

		buildPlatforms=new Platform[platforms.Length];
		
		int i=0;
		foreach(Transform basePlane in platforms){
			//if the platform object havent got a platform componet on it, assign it
			Platform platform=basePlane.gameObject.GetComponent<Platform>();
			
			if(platform==null){
				platform=basePlane.gameObject.AddComponent<Platform>();
				platform.buildableType=new _TowerType[6];
				
				//by default, all tower type is builidable
				platform.buildableType[0]=_TowerType.TurretTower;
				platform.buildableType[1]=_TowerType.AOETower;
				platform.buildableType[2]=_TowerType.DirectionalAOETower;
				platform.buildableType[3]=_TowerType.SupportTower;
				platform.buildableType[4]=_TowerType.ResourceTower;
				platform.buildableType[5]=_TowerType.Mine;
			}
			
			buildPlatforms[i]=platform;
			
			//make sure the plane is perfectly horizontal, rotation around the y-axis is presreved
			basePlane.eulerAngles=new Vector3(0, basePlane.rotation.eulerAngles.y, 0);
			
			//adjusting the scale
			float scaleX=Mathf.Floor(basePlane.localScale.x*10/gridSize)*gridSize*0.1f;
			float scaleZ=Mathf.Floor(basePlane.localScale.z*10/gridSize)*gridSize*0.1f;
			
			if(scaleX==0) scaleX=gridSize*0.1f;
			if(scaleZ==0) scaleZ=gridSize*0.1f;
			
			basePlane.localScale=new Vector3(scaleX, 1, scaleZ);
			
			//adjusting the texture
			if(AutoAdjstTextureToGrid){
				Material mat=basePlane.renderer.material;
				
				float x=(basePlane.localScale.x*10f)/gridSize;
				float z=(basePlane.localScale.z*10f)/gridSize;
				
				mat.mainTextureOffset=new Vector2(0.5f, 0.5f);
				mat.mainTextureScale=new Vector2(x, z);
			}
			
			//get the platform component, if any
			//Platform p=basePlane.gameObject.GetComponent<Platform>();
			//buildPlatforms[i]=new BuildPlatform(basePlane, p);
			i++;
		}

	}
	
	private static GameObject indicator;
	private static GameObject indicator2;
	
	void Start(){
		indicator=GameObject.CreatePrimitive(PrimitiveType.Cube);
		indicator.name="indicator";
		indicator.active=false;
		indicator.transform.localScale=new Vector3(gridSize, 0.025f, gridSize);
		indicator.transform.renderer.material=(Material)Resources.Load("IndicatorSquare");
		
		indicator2=GameObject.CreatePrimitive(PrimitiveType.Cube);
		indicator2.name="indicator2";
		indicator2.active=false;
		indicator2.transform.localScale=new Vector3(gridSize, 0.025f, gridSize);
		indicator2.transform.renderer.material=(Material)Resources.Load("IndicatorSquare");
		
		Destroy(indicator.collider);
		Destroy(indicator2.collider);
	}
	
	// Update is called once per frame
	void Update () {
		
	}
	
	static public void ClearBuildPoint(){
		currentBuildInfo=null;
		ClearIndicator();
	}
	
	static public void ClearIndicator(){
		if(indicator!=null) indicator.active=false;
	}
	
	//called to set indicator to a particular node, set the color as well
	//not iOS performance friendly
	static public void SetIndicator(Vector3 pointer){
		
		LayerMask mask=1<<LayerManager.LayerPlatform();
		Ray ray = Camera.main.ScreenPointToRay(pointer);
		RaycastHit hit;
		if(Physics.Raycast(ray, out hit, Mathf.Infinity, mask)){
			
			for(int i=0; i<buildManager.buildPlatforms.Length; i++){
				
				Transform basePlane=buildManager.buildPlatforms[i].thisT;
				if(hit.transform==basePlane){
					
					//calculating the build center point base on the input position
					
					//check if the row count is odd or even number
					float remainderX=basePlane.localScale.x*10/_gridSize%2;
					float remainderZ=basePlane.localScale.z*10/_gridSize%2;
					
					//get the rotation offset of the plane
					Quaternion rot=Quaternion.LookRotation(hit.point-basePlane.position);
					
					//get the x and z distance from the centre of the plane in the baseplane orientation
					//from this point on all x and z will be in reference to the basePlane orientation
					float dist=Vector3.Distance(hit.point, basePlane.position);
					float distX=Mathf.Sin((rot.eulerAngles.y-basePlane.rotation.eulerAngles.y)*Mathf.Deg2Rad)*dist;
					float distZ=Mathf.Cos((rot.eulerAngles.y-basePlane.rotation.eulerAngles.y)*Mathf.Deg2Rad)*dist;
					
					//get the sign (1/-1) of the x and y direction
					float signX=distX/Mathf.Abs(distX);
					float signZ=distZ/Mathf.Abs(distZ);
					
					//calculate the tile number selected in z and z direction
					float numX=Mathf.Round((distX+(remainderX-1)*(signX*_gridSize/2))/_gridSize);
					float numZ=Mathf.Round((distZ+(remainderZ-1)*(signZ*_gridSize/2))/_gridSize);
					
					//calculate offset in x-axis, 
					float offsetX=-(remainderX-1)*signX*_gridSize/2;
					float offsetZ=-(remainderZ-1)*signZ*_gridSize/2;
					
					//get the pos and apply the offset
					Vector3 p=basePlane.TransformDirection(new Vector3(numX, 0, numZ)*_gridSize);
					p+=basePlane.TransformDirection(new Vector3(offsetX, 0, offsetZ));
					
					//set the position;
					Vector3 pos=p+basePlane.position;
					
					
					indicator2.active=true;
		
					indicator2.transform.position=pos;
					indicator2.transform.rotation=basePlane.rotation;
					
					Collider[] cols=Physics.OverlapSphere(pos, _gridSize/2*0.9f, ~mask);
					if(cols.Length>0){
						indicator2.renderer.material.SetColor("_TintColor", Color.red);
					}
					else{
						indicator2.renderer.material.SetColor("_TintColor", Color.green);
					}
				}
			}
		}
		else indicator2.active=false;
	}
	
	
	static public bool CheckBuildPoint(Vector3 pointer){
		
		//if(currentBuildInfo!=null) return false;
		
		BuildableInfo buildableInfo=new BuildableInfo();
		
		LayerMask mask=1<<LayerManager.LayerPlatform();
		Ray ray = Camera.main.ScreenPointToRay(pointer);
		RaycastHit hit;
		if(Physics.Raycast(ray, out hit, Mathf.Infinity, mask)){
			
			for(int i=0; i<buildManager.buildPlatforms.Length; i++){
				
				Transform basePlane=buildManager.buildPlatforms[i].thisT;
				if(hit.transform==basePlane){
					
					//calculating the build center point base on the input position
					
					//check if the row count is odd or even number
					float remainderX=basePlane.localScale.x*10/_gridSize%2;
					float remainderZ=basePlane.localScale.z*10/_gridSize%2;
					
					//get the rotation offset of the plane
					Quaternion rot=Quaternion.LookRotation(hit.point-basePlane.position);
					
					//get the x and z distance from the centre of the plane in the baseplane orientation
					//from this point on all x and z will be in reference to the basePlane orientation
					float dist=Vector3.Distance(hit.point, basePlane.position);
					float distX=Mathf.Sin((rot.eulerAngles.y-basePlane.rotation.eulerAngles.y)*Mathf.Deg2Rad)*dist;
					float distZ=Mathf.Cos((rot.eulerAngles.y-basePlane.rotation.eulerAngles.y)*Mathf.Deg2Rad)*dist;
					
					//get the sign (1/-1) of the x and y direction
					float signX=distX/Mathf.Abs(distX);
					float signZ=distZ/Mathf.Abs(distZ);
					
					//calculate the tile number selected in z and z direction
					float numX=Mathf.Round((distX+(remainderX-1)*(signX*_gridSize/2))/_gridSize);
					float numZ=Mathf.Round((distZ+(remainderZ-1)*(signZ*_gridSize/2))/_gridSize);
					
					//calculate offset in x-axis, 
					float offsetX=-(remainderX-1)*signX*_gridSize/2;
					float offsetZ=-(remainderZ-1)*signZ*_gridSize/2;
					
					//get the pos and apply the offset
					Vector3 p=basePlane.TransformDirection(new Vector3(numX, 0, numZ)*_gridSize);
					p+=basePlane.TransformDirection(new Vector3(offsetX, 0, offsetZ));
					
					//set the position;
					Vector3 pos=p+basePlane.position;
					
					//check if the position is blocked, by any other obstabcle other than the baseplane itself
					Collider[] cols=Physics.OverlapSphere(pos, _gridSize/2*0.9f, ~mask);
					if(cols.Length>0){
						//Debug.Log("something's in the way "+cols[0]);
						return false;
					}
					else{
						//confirm that we can build here
						buildableInfo.buildable=true;
						buildableInfo.position=pos;
						buildableInfo.platform=buildManager.buildPlatforms[i];
					}
					
					//check if the platform is walkable, if so, check if building on the point wont block all possible path
					if(buildManager.buildPlatforms[i].IsWalkable()){
						//return true is the platform is not block
						if(buildManager.buildPlatforms[i].CheckForBlock(pos)){
							return false;
						}
					}

					buildableInfo.buildableType=buildManager.buildPlatforms[i].buildableType;
					buildableInfo.specialBuildableID=buildManager.buildPlatforms[i].specialBuildableID;
					
					break;
				}
				
			}

		}
		else return false;
		
		currentBuildInfo=buildableInfo;
		
		indicator.active=true;
		indicator.transform.position=currentBuildInfo.position;
		indicator.transform.rotation=currentBuildInfo.platform.thisT.rotation;
		
		return true;
	}
	
	//similar to CheckBuildPoint but called by UnitTower in DragNDrop mode, check tower type before return
	public static bool CheckBuildPoint(Vector3 pointer, _TowerType type){
		return CheckBuildPoint(pointer, type, -1);
	}
	
	public static bool CheckBuildPoint(Vector3 pointer, _TowerType type, int specialID){
		if(!CheckBuildPoint(pointer)) return false;
		
		if(specialID>0){
			if(currentBuildInfo.specialBuildableID!=null && currentBuildInfo.specialBuildableID.Length>0){
				foreach(int specialBuildableID in currentBuildInfo.specialBuildableID){
					if(specialBuildableID==specialID){
						return true;
					}
				}
			}
			return false;
		}
		else{
			if(currentBuildInfo.specialBuildableID!=null && currentBuildInfo.specialBuildableID.Length>0){
				return false;
			}
			
			foreach(_TowerType buildabletype in currentBuildInfo.buildableType){
				if(type==buildabletype){
					return true;
				}
			}
		}
		
		currentBuildInfo.buildable=false;
		return false;
	}
	
	
	
	//called when a tower building is initated in DragNDrop, instantiate the tower and set it in DragNDrop mode
	public static bool BuildTowerDragNDrop(UnitTower tower){
		
		if(tower.type==_TowerType.ResourceTower && GameControl.gameState==_GameState.Idle){
			GameMessage.DisplayMessage("Cant Build Tower before spawn start");
			return false; 
		}
		
		if(GameControl.HaveSufficientResource(tower.GetCost())){
			Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			Vector3 pos=ray.GetPoint(10000);
			
			GameObject towerObj=(GameObject)Instantiate(tower.thisObj, pos, Quaternion.identity);
			UnitTower towerCom=towerObj.GetComponent<UnitTower>();
			
			towerCom.StartCoroutine(towerCom.DragNDropRoutine());
			
			return true;
		}
		
		GameMessage.DisplayMessage("Insufficient Resource");
		return false;
	}
	
	public static void DragNDropBuilt(UnitTower tower){
		tower.SetTowerID(towerCount+=1);
		//Debug.Log("built null");
		if(currentBuildInfo.platform!=null){
			if(tower.type!=_TowerType.Mine)
				currentBuildInfo.platform.Build(currentBuildInfo.position, tower);
			//Debug.Log("not null");
		}
		else Debug.Log("null");
		
		ClearBuildPoint();
	}
	
	//called by any external component to build tower, uses currentBuildInfo, return false if there isnt one
	public static bool BuildTowerPointNBuild(UnitTower tower){
		if(currentBuildInfo==null) return false;
		
		return BuildTowerPointNBuild(tower, currentBuildInfo.position, currentBuildInfo.platform);
	}
	
	//called by any external component to build tower
	public static bool BuildTowerPointNBuild(UnitTower tower, Vector3 pos, Platform platform){
		
		//dont allow building of resource tower before game started
		if(tower.type==_TowerType.ResourceTower && GameControl.gameState==_GameState.Idle){
			GameMessage.DisplayMessage("Cant Build Tower before spawn start");
			return false; 
		}
		
		//check if there are sufficient resource
		int[] cost=tower.GetCost();
		if(GameControl.HaveSufficientResource(cost)){
			GameControl.SpendResource(cost);
			
			GameObject towerObj=(GameObject)Instantiate(tower.thisObj, pos, Quaternion.identity);
			UnitTower towerCom=towerObj.GetComponent<UnitTower>();
			towerCom.InitTower(towerCount+=1);
			
			//register the tower to the platform
			if(platform!=null) platform.Build(pos, towerCom);
			
			//clear the build info and indicator for build manager
			ClearBuildPoint();
			
			return true;
		}
		
		GameMessage.DisplayMessage("Insufficient Resource");
		return false;
	}
	
	
	private UnitTower[] sampleTower;
	private int currentSampleID=-1;
	public static void InitiateSampleTower(){
		buildManager.sampleTower=new UnitTower[buildManager.towers.Length];
		for(int i=0; i<buildManager.towers.Length; i++){
			GameObject towerObj=(GameObject)Instantiate(buildManager.towers[i].gameObject);
			buildManager.sampleTower[i]=towerObj.GetComponent<UnitTower>();
			towerObj.SetActiveRecursively(false);
			UnitUtility.SetAdditiveMatColorRecursively(towerObj.transform, Color.green);
		}
	}
	
	static public void ShowSampleTower(int ID){
		buildManager._ShowSampleTower(ID);
	}
	public void _ShowSampleTower(int ID){
		if(currentSampleID==ID || currentBuildInfo==null) return;
		
		if(currentSampleID>0){
			ClearSampleTower();
		}
		currentSampleID=ID;
		sampleTower[ID].thisT.position=currentBuildInfo.position;
		sampleTower[ID].thisObj.SetActiveRecursively(true);
		GameControl.ShowIndicator(sampleTower[ID]);
	}
	
	static public void ClearSampleTower(){
		buildManager._ClearSampleTower();
	}
	public void _ClearSampleTower(){
		if(currentSampleID<0) return;
		
		sampleTower[currentSampleID].thisObj.SetActiveRecursively(false);
		GameControl.ClearIndicator();
		currentSampleID=-1;
	}
	
	
	static public BuildableInfo GetBuildInfo(){
		return currentBuildInfo;
	}
	
	static public UnitTower[] GetTowerList(){
		return buildManager.towers;
	}
	
	static public float GetGridSize(){
		return _gridSize;
	}
	
	Vector3 poss;
	//public bool debugSelectPos=true;
	void OnDrawGizmos(){
		
		//if(debugSelectPos) Gizmos.DrawCube(SelectBuildPos(Input.mousePosition), new Vector3(gridSize, 0, gridSize));
		
	}
	
}





[System.Serializable]
public class BuildableInfo{
	public bool buildable=false;
	public Vector3 position=Vector3.zero;
	public Platform platform;
	public _TowerType[] buildableType=null;
	//public GameObject[] buildableTower=null;
	
	public int[] specialBuildableID;
	
	//cant build
	public void BuildSpotInto(){}
	
	//can build anything
	public void BuildSpotInto(Vector3 pos){
		position=pos;
	}
	
	//can build with restriction to certain tower type
	public void BuildSpotInto(Vector3 pos, _TowerType[] bT){
		position=pos;
		buildableType=bT;
	}
}

5.最终效果

                 

五、心得体会

       算是大学以来真正独立地去完成一个编程小项目吧,过程感觉很艰辛,好多次都想着不去做了,太辛苦了,感觉自己什么都不会。还好,两个人一直互相监督,互相鼓励:一个人总会懈怠,两个人总会有一个人当时不怎么懈怠,经历了这么长时间,总算是弄完了。平常写代码都是自己一个人写,这次结对编程,一个人负责敲代码,一个人负责设计思想和算法,这样有好处也有不好的。像有一次两个人就因为意见不统一吵起来了,还有点严重,过了一两天才各自想明白。但是,另个人结对编程确实很好,代码出错率减少了,并且,总能将我们开始设计时的不足和遗漏在编程的时候修正好。这么多天下来,两个人关系比原来更好了,也都学会了怎么和别人合作编程,和别人合作时需要注意的问题和技巧也都有了提高,现在回想起来觉得获益匪浅啊。当然,最高兴的时候还是看到自己做的小游戏能够运行了,好有成就感,这么多天的努力终于有回报了!



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值