赞
踩
靶子是由五个同心的圆柱体组成的预制体,每个圆柱体代表一个环,每个环的分值不一样。并且每个圆柱体都需要添加MeshCollider组件,并设置为IsTrigger,用来检测箭是否中靶。详细设置:
箭主要有两部分,箭身以及子对象箭头,设置标签为arrow详细设置如下图:
弓同样由两部分组成,弓本身以及一个架在弓上的箭,所以需要添加一个箭的预制:
箭的多种动作类的共同父类,使得可以统一管理箭的多种动作:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SSAction : ScriptableObject { public bool enable = true; //是否正在进行此动作 public bool destroy = false; //是否需要被销毁 public GameObject gameobject; //动作对象 public Transform transform; //动作对象的transform public ISSActionCallback callback; //动作完成后的消息通知者 protected SSAction() { } //子类可以使用下面这两个函数 public virtual void Start() { throw new System.NotImplementedException(); } public virtual void Update() { throw new System.NotImplementedException(); } public virtual void FixedUpdate() { throw new System.NotImplementedException(); } }
通过一个作为初始动力的冲量pulseForce和一个持续的风力windForce驱使箭飞行。在箭中靶或者脱靶,即飞行动作结束之后,回调箭的颤抖动作:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ArrowShootAction : SSAction { private Vector3 pulseForce; //射箭提供的冲量 private Vector3 windForce; //风力 private ArrowShootAction(){} public static ArrowShootAction GetSSAction(Vector3 wind) { ArrowShootAction shootarrow = CreateInstance<ArrowShootAction>(); shootarrow.pulseForce = new Vector3(0,0,25); shootarrow.windForce = wind; return shootarrow; } // Start is called before the first frame update public override void Start() { //摆脱跟随弓移动 gameobject.transform.parent = null; gameobject.GetComponent<Rigidbody>().velocity = Vector3.zero; //添加初始的冲量 gameobject.GetComponent<Rigidbody>().AddForce(pulseForce,ForceMode.Impulse); //关闭运动学控制 gameobject.GetComponent<Rigidbody>().isKinematic = false; } // Update is called once per frame public override void Update() { } public override void FixedUpdate(){ //添加风力 this.gameobject.GetComponent<Rigidbody>().AddForce(windForce,ForceMode.Force); //射击动作结束,即中靶或者脱靶,回调颤抖动作 if(this.transform.position.z > 35 || this.gameobject.tag == "onTarget" ) { this.destroy = true; this.callback.SSActionEvent(this,1,this.gameobject); } } }
围绕箭的头部进行细微的运动,实现颤抖的效果,该动作在结束时也会调用回调函数,不过没有下一步的动作,所以回调的函数为空;
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ArrowTrembleAction : SSAction { float tremble_radius = 1.2f;//颤抖的程度 float tremble_time = 1.0f; //颤抖的时间 Vector3 arrow_pos; //箭的原位置 private ArrowTrembleAction(){} //放回一个颤抖的动作 public static ArrowTrembleAction GetSSAction() { ArrowTrembleAction tremble_action = CreateInstance<ArrowTrembleAction>(); return tremble_action; } // Start is called before the first frame update public override void Start() { //得到箭中靶时的位置 arrow_pos = this.transform.position; } //实现箭的颤抖动作 // Update is called once per frame public override void Update() { //更新时间,得到剩余的颤抖时间 tremble_time -= Time.deltaTime; //需要继续颤抖 if(tremble_time > 0){ ///获取头部的位置 Vector3 head_pos = this.transform.GetChild(0).position; //围绕箭头颤抖 this.transform.RotateAround(head_pos,tremble_radius); } else{ //将箭返回一开始的位置 transform.position = arrow_pos; //开始销毁动作 this.destroy = true; //通过接口回调,调用新的动作,实际上没有新的动作,回调为空 this.callback.SSActionEvent(this); } } public override void FixedUpdate() { } }
该类维护着一个动作队列actions,在每次更新时,即Update()或者FixedUpdate(),都会添加新的动作,或者删除需要销毁的动作,并运行需要更新的动作;同时提供了一个添加新动作的方法RunAction和回调接口SSActionEvent;
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SSActionManager : MonoBehaviour,ISSActionCallback { private Dictionary<int,SSAction> actions = new Dictionary<int,SSAction>(); //执行动作的字典集 //等待添加的动作 private List<SSAction> waitingAdd = new List<SSAction>(); //等待销毁的动作 private List<int> waitingDelete = new List<int>(); // Start is called before the first frame update // Update is called once per frame protected void Update() { //添加新增的动作 for(int i = 0; i < waitingAdd.Count; ++i) { actions[waitingAdd[i].GetInstanceID()] = waitingAdd[i]; } waitingAdd.Clear(); //销毁需要删除的动作 foreach (KeyValuePair<int,SSAction> kv in actions){ SSAction t_ac = kv.Value; if(t_ac.destroy) { waitingDelete.Add(t_ac.GetInstanceID()); } //动作不需要销毁则更新 else if(t_ac.enable) { t_ac.Update(); } } //销毁动作 foreach (int key in waitingDelete){ SSAction ac = actions[key]; actions.Remove(key); DestroyObject(ac); } waitingDelete.Clear(); } protected void FixedUpdate() { for(int i = 0; i < waitingAdd.Count; ++i) { actions[waitingAdd[i].GetInstanceID()] = waitingAdd[i]; } waitingAdd.Clear(); foreach (KeyValuePair<int,SSAction> kv in actions){ SSAction t_ac = kv.Value; if(t_ac.destroy) { waitingDelete.Add(t_ac.GetInstanceID()); } else if(t_ac.enable) { t_ac.FixedUpdate(); } } foreach (int key in waitingDelete){ SSAction ac = actions[key]; actions.Remove(key); DestroyObject(ac); } waitingDelete.Clear(); } //新增一个动作,运行该动作 public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager) { action.gameobject = gameobject; action.transform = gameobject.transform; action.callback = manager; waitingAdd.Add(action); action.Start(); } //回调新的动作类型 public void SSActionEvent(SSAction source, int param = 0,GameObject arrow = null) { //执行完飞行动作,开始颤抖动作。 if(param == 1) { ArrowTrembleAction tremble = ArrowTrembleAction.GetSSAction(); RunAction(arrow,tremble,this); } else{ } } }
该类继承了SSActionManager,提供了一个方法ArrowShoot,这个方法调用父类的RunAction方法执行射击动作。
using System.Collections; using System.Collections.Generic; using UnityEngine; // public class ArrowShootActionManager : SSActionManager { //箭飞行的动作 private ArrowShootAction shoot; public FirstSceneController scene_controller; //当前场景的场景控制器 protected void Start() { //设置firstscenecontroller的arrow——manager scene_controller = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController; scene_controller.arrow_manager = this; } //箭飞行 public void ArrowShoot(GameObject arrow,Vector3 wind) { //实例化一个飞行动作。 shoot = ArrowShootAction.GetSSAction(wind); //调用SSActionmanager的方法运行动作。 this.RunAction(arrow, shoot, this); } }
提供一个单实例,用来实现界面的统一管理,此游戏只需要一个界面。
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;
}
}
用来管理箭的生产和回收,主要提供两个方法,GetArrow()和RecycleArrow()分别用来得到空闲的箭和回收重复利用使用过的箭:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class ArrowFactory : MonoBehaviour { //空闲队列 private Queue<GameObject> FreeArrow = new Queue<GameObject>(); //使用的箭的队列 private List<GameObject> UsedArrow = new List<GameObject>(); public FirstSceneController scenecontrolller; public GameObject arrow = null; public GameObject GetArrow(){ if(FreeArrow.Count == 0){ arrow = Instantiate(Resources.Load<GameObject>("Prefabs/arrow")); } else{ arrow = FreeArrow.Dequeue(); if(arrow.tag == "onTarget")//箭在靶子上 {//使用动力学控制 arrow.GetComponent<Rigidbody>().isKinematic = false; arrow.tag = "arrow"; arrow.transform.GetChild(0).gameObject.SetActive(true); } arrow.gameObject.SetActive(true); } scenecontrolller = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController; //得到弓箭上搭箭的位置 Transform bow_mid = scenecontrolller.bow.transform.GetChild(0); //将箭的位置设置为弓中间的位置 arrow.transform.position = bow_mid.transform.position; //箭随弓的位置变化 arrow.transform.parent = scenecontrolller.bow.transform; UsedArrow.Add(arrow); return arrow; } public void RecycleArrow(GameObject arrow) { for(int i = 0 ; i < UsedArrow.Count; ++i) { if(arrow.GetInstanceID() == UsedArrow[i].gameObject.GetInstanceID())/// { //UsedArrow[i].gameObject.SetActive(false); FreeArrow.Enqueue(UsedArrow[i]); UsedArrow.RemoveAt(i); break; } } } }
将相机的位置与弓的位置之间维持固定的位移偏差offset,即随着弓移动:
using UnityEngine; using System.Collections; public class CameraFlow : MonoBehaviour { public GameObject bow; //跟随的物体 public float smothing = 5f; //相机跟随的速度 Vector3 offset; //相机与物体相对偏移位置 void Start() { //设置相机与弓箭的位移差 offset = new Vector3(2,0,-6); } void FixedUpdate() { //相机的目标位置 Vector3 target = bow.transform.position + offset; //摄像机自身位置到目标位置平滑过渡 transform.position = Vector3.Lerp(transform.position, target, smothing * Time.deltaTime); } }
在小窗口更加详细的展示箭的中靶位置和箭的颤抖动作,设置的展示时间为每次射击后的三秒钟:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SecondCamera : MonoBehaviour { public float show_time = 3f; public bool show = false; void Update(){ if(show == true){ show_time -= Time.deltaTime; //展示时间超过3秒,停止展示 if(show_time < 0){ show = false; this.gameObject.SetActive(false); } } } //展示服相机 public void ShowCamera() { show = true; this.gameObject.SetActive(true); show_time = 3f; } }
主要有一个成员变量RingScore用来标记当前环的分值,需要在Inspector窗口设置相应的分值;还有一个触发器检测,用来检查是否有箭击中该靶。如果有箭中靶,需要更改标签,以便调用相应的动作:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RingController : MonoBehaviour { //当前环的分值 public int RingScore = 0; public ISceneController scene; public ScoreRecorder sc_recorder; // Start is called before the first frame update void Start() { scene = SSDirector.GetInstance().CurrentScenceController as FirstSceneController; sc_recorder = Singleton<ScoreRecorder>.Instance; } // Update is called once per frame void Update() { } //碰撞检测,如果箭击中该环,就响应。 void OnTriggerEnter(Collider arrow_head){ //得到箭身 Transform arrow = arrow_head.gameObject.transform.parent; if(arrow == null) { return ; } //有箭中靶 if(arrow.tag == "arrow"){ //将箭的速度设为0 arrow.GetComponent<Rigidbody>().velocity = new Vector3(0,0,0); //使用运动学运动控制 arrow.GetComponent<Rigidbody>().isKinematic = true; //计分 sc_recorder.RecordScore(RingScore); //将箭头设置为消失, arrow_head.gameObject.SetActive(false);/ //标记箭为中靶 arrow.tag = "onTarget"; } } }
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; } } }
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ISceneController{
void LoadResources();
}
//箭的飞行和颤抖动作的回调接口
public interface ISSActionCallback
{
void SSActionEvent(SSAction source,int param = 0, GameObject arrow = null);
}
显示相应的信息,以及响应玩家的动作,如点击开始游戏的按钮,和通过方向键控制弓的移动,点击空格箭发射。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class UserGUI : MonoBehaviour { //private IUserAction action; private FirstSceneController action; GUIStyle tip_style = new GUIStyle(); GUIStyle wind_style = new GUIStyle(); GUIStyle button_style = new GUIStyle(); GUIStyle state_style = new GUIStyle(); private int game_state = 0; //1-》游戏结束 0->游戏正在进行 // Start is called before the first frame update void Start() { action = this.transform.gameObject.GetComponent<FirstSceneController>(); tip_style.fontSize = 16; tip_style.normal.textColor = new Color(1,1,1,1); wind_style.normal.textColor = new Color(1,0.8f,0.8f,1); wind_style.fontSize = 16; button_style.normal.textColor = new Color(1,0.5f,0.7f,1); button_style.fontSize = 20; state_style.normal.textColor = new Color(0.9f,0.7f,0.5f,1); state_style.fontSize = 30; game_state = 0;//游戏状态 0->未开始 1-》开始 } // Update is called once per frame //获取方向键的偏移量并移动弓 void Update() { if(game_state == 1) { //点击空格键射击 if(Input.GetKeyDown(KeyCode.Space)) { action.ShootArrow(); } //获取方向键的位移 float transY = Input.GetAxis("Vertical"); float transX = Input.GetAxis("Horizontal"); //移动弓相应的位移 action.MoveBow(transX,transY); // } } // private void OnGUI() {//游戏已经开始 if(game_state == 1) { //游戏正在进行 if(action.GetGameState() == 1) // { //显示各种信息 GUI.Label(new Rect(20,10,150,50),"Score: ",button_style); GUI.Label(new Rect(100,10,100,50),action.GetScore().ToString(),state_style); GUI.Label(new Rect(200,10,100,50),"wind: ",wind_style); Vector3 wind = action.GetWind(); string wind_data = "(" + wind.x +"," + wind.y + ",0)"; GUI.Label(new Rect(250,10,100,50),wind_data,wind_style); GUI.Label(new Rect(20,50,150,50),"ArrowNum: ",button_style); GUI.Label(new Rect(120,50,100,50),action.GetArrowNum().ToString(),button_style); //重新开始的按钮 if (GUI.Button(new Rect(80,100, 100, 50), "重新开始")) { action.Restart(); } } else{ GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 250, 100, 100), "游戏结束", button_style); } } //游戏未开始,检测开始按键 else{ GUI.Label(new Rect(Screen.width / 2 - 150, Screen.width / 2 - 370, 100, 100), "Bowman", wind_style); //开始按键如果按下,游戏开始 if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "游戏开始")) { game_state = 1; action.BeginGame(); } GUI.Label(new Rect(Screen.width / 2 - 100, Screen.width / 2 - 320, 400, 100), "使用方向键控制弓箭移动,点击空格键射箭", wind_style); } } }
获取所需要的资源,响应玩家交互类传过来的动作,更新风向,分数等信息,调用箭的动作等等。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class FirstSceneController : MonoBehaviour,ISceneController { //箭的动作管理者 public ArrowShootActionManager arrow_manager; //箭的工厂 public ArrowFactory factory; //副相机 public Camera second_camera; //主相机 public Camera main_camera; //记录分数 public ScoreRecorder recorder; //弓 public GameObject bow; //靶 public GameObject target; //风 public Vector3 wind; //箭 public GameObject arrow; private int arrow_num = 0; //射出的箭数 //箭的队列 private List<GameObject> arrows = new List<GameObject>(); private int gameState = 0; //0-游戏未开始,1-》游戏进行 //加载箭和靶子 public void LoadResources() { bow = Instantiate(Resources.Load("Prefabs/bow",typeof(GameObject))) as GameObject; target = Instantiate(Resources.Load("Prefabs/target",typeof(GameObject))) as GameObject; } //进行资源加载 // Start is called before the first frame update void Start() { SSDirector director = SSDirector.GetInstance(); factory = Singleton<ArrowFactory>.Instance; recorder = Singleton<ScoreRecorder>.Instance; director.CurrentScenceController = (ISceneController)this; arrow_manager = this.gameObject.AddComponent<ArrowShootActionManager>() as ArrowShootActionManager; LoadResources(); main_camera.GetComponent<CameraFlow>().bow = bow; float windx = Random.Range(-2,2); float windy = Random.Range(-2,2); wind = new Vector3(windx,windy,0); } // Update is called once per frame void Update() { if(gameState == 1) { for(int i = 0; i < arrows.Count; ++i) { //当前界面如果箭飞出靶外或者数量大于3则回收 GameObject t_arrow = arrows[i]; if(t_arrow.transform.position.z > 35 || arrows.Count > 3) { factory.RecycleArrow(t_arrow); arrows.RemoveAt(i); } } } } //通过方向键移动弓 public void MoveBow(float transX, float transY) { //限制弓的位置在一定范围内,x,y的绝对值小于5,z为-5; if(gameState == 1) { bow.transform.position = new Vector3(bow.transform.position.x,bow.transform.position.y,-5); if(bow.transform.position.x > 5) { bow.transform.position = new Vector3(5,bow.transform.position.y,bow.transform.position.z); return; } if(bow.transform.position.x < -5) { bow.transform.position = new Vector3(-5,bow.transform.position.y,bow.transform.position.z); return; } if(bow.transform.position.y > 5) { bow.transform.position = new Vector3(bow.transform.position.x,5,bow.transform.position.z); return; } if(bow.transform.position.y < -5) { bow.transform.position = new Vector3(bow.transform.position.x,-5,bow.transform.position.z); return; } //移动特定的距离 bow.transform.Translate(new Vector3(0,transY,-transX)*Time.deltaTime); } } //射箭 public void ShootArrow() { if(gameState == 1) { //从工厂得到箭 arrow = factory.GetArrow(); arrows.Add(arrow); //通过管理类发射动作 arrow_manager.ArrowShoot(arrow,wind); //开启副相机 second_camera.GetComponent<SecondCamera>().ShowCamera(); arrow_num += 1; //每次射击后风向改变; float windx = Random.Range(-2,2); float windy = Random.Range(-2,2); wind = new Vector3(windx,windy,0); } } //返回当前分数 public int GetScore() { return recorder.score; } //得到风向 public Vector3 GetWind() { return wind; } //得到射出的箭的数量 public int GetArrowNum() { return arrow_num; } //重新开始游戏 public void Restart() { arrow_num = 0; recorder.score = 0; for(int i = 0; i < arrows.Count; ++i){ factory.RecycleArrow(arrows[i]); } arrows.Clear(); } //开始游戏 public void BeginGame() { gameState = 1; } //得到游戏的状态 public int GetGameState() { return gameState; } }
需要添加一个新的相机作为副相机,需要设置副相机Viewport Rect属性,设置为小窗口显示:并添加脚本SecondCamera,同时主相机也需要添加脚本CameraFlow:
创建一个空对象,并添加和设置相应脚本:
每个环都要添加RingController,并设置RingScore;
Unity3D射箭
点击Scenes/BowAndArrow可运行项目;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。