赞
踩
1.靶对象为 5 环,按环计分;
2.箭对象,射中后要插在靶上
3.游戏仅一轮,无限 trials;
1.按空格键取箭,然后按住鼠标左键同时移动鼠标,箭头会跟着鼠标移动,此时松开左键,箭就会射出。射中后箭体会进行抖动。
2.靶上一共有5环,击中n环加n分,即5环加50分。
3.左上角会提示当前风力方向和强度,会影响箭的飞行轨迹。
靶上面要有5环,因此我在一个空对象Target下面创建了5个Cylinder子对象,各自带上Mesh Collider网格碰撞器,并且对Mesh Collider的Convex选项打勾,即为凸的网格,这样才能跟其他碰撞器产生碰撞作用。
对于箭的设置来说,首先在一个空对象Arrow下创建一个柱体Cylinder和正方体Cube构成箭身和箭头。给空对象Arrow加上刚体Rigidbody并且勾选Is Kinematic,即开始时候为运动学刚体;给箭身加碰撞器,箭头也加上加碰撞器并且勾选Is Trigger,同时挂载检测碰撞的脚本。
public class ArrowFlyActionManager : SSActionManager { private ArrowFlyAction fly; public controllor scene_controller; protected void Start() { scene_controller = (controllor)SSDirector.GetInstance().CurrentScenceController; scene_controller.action_manager = this; } public void ArrowFly(GameObject arrow,Vector3 wind,Vector3 f){ fly = ArrowFlyAction.GetSSAction(wind, f); this.RunAction(arrow, fly, this); } } public class ArrowFlyAction : SSAction { public Vector3 force; public Vector3 wind; private ArrowFlyAction() { } public static ArrowFlyAction GetSSAction(Vector3 wind,Vector3 force_) { ArrowFlyAction action = CreateInstance<ArrowFlyAction>(); action.force = force_; action.wind = wind; return action; } public override void Update(){} public override void FixedUpdate() { this.gameobject.GetComponent<Rigidbody>().AddForce(wind, ForceMode.Force); if (this.transform.position.z > 30 || this.gameobject.tag == "hit"){ this.destroy = true; this.callback.SSActionEvent(this,this.gameobject); } } public override void Start(){ gameobject.transform.LookAt(force); gameobject.GetComponent<Rigidbody>().isKinematic = false; gameobject.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse); gameobject.GetComponent<Rigidbody>().velocity = Vector3.zero; gameobject.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse); } }
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class controllor : MonoBehaviour, IUserAction, ISceneController { public scoreRecorder recorder; public arrowFactory arrow_factory; public ArrowFlyActionManager action_manager; public UserGUI user_gui; private GameObject arrow; private GameObject target; private bool game_start = false; private string wind = ""; private float wind_directX; private float wind_directY; public int GetScore(){return recorder.score;} public string GetWind(){return wind;} public void Restart(){SceneManager.LoadScene(0);} public void BeginGame(){game_start = true;} void Update (){if(game_start) arrow_factory.FreeArrow ();} public void LoadResources(){target = Instantiate(Resources.Load("Prefabs/target", typeof(GameObject))) as GameObject;} public bool haveArrowOnPort() {return (arrow != null);} void Start () { SSDirector director = SSDirector.GetInstance(); director.CurrentScenceController = this; arrow_factory = singleton<arrowFactory>.Instance; recorder = gameObject.AddComponent<scoreRecorder>() as scoreRecorder; user_gui = gameObject.AddComponent<UserGUI> () as UserGUI; action_manager = gameObject.AddComponent<ArrowFlyActionManager>() as ArrowFlyActionManager; LoadResources(); } public void create(){ if (arrow == null) { wind_directX = Random.Range(-4, 4); wind_directY = Random.Range(-4, 4); CreateWind(); arrow = arrow_factory.GetArrow (); } } public void MoveBow(Vector3 mousePos) { if (!game_start){return;} arrow.transform.LookAt(mousePos * 30); } public void Shoot(Vector3 mousePos) { if(game_start) { Vector3 wind = new Vector3(wind_directX, wind_directY, 0); action_manager.ArrowFly(arrow, wind,mousePos*30); arrow = null; } } public void CreateWind() { string Horizontal = "", Vertical = "", level = ""; if (wind_directX > 0) { Horizontal = "西"; } else if (wind_directX <= 0) { Horizontal = "东"; } if (wind_directY > 0) { Vertical = "南"; } else if (wind_directY <= 0) { Vertical = "北"; } if ((wind_directX + wind_directY) / 2 > -1 && (wind_directX + wind_directY) / 2 < 1) { level = "1 级"; } else if ((wind_directX + wind_directY) / 2 > -2 && (wind_directX + wind_directY) / 2 < 2) { level = "2 级"; } else if ((wind_directX + wind_directY) / 2 > -3 && (wind_directX + wind_directY) / 2 < 3) { level = "3 级"; } else if ((wind_directX + wind_directY) / 2 > -5 && (wind_directX + wind_directY) / 2 < 5) { level = "4 级"; } wind = Horizontal + Vertical + "风" + " " + level; } }
同样,设置一个usingList和unusedList,进行箭的循环利用。由场景控制器中的Update()方法让箭工厂每帧检测回收掉地的箭。箭掉地后,复位。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class arrowFactory : MonoBehaviour { public GameObject arrow = null; private List<GameObject> usingArrowList = new List<GameObject>(); private Queue<GameObject> unusedArrowList = new Queue<GameObject>(); public controllor sceneControler; public GameObject GetArrow() { if (unusedArrowList.Count == 0) arrow = Instantiate(Resources.Load<GameObject>("Prefabs/arrow")); else { arrow = unusedArrowList.Dequeue(); arrow.gameObject.SetActive(true); } arrow.transform.position = new Vector3 (0, 0, 0); usingArrowList.Add(arrow); return arrow; } public void FreeArrow() { for (int i = 0; i < usingArrowList.Count; i++) { if (usingArrowList[i].transform.position.y <= -8||usingArrowList[i].transform.position.y >= 8) { usingArrowList[i].GetComponent<Rigidbody>().isKinematic = true; usingArrowList[i].SetActive(false); usingArrowList[i].transform.position = new Vector3 (0, 0, 0); unusedArrowList.Enqueue(usingArrowList[i]); usingArrowList.Remove(usingArrowList[i]); i--; } } } }
此类挂载在箭头上。由于上面设置了箭头Is Trigger为true,即不会产生碰撞效果,所以永远不会触发OnCollisionEnter方法。因此,需要将碰撞进入方法调用void OnTriggerEnter。为了防止多次触发OnTriggerEnter方法,需要设置箭头为inactive。由于触碰后整个箭组合会变为运动学刚体而静止,所以箭身会插在靶上。最后根据射中的位置与中心点距离确定环数。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class arrowCollider : MonoBehaviour { public controllor scene_controller; public scoreRecorder recorder; void Start() { scene_controller = SSDirector.GetInstance().CurrentScenceController as controllor; recorder = singleton<scoreRecorder>.Instance; } void OnTriggerEnter(Collider c) { if (c.gameObject.name == "T1"||c.gameObject.name == "T2"||c.gameObject.name == "T3"||c.gameObject.name == "T4"||c.gameObject.name == "T5") { gameObject.transform.parent.gameObject.GetComponent<Rigidbody>().isKinematic = true; gameObject.SetActive(false); float point = Mathf.Sqrt (this.gameObject.transform.position.x * this.gameObject.transform.position.x + this.gameObject.transform.position.y * this.gameObject.transform.position.y); recorder.Record(5-(int)Mathf.Floor(point*2)); } } }
public class ArrowTremble : SSAction { float radian = 0; float per_radian = 3f; float radius = 0.01f; Vector3 old_pos; public float left_time = 0.8f; private ArrowTremble() { } public override void Start(){old_pos = transform.position;} public static ArrowTremble GetSSAction(){ ArrowTremble action = CreateInstance<ArrowTremble>(); return action; } public override void Update(){ left_time -= Time.deltaTime; if (left_time <= 0){ transform.position = old_pos; this.destroy = true; this.callback.SSActionEvent(this); } radian += per_radian; float dy = Mathf.Cos(radian) * radius; transform.position = old_pos + new Vector3(0, dy, 0); } public override void FixedUpdate(){} }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UserGUI : MonoBehaviour { private IUserAction action; GUIStyle style = new GUIStyle(); GUIStyle style2 = new GUIStyle(); private bool game_start = false; void Start () { action = SSDirector.GetInstance().CurrentScenceController as IUserAction; style.normal.textColor = new Color(0, 0, 0, 1); style.fontSize = 16; style2.normal.textColor = new Color(1, 1, 1); style2.fontSize = 25; } void Update() { if(game_start) { if (action.haveArrowOnPort ()) { if (Input.GetMouseButton(0)) { Vector3 mousePos = Camera.main.ScreenPointToRay(Input.mousePosition).direction; action.MoveBow(mousePos); } if (Input.GetMouseButtonUp(0)) { Vector3 mousePos = Camera.main.ScreenPointToRay(Input.mousePosition).direction; action.Shoot(mousePos); } } if (Input.GetKeyDown(KeyCode.Space)) action.create(); } } private void OnGUI() { if(game_start) { GUI.Label(new Rect(10, 5, 200, 50), "分数:", style); GUI.Label(new Rect(55, 5, 200, 50), action.GetScore().ToString(), style); GUI.Label(new Rect(Screen.width - 170, 30, 200, 50), "风向: ", style); GUI.Label(new Rect(Screen.width - 110, 30, 200, 50), action.GetWind(), style); if (GUI.Button(new Rect(Screen.width - 170, 0, 100, 30), "重新开始")) { action.Restart(); return; } } else { GUI.Label(new Rect(Screen.width / 2 - 80, Screen.width / 2 - 320, 100, 100), "Arrow Shooting", style2); if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "游戏开始")) { game_start = true; action.BeginGame(); } } } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public interface ISceneController { void LoadResources(); } public interface IUserAction { void MoveBow(Vector3 mousePos); void Shoot (Vector3 mousePos); int GetScore(); void Restart(); string GetWind(); void BeginGame(); void create (); bool haveArrowOnPort(); }
最后是计分器类以及导演类、单例类等框架,与前几次基本相同。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class scoreRecorder : MonoBehaviour {
public int score;
void Start(){score = 0;}
public void Record(int point){score = point + score;}
}
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SSDirector : System.Object { private static SSDirector _instance; public ISceneController CurrentScenceController { get; set; } public static SSDirector GetInstance() { if (_instance == null) { _instance = new SSDirector(); } return _instance; } }
using System.Collections; using System.Collections.Generic; using UnityEngine; public class singleton<T> : MonoBehaviour where T : MonoBehaviour { protected static T instance; public static T Instance { get { if (instance == null) { instance = (T)FindObjectOfType(typeof(T)); if (instance == null) { Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but there is none."); } } return instance; } } }
最后在屏幕中加上一个小的摄像头,可以看到箭射中靶心的情况。
这样就完成了打靶游戏的设计。
总的来说3D作业越来越让我认识到设计的过程中一个好的架构的重要性。几次作业中使用了相同的MVC架构,并且将动作分离,这样加设计的过程中就可以将游戏的每个部分分开来设计,最后通过架构组合到一起,这时的设计的过程中每个部分的作用都很清晰,出现的问题也可以有针对性地解决,十分强大.
视频展示:打靶游戏
GitHub地址:ArrowShooting
最后感谢师兄的博客!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。