ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

交互入门2——射击打靶游戏

2021-01-18 16:00:37  阅读:237  来源: 互联网

标签:入门 int void Vector3 打靶 arrow new 交互 public


文章目录

射箭游戏设计与实现

游戏要求:

游戏内容要求:

  1. 靶对象为 5 环,按环计分;

  2. 箭对象,射中后要插在靶上

    增强要求:射中后,箭对象产生颤抖效果,到下一次射击 或 1秒以后

  3. 游戏仅一轮,无限 trials;
    增强要求:添加一个风向和强度标志,提高难度

具体实现思路:

  1. 设计扁平圆柱体作为靶标,多个不同大小的圆柱体叠加形成不同的环,并利用颜色区分,由于叠加会影响正常显示,所以每个圆柱体的宽度(高)需要不一致,或者利用位置不同实现一个层级的效果,小的在前,大的在后就能显示出一个个环的效果。
  2. 由于对游戏轮次没有太大的要求,所以这里设置为游戏开始时拥有一定数量的箭,箭用完就算结束,显示得分,由用户决定是否再来一次(对于无限trails的规则不太清楚)。
  3. 风向则可是使用一个持续添加在箭上的力来实现。

具体实现代码

首先用到之前几个游戏的一些基类:Director、SSAction、SSActionManager、Singleton等,这几个类由于只是基类,真正的逻辑实现都在子类中,所以直接重用。

动作部分

先来说说游戏的动作部分,首先分析主要的运动对象:箭。。。没了。所以与之前打飞碟的游戏类似,只需要实现将箭飞出去的动作,通过物理引擎的实现之前也提到过,只需要在出去的瞬间给它添加一个力就可以了。
其次是风力的作用,与飞出去的瞬间力不同,风力是需要持续作用的力,所以需要在FixedUpdate里持续添加。
从以上可以看出,此动作类主要的关键属性有:运动方向、风力作用方向。
ArrowAction 代码如下:

public class ArrowAction : SSAction{
    public Vector3 force;
    public Vector3 affect;
    public static ArrowAction GetSSAction(Vector3 f, Vector3 wind) {
        ArrowAction action = ScriptableObject.CreateInstance<ArrowAction>();

        action.force = f;
        action.affect = wind;
        return action;
    }

    public override void FixedUpdate() {
        this.gameObject.GetComponent<Rigidbody>().AddForce(affect, ForceMode.Acceleration);

        if (this.transform.position.z > 3 ||  Mathf.Abs(this.transform.position.y) > 7 || 
            Mathf.Abs(this.transform.position.x) > 10 || this.gameObject.tag == "ontarget") {
            this.destroy = true;
            if (this.gameObject.tag != "ontarget")
                this.callBack.SSActionEvent(this);
        }
    }
    public override void Update(){}
    public override void Start() {
        this.gameObject.GetComponent<Rigidbody>().velocity = Vector3.zero;
        this.gameObject.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse);
    }
}
  •  

所以构造此类的时候需要的参数也就是两个,分别对应两个关键属性。
此外,当箭超出一定范围时,我们就可以认为它无法到达靶标,直接中止动作执行。或者当其到达目标时,(名字会设置为ontarget)也可以中止动作。

动作管理器类也就是简单的包装一下动作类,并且使其执行,并且实现回调函数,这里的回调就是用弓箭工厂的方法将箭的对象释放掉(加入到free的队列去,以便重新使用):
ArrowActionManager

public class ArrowActionManager : SSActionManager, ISSActionCallback {

    ArrowAction arrowAction;
    Controller controller;

    private void Start()
    {
        controller = Director.getInstance().currentSceneController as Controller;
        controller.actionManager = this;
    }

    public void arrowFly(GameObject arrow, Vector3 target, Vector3 wind) {
        arrowAction = ArrowAction.GetSSAction(target, wind);
        if (arrow.GetComponent<Rigidbody>() == null)
            arrow.AddComponent<Rigidbody>();
        else 
            arrow.GetComponent<Rigidbody>().isKinematic = false;
        this.RunAction(arrow, arrowAction, this);
    }

    public void SSActionEvent(SSAction action){
        Singleton<ArrowFactory>.Instance.freeArrow(action.gameObject);
        if (controller.arrow.name == "arrow")
            controller.getArrow();
    }
}
  •  

值得注意的一点是,由于箭未发出的时候,是需要在弓上停留的,也即是说不能有刚体的重力作用,否则就会掉下去。所以在运动管理器中,需要在执行射箭动作之前,恢复刚体的作用,这里采用的是运动学模式和物理模式切换的方式来实现。如果是新创建的实例,还没有添加刚体部件则需要添加。
回调函数里主要是运动结束后执行的行为,运动结束主要有两个状态,上靶和未中靶,上靶的箭不能free掉,因为要保留来积分(模拟真实场景),所以还需要额外判断当前的箭是脱靶了还是中靶的。

碰撞检测

