Unity游戏设计之飞碟游戏

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity游戏设计之飞碟游戏相关的知识,希望对你有一定的参考价值。

编制一个射飞碟游戏。

具体要求如下:

1 假设有一支枪在摄像机位置(0,1,-10),在(0,0,0-10-20)放置三个小球作为距离标记,调整视角直到小球在下中部

2 将鼠标所在平面坐标,转换为子弹(球体)射出的角度方向。子弹使用物理引擎,初速度恒定。(U3d 坐标变换: http://www.cnblogs.com/tekkaman/p/3809409.html

 

Vector3 mp = Input.mousePosition; //get Screen Position

print (mp.ToString());

 

Vector3 mp1 = cam.camera.ScreenToViewportPoint (mp);

mp1.z = 10; //距摄像头 10 位置立面

mp1 = cam.camera.ViewportToWorldPoint (mp1);

print (mp1.ToString());

 

3 游戏要分多个 round , 飞碟数量每个 round 都是 n 个,但色彩,大小;发射位置,速度,角度,每次发射数量按预定规则变化。

4 用户按空格后,321倒数3秒,飞碟飞出(物理引擎控制),点击鼠标,子弹飞出。飞碟落地,或被击中,则准备下一次射击。

5 以下是一些技术要求:

? 子弹仅需要一个,不用时处于 deactive 状态

? 飞碟用一个带缓存的工厂生产,template 中放置预制的飞碟对象

? 程序类图设计大致如下:

 技术分享

具体实现:

脚本实现子弹射击

脚本挂在在摄像机上

子弹射击的思路:当用户点击鼠标时,从摄像机到鼠标创建一条射线,射线的方向即是子弹发射的方向,子弹采用刚体组件,因此发射子弹只需要给子弹施加一个力。子弹对象只有一个,下一次发射子弹时,必须改变子弹的位置(虽然有了刚体组件不建议修改transform,但也没有其它方法改变子弹位置了吧)。为了不让子弹继承上一次发射的速度,必须将子弹的速度归零重置。

子弹的击中判断:采用射线而不是物理引擎,因为物理引擎在高速物体碰撞时经常不能百分百检测得到。

 技术分享

完成飞碟工厂

创建新的命名空间Com.Mygame,单例类DiskFactory和SceneController都定义其中。飞碟工厂类的目的是管理飞碟实例,同时对外屏蔽飞碟实例的的提取和回收细节,对于需要使用飞碟的其他对象,只能使用工厂类提供的3个函数,分别是getDisk()、getDiskObject()、free()。

 

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Com.Mygame;
namespace Com.Mygame {
	public class DiskFactory: System.Object {
		private static DiskFactory _instance;
		private static List<GameObject> diskList;
		public GameObject diskPrefab;
		public static DiskFactory getInstance() {  
			if (_instance == null) {  
				_instance = new DiskFactory();  
				diskList = new List<GameObject>();  
			}  
			return _instance;  
		}
		// 获取可用飞碟id  
		public int getDisk() {   
			for (int i = 0; i < diskList.Count; ++i) {  
				if (!diskList[i].activeInHierarchy) {  
					return i;   // 飞碟空闲  
				}  
			}  
			// 无空闲飞碟,则实例新的飞碟预设
			diskList.Add(GameObject.Instantiate(diskPrefab) as GameObject);  
			return diskList.Count-1;  
		}
		// 获取飞碟对象  
		public GameObject getDiskObject(int id) {  
			if (id >= 0 && id < diskList.Count) {  
				return diskList[id];  
			}  
			return null;  
		}
		// 回收飞碟  
		public void free(int id) {  
			if (id >= 0 && id < diskList.Count) {  
				// 重置飞碟速度  
				diskList[id].GetComponent<Rigidbody>().velocity = Vector3.zero;  
				// 重置飞碟大小  
				diskList[id].transform.localScale = diskPrefab.transform.localScale;  
				diskList[id].SetActive(false);  
			}  
		}
	}
}
public class DiskFactoryBaseCode : MonoBehaviour {
	public GameObject disk;  
	void Awake () {  
		// 初始化预设对象  
		DiskFactory.getInstance().diskPrefab = disk;  
	}  
}

 

完成游戏场景

场景类是整个飞碟射击游戏的核心类,主要负责飞碟动作的处理。参考师兄的设计:首先需要倒计时功能,可以通过几个整型变量和布尔变量完成。另外需要飞碟发射功能,通过setting函数保存好飞碟的发射信息,每次倒计时完成后,通过emitDisks获取飞碟对象,并通过发射信息初始化飞碟,再给飞碟一个力就可以发射了。而飞碟的回收在Update里完成,一种是飞碟被击中(飞碟不在场景中)了,需要调用Judge获得分数。另一种是飞碟在场景中,但是掉在地上了,需要调用Judge丢失分数。

 

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Com.Mygame;
public class GameModel : MonoBehaviour {
	public float countDown = 3f;
	public float timeToEmit;
	private bool counting;
	private bool shooting;
	public bool isCounting() {
		return counting;
	}
	public bool isShooting() {
		return shooting;
	}
	private List<GameObject> disks = new List<GameObject> ();
	private List<int> diskIds = new List<int> ();
	private int diskScale;
	private Color diskColor;
	private Vector3 emitPosition;
	private Vector3 emitDirection;
	private float emitSpeed;
	private int emitNumber;
	private bool emitEnable;
	private SceneController scene;
	void Awake() {
		scene = SceneController.getInstance ();
		scene.setGameModel (this);
	}
	public void setting(int scale, Color color, Vector3 emitPos, Vector3 emitDir, float speed, int num) {
		diskScale = scale;
		diskColor = color;
		emitPosition = emitPos;
		emitDirection = emitDir;
		emitSpeed = speed;
		emitNumber = num;
	}
	public void prepareToEmitDisk() {
		if (!counting && !shooting) {
			timeToEmit = countDown;
			emitEnable = true;
		}
	}
	void emitDisks() {
		for (int i = 0; i < emitNumber; i++) {
			diskIds.Add (DiskFactory.getInstance ().getDisk ());
			disks.Add (DiskFactory.getInstance ().getDiskObject (diskIds [i]));
			disks [i].transform.localScale *= diskScale;
			disks [i].GetComponent<Renderer> ().material.color = diskColor;
			disks [i].transform.position = new Vector3 (emitPosition.x, emitPosition.y + i, emitPosition.z);
			disks [i].SetActive (true);
			disks [i].GetComponent<Rigidbody> ().AddForce (emitDirection * Random.Range (emitSpeed * 5, emitSpeed * 10) / 10, ForceMode.Impulse);
		}
	}
	void freeDisk(int i) {
		DiskFactory.getInstance ().free (diskIds [i]);
		disks.RemoveAt (i);
		diskIds.RemoveAt (i);
	}
	void FixedUpdate() {
		if (timeToEmit > 0) {
			counting = true;
			timeToEmit -= Time.deltaTime;
		} else {
			counting = false;
			if (emitEnable) {
				emitDisks ();
				emitEnable = false;
				shooting = true;
			}
		}
	}

	// Use this for initialization
	void Start () {

	}

	// Update is called once per frame
	void Update () {
		for (int i = 0; i < disks.Count; i++) {
			if (!disks [i].activeInHierarchy) {
				scene.getJudge ().scoreADisk ();
				freeDisk (i);
			} else if (disks [i].transform.position.y < 0) {
				scene.getJudge ().failADisk ();
				freeDisk (i);
			}
		}
		if (disks.Count == 0) {
			shooting = false;
		}
	}
}

 

场景控制器

场景控制类主要实现接口定义和保存注入对象。另外它有两个私有变量round和point,分别记录游戏正在进行的回合,以及玩家目前的得分。

 

using UnityEngine;
using System.Collections;
using Com.Mygame;
namespace Com.Mygame {
	// Com.Mygame内添加

	public interface IUserInterface {
		void emitDisk();
	}
	public interface IQueryStatus {
		bool isCounting();
		bool isShooting();
		int getRound();
		int getPoint();
		int getEmitTime();
	}
	public class SceneController : System.Object, IQueryStatus, IUserInterface {
		private static SceneController _instance;
		private GameModel _gameModel;
		private SceneControllerBaseCode _baseCode;
		private int _round;
		private int _point;
		public static SceneController getInstance() {
			if (_instance == null) {
				_instance = new SceneController ();
			}
			return _instance;
		}
		public void setSceneControllerBaseCode (SceneControllerBaseCode obj) {
			_baseCode = obj;
		}  
		internal SceneControllerBaseCode getSceneControllerBC() {
			return _baseCode;
		} 
		public void setGameModel(GameModel obj) {
			_gameModel = obj;
		}
		// 当前程序或派生类可用
		internal GameModel getGameModel() {
			return _gameModel;
		}
		public void emitDisk() {
			_gameModel.prepareToEmitDisk ();
		}
		public bool isCounting() {
			return _gameModel.isCounting ();
		}
		public bool isShooting() {
			return _gameModel.isShooting ();
		}
		public int getRound() {
			return _round;
		}
		public int getPoint() {
			return _point;
		}
		public int getEmitTime() {
			return (int)_gameModel.timeToEmit + 1;
		}
		public void setPoint(int point) {
			_point = point;
		}
		public void nextRound() {
			_point = 0;
		}
	}
}
public class SceneControllerBaseCode : MonoBehaviour {  
	private Color color;  
	private Vector3 emitPos;  
	private Vector3 emitDir;  
	private float speed;  
	void Awake() {  
		SceneController.getInstance().setSceneControllerBaseCode(this);  
	}  
	void Start() {
		color = Color.green;  
		emitPos = new Vector3(-2.5f, 0.2f, -5f);  
		emitDir = new Vector3(24.5f, 40.0f, 67f);  
		speed = 4;
		SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1);  
	}
}

  

