初试3d游戏的制作

题目:3d塔防游戏制作

一.准备阶段

1.学生:
唐凡   董晟

2.平台及使用材质包:
Unity3d  ,Tower Defense Toolkit,c#

  3 .分工:
唐凡: 编码及页面布局.
董晟: 材料包查找及使用解释,流程图绘制。

     4.流程图绘制:

https://www.gliffy.com/




二. 具体过程

1.安装unity3d

       首先是安装软件,我们是按照这个网站安装的,说的很详细。http://www.zf3d.com/Products_rj.asp?id=1068  倒是


装的时候出现了缺失材料包的现象,后来去网上找了一堆材质包。倒是解决了这个问题。

2.一些基本的概念及要点

     灯源(light):就像名字一样,这个是给予照明的,没有这个,摄像机就只能看见一片黑。
     摄像机(camera):摄像机的拍摄的部分就是玩家视野内的部分。

     

     运动脚本:采用的是c#制作的运动逻辑控制脚本。

3.目录设计与结构图

这个游戏的结构图如下:

         


4.操作步骤

(一)建立塔基安放点,即采用cube模块,把长宽设置为9,高设置为0.25。一共配置了八块。
cube配置
(二)光源和摄像机安放,大概设置在45角向下投射。

       

·
大概是 这个角度



