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