完善UserInterface

 

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using Com.Mygame;
public class UserInterface : MonoBehaviour {
	public Text mainText;
	public Text scoreText;
	public Text roundText;
	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;
	// Use this for initialization
	void Start () {
		bullet = GameObject.Instantiate (bullet) as GameObject;
		explosion = GameObject.Instantiate (explosion) as ParticleSystem;
		userInt = SceneController.getInstance () as IUserInterface;
		queryInt = SceneController.getInstance () as IQueryStatus;
	}

	// Update is called once per frame
	void Update () {
		if (queryInt.isCounting ()) {
			mainText.text = ((int)queryInt.getEmitTime ()).ToString ();
		} else {
			if (Input.GetKeyDown (KeyCode.Space)) {
				userInt.emitDisk ();
			}
			if (queryInt.isShooting ()) {
				mainText.text = " ";
			} else {
				mainText.text = "Press space";
			}
			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.getPoint ().ToString ();
		if (round != queryInt.getRound ()) {
			round = queryInt.getRound ();
			mainText.text = "Round: " + round.ToString() + "!";
		}
	}
}

 

补充游戏规则–Judge计分系统

游戏规则单独作为一个类,有利于日后修改。这里需要处理的规则无非就两个,得分和失分。另外,得分需要判断是否能晋级下一关。能就调用接口函数nextRound()。

using UnityEngine;  
using System.Collections;  
using Com.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.setPoint(scene.getPoint() + oneDiskScore);  
		if (scene.getPoint() == disksToWin*oneDiskScore) {  
			scene.nextRound();  
		}  
	}  

	// 掉落飞碟失分  
	public void failADisk() {  
		scene.setPoint(scene.getPoint() - oneDiskFail);  
	}  
}

 

