[Unity3D课堂作业] 打靶游戏 ShootingArrow

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33000225/article/details/68928488

游戏规则:

       1、按空格键取箭,然后按住鼠标左键同时移动鼠标,箭头会跟着鼠标移动,此时松开左键,箭就会射出。

       2、靶上一共有5环,击中n环加n*10分,即5环加50分

       3、左上角会提示当前风力方向和强度,会影响箭的飞行轨迹


游戏效果:



游戏UML类图:


      

       此次作业的项目结构与实现思路都跟上一次打飞碟游戏比较类似,可以参照着看哦~


难点实现思路:

      这里先说一下作业的一些难点的实现思路:

1、靶标的实现:

      靶上面要有5环,因此我在一个空对象Target下面创建了5个Cylinder子对象,各自带上Mesh Collider网格碰撞器。(注意要对Mesh Collider勾上Convex选项,即为凸的网格,才能跟其他碰撞器产生碰撞作用)。因为5个叠在一起,可能显示上会有先后顺序,此时改一下z坐标即可,即内环比外环前一点点:


2、箭的刚体、碰撞器组合情况:

      我在一个空对象Arrow下创建一个柱体Cylinder和正方体Cube构成箭身和箭头。给空对象Arrow加上刚体Rigidbody(勾选Is Kinematic,即开始时候为运动学刚体),给箭身加碰撞器,箭头加碰撞器(但需要勾选Is Trigger,同时挂载检测碰撞的脚本)。

    

  

3、增加风力:

      加一个普通的带方向的力即可。


代码解释:

1、SceneController.cs

       单例场景控制类,实现IUserAction、IGameStatusOp两个接口。此类有两个子对象:GameModel(负责管理场景内的各个游戏对象)、GameStatus(负责管理场景各种状态)。因此SceneController类实现接口的方法均为直接调用两个子对象的public方法:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Com.Shooting {
    public interface IUserAction {
        void createArrow();
        void arrowFollowMouse(Vector3 mousePos);
        void shootTheArrow(Vector3 mousePos);
    }

    public interface IGameStatusOp {
        bool haveArrowOnPort();
        void addScore(int point);
        void showTipsText(int point);
        void modifyWind();
        int getWindDir();
        int getWindStrength();
    }

    public class SceneController : System.Object, IUserAction, IGameStatusOp {
        private static SceneController instance;
        private GameModel myGameModel;
        private GameStatus myGameStatus;

        public static SceneController getInstance() {
            if (instance == null)
                instance = new SceneController();
            return instance;
        }
        
        internal void setGameModel(GameModel _myGameModel) {
            if (myGameModel == null) {
                myGameModel = _myGameModel;
            }
        }

        internal void setGameStatus(GameStatus _myGameStatus) {
            if (myGameStatus == null) {
                myGameStatus = _myGameStatus;
            }
        }

        /********************* 实现IUserAction接口 ************************/
        public void createArrow() {
            myGameModel.createArrow();
        }

        public void arrowFollowMouse(Vector3 mousePos) {
            myGameModel.arrowFollowMouse(mousePos);
        }

        public void shootTheArrow(Vector3 mousePos) {
            myGameModel.shootTheArrow(mousePos);
        }

        /********************* 实现IGameStatusOp接口 ************************/
        public bool haveArrowOnPort() {
            return myGameModel.haveArrowOnPort();
        }

        public void addScore(int point) {
            myGameStatus.addScore(point);
        }

        public void showTipsText(int point) {
            myGameStatus.showTipsText(point);
        }

        public void modifyWind() {
            myGameStatus.modifyWind();
        }

        public int getWindDir() {
            return myGameStatus.getWindDir();
        }

        public int getWindStrength() {
            return myGameStatus.getWindStrength();
        }
    }
}

2、 GameModel.cs

       上面说了,这是场景控制类SceneController的子对象,负责管理箭、靶两个游戏对象的行为逻辑。

       (1)箭由箭工厂产生;Update()方法让箭工厂检测回收掉地的箭

       (2)箭的发射为添加一个冲力Impulse。但是由于未射箭时需要跟着鼠标移动,而设置为运动学刚体,不受物理作用,所以发射瞬间需要先设置为非运动学刚体(**.isKinematic = false)。为了使箭头也朝着飞出方向,所以调用transform.LookAt方法改变朝向

       (3)箭飞出时获取当前风力方向和强度,然后给箭加一个力

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Shooting;

public class GameModel : MonoBehaviour {
    public GameObject TargetItem, ArrowItem;

    private GameObject holdingArrow = null, target;
    private const int ARROW_SPEED = 30;

    private SceneController scene;

    void Awake() {
        ArrowFactory.getInstance().initItem(ArrowItem);
    }

	void Start () {
        scene = SceneController.getInstance();
        scene.setGameModel(this);

        target = Instantiate(TargetItem);
    }
	
	void Update () {
        ArrowFactory.getInstance().detectReuseArrows();

    }

