最终实现效果:
打靶游戏
一、实现思路
改编自之前的飞碟工厂,在弓弩上没有弓箭时重新生成新的弓箭,并将射出的弓箭在一定时间后进行回收(下落到一定地方)。在右下角通过小窗口展示靶子的情况,射中不同的环数给予不同得分。
二、主要涉及技术
物理引擎的使用、游戏对象的生产与回收、物体碰撞、刚体编程、动画编程、地形制作
三、图形设计
(一)靶子的实现
通过不同大小但是对称轴重合的圆柱体实现靶子。需要注意的是:当圆柱体的高完全相同时,外面更大的圆柱体会覆盖里面更小的圆柱体。所以需要让多个圆柱体从里往外高逐渐递减。
另外还有一些碰撞相关的设置:将根组件设为 Rigidbody ,其他子组件设置为 Mesh Renderer。
(二)弓箭
弓箭是引用Unity资源商店下载的Classical Crossbow。
(三)加入天空特效
用Skybox中的天空盒在window属性进行设置并将相机添加skybox。后面在FirstSceneController.cs中的代码可以实现通过按下按钮而改变天空盒。
(四)加入地形
创建一个terrain,用inspector中的paint terrain画地形,导入资源包,并在上面放置若干树。
四、原理实现
1. FirstController.js
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstSceneController : MonoBehaviour, IUserAction, ISceneController {
public Camera child_camera;
public ScoreRecorder recorder;
public ArrowFactory arrow_factory;
public ArrowFlyActionManager action_manager;
public Material skybox1;
public Material skybox2;
public int weather = 1;
public GameObject bow;
private GameObject arrow;
private GameObject target;
private GameObject Terrain;
private int round = 0;
private float downtime = 0;
private bool game_over = false;
private bool game_start = false;
private string wind_name = "";
private Vector3 wind = new Vector3(0, 0, 0);
private Vector3 force;
void Start () {
SSDirector director = SSDirector.GetInstance();
arrow_factory = Singleton<ArrowFactory>.Instance;
recorder = Singleton<ScoreRecorder>.Instance;
director.CurrentScenceController = this;
action_manager = gameObject.AddComponent<ArrowFlyActionManager>() as ArrowFlyActionManager;
LoadResources();
CreateWind();
}
void Update () {
if(game_start) {
Vector3 mpos = Camera.main.ScreenPointToRay(Input.mousePosition).direction;
if(Input.GetButton("Fire1"))
{
downtime += Time.deltaTime;
}
if (Input.GetButtonUp("Fire1") && downtime < 0.4)
{
downtime = 0;
}
if (Input.GetButtonUp("Fire1") && downtime >0.4) {
Shoot(mpos * 15 );
downtime = 0;
}
if (Input.GetButtonDown("Fire2"))
{
ChangeWeather();
}
if (arrow == null) {
arrow = arrow_factory.GetArrow();
arrow.transform.position = bow.transform.position;
arrow.gameObject.SetActive(true);
arrow.GetComponent<Rigidbody>().isKinematic = true;
}
bow.transform.LookAt(mpos * 30);
arrow.transform.LookAt(mpos * 30);
arrow_factory.FreeArrow();
}
}
public void ChangeWeather()
{
int j = -1;
weather *= j;
if(weather == 1)
{
skybox1 = Instantiate(Resources.Load("Materials/Skybox_Day", typeof(Material))) as Material;
RenderSettings.skybox = skybox1;
}
if(weather == -1)
{
skybox2 = Instantiate(Resources.Load("Materials/Skybox_Set", typeof(Material))) as Material;
RenderSettings.skybox = skybox2;
}
}
public void LoadResources() {
bow = Instantiate(Resources.Load("Prefabs/bow", typeof(GameObject))) as GameObject;
target = Instantiate(Resources.Load("Prefabs/target", typeof(GameObject))) as GameObject;
Terrain = Instantiate(Resources.Load("Prefabs/Terrain", typeof(GameObject))) as GameObject;
}
public void Shoot(Vector3 force) {
if (arrow != null) {
arrow.GetComponent<Rigidbody>().isKinematic = false;
action_manager.ArrowFly(arrow, wind, force);
child_camera.GetComponent<ChildCamera>().StartShow();
arrow = null;
CreateWind();
round++;
}
}
public int GetScore() {
return recorder.score;
}
public bool GetGameover() {
return game_over;
}
public string GetWind() {
return wind_name;
}
public void CreateWind() {
float wind_directX = ((Random.Range(-10, 10) > 0) ? 1 : -1) * round;
float wind_directY = ((Random.Range(-10, 10) > 0) ? 1 : -1) * round;
Debug.Log(wind_directX);
wind = new Vector3(wind_directX, wind_directY, 0);
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 = "北";
}
level = round.ToString();
wind_name = Horizontal + Vertical + "风" + " " + level;
}
public void BeginGame() {
Cursor.visible = false;
game_start = true;
}
}
2.UserGUI
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour {
private IUserAction action;
GUIStyle score_style = new GUIStyle();
GUIStyle text_style = new GUIStyle();
private bool game_start = false;
void Start () {
action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
text_style.normal.textColor = new Color(0, 0, 0, 1);
text_style.fontSize = 16;
score_style.normal.textColor = new Color(1, 0, 0, 1);
score_style.fontSize = 16;
}
void Update() {}
private void OnGUI() {
if(game_start) {
GUI.Label(new Rect(15, 10, 205, 55), "分数:", text_style);
GUI.Label(new Rect(60, 10, 205, 55), action.GetScore().ToString(), score_style);
GUI.Label(new Rect(15, 55, 205, 110), "按下鼠标右键切换天气!", text_style);
GUI.Label(new Rect(Screen.width - 170, 30, 200, 50), "风向: ", text_style);
GUI.Label(new Rect(Screen.width - 110, 30, 200, 50), action.GetWind(), text_style);
}
else {
GUI.Label(new Rect(Screen.width / 2 - 30, Screen.width / 2 - 320, 100, 100), "打靶游戏", text_style);
if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "游戏开始")) {
game_start = true;
action.BeginGame();
}
}
}
}
3.动画animator
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Animator : MonoBehaviour
{
Animator animator;
public int startint;
private float downtime = 0;
// Start is called before the first frame update
void Start()
{
animator = GetComponent<Animator>();
startint = 1;
}
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1") && startint == 1)
{
startint = 0;
}
if (Input.GetButton("Fire1") && startint == 0)
{
downtime += Time.deltaTime;
}
if(downtime > 0.15)
{
animator.SetBool("Fire", true);
}
if (Input.GetButtonUp("Fire1"))
{
animator.SetBool("Fire", false);
downtime = 0;
}
}
}
4.Arrow Factory
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//工厂模式,箭工厂
public class ArrowFactory : MonoBehaviour {
public GameObject arrow = null;
private List<GameObject> used = new List<GameObject>();
private Queue<GameObject> free = new Queue<GameObject>();
public FirstSceneController sceneController;
public void Start() {
sceneController = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController;
}
public GameObject GetArrow() {
if (free.Count == 0) {
arrow = Instantiate(Resources.Load<GameObject>("Prefabs/arrow"));
} else {
arrow = free.Dequeue();
arrow.gameObject.SetActive(true);
}
used.Add(arrow);
return arrow;
}
public void FreeArrow() {
for (int i = 0; i < used.Count; i++) {
if (used[i].gameObject.transform.position.y < -30) {
used[i].gameObject.SetActive(false);
free.Enqueue(used[i]);
used.Remove(used[i]);
break;
}
}
}
}
5.ArrowFlyAction
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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 Start() {
gameobject.GetComponent<Rigidbody>().AddForce(_force, ForceMode.Impulse);
gameobject.GetComponent<Rigidbody>().AddForce(_wind);
}
public override void Update() {}
//使用物理引擎
public override void FixedUpdate(){
if (transform.position.y < -30) {
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
}
6.Collision Detection 碰撞检测
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//碰撞检测器,检测碰撞
public class CollisionDetection : MonoBehaviour {
public FirstSceneController scene_controller;
public ScoreRecorder recorder;
void Start() {
scene_controller = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
recorder = Singleton<ScoreRecorder>.Instance;
}
void OnTriggerEnter(Collider arrow_head) {
Transform arrow = arrow_head.gameObject.transform.parent;
if (arrow == null) return;
arrow.GetComponent<Rigidbody>().isKinematic = true;
arrow_head.gameObject.SetActive(false);
recorder.Record(this.gameObject);
}
}