Unity3D学习——射箭游戏(工厂模式)

成品展示


这里写图片描述
每两秒会获得一个稳定风速,玩家可以把握住这个瞬间射箭,击中不同环得分不同,不击中不扣分,60s内够100分则获胜

游戏制作

脚本挂载与预制

箭靶子预制
在一个空对象上挂载五个扁平同心圆柱,设置圆柱厚度不同以便箭识别射中第几环,空对象上挂刚体组件,其余五个圆柱挂mesh碰撞器
这里写图片描述

箭的预制
在一个空对象上,挂上两个柱体分别作箭头和箭身,两个方形作箭尾,空对象设置为刚体,箭头和箭身添加碰撞器,以便和箭靶子碰撞后触发计分和停留。
箭的预制

游戏设计以及代码分析

类的UML图


UML
沿用上一次设计模式,组合动作模式、单实例模式、工厂模式

各类说明

  • 导演类:管理游戏全局状态,获取当前游戏的场景,协调各个类之间的通讯,是单实例类,不被Unity内存管理所管理
  • 界面:负责与用户交互,检测用户的设计动作
  • 场记:负责调动工厂产生和回收箭、用户设计后让动作管理者设计箭的轨迹、让记分员计分、还有管理箭与靶子的碰撞
  • 动作管理者:被场记调用,为场景中的对象设计具体动作并执行
  • 记分员:记分员按箭射中的环数据计分,拥有计分规则
  • 箭工厂:负责生产和回收箭
  • 箭的脚本:因为箭是刚体,碰撞后需要触发事件
各类具体代码

因为这次只在飞碟游戏基础上修改了很少,所以这里只给出修改过的部分的代码,其余代码与上一篇飞碟游戏对应的类一样。

  • 箭的脚本 ArrowScript
    需要OnTriggerEnter方法设置碰撞事件,另外因为在射中箭靶子后需要马上计分,所以在这里需要实例化场记中的记分员计分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ArrowScript : MonoBehaviour
{
    public string shootring;
    private ScoreRecorder recorder;

    void OnTriggerEnter(Collider other)
    {
        shootring = other.gameObject.tag;

        //使得箭停在靶子上
        Destroy(GetComponent<Rigidbody>());

        //让记分员计分
        recorder.Record(shootring);

    }

    // Use this for initialization  
    void Awake()
    {
        recorder = (ScoreRecorder)FindObjectOfType(typeof(ScoreRecorder));
    }
}
  • 记分员 ScoreRecorder
    只是把之前飞碟的计分方法改一下,击中不同的环得分不同
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreRecorder : MonoBehaviour {
    private float score;

    public float getScore()
    {
        return score;
    }

    public void Record(string target_ring)
    {
        switch (target_ring)
        {
            case "Ring10": score += 10; break;
            case "Ring8": score += 8; break;
            case "Ring6": score += 6; break;
            case "Ring4": score += 4; break;
            case "Ring2": score += 2; break;
        }
    }

    public void Reset()
    {
        score = 0;
    }
}
  • 界面 UserGUI
    增加两个Text显示风力和风向,在update方法中显示,其余代码和之前相同
    public Text windforce;//风力
    public Text winddirection;//风向

    void Update()
    {
        //检测用户射击
        action.shoot();

        float force = actionmanager.getWindForce();
        if (force < 0)
            winddirection.text = "风向 : 左";
        else if (force > 0)
            winddirection.text = "风向 : 右";
        else
            winddirection.text = "风向 : 没风";

        windforce.text = "风力 : " + actionmanager.getWindForce();
    }
  • 动作管理者
    只是在RoundSSActionManager类里新增射箭的函数,其余动作的基本类与之前相同
    设置箭的朝向为摄像机与鼠标点击点所成的方向,另外设置箭速和风力大小
public class RoundSSActionManager : SSActionManager, ISSActionCallback
{
    public RoundController scene;
    public MoveToAction action1, action2;
    public SequenceAction saction;
    private float speed = 20.0f;//箭速
    private float force = 0f; //风力

    //增加射箭动作
    public void shootArrowAction(GameObject arrow, Vector3 dir)
    {
        arrow.transform.position = new Vector3(0, 0, -9);
        arrow.transform.up = dir;
        arrow.GetComponent<Rigidbody>().velocity = dir * speed; //设置箭速
    }

    public void wind(GameObject arrow, float time)
    {   
        if(time % 2 == 0)//每两秒获取随机的风力 
            force = UnityEngine.Random.Range(-30, 30);
        if(arrow != null && arrow.GetComponent<Rigidbody>() != null)//箭还没射到靶子上,还没解除刚体组件
        {
            arrow.GetComponent<Rigidbody>().AddForce(new Vector3(force, 0, 0), ForceMode.Force);
        }

    }