主要是利用碰撞体的Trigger,来检测是否碰撞到了,如果碰到了就积分,不同的碰撞体不同分,然后将箭的名字(用来代表状态)改成ontarget,这样就防止别的碰撞器也重复积分。因为会实际上两个碰撞器的距离很微小,所以能够同时碰到多个碰撞器。

public class CollisionRev : MonoBehaviour {
    private void OnTriggerEnter(Collider other)
    {   
        
        GameObject arrow = other.gameObject;
        if (arrow.name == "arrow") {
            string str = this.name;
            arrow.GetComponent<Rigidbody>().velocity = Vector3.zero;
            arrow.GetComponent<Rigidbody>().isKinematic = true;

            Singleton<Judger>.Instance.addScore(str);
            arrow.transform.position += Vector3.forward * 0.001f;
            arrow.name = "ontarget";
            Controller controller = Director.getInstance().currentSceneController as Controller;
            controller.hit(arrow);
            // if (controller.arrow == null)
            controller.getArrow();
        }
    }
}
  •  

工厂类生产箭

与前一个实验的飞碟工厂很像,而且不需要添加属性什么的,更加简单。
只需要将空闲的或者刚创建的箭,初始化位置等就可以了。还有一个Free的方法,也是跟之前类似。

public class ArrowFactory : MonoBehaviour {
    public GameObject arrow = null;
    private List<GameObject> activeList = new List<GameObject>();
    private List<GameObject> freeList = new List<GameObject>();
    public GameObject getArrow() {
        if (freeList.Count > 0) {
            arrow = freeList[0].gameObject;
            freeList.Remove(freeList[0]);
            arrow.GetComponent<Rigidbody>().isKinematic = true;
            
        }
        else {
            arrow = Instantiate(Resources.Load("Prefabs/arrow", typeof(GameObject))) as GameObject;
        }

        arrow.transform.rotation = Quaternion.Euler(0,0,0);
        arrow.transform.position = new Vector3(-0.1f, 0.85f, -9.7f);
        arrow.SetActive(true);
        arrow.name = "ready";
        activeList.Add(arrow);
        return arrow;
    }

    public void freeArrow(GameObject a) {
        for (int i  = 0; i < activeList.Count; i ++) {
            if (a.GetInstanceID() == activeList[i].gameObject.GetInstanceID()) {
                activeList[i].gameObject.SetActive(false);
                freeList.Add(activeList[i]);
                activeList.Remove(activeList[i]);
                break;
            }
        }
    }
}
  •  

Controller类

public class Controller : MonoBehaviour, SceneController, Interaction
{
    public ArrowActionManager actionManager;
    public ArrowFactory factory;
    public GameObject bow;
    public GameObject target;
    public GameObject arrow;
    public Judger judger;
    public Vector3 direction;
    public UI ui;

    public int state = 0;
    private int arrowNumber = 0;
    private Queue<GameObject> hit_arrow = new Queue<GameObject>();
    public Vector3 wind = Vector3.zero; 
    private int[] direc = {1,-1,0};
    private void Start() {
        Director director = Director.getInstance();
        director.currentSceneController = this;
        factory = this.gameObject.AddComponent<ArrowFactory>();
        actionManager = this.gameObject.AddComponent<ArrowActionManager>() as ArrowActionManager;
        ui = this.gameObject.AddComponent<UI>();
        judger = this.gameObject.AddComponent<Judger>();
        // factory = Singleton<ArrowFactory>.Instance;
        loadResources();
        int x = Random.Range(0,3);
        int y = Random.Range(0,3);
        x = direc[x];
        y = direc[y];
        int level = Random.Range(1,5);
        wind = new Vector3(x, y, 0) * level;
    }

    public void loadResources() {
        bow = Instantiate(Resources.Load("Prefabs/bow", typeof(GameObject))) as GameObject;
        target = Instantiate(Resources.Load("Prefabs/target", typeof(GameObject))) as GameObject;
        arrow = factory.getArrow();
    }

    private void Update()
    {
    }

    public void moveArrowDirection(Vector3 to) {
        if (state <= 0) {
            return;
        }
        arrow.transform.rotation = Quaternion.LookRotation(to);
        bow.transform.rotation = Quaternion.LookRotation(to);
        direction = to;
    }

    public void reuse() {
        int tmp = hit_arrow.Count;
        for (int i = 0; i < tmp; i ++) {
            factory.freeArrow(hit_arrow.Dequeue());
        }
        arrowNumber = 0;
    }
    public void shoot(Vector3 force) {

        if (state > 0 && arrow != null) {
            // arrow = factory.getArrow();
            arrow.name = "arrow";
            actionManager.arrowFly(arrow, direction * 15, wind);
            arrowNumber ++;
        }
    }
    public void hit(GameObject arrow) {
        hit_arrow.Enqueue(arrow);
    }
    public void getArrow() {
        int x = Random.Range(0,3);
        int y = Random.Range(0,3);
        x = direc[x];
        y = direc[y];
        int level = Random.Range(1,5);
        wind = new Vector3(x, y, 0) * level;
        if (state == 1) {
            if (arrowNumber > 7) {
                setState(-1);
            }
        }
        arrow = factory.getArrow();
    }
    public void setState(int s) {
        state = s;
    }
    public void restart() {
        state = 0;
        arrowNumber = 0;
    }