在场景控制器中添加相应裁判的代码

 

// Com.Mygame内添加
	public interface IjudgeEvent {
		void nextRound();
		void setPoint(int point);
	}
// 类内部添加并且类继承IjudgeEvent
		private Judge _judge;  
		public void setJudge(Judge obj) { _judge = obj; }  
		internal Judge getJudge() { return _judge; }

 

在GameModel中调用裁判计分功能

void Update () {
		for (int i = 0; i < disks.Count; i++) {
			if (!disks [i].activeInHierarchy) {
				scene.getJudge ().scoreADisk ();
				freeDisk (i);
			} else if (disks [i].transform.position.y < 0) {
				scene.getJudge ().failADisk ();
				freeDisk (i);
			}
		}
		if (disks.Count == 0) {
			shooting = false;
		}
	}

 

设置关卡

在SceneControllerBaseCode中添加关卡信息,通过添加loadRoundData来完成每个关卡对游戏对象属性的设置。

public void loadRoundData(int round) {  
		switch(round) {  
		case 1:     // 第一关  
			color = Color.green;  
			emitPos = new Vector3(-2.5f, 0.2f, -5f);  
			emitDir = new Vector3(24.5f, 40.0f, 67f);  
			speed = 4;  
			SceneController.getInstance().getGameModel().setting(1, color, emitPos, emitDir.normalized, speed, 1);  
			break;  
		case 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);  
			break;
		case 3:     // 第二关  
			color = Color.yellow;  
			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, 3);  
			break;  
		}  
	}

 

游戏效果:

技术分享

 

参考链接:

http://blog.csdn.net/simba_scorpio/article/details/51051241

 

以上是关于Unity游戏设计之飞碟游戏的主要内容,如果未能解决你的问题,请参考以下文章

飞碟的英文

Unity第三人称射击游戏开发过程之瞄准状态设计(TPS.S.P1)

Unity---游戏设计模式之单例模式

游戏设计模式之三 状态模式有限状态机 & Unity版本实现

游戏设计模式之三 状态模式有限状态机 & Unity版本实现

Unity3D 序列化与动态加载