打靶游戏要求:
1.靶对象为5环,按环计分;
2.箭对象,射中后要插在靶上;
3.游戏仅一轮,无限trials;
4.增强要求:添加一个风向个强度标志,提高难度;
游戏演示:
代码结构:
设计思路与细节:
1.构建基本游戏对象:
这里的主要游戏对象有两个,分别是靶子和箭。首先说一下靶子,如何通过unity的基本游戏对象创建一个靶子使得箭可以识别靶的不同环?在这里我通过将几个半径不同的扁圆柱体共圆心,且每一个的厚度由内向外递减(以比较小的差别),再配以不同颜色,就生成了一个近似现实世界的靶子。
下面是靶的各个部分的详细属性,其中bart为空对象,用来裝靶的各个部分,每一个部分有独立的碰撞器,这里我用Mesh Collider,这样方便我们后面识别箭打在靶上时打到了第几环。
然后是箭的设计,在这里我用几个基本物体组合起来,再调节一下大小和位置属性,其中脚本ArrowScript挂在箭上,用于识别触发并将信息传入ScoreRecorder进行计分。
2.射出箭矢:
在这里,我的思路是点击屏幕,箭矢能够向我所点击的方向射出。在这里,我用课上老师所讲的射线Ray来实现。在屏幕中(相机为透视模式),屏幕每一个点p对应标准面上的一个点p',射线就利用这个原理。我通过摄像机调用ScreenPointToRay函数,能得到从相机通过鼠标所点的屏幕点的光线
,这样我们就有了一个理想的射击方向。代码实现如下(在UserInterface中):
if (Input.GetMouseButtonDown(0)) {
Ray mouseRay = camera.ScreenPointToRay (Input.mousePosition);
Debug.Log("RayDir = " + mouseRay.direction);
Controller.shootArrow (mouseRay.direction);
}
顺便,我还实现了箭头也能面向点击方向,只需设置箭的up或forward等为射线方向即可(具体根据箭对象的头是up还是forward设置)。
3.触发识别(ArrowScript与ScoreRecorder):
在这里我用OnTriggerEnter来实现箭与靶碰撞的识别。根据上面介绍,我的靶是不同半径的圆柱体重叠而成,因为帧数与运动的原因,这样就有可能触发多个不同的环。那我就储存第一个碰到的环即可,只要曾经存过,就不再存储(我存环的名字来区别不同的环,为bartNum)。ArrowScript中代码如下:
void OnTriggerEnter(Collider other) {;
if (bartNum == "") {
bartNum = other.gameObject.name;
Debug.Log ("arrowTrigger = " + bartNum);
Destroy (GetComponent<Rigidbody>()); //使得箭停在靶子上
Component []comp = GetComponentsInChildren<CapsuleCollider>();
foreach (CapsuleCollider i in comp) {
i.enabled = false;
}
//使得箭不会触发靶子上的箭
GetComponent<MeshCollider>().isTrigger = false;
recorder.countScore (bartNum);
//记录分数
}
}
ScoreRecorder中相关代码如下:
public void countScore(string type) {
if (type == "circle01")
Score += 10;
else if (type == "circle02")
Score += 8;
else if (type == "circle03")
Score += 6;
else if (type == "circle04")
Score += 4;
else if (type == "circle05")
Score += 2;
Debug.Log ("Score = " + Score);
}
4.计时机制回收箭矢(ArrowScript和ArrowFactory):
因为要求箭矢要留在靶上,所以决不能在一碰撞就回收箭矢。为了实现工厂回收功能,我通过设置一个计时器来满足两个需求。ArrowScript中的代码如下:
private float time; //计时器
void Update () {
time += Time.deltaTime;
if (time >= LIMITTIME) {
this.gameObject.SetActive (false);
initial ();//重置变量值
}
}
ArrowFactory中相关代码如下:
private void freeArrow() {
for (int i = 0; i < UsedArrow.Count; i++) {
GameObject temp = UsedArrow [i];
if (!temp.activeInHierarchy) {
UsedArrow.RemoveAt (i);
FreeArrow.Add (temp);
}
}
}
void Update () {
Debug.Log ("Used :" + UsedArrow.Count);
Debug.Log ("Free :" + FreeArrow.Count);
freeArrow ();
}
完整代码:
1.UserInterface:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UserInterface : MonoBehaviour {
private SceneController Controller;
public Camera camera;
public Text Score;
public Text WindForce;
public Text WindDirection;
// Use this for initialization
void Start () {
Controller = (SceneController)FindObjectOfType(typeof(SceneController));
Debug.Log ("Controller = "+ Controller);
}
// Update is called once per frame
void Update () {
Score.text = "Score : " + Controller.getRecorder ().getScore (); //显示分数
float force = Controller.getActionManager ().getWindForce ();
if (force < 0) {
WindDirection.text = "Wind Direction : Left";
} else if (force > 0) {
WindDirection.text = "Wind Direction : Right";
} else {
WindDirection.text = "Wind Direction : No Wind";
}
//显示风向
WindForce.text = "Wind Force : " + Controller.getActionManager ().getWindForce (); //显示风力
if (Input.GetMouseButtonDown(0)) {
Ray mouseRay = camera.ScreenPointToRay (Input.mousePosition);
Debug.Log("RayDir = " + mouseRay.direction);
Controller.shootArrow (mouseRay.direction);//以点击的方向发射箭矢
}
}
}
2.Director:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Director : System.Object {
private static Director _instance;
public SceneController Controller{ get; set;}
public static Director getinstance() {
if (_instance == null) {
_instance = new Director ();
}
return _instance;
}
public int getFPS() {
return Application.targetFrameRate;
}
public void setFPS(int fps) {
Application.targetFrameRate = fps;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneController : MonoBehaviour {
private ActionManager actionManager;
private ScoreRecorder scoreRecorder;
private ArrowFactory arrowFactory;
void Awake() {
Director director = Director.getinstance ();
director.setFPS (60);
director.Controller = this;
actionManager = (ActionManager)FindObjectOfType (typeof(ActionManager));
scoreRecorder = (ScoreRecorder)FindObjectOfType (typeof(ScoreRecorder));
arrowFactory = (ArrowFactory)FindObjectOfType (typeof(ArrowFactory));
Debug.Log("Controller");
}
public ArrowFactory getFactory() {
return arrowFactory;
}
public ScoreRecorder getRecorder() {
return scoreRecorder;
}
public ActionManager getActionManager() {
return actionManager;
}
public void shootArrow(Vector3 dir) {
GameObject arrow = arrowFactory.getArrow (); //获得箭矢
arrow.transform.position = new Vector3(0, 2, 0); //设置箭矢初始位置
actionManager.shoot (arrow, dir);//调用ActionManager实现动作细节
}
}
4.ArrowFactory:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowFactory : MonoBehaviour {
private List<GameObject> UsedArrow; //储存使用中的箭矢
private List<GameObject> FreeArrow; //储存空闲箭矢
private GameObject arrowPrefab;
void Awake() {
arrowPrefab = Instantiate(Resources.Load ("Prefabs/arrow")) as GameObject;
arrowPrefab.SetActive (false);
FreeArrow = new List<GameObject> ();
UsedArrow = new List<GameObject> ();
Debug.Log ("ArrowFactory");
}
// Update is called once per frame
public GameObject getArrow() {
GameObject temp;
if (FreeArrow.Count == 0) {
temp = GameObject.Instantiate (arrowPrefab) as GameObject;
temp.SetActive (true);
//如果空闲箭矢中没有箭矢,就生成新的箭矢
} else {
temp = FreeArrow [0];
temp.SetActive (true);
if (temp.GetComponent<Rigidbody>() == null)
temp.AddComponent<Rigidbody> ();
Component []comp = temp.GetComponentsInChildren<CapsuleCollider>();
foreach (CapsuleCollider i in comp) {
i.enabled = true;
}
temp.GetComponent<MeshCollider>().isTrigger = true;
Debug.Log ("temp = " + temp);
FreeArrow.RemoveAt (0);
//空闲箭矢中有箭矢,初始化相关属性,加入使用
}
UsedArrow.Add (temp);
return temp;
}
private void freeArrow() {
for (int i = 0; i < UsedArrow.Count; i++) {
GameObject temp = UsedArrow [i];
if (!temp.activeInHierarchy) {
UsedArrow.RemoveAt (i);
FreeArrow.Add (temp);
}
}
}
void Update () {
Debug.Log ("Used :" + UsedArrow.Count);
Debug.Log ("Free :" + FreeArrow.Count);
freeArrow ();
}
}
5.ActionManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ActionManager : MonoBehaviour {
private float speed = 40f; //箭矢初速
private float Force = 0f; //第一箭风力默认为0
void Awake() {
Debug.Log ("ActionManager");
}
public void shoot(GameObject arrow, Vector3 dir) {
arrow.transform.up = dir;
//设置一下箭的朝向
arrow.GetComponent<Rigidbody> ().velocity = dir * speed; //设置箭矢的速度。
wind (arrow);
Force = Random.Range(-100,100); //获取随机的风力
}
private void wind(GameObject arrow) {
Debug.Log ("AddForce");
arrow.GetComponent<Rigidbody> ().AddForce (new Vector3 (Force, 0, 0), ForceMode.Force); //对箭矢施加恒定的风力
}
public float getWindForce() {
return Force;
}
}
6.ScoreRecorder:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreRecorder : MonoBehaviour {
private int Score = 0;//初始分数为0
public void countScore(string type) {
if (type == "circle01")
Score += 10;
else if (type == "circle02")
Score += 8;
else if (type == "circle03")
Score += 6;
else if (type == "circle04")
Score += 4;
else if (type == "circle05")
Score += 2;
Debug.Log ("Score = " + Score);
//不同环不同分
}
public int getScore() {
return Score;
}
}
7.ArrowScript:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowScript : MonoBehaviour {
private string bartNum;
private ScoreRecorder recorder;
private float time; //计时器
private static float LIMITTIME = 4;
void initial() {
time = 0;
bartNum = "";
}
void OnTriggerEnter(Collider other) {;
if (bartNum == "") {
bartNum = other.gameObject.name;
Debug.Log ("arrowTrigger = " + bartNum);
Destroy (GetComponent<Rigidbody>()); //使得箭停在靶子上
Component []comp = GetComponentsInChildren<CapsuleCollider>();
foreach (CapsuleCollider i in comp) {
i.enabled = false;
}
//使得箭不会触发靶子上的箭
GetComponent<MeshCollider>().isTrigger = false;
recorder.countScore (bartNum);
//记录分数
}
}
public string getBartNum() {
return bartNum;
}
// Use this for initialization
void Awake () {
initial ();
recorder = (ScoreRecorder)FindObjectOfType (typeof(ScoreRecorder));
}
// Update is called once per frame
void Update () {
time += Time.deltaTime;
if (time >= LIMITTIME) {
this.gameObject.SetActive (false);
initial ();//重置变量值
}
}
}