    public int getState() {
        return state;
    }

    public string arrowState() {
        return arrow != null ? arrow.name : null;
    }

    public Vector3 getWind() {
        return wind;
    }
}
  •  

这里面主要是实现了射箭、获取箭的函数,还有一部分与用户交互的函数。
loadResources就是加载弓箭和靶子的资源。
射箭shoot主要是状态的改变,之前已经说过,用名字来代表状态,在射出的时候更改状态为arrow表示在飞行中。
moveDirection函数主要是更改弓箭的朝向,这里是与鼠标的位置相关,也就是利用鼠标更改朝向,使得其能够瞄准。
getArrow则是获取弓箭,也就是下一次射箭的准备工作,需要把风向提前设置好,这里使用随机的方式生成8个方向的风,风力等级也是随机,有4个等级。
hit则是类似回调,将中箭的加入使用中的队列,因为这部分箭不会自动收回(只收回了脱靶的)
还有一些获取状态的函数,游戏状态、风力信息、弓箭状态等。

UI类

UI主要是负责用户的交互,所以需要获取Controller的一些状态来判断用户操作是否合法或者限制用户的操作。

public class UI : MonoBehaviour {
    Interaction interaction;
    bool flag = true;
    GUIStyle style1;
    GUIStyle style2;
    GUIStyle style3;
    float time = 0;

    private void Start() {
        interaction = Director.getInstance().currentSceneController as Interaction;
        style1 = new GUIStyle("button");
		style1.fontSize = 25;
        style2 = new GUIStyle();
		style2.fontSize = 35;
		style2.alignment = TextAnchor.MiddleLeft;
        style3 = new GUIStyle();
        style3.fontSize = 15;
        style3.alignment = TextAnchor.MiddleLeft;
    }
    
    private void OnGUI()
    {   
        if (interaction.getState() == -1) {
            if (time < 2) {
                time += Time.deltaTime;
                GUI.Label(new Rect(Screen.width/2-70, Screen.height/2-135, 200, 30), "Preparing Arrow...", style2);
            } else {
                GUI.Label(new Rect(Screen.width/2-70, Screen.height/2-135, 200, 30), "Your Score:" + Singleton<Judger>.Instance.getScore().ToString(), style2);
                if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2 - 20, 180, 70), "Play again", style1)) {
                    interaction.reuse();
                    interaction.setState(1);
                    Singleton<Judger>.Instance.restart();
                    time = 0;
                }
            }
        }
        GUI.Label(new Rect(5, 5, 100, 30), "Score: " + Singleton<Judger>.Instance.getScore().ToString(), style3);
        Vector3 wind = interaction.getWind();
        int x = (int)wind.x;
        int y = (int)wind.y;
        string str1, str2, level;
        if (x < 0) 
            str1 = "West";
        else if (x > 0)
            str1 = "East";
        else 
            str1 = "";

        if (y < 0) 
            str2 = "South";
        else if (y > 0)
            str2 = "North";
        else 
            str2 = "";
        
        if (x == 0 && y == 0) {
            str1 = "No wind";
        }
        if (x != 0) {
            int tmp = x > 0 ? x : -x;
            level = tmp.ToString();
        } else if (y != 0) {
            int tmp = y > 0 ? y : -y;
            level = tmp.ToString();
        } else {
            level = "0";
        }

        GUI.Label(new Rect(5, 35, 200, 30), "Wind Direction: " + str1 + str2, style3);
        GUI.Label(new Rect(5, 65, 100, 30), "Wind Level: " + level , style3);
        if (flag) {
            GUI.Label(new Rect(Screen.width/2-60, Screen.height/2-135, 100, 50), "ShootArrow!", style2);
            if(GUI.Button(new Rect(Screen.width/2-70, Screen.height/2 - 20, 150, 70), "Play", style1)) {
                flag = false;
                interaction.setState(1);
            }
        }
        
    }

    private void Update()
    {
        if (interaction.getState() > 0) {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (interaction.arrowState() == "ready") {
                interaction.moveArrowDirection(ray.direction);
                if (Input.GetButtonDown("Fire1")) {
                    interaction.shoot(ray.direction);
                }
            }
        }
    }
}
  •  

这里主要是根据Controller的状态(游戏未开始、进行中、结束)来显示不同的界面,如果是进行中,还需要显示分数、风力信息等,这里是通过Controller的风力向量,临时计算风力信息,有点不太好。
还有最重要的一点是,获取鼠标位置,并且设置相应的弓箭朝向。响应点击事件来射箭,具体也就是调用Controller的接口来执行动作。

游戏效果

在这里插入图片描述

本次实验到此结束!

标签:入门,int,void,Vector3,打靶,arrow,new,交互,public
来源: https://blog.csdn.net/weixin_40552127/article/details/112785511

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有