    //检测是否握住箭
    public bool haveArrowOnPort() {
        return (holdingArrow != null);
    }

    //产生箭
    public void createArrow() {
        if (holdingArrow == null) {
            holdingArrow = ArrowFactory.getInstance().getArrow();
        }
    }

    //使箭跟着鼠标移动
    public void arrowFollowMouse(Vector3 mousePos) {
        holdingArrow.transform.LookAt(mousePos * 30);
    }

    //射箭
    public void shootTheArrow(Vector3 mousePos) {
        holdingArrow.transform.LookAt(mousePos * 30);
        holdingArrow.GetComponent<Rigidbody>().isKinematic = false;

        //加风力
        addWindForce();

        holdingArrow.GetComponent<Rigidbody>().AddForce(mousePos * 30, ForceMode.Impulse);
        holdingArrow = null;
    }
    void addWindForce() {
        int windDir = scene.getWindDir();
        int windStrength = scene.getWindStrength();

        Vector3 windForce;
        switch (windDir) {
            case 0:
                windForce = new Vector3(0, 1, 0);
                break;
            case 1:
                windForce = new Vector3(1, 1, 0);
                break;
            case 2:
                windForce = new Vector3(1, 0, 0);
                break;
            case 3:
                windForce = new Vector3(1, -1, 0);
                break;
            case 4:
                windForce = new Vector3(0, -1, 0);
                break;
            case 5:
                windForce = new Vector3(-1, -1, 0);
                break;
            case 6:
                windForce = new Vector3(-1, 0, 0);
                break;
            case 7:
                windForce = new Vector3(-1, 1, 0);
                break;
            default:
                windForce = Vector3.zero;
                break;
        }
        holdingArrow.GetComponent<Rigidbody>().AddForce(windForce * windStrength * 20, ForceMode.Force);
    }
}

3、ArrowFactory.cs

      单例箭工厂。同样,设置一个usingList和unusedList,进行箭的循环利用。由GameModel的Update()方法让箭工厂每帧检测回收掉地的箭。箭掉地后,复位,同时要设置为运动学刚体。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Com.Shooting {
    public class ArrowFactory : System.Object {
        private static ArrowFactory instance;
        private List<GameObject> usingArrowList = new List<GameObject>();
        private List<GameObject> unusedArrowList = new List<GameObject>();

        private GameObject ArrowItem;
        private Vector3 ARROW_INIT_POS = new Vector3(0, 0, -8);

        public static ArrowFactory getInstance() {
            if (instance == null)
                instance = new ArrowFactory();
            return instance;
        }

        public void initItem(GameObject _ArrowItem) {
            ArrowItem = _ArrowItem;
        }

        //提供箭
        public GameObject getArrow() {
            if (unusedArrowList.Count == 0) {    //没有存储箭
                GameObject newArrow = Camera.Instantiate(ArrowItem);
                usingArrowList.Add(newArrow);
                return newArrow;
            }
            else {                      //有存储箭
                GameObject oldArrow = unusedArrowList[0];
                unusedArrowList.RemoveAt(0);
                oldArrow.SetActive(true);
                usingArrowList.Add(oldArrow);
                return oldArrow;
            }
        }

        //检测箭落地,回收。此方法由GameModel的update()方法触发
        public void detectReuseArrows() {
            for (int i = 0; i < usingArrowList.Count; i++) {
                if (usingArrowList[i].transform.position.y <= -8) {
                    usingArrowList[i].GetComponent<Rigidbody>().isKinematic = true;
                    usingArrowList[i].SetActive(false);
                    usingArrowList[i].transform.position = ARROW_INIT_POS;
                    unusedArrowList.Add(usingArrowList[i]);
                    usingArrowList.Remove(usingArrowList[i]);
                    i--;

                    SceneController.getInstance().modifyWind();
                }
            }
        }
    }
}

4、GameStatus.cs

      游戏状态类,管理分数、风力状态等。每一次射箭后都会随机改变风力状态。射中靶后加分,并提示环数。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Shooting;
using UnityEngine.UI;

public class GameStatus : MonoBehaviour {
    public GameObject canvasItem, scoreTextItem, TipsTextItem, windTextItem;

    private int score = 0;
    private int windDir = 0;
    private int windStrength = 0;
    private string[] windTextSet;

    private const float TIPS_TEXT_SHOW_TIME = 0.6f;

    private GameObject canvas, scoreText, TipsText, windText;
    private SceneController scene;

    void Start () {
        scene = SceneController.getInstance();
        scene.setGameStatus(this);

        canvas = Instantiate(canvasItem);
        scoreText = Instantiate(scoreTextItem, canvas.transform);
        scoreText.transform.Translate(canvas.transform.position);
        scoreText.GetComponent<Text>().text = "Score: " + score;

        TipsText = Instantiate(TipsTextItem, canvas.transform);
        TipsText.transform.Translate(canvas.transform.position);
        TipsText.SetActive(false);

        windTextSet = new string[8] { "↑", "↗", "→", "↘", "↓", "↙", "←", "↖" };
        windText = Instantiate(windTextItem, canvas.transform);
        windText.transform.Translate(canvas.transform.position);
        modifyWind();
    }
	