(三)路径点脚本,采用的c#,采用的是  Tower Defense Toolkit 的模板,我进行了一点修改。使得路径点符合路径。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Path : MonoBehaviour {

	
	public Transform[] waypoints;
	public float heightOffsetOnPlatform=1;
	
	public float dynamicWP=1;
	
	public bool generatePathObject=true;
	public bool showGizmo=true;
	
	private Transform thisT;
	//private GameObject thisObj;
	
	private List<PathSection> path=new List<PathSection>();
	
	
	void Awake(){
		thisT=transform;
		//thisObj=gameObject;
	}
	
	void Start(){
		
		//run through the list, generate a list of PathSection
		//if the element is a platform, generate the node and set it to walkable
		for(int i=0; i<waypoints.Length; i++){
			Transform wp=waypoints[i];
			
			//check if this is a platform, BuildManager would have add the component and have them layered
			if(wp.gameObject.layer==LayerManager.LayerPlatform()){
				//Debug.Log("platform");
				Platform platform=wp.gameObject.GetComponent<Platform>();
				path.Add(new PathSection(platform));
			}
			else path.Add(new PathSection(wp.position));
		}
		
		//scan through the path, setup the platform pathSection if there's any
		//first initiate all the basic parameter
		for(int i=0; i<path.Count; i++){
			PathSection pSec=path[i];
			if(path[i].platform!=null){
				//get previous and next pathSection
				PathSection sec1=path[Mathf.Max(0, i-1)];
				PathSection sec2=path[Mathf.Min(path.Count-1, i+1)];
				
				//path[i].platform.SetNeighbouringWP(sec1, sec2);
				pSec.platform.SetPathObject(this, pSec, sec1, sec2);
				
				pSec.platform.SetWalkable(true);
				if(!pSec.platform.IsNodeGenerated()) 
					pSec.platform.GenerateNode(heightOffsetOnPlatform);
				
				pSec.platform.SearchForNewPath(pSec);
			}
		}
		
		//now the basic have been setup,  setup the path
		//~ for(int i=0; i<path.Count; i++){
			//~ if(path[i].platform!=null){
				//~ path.platform.GetPath();
			//~ }
		//~ }
		
		if(generatePathObject){
			CreateLinePath();
		}
	}

	//create line-renderer along the path as indicator
	void CreateLinePath(){
		
		Vector3 offsetPos=new Vector3(0, 0, 0);
		
		for(int i=1; i<waypoints.Length; i++){
			//waypoint to waypoint
			if(path[i].platform==null && path[i-1].platform==null){
				GameObject obj=new GameObject();
				obj.name="path"+i.ToString();
				
				Transform objT=obj.transform;
				objT.parent=thisT;
				
				LineRenderer line=obj.AddComponent<LineRenderer>();
				line.material=(Material)Resources.Load("PathMaterial");
				line.SetWidth(0.3f, 0.3f);
				
				line.SetPosition(0, waypoints[i-1].position+offsetPos);
				line.SetPosition(1, waypoints[i].position+offsetPos);
			}
			//platform to waypoint
			else if(path[i].platform==null && path[i-1].platform!=null){
				GameObject obj=new GameObject();
				obj.name="path"+i.ToString();
				
				Transform objT=obj.transform;
				objT.parent=thisT;
				
				LineRenderer line=obj.AddComponent<LineRenderer>();
				line.material=(Material)Resources.Load("PathMaterial");
				line.SetWidth(0.3f, 0.3f);
				
				List<Vector3> path1=path[i-1].GetSectionPath();
				
				line.SetPosition(0, path1[path1.Count-1]+offsetPos);
				line.SetPosition(1, waypoints[i].position+offsetPos);
			}
			//waypoint to platform
			else if(path[i].platform!=null && path[i-1].platform==null){
				GameObject obj=new GameObject();
				obj.name="path"+i.ToString();
				
				Transform objT=obj.transform;
				objT.parent=thisT;
				
				LineRenderer line=obj.AddComponent<LineRenderer>();
				line.material=(Material)Resources.Load("PathMaterial");
				line.SetWidth(0.3f, 0.3f);
				
				List<Vector3> path1=path[i].GetSectionPath();
				
				line.SetPosition(0, waypoints[i-1].position+offsetPos);
				line.SetPosition(1, path1[0]+offsetPos);
			}
			//platform to platform
			else if(path[i].platform!=null && path[i-1].platform!=null){
				GameObject obj=new GameObject();
				obj.name="path"+i.ToString();
				
				Transform objT=obj.transform;
				objT.parent=thisT;
				
				LineRenderer line=obj.AddComponent<LineRenderer>();
				line.material=(Material)Resources.Load("PathMaterial");
				line.SetWidth(0.3f, 0.3f);
				
				List<Vector3> path1=path[i-1].GetSectionPath();
				List<Vector3> path2=path[i].GetSectionPath();
				
				line.SetPosition(0, path1[path1.Count-1]+offsetPos);
				line.SetPosition(1, path2[0]+offsetPos);
			}
			
		}
		
		foreach(PathSection ps in path){
			if(ps.platform==null)
				Instantiate((GameObject)Resources.Load("wpNode"), ps.pos+offsetPos, Quaternion.identity);
		}
		
	}
	
	public List<PathSection> GetPath(){
		return path;
	}
	
	void OnDrawGizmos(){
		if(showGizmo){
			Gizmos.color = Color.blue;
			if(waypoints!=null && waypoints.Length>0){
				
				for(int i=1; i<waypoints.Length; i++){
					Gizmos.DrawLine(waypoints[i-1].position, waypoints[i].position);
				}
			}
		}
	}
	
}

public class PathSection{
	public Platform platform;
	public Vector3 pos;
	
	private List<Vector3> sectionPath=new List<Vector3>();
	private int pathID=0;	//a unique randomly generated number assigned whenever a new path is found
									//so the unit on this platform can know that a new path has been updated
	
	public PathSection(Vector3 p){
		pos=p;
		sectionPath.Add(pos);
	}
	
	public PathSection(Platform p){
		platform=p;
	}
	
	public void SetSectionPath(List<Vector3> L, int id){
		sectionPath=L;
		pathID=id;
	}
	
	public List<Vector3> GetSectionPath(){
		return sectionPath;
	}
	
	public int GetPathID(){
		return pathID;
	}
}

(三)UI设计

拖拽下方的按钮到上方的工作区,添加脚本。

按钮列表


            
工作区

(四)敌人生成器