    public float getWindForce()
    {
        return force;
    }

    protected new void Start()
    {
        scene = (RoundController)SSDirector.getInstance().currentScenceController;
        scene.actionManager = this;
    }

    protected new void Update()
    {
        base.Update();
    }

    public void actionDone(SSAction source)
    {
        Debug.Log("Done");
    }
}
  • 场记 RoundController
    这里修改三处地方:

    • 把之前的射飞碟回收飞碟方法,改为射箭与回收箭的方法
    • 修改用户动作接口shoot()方法的具体实现
    • 增加FixedUpdate(),在其中添加力作用于箭,因为箭是刚体,所以它的物理运动在每一帧结束后才作用,形成风力效果

    完整代码如下:

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

public enum State { WIN, LOSE, PAUSE, CONTINUE, START };

public interface ISceneController
{
    State state { get; set; }
    void LoadResources();
    void Pause();
    void Resume();
    void Restart();
}

public class RoundController : MonoBehaviour, IUserAction, ISceneController {

    public ArrowFactory arrowFactory;
    public RoundSSActionManager actionManager;
    public ScoreRecorder scoreRecorder;
    public List<GameObject> arrow;
    public GameObject new_arrow;
    public GameObject target;

    //游戏状态
    public State state { get; set; }

    //计时器, 每关60秒倒计时
    public int leaveSeconds;

    IEnumerator DoCountDown()
    {
        while (leaveSeconds >= 0)
        {
            yield return new WaitForSeconds(1);
            leaveSeconds--;
        }
    }

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.setFPS(60);
        director.currentScenceController = this;
        SSDirector.getInstance().currentScenceController.state = State.PAUSE;


        arrowFactory = Singleton<ArrowFactory>.Instance;
        scoreRecorder = Singleton<ScoreRecorder>.Instance;

        state = State.PAUSE;
        leaveSeconds = 60;
    }

    // Use this for initialization
    void Start () {
        actionManager = GetComponent<RoundSSActionManager>() as RoundSSActionManager;
        LoadResources();
    }

    void Update()
    {
        RecycleArrow();//回收箭
        Judge(); //判断输赢
    }

    private void FixedUpdate()
    {
        //物理运动在每一帧结束后才作用,添加风力效果
        actionManager.wind(new_arrow, leaveSeconds);
    }

    public void LoadResources()
    {
        Camera.main.transform.position = new Vector3(0, 0, -10);
        target =  GameObject.Instantiate(Resources.Load("Prefabs/Target")) as GameObject;
        target.transform.position = new Vector3(0, 0, 3);
    }

    public void shoot()
    {
        if (Input.GetMouseButtonDown(0) && (state == State.START || state == State.CONTINUE))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Debug.Log("鼠标点击:" + ray.direction);

            //从工厂得到箭
            new_arrow = arrowFactory.GetArrow();
            arrow.Add(new_arrow);

            //提供鼠标点击方向,箭由动作管理器射出去
            actionManager.shootArrowAction(new_arrow, ray.direction);
        }
    }

    public void RecycleArrow()//回收
    {
        for(int i = 0; i < arrow.Count; i++)
        {
            if(arrow[i].transform.position.y < -10 || arrow[i].transform.position.z > 15)
            {
                arrowFactory.FreeArrow(arrow[i]);//让飞碟工厂回收
                arrow.Remove(arrow[i]);
            }
        }
    }

    public void Judge()//判断游戏状态,是否射中以及够不够分数进入下一回合
    {
        if (leaveSeconds == 0 && scoreRecorder.getScore() < 100)//时间到,分数不够,输了
        {
            StopAllCoroutines();//停止倒数
            state = State.LOSE;
        }
        else if ((leaveSeconds > 0 && scoreRecorder.getScore() >= 100))
        {
            StopAllCoroutines();//停止倒数
            state = State.WIN;
        }

    }

    public void Pause()
    {
        state = State.PAUSE;
        StopAllCoroutines();
        for (int i = 0; i < arrow.Count; i++)
        {
            arrow[i].SetActive(false);//暂停后飞碟不可见
        }
    }

    public void Resume()
    {
        StartCoroutine(DoCountDown());         //开启协程计时
        state = State.CONTINUE;
        for (int i = 0; i < arrow.Count; i++)
        {
            arrow[i].SetActive(true);//恢复后飞碟可见
        }
    }

    public void Restart()
    {
        scoreRecorder.Reset();
        Application.LoadLevel(Application.loadedLevelName);
        SSDirector.getInstance().currentScenceController.state = State.START;
    }

}

到这里修改就完成了,这次的修改很简单,只是箭多了个刚体的组件处理,因为和上次飞碟游戏类似,所以代码都套用上次的了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值