	void Update () {
		
	}

    //得分,+10
    public void addScore(int point) {
        score += point;
        scoreText.GetComponent<Text>().text = "Score:  " + score;

        modifyWind();
    }

    //提示环数
    public void showTipsText(int point) {
        TipsText.GetComponent<Text>().text = point + " points!";
        TipsText.SetActive(true);
        StartCoroutine(waitForSomeAndDisappearTipsText());
    }
    //延时消失
    IEnumerator waitForSomeAndDisappearTipsText() {
        yield return new WaitForSeconds(TIPS_TEXT_SHOW_TIME);

        TipsText.SetActive(false);
    }

    //调整风向、强度
    public void modifyWind() {
        windDir = Random.Range(0, 8);
        windStrength = Random.Range(0, 8);
        windText.GetComponent<Text>().text = "Wind: " + windTextSet[windDir] + "  x" + windStrength;
    }
    public int getWindDir() {
        return windDir;
    }
    public int getWindStrength() {
        return windStrength;
    }
}

5、UserInterface.cs

      用户操作类,检测用户按空格键(取箭)、按住鼠标左键移动(使箭跟着移动)、松开左键(射箭)三种行为。通过场景控制器SceneController的接口方法做出相关逻辑实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Shooting;

public class UserInterface : MonoBehaviour {
    private IUserAction action;
    private IGameStatusOp gameStatusOp;

    void Start () {
        action = SceneController.getInstance() as IUserAction;
        gameStatusOp = SceneController.getInstance() as IGameStatusOp;
    }
	
	void Update () {
        detectMouseInput();
        detectKeyInput();
    }

    void detectMouseInput() {
        if (gameStatusOp.haveArrowOnPort()) {
            if (Input.GetMouseButton(0)) {    //按住鼠标,箭跟鼠标移动
                Vector3 mousePos = Camera.main.ScreenPointToRay(Input.mousePosition).direction;
                action.arrowFollowMouse(mousePos);
            }

            if (Input.GetMouseButtonUp(0)) {    //松开鼠标,发射
                Vector3 mousePos = Camera.main.ScreenPointToRay(Input.mousePosition).direction;
                action.shootTheArrow(mousePos);
            }
        }
    }

    void detectKeyInput() {
        if (Input.GetKeyDown(KeyCode.Space)) {    //按下空格键,产生箭
            action.createArrow();
        }
    }
}

6、ArrowCollider.cs

       这应该是这次作业最重要的一个类了(虽然代码不多)。

       (1)此类挂载在箭头上。(注意不能挂载到整个箭组合上,因为:当箭头从靶上方飞过,但刚好箭身触碰到靶时,也会触发碰撞Enter方法,使箭变为运动学刚体而静止,从而造成奇异的效果)

       (2)由于上面设置了箭头Is Trigger为true,即不会产生碰撞效果,所以永远不会触发OnCollisionEnter方法。因此,需要将碰撞进入方法改为void OnTriggerEnter(Collider c)。

       (3)为什么需要设置箭头为inactive:这样设置是为了防止多次触发OnTriggerEnter方法

       (4)由于触碰后整个箭组合会变为运动学刚体而静止,所以箭身会插在靶上

       (5)根据射中的内靶确定环数

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Com.Shooting;

/*********************************************
* 此脚本加在箭头上
*********************************************/

public class ArrowCollider : MonoBehaviour {
    private IGameStatusOp gameStatusOp;

    void Start () {
        gameStatusOp = SceneController.getInstance() as IGameStatusOp;
    }
	
	void Update () {
		
	}

    void OnTriggerEnter(Collider c) {
        if (c.gameObject.tag == "target") {
            //Debug.Log(this.gameObject.name + ": " + c.gameObject.name);
            gameObject.transform.parent.gameObject.GetComponent<Rigidbody>().isKinematic = true;
            gameObject.SetActive(false);     //这样设置是为了防止多次触发

            int point = c.gameObject.name[c.gameObject.name.Length - 1] - '0';  //得分
            gameStatusOp.showTipsText(point);
            gameStatusOp.addScore(point * 10);

        }
    }
}


操作步骤:

1、所有的预设如下:

       

       (1)画板啊文字啊那些按照上次作业那样设置就好了。

       (2)箭组合的设计上面提到了

       (3)靶那加了个小Camera,就像效果图那右上角的小图,能够看得更清楚箭射中的情况。

2、上述6个脚本,UserInterface.cs挂载到主摄像机上;创建一个空对象Empty,挂载GameModel.cs、GameStatus.cs两个脚本



那就应该差不多了~~~













猜你喜欢

转载自blog.csdn.net/qq_33000225/article/details/68928488
今日推荐