敌人分为两种,一种是地面单位,一种是空中单位,需要写不同的脚本控制生产器的定时装置,以做到交替产生敌人。代码是自动生成的,只需要修改一下速度和血量设置。(另外说一句,血量的显示是生成的两个条,一个不断减小,另一个不变。)

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class UnitCreep : Unit {

	public float moveSpeed=3;
	//修改这里的变量
	public bool immuneToSlow=false;
	
	public bool flying=false;
	public float flightHeightOffset=3f;
	
	public int[] value=new int[1];
	
	public GameObject spawnEffect;
	public GameObject deadEffect;
	public GameObject scoreEffect;
	
	public GameObject animationBody;
	private Animation aniBody;
	public AnimationClip[] animationSpawn;
	public AnimationClip[] animationMove;
	public AnimationClip[] animationHit;
	public AnimationClip[] animationDead;
	public AnimationClip[] animationScore;
	public float moveAnimationModifier=1.0f;
	
	public AudioClip audioSpawn;
	public AudioClip audioHit;
	public AudioClip audioDead;
	public AudioClip audioScore;
	
	[HideInInspector] public Vector3 dynamicOffset;
	
	[HideInInspector] public int waveID;
	
	public GameObject spawnUponDestroyed;
	public int spawnNumber;
	

	public override void Awake () {
		base.Awake();
		
		SetSubClassInt(this);
		
		if(thisObj.collider==null){
			SphereCollider col=thisObj.AddComponent<SphereCollider>();
			col.center=new Vector3(0, 0.0f, 0);
			col.radius=0.25f;
		}
		
		if(animationBody!=null){
			aniBody=animationBody.GetComponent<Animation>();
			if(aniBody==null) aniBody=animationBody.AddComponent<Animation>();
			
			if(animationSpawn!=null && animationSpawn.Length>0){
				foreach(AnimationClip clip in animationSpawn){
					aniBody.AddClip(clip, clip.name);
					aniBody.animation[clip.name].layer=1;
					aniBody.animation[clip.name].wrapMode=WrapMode.Once;
				}
			}
			
			if(animationMove!=null && animationMove.Length>0){
				foreach(AnimationClip clip in animationMove){
					aniBody.AddClip(clip, clip.name);
					aniBody.animation[clip.name].layer=0;
					aniBody.animation[clip.name].wrapMode=WrapMode.Loop;
				}
			}
			
			if(animationHit!=null && animationHit.Length>0){
				foreach(AnimationClip clip in animationHit){
					aniBody.AddClip(clip, clip.name);
					aniBody.animation[clip.name].layer=3;
					aniBody.animation[clip.name].wrapMode=WrapMode.Once;
				}
			}
			
			if(animationDead!=null && animationDead.Length>0){
				foreach(AnimationClip clip in animationDead){
					aniBody.AddClip(clip, clip.name);
					aniBody.animation[clip.name].layer=3;
					aniBody.animation[clip.name].wrapMode=WrapMode.Once;
				}
			}
			
			if(animationScore!=null && animationScore.Length>0){
				foreach(AnimationClip clip in animationScore){
					aniBody.AddClip(clip, clip.name);
					aniBody.animation[clip.name].layer=3;
					aniBody.animation[clip.name].wrapMode=WrapMode.Once;
				}
			}
		}
		
		if(spawnEffect!=null) ObjectPoolManager.New(spawnEffect, 5, false);
		if(deadEffect!=null) ObjectPoolManager.New(deadEffect, 5, false);
	}

	public override void Start () {
		if(!flying) thisObj.layer=LayerManager.LayerCreep();
		else thisObj.layer=LayerManager.LayerCreepF();
		
		base.Start();
	}
	
	public void SetMoveSpeed(float moveSpd){
		moveSpeed=moveSpd;
	}
	
	
	//init a unitCreep, give it a list of Vector3 point as the path it should follow
	public void Init(List<Vector3> waypoints, int wID){
		base.Init();
		waveID=wID;
		wp=waypoints;
		wpMode=true;
		
		if(flying) thisT.position+=new Vector3(0, flightHeightOffset, 0);
		
		currentMoveSpd=moveSpeed;
		
		if(aniBody!=null && animationMove!=null && animationMove.Length>0){
			foreach(AnimationClip clip in animationMove){
				aniBody.animation[clip.name].speed=currentMoveSpd*moveAnimationModifier;
			}
		}
		
		float allowance=BuildManager.GetGridSize();
		dynamicOffset=new Vector3(Random.Range(-allowance, allowance), 0, Random.Range(-allowance, allowance));
		thisT.position+=dynamicOffset;
		
		PlaySpawn();
		PlayMove();
	}
	
	//init a unitCreep, give it a path instance so it can retirve the path
	public void Init(Path p, int wID){
		base.Init();
		waveID=wID;
		path=p;
		wpMode=false;
		
		if(flying) thisT.position+=new Vector3(0, flightHeightOffset, 0);
		
		currentMoveSpd=moveSpeed;
		
		if(aniBody!=null && animationMove!=null && animationMove.Length>0){
			foreach(AnimationClip clip in animationMove){
				aniBody.animation[clip.name].speed=currentMoveSpd*moveAnimationModifier;
			}
		}
		
		//if using dynamic wap pos, set an offset based on gridsize
		if(path.dynamicWP>0){
			//float allowance=BuildManager.GetGridSize()*0.35f;
			float allowance=path.dynamicWP;
			dynamicOffset=new Vector3(Random.Range(-allowance, allowance), 0, Random.Range(-allowance, allowance));
			thisT.position+=dynamicOffset;
		}
		else dynamicOffset=Vector3.zero;
		
		if(spawnEffect!=null) ObjectPoolManager.Spawn(spawnEffect, thisT.position, Quaternion.identity);
		PlaySpawn();
		PlayMove();
	}
	
	public void Dead(){
		GameControl.GainResource(value);
		//for(int i=0; i<value.Length; i++){
		//	GameControl.GainResource(i, value[i]);
		//}
		
		if(deadEffect!=null) ObjectPoolManager.Spawn(deadEffect, thisT.position, Quaternion.identity);
		float duration=PlayDead();
		StartCoroutine(Unspawn(duration));
		
		//spawn more unit if there's one assigned
		if(spawnUponDestroyed!=null){
			
			SpawnManager.AddActiveUnit(waveID, spawnNumber);
			
			for(int i=0; i<spawnNumber; i++){
				//generate a small offset position within the grid size so not all creep spawned on top of each other and not too far apart
				float allowance=BuildManager.GetGridSize()/2;
				
				float x=Random.Range(-allowance, allowance);
				float y=Random.Range(-allowance, allowance);
				
				Vector3 pos=thisT.position+new Vector3(x, 0, y);
				GameObject obj=ObjectPoolManager.Spawn(spawnUponDestroyed, pos, thisT.rotation);
				
				UnitCreep unit=obj.GetComponent<UnitCreep>();
				unit.Init(path, waveID);
				//resume the path currently followed by this unit
				StartCoroutine(unit.ResumeParentPath(wpMode, wp, wpCounter, currentPS, subPath, currentPathID, subWPCounter));
			}
		}
	}
	
	public void Score(){
		if(scoreEffect!=null) ObjectPoolManager.Spawn(scoreEffect, thisT.position, Quaternion.identity);
		float duration=PlayScore();
		
		StartCoroutine(Unspawn(duration));
	}
	
	public override void Update () {
		base.Update();
		
		//~ if(GetWPCounter()==wp.Count){
			//~ //final waypoint reached
			//~ WaypointReachEvent();
			//~ ObjectPoolManager.Unspawn(thisObj);
		//~ }
	}
	
	public void Stunned(){
		if(aniBody!=null){
			if(animationMove!=null && animationMove.Length>0){
				for(int i=0; i<animationMove.Length; i++){
					aniBody.Stop(animationMove[i].name);
				}
			}
		}
	}
	
	public void Unstunned(){
		PlayMove();
	}
	
	private AnimationClip[] animationAttack;
	private AnimationClip animationIdle;
	
	public void SetAttackAnimation(AnimationClip[] aniAttack){
		animationAttack=aniAttack;
		
		if(aniBody!=null && animationAttack!=null && animationAttack.Length>0){
			foreach(AnimationClip clip in animationHit){
				aniBody.AddClip(clip, clip.name);
				aniBody.animation[clip.name].layer=5;
				aniBody.animation[clip.name].wrapMode=WrapMode.Once;
			}
		}
	}
	
	public void SetIdleAnimation(AnimationClip aniIdle){
		animationIdle=aniIdle;
		if(aniBody!=null){
			aniBody.AddClip(animationIdle, animationIdle.name);
			aniBody.animation[animationIdle.name].layer=-1;
			aniBody.animation[animationIdle.name].wrapMode=WrapMode.Loop;
		}
	}
	
	public void StopAnimation(){
		if(aniBody!=null) aniBody.Stop();
		
		if(aniBody!=null && animationIdle!=null){
			aniBody.Play(animationIdle.name);
		}
	}
	
	public void ResumeAnimation(){
		PlayMove();
	}
	
	public bool PlayAttack(){
		if(aniBody!=null && animationAttack!=null && animationAttack.Length>0){
			aniBody.CrossFade(animationAttack[Random.Range(0, animationAttack.Length-1)].name);
			return true;
		}
		return false;
	}
	
	public void PlayMove(){
		if(aniBody!=null && animationMove!=null && animationMove.Length>0){
			aniBody.Play(animationMove[Random.Range(0, animationMove.Length-1)].name);
		}
	}
	
	public void PlaySpawn(){
		if(aniBody!=null && animationSpawn!=null && animationSpawn.Length>0){
			aniBody.CrossFade(animationSpawn[Random.Range(0, animationSpawn.Length-1)].name);
		}
		
		if(audioSpawn!=null) AudioManager.PlaySound(audioSpawn, thisT.position);
	}
	
	public void PlayHit(){
		//Debug.Log("Play hit");
		if(aniBody!=null && animationHit!=null && animationHit.Length>0){
			aniBody.CrossFade(animationHit[Random.Range(0, animationHit.Length-1)].name);
		}
		
		if(audioHit!=null) AudioManager.PlaySound(audioHit, thisT.position);
	}
	
	public float PlayDead(){
		float duration=0;
		
		if(aniBody!=null){
			aniBody.Stop();
		}
		
		if(aniBody!=null && animationDead!=null && animationDead.Length>0){
			int rand=Random.Range(0, animationDead.Length-1);
			aniBody.CrossFade(animationDead[rand].name);
			duration=animationDead[rand].length;
		}
		
		if(audioDead!=null){
			AudioManager.PlaySound(audioDead, thisT.position);
			duration=Mathf.Max(audioDead.length, duration);
		}
		
		return duration;
	}
	
	public float PlayScore(){
		float duration=0;
		
		if(aniBody!=null && animationScore!=null && animationScore.Length>0){
			int rand=Random.Range(0, animationDead.Length-1);
			aniBody.CrossFade(animationScore[rand].name);
			duration=animationScore[rand].length;
		}
		
		if(audioScore!=null) {
			AudioManager.PlaySound(audioScore, thisT.position);
			duration=Mathf.Max(audioScore.length, duration);
		}
		
		return duration;
	}
	
	IEnumerator Unspawn(float duration){
		yield return new WaitForSeconds(duration);
		ObjectPoolManager.Unspawn(thisObj);
	}

}



(五)运行图:








三.感想心得

我们在这一次的作业中,应老师的要求对已有软件做了3D游戏的制作。在这次作业完成过程中,我们不仅学习到了对软件开发的技术方面的知识,而且还学习到了一种不满足于已有软件的精神。对于我们学习计算机的人来说 ,本身对于软件的开发与维护就是我们以后工作的重点之一。而对于这种工作来说,一颗创新与不满足的心就是特别重要的。对已有软件性能上缺陷的改进,BUG的修复,以及对界面友好度的提升而这次的作业当中,我们也初步体会到了身为一个程序员维护的自己程序的辛苦。其中不仅要求在代码开发的还对代码做好备注,还要求我们不能对自己已开发的东西满足,要一直有进取精神,这样才能将自己做的代码做得更加出色


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值