ch06-物理系统与碰撞——Arrowshooting射箭小游戏

在这里插入图片描述


游戏内容要求:

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

值得一提的是,材质交替使用深色和浅色木纹更好看!

    • 父对象
      在这里插入图片描述

    • 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学习之路-简单打靶游戏

猜你喜欢

转载自blog.csdn.net/yesor_not/article/details/128059306