Unity 3D-learning 简单打飞碟游戏

一、编写一个简单的打飞碟游戏

  • 游戏内容要求:
    1. 游戏有 n 个 round,每个 round 都包括10 次 trial;
    2. 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
    3. 每个 trial 的飞碟有随机性,总体难度随 round 上升;
    4. 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
  • 游戏的要求:
    • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
    • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离

下面开始介绍游戏制作过程。

相比此前的牧师与魔鬼游戏,飞碟游戏里需要考虑的预设对象很少,只需要制作一个飞碟对象以及一个子弹对象。为了简化外形,我们设定飞盘为圆柱体,子弹为球体,具体的尺寸设置如下。

在子弹打击到飞碟时,考虑到飞碟 的摧毁效果,我们可以添加一个粒子系统名为explosion


与此同时,我们还需要显示出游戏的轮数和当前得分,所以我们需要设置 UI的Text控件


除此之外,最好有一个游戏说明,于是我们设计一个按钮,当按钮被按下时,会显示游戏说明


这个按钮并不在Hierarchy中设计,而是在IUserInterface类中的OnGUI函数中完成实现。

值得一提的是,本次实验中我们引入了物理学属性刚体,可以更好的模拟物体的运动。以下是为飞盘、子弹对象添加刚体属性的截图: 



完成了物体预制后,我们就应该开始进行脚本的书写了,首先我们应该分析工厂模式并画出UML图


并按照UML图实现代码。

SceneControllerBC类的实现:

1.SceneController类主要实现接口定义和保存预制好的对象。另外它有两个私有变量round和score,分别记录当前游戏轮数,以及玩家目前的得分。SceneControllerBC类保存了各个round飞碟的大小、颜色、发射位置、发射角度、发射数量等信息。它只有一个函数loadRoundData(),用来初始化游戏场景。

using UnityEngine;  
using System.Collections;  
using Mygame;  

namespace Mygame {  
	public interface IUserInterface {  
		void emitDisk();  
	}  

	public interface IQueryStatus {  
		bool isCounting();  
		bool isShooting();  
		int getRound();  
		int getScore();  
		int getEmitTime();  
	}  

	public interface IJudgeEvent {  
		void nextRound();  
		void setScore(int Score);  
	}  

	public class SceneController : System.Object, IQueryStatus, IUserInterface, IJudgeEvent {  
		private static SceneController instance;  
		private SceneControllerBC baseCode;  
		private GameModel gameModel;  
		private Judge judge;  

		private int round;  
		private int score;  

		public static SceneController getInstance() {  
			if (instance == null) {  
				instance = new SceneController();  
			}  
			return instance;  
		}  

		public void setGameModel(GameModel obj) { 
			gameModel = obj;
		}  

		internal GameModel getGameModel() { 
			return gameModel;
		}  

		public void setJudge(Judge obj) { 
			judge = obj;
		}  

		internal Judge getJudge() { 
			return judge;
		}  

		public void setSceneControllerBC(SceneControllerBC obj) { 
			baseCode = obj;
		}  

		internal SceneControllerBC getSceneControllerBC() {
			return baseCode;
		}  

		public void emitDisk() { 
			gameModel.prepareToEmitDisk();
		}  
 
		public bool isCounting() { 
			return gameModel.isCounting();
		}  

		public bool isShooting() { 
			return gameModel.isShooting();
		}  

		public int getRound() { 
			return round; 
		}  

		public int getScore() { 
			return score;
		}  

		public int getEmitTime() { 
			return (int)gameModel.timeToEmit + 1;
		}  
 
		public void setScore(int score_) { 
			score = score_;
		}  
		public void nextRound() { 
			score = 0;
			baseCode.loadRoundData(++round);
		}  
	}  

	public class SceneControllerBC : MonoBehaviour {  
		private Color color;  
		private Vector3 emitPos;  
		private Vector3 emitDir;  
		private float speed;  

		void Awake() {  
			SceneController.getInstance().setSceneControllerBC(this);  
		}  

		public void loadRoundData(int round) {  
			if (round == 1) {
				color = Color.green;  
				emitPos = new Vector3(-2.5f, 0.2f, -5f);  
				emitDir = new Vector3(24.5f, 40.0f, 67f);  
				speed = 3;  
				SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1);  
			}
			else if (round==2) {
				color = Color.red;  
				emitPos = new Vector3(2.5f, 0.2f, -5f);  
				emitDir = new Vector3(-24.5f, 35.0f, 67f);  
				speed = 4;  
				SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 2);  
			}
		}  
	}  
}  

DiskFactoryBC类的实现

1.DiskFactoryBC类的目的是管理飞碟实例,使用单例模式,得到飞碟id,得到飞碟对象,释放飞碟对象,为了避免开销大的问题,没有用destory,而是设置为diskList[id].SetActive(false);

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

namespace Mygame {  
	public class DiskFactory : System.Object {  
		private static DiskFactory _instance;  
		private static List<GameObject> diskList;
		public GameObject Disk;           

		public static DiskFactory getInstance() {  
			if (_instance == null) {  
				_instance = new DiskFactory();  
				diskList = new List<GameObject>();  
			}  
			return _instance;  
		}  


