游戏内容要求:
1.靶对象为 5 环,按环计分;
2.箭对象,射中后要插在靶上
- 增强要求:射中后,箭对象产生颤抖效果,到下一次射击 或 1秒以后
3.游戏仅一轮,无限 trials;
- 增强要求:添加一个风向和强度标志,提高难度
游戏设计:
预制件构建
-
弓
-
靶
- T1
- T2:和T1一样,只是scale的x和z改成2
- T3:和T1一样,只是scale的x和z改成1.5
- T4:和T1一样,只是scale的x和z改成1
- T5:和T1一样,只是scale的x和z改成0.5
- T1
值得一提的是,材质交替使用深色和浅色木纹更好看!
- 箭
-
父对象
-
Body
-
Head:由于技术不够好,只能显式的使用了一个cube,不过好在足够小,不影响美观
-
- 地面:普通的plane加上材质
脚本挂载
代码
- SSDirector:导演类,前面几次作业用过很多次
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;
}
}
- ArrowFactory :收发箭,跟飞碟游戏里的差不多
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowFactory : MonoBehaviour
{
private GameObject arrow = null;
private GameObject AB = null;
private List<GameObject> usingArrowList = new List<GameObject>();
private Queue<GameObject> unusedArrowList = new Queue<GameObject>();
public Controllor sceneControler;
public void initiate(GameObject AB_)
{
AB = AB_;
}
public GameObject GetArrow()
{
//还原工厂
AB.transform.SetPositionAndRotation(new Vector3(0, 0, 0), Quaternion.Euler(0, 90, 0));
AB.transform.localScale = new Vector3(1, 1, 1);
if (unusedArrowList.Count == 0)
{
arrow = Instantiate(Resources.Load<GameObject>("Prefabs/Arrow"));
}
else
{
arrow = unusedArrowList.Dequeue();
arrow.gameObject.SetActive(true);
}
arrow.transform.parent = AB.transform;//将弓箭作为AB的子物体
arrow.transform.position = new Vector3(0, 0, 0);
arrow.transform.rotation = Quaternion.Euler(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 <= -7 || usingArrowList[i].transform.position.y >= 7)
{
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--;
}
}
}
}
- Controllor :这里借鉴了师兄的风控代码,重点是弓箭的绕轴旋转、运动和碰撞
- MoveBow函数让弓箭绕交点随着鼠标的位置反方向扰动,这也是符合我们实际操作弓箭的动作
- Shoot函数为Arrow添加力,在物理引擎的带动下弓箭仿真运动
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 AB;
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;
AB = Instantiate(Resources.Load("Prefabs/AB", typeof(GameObject))) as GameObject;
}
public bool haveArrowOnPort() { return (arrow != null); }
void Start()
{
SSDirector director = SSDirector.GetInstance();
director.CurrentScenceController = this;
LoadResources();
arrow_factory = singleton<ArrowFactory>.Instance;
arrow_factory.initiate(AB);
recorder = gameObject.AddComponent<scoreRecorder>() as scoreRecorder;
user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
action_manager = gameObject.AddComponent<ArrowFlyActionManager>() as ArrowFlyActionManager;
}
public void create()
{
if (arrow == null)
{
wind_directX = Random.Range(-0.4f, 0.4f);
wind_directY = Random.Range(-0.4f, 0.4f);
CreateWind();
arrow = arrow_factory.GetArrow();
}
}
public void MoveBow(Vector3 fwd,float Xinput,float Yinput)
{
if (!game_start) { return; }
Vector3 vaxis = Vector3.Cross(fwd, Vector3.right);
AB.transform.Rotate(vaxis, -Xinput * 5, Space.World);
Vector3 haxis = Vector3.Cross(fwd, Vector3.up);
AB.transform.Rotate(haxis, -Yinput * 5, Space.World);
}
public void Shoot(float holdTime)
{
if (game_start)
{
Vector3 wind = new Vector3(wind_directX, wind_directY, 0);
action_manager.ArrowFly(arrow, wind, holdTime);
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) / 0.2f > -0.1f && (wind_directX + wind_directY) / 0.2f < 0.1f)
{
level = "1 级";
}
else if ((wind_directX + wind_directY) / 0.2f > -0.2f && (wind_directX + wind_directY) / 0.2f < 0.2f)
{
level = "2 级";
}
else if ((wind_directX + wind_directY) / 0.2f > -0.3f && (wind_directX + wind_directY) / 0.2f < 0.3f)
{
level = "3 级";
}
else if ((wind_directX + wind_directY) / 0.2f > -0.5f && (wind_directX + wind_directY) / 0.2f < 0.5f)
{
level = "4 级";
}
wind = Horizontal + Vertical + "风" + " " + level;
}
}
- ArrowCollider :这里主要是借鉴了师兄的代码,学会了能够使箭射中靶后停留在靶上的方法,我自己确实没想出来
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);
//Debug.Log(this.gameObject.transform.position.x);
//Debug.Log(this.gameObject.transform.position.y);
float dis = Mathf.Sqrt(this.gameObject.transform.position.x * this.gameObject.transform.position.x + this.gameObject.transform.position.y * this.gameObject.transform.position.y);
//Debug.Log("dis"+dis.ToString());
float point = 0;
if (dis >= 0 && dis < 0.5) point = 4f;
else if(dis >= 0.5 && dis < 1) point = 3f;
else if(dis >= 1 && dis < 1.5) point = 2f;
else if(dis >= 1.5 && dis < 2) point = 1f;
else if(dis >= 2 && dis < 2.5) point = 0f;
recorder.Record((int)Mathf.Floor(point));
}
}
}
- UserGUI:负责管理与用户交互
因为带中文会乱码,就不贴出来了
- action: 管理动作
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;
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(); }
}
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback { void SSActionEvent(SSAction source, GameObject arrow = null); }
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>();
protected void Update()
{
foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy) waitingDelete.Add(ac.GetInstanceID());
else if (ac.enable) ac.Update();
}
foreach (int key in waitingDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
waitingDelete.Clear();
}
protected void FixedUpdate()
{
foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
waitingAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy) waitingDelete.Add(ac.GetInstanceID());
else if (ac.enable) 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, GameObject arrow = null)
{
if (arrow != null)
{
ArrowTremble tremble = ArrowTremble.GetSSAction();
this.RunAction(arrow, tremble, this);
}
else
{
Controllor scene_controller = (Controllor)SSDirector.GetInstance().CurrentScenceController;
}
}
}
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, float holdTime)
{
fly = ArrowFlyAction.GetSSAction(wind, holdTime);
this.RunAction(arrow, fly, this);
}
}
public class ArrowFlyAction : SSAction
{
private float holdTime;
public Vector3 wind;
private ArrowFlyAction() { }
public static ArrowFlyAction GetSSAction(Vector3 wind, float holdTime_)
{
ArrowFlyAction action = CreateInstance<ArrowFlyAction>();
action.holdTime = holdTime_;
action.wind = wind;
return action;
}
public override void Update() { }
public override void FixedUpdate()
{
//加上风的力
this.gameobject.GetComponent<Rigidbody>().AddForce(wind*10, ForceMode.Force);
if (this.transform.position.z >=20 || this.gameobject.tag == "hit")
{
this.destroy = true;
this.callback.SSActionEvent(this, this.gameobject);
}
}
public override void Start()
{
Transform arrow = this.gameobject.transform;
arrow.GetComponent<Rigidbody>().isKinematic = false;
Debug.Log(arrow.position);
arrow.GetComponent<Rigidbody>().AddForce(-arrow.GetChild(0).position * 40f * holdTime, ForceMode.Impulse);
arrow.SetParent(null);//解除父子关系
}
}
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() { }
}
- interface:里面含有动作类的接口
略
- scoreRecorder:记录分数,离中心的距离越近分数越高
略
- singleton:单实例
略
使用的免费Asset:
- Fantasy Skybox FREE:常用天空盒
- Low Poly RPG Fantasy Weapons Lite:弓箭
- Military target:靶(这个我没有用,不过做出来应该不错)
链接:
项目地址: ch06-ArrowShooting
演示视频: 【Unity3D】ArrowShooting小游戏演示
参考博客-1: Unity3d–打靶游戏
参考博客-2: Unity3d学习之路-简单打靶游戏