		public int getDisk() {   
			for (int i = 0; i < diskList.Count; ++i) {  
				if (!diskList[i].activeInHierarchy) {  
					return i;  
				}  
			}  
			diskList.Add(GameObject.Instantiate(Disk) as GameObject);  
			return diskList.Count-1;  
		}  

		public GameObject getDiskObject(int id) {  
			if (id > -1 && id < diskList.Count) {  
				return diskList[id];  
			}  
			return null;  
		}  
 
		public void free(int id) {  
			if (id > -1 && id < diskList.Count) {  
				diskList[id].GetComponent<Rigidbody>().velocity = Vector3.zero;  
				diskList[id].transform.localScale = Disk.transform.localScale;  
				diskList[id].SetActive(false);  
			}  
		}  
	}  
}  

public class DiskFactoryBC : MonoBehaviour {  
	public GameObject disk;  

	void Awake () {   
		DiskFactory.getInstance().Disk = disk;  
	}  
} 

实现Judge类

1.控制得分系统,打中飞碟加分,错过飞碟扣分,当分数累积到一定的程度自动进入下一关

using UnityEngine;  
using System.Collections;  
using Mygame;  

public class Judge : MonoBehaviour {  
	public int oneDiskScore = 10;  
	public int oneDiskFail = 10;  
	public int disksToWin = 4;  

	private SceneController scene;  

	void Awake() {  
		scene = SceneController.getInstance();  
		scene.setJudge(this);  
	}  

	void Start() {  
		scene.nextRound();  
	}  

	public void scoreADisk() {  
		scene.setScore(scene.getScore() + oneDiskScore);  
		if (scene.getScore() == disksToWin*oneDiskScore) {  
			scene.nextRound();  
		}  
	}  
		
	public void failADisk() {  
		scene.setScore(scene.getScore() - oneDiskFail);  
	}  
}  

实现IUserInterface类

1.用户界面,在此实现得分板,当前关卡的显示以及设置一个RepeatButton可以显示游戏规则;处理用户的输入,用户输入入有两种:鼠标左键和空格。左键发射子弹,空格发射飞碟。

        子弹射击的思路:当用户点击鼠标时,从摄像机到鼠标创建一条射线,射线的方向即是子弹发射的方向,子弹采用刚体组件,因此发射子弹只需要给子弹施加一个力。子弹对象只有一个,下一次发射子弹时,必须改变子弹的位置。为了不让子弹继承上一次发射的速度,必须将子弹的速度归零重置。采用射线而来判断子弹是否击中飞碟。     

using UnityEngine;  
using UnityEngine.UI;  
using System.Collections;  
using Mygame;  

public class UserInterface : MonoBehaviour {  
	public Text mainText;   
	public Text scoreText; 
	public Text roundText; 
	string ruleText = "按下空格键以发射飞碟,点击鼠标左键发射子弹,每打中一个飞碟可得到10分,每错过一个飞碟扣十分,累积到一定的分数可进入下一关。";

	private int round;  

	public GameObject bullet;           
	public ParticleSystem explosion;   
	public float fireRate = .25f;       
	public float speed = 500f;   

	private float nextFireTime;      

	private IUserInterface userInt;  
	private IQueryStatus queryInt;    

	void Start() {  
		bullet = GameObject.Instantiate(bullet) as GameObject;  
		explosion = GameObject.Instantiate(explosion) as ParticleSystem;  
		userInt = SceneController.getInstance() as IUserInterface;  
		queryInt = SceneController.getInstance() as IQueryStatus;  
	}  

	void Update () {  
		
		if (queryInt.isCounting()) {  
			mainText.text = ((int)queryInt.getEmitTime()).ToString();  
		}  
		else {  
			if (Input.GetKeyDown("space")) {  
				userInt.emitDisk();    
			}  
			if (queryInt.isShooting()) {  
				mainText.text = "";    
			}  
			if (queryInt.isShooting() && Input.GetMouseButtonDown(0) && Time.time > nextFireTime ) {  
				nextFireTime = Time.time + fireRate;  

				Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); 
				bullet.GetComponent<Rigidbody>().velocity = Vector3.zero;     
				bullet.transform.position= transform.position;           
				bullet.GetComponent<Rigidbody>().AddForce(ray.direction*speed, ForceMode.Impulse);  

				RaycastHit hit;  
				if (Physics.Raycast(ray, out hit) && hit.collider.gameObject.tag == "Disk") {  
					explosion.transform.position = hit.collider.gameObject.transform.position;  
					explosion.GetComponent<Renderer>().material.color = hit.collider.gameObject.GetComponent<Renderer>().material.color;  
					explosion.Play();  
					hit.collider.gameObject.SetActive(false);  
				}  
			}  
		}  
		roundText.text = "Round: " + queryInt.getRound().ToString();  
		scoreText.text = "Score: " + queryInt.getScore().ToString();
		if (round != queryInt.getRound()) {  
			Debug.Log (round);
			round = queryInt.getRound();  
			mainText.text = "Round " + round.ToString() + " !";  
			Debug.Log (round);
		}  
	} 

	void OnGUI() {
		if (GUI.RepeatButton (new Rect (10, 10, 100, 40), "Help")) {
			GUI.TextArea(new Rect(10, 60, 750, 100),ruleText);
		}
	}
}  

在完成了脚本的设计后,将五个脚本都挂载到摄像机上

并且将预制好的游戏对象分别拖入到相应的位置即可完成游戏的设计。


阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页