Unity-3D-第一人称射箭打靶游戏

一、简介

        该项目实现了一个第一人称的打靶射箭游戏,玩家通过控制角色在空间类行走,在不同的靶场对应的点位进行不同难度的射箭打靶,最后能够得到一个得分。

1、项目要求 

  • 游戏场景(14分)
    •  地形(2分):使用地形组件,上面有山、路、草、树;(可使用第三方资源改造)
    •  天空盒(2分):使用天空盒,天空可随 玩家位置 或 时间变化 或 按特定按键切换天空盒
    •  固定靶(2分):使用静态物体,有一个以上固定的靶标;(注:射中后状态不会变化)
    •  运动靶(2分):使用动画运动,有一个以上运动靶标,运动轨迹,速度使用动画控制;(注:射中后需要有效果或自然落下)
    •  射击位(2分):地图上应标记若干射击位,仅在射击位附近或区域可以拉弓射击,每个位置有 n 次机会;
    •  摄像机(2分):使用多摄像机,制作 鸟瞰图 或 瞄准镜图 使得游戏更加易于操控;
    •  声音(2分):使用声音组件,播放背景音 与 箭射出的声效;
  • 运动与物理与动画(8分)
    •  游走(2分):使用第一人称组件,玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;(注:建议使用 unity 官方案例
    •  射击效果(2分):使用 物理引擎 或 动画 或 粒子,运动靶被射中后产生适当效果。
    •  碰撞与计分(2分):使用 计分类 管理规则,在射击位射中靶标得相应分数,规则自定;(注:应具有现场修改游戏规则能力)
    •  驽弓动画(2分):使用 动画机 与 动画融合, 实现十字驽蓄力半拉弓,然后 hold,择机 shoot
  • 游戏与创新(不限项,每项 2 分)
    •  场景与道具类: 有趣的事物 或 模型等 (可以模仿 Unity 官方案例,在地形基础上搭建几何型场地)
    •  效果类:如显示箭的轨迹,特殊声效,等
    •  力场类: 如运用力场实现 ai 导航 与 捕获等
    •  玩法类:
    •  游戏感: 这是一个模糊的指标,有游戏感也许是某些人的天赋

2、演示视频

3D-Unity-第一人称射击游戏实验视频

二、具体要求实现

1、地形

        使用了Unity的Terrain组件,再加上下载的资源的草和树,通过对地形的自主定义和操作的操作就可以实现山、路、树、草的添加。

2、天空盒

        首先要下载store中的资源Fantasy Skybox FREE并加入项目中。玩家按下1、2、3切换至不同的射箭点位时,天空的景色会随之一起变化。在控制惹人物移动的BowController脚本中,在人物移动的同时加载了对应的天空资源。

3、固定靶

        使用TargetController脚本来控制。脚本主要分为两部本,一部分用于将得分控制实例化,将靶子实例化于场景中时,设置RingScore的值来控制得分。第二部分用于处理箭与靶子的碰撞事件,当箭与靶子碰撞之后将靶子的tag修改为对应的标记值然后将箭和靶子绑定在一起,从而表现中靶动作。

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

// 靶子的控制器
public class TargetController : MonoBehaviour        // 用于处理靶子的碰撞
{
    public int RingScore = 0;    //当前靶子或环的分值

    public ScoreRecorder sc_recorder;

    void Start(){
        sc_recorder = Singleton<ScoreRecorder>.Instance;
    }

    void Update(){      
    }

    void OnCollisionEnter(Collision collision)    // 检测碰撞
    {
        Transform arrow = collision.gameObject.transform;        // 得到箭身
        if(arrow == null) return;
        if(arrow.tag == "arrow"){
            //将箭的速度设为0
            arrow.GetComponent<Rigidbody>().velocity = new Vector3(0,0,0);
            //使用运动学运动控制
            arrow.GetComponent<Rigidbody>().isKinematic = true;
            arrow.transform.rotation = Quaternion.Euler(0, 0, 0);    // 使箭的旋转角度为0
            arrow.transform.parent = this.transform;                // 将箭和靶子绑定
            sc_recorder.RecordScore(RingScore);         //计分
            arrow.tag = "onTarget";            //标记箭为中靶
        }
    }
}

4、运动靶

        有两种不同动作的运动靶,一种较快,一种较慢,需要先自己录制动作,再设置成对应的Animation来控制靶子的运动。

5、射击位

        采用了变量canshoot变量来标识是否能射击,键入1、2、3可以传送至对应的射击点位,而同时canshoot变量也被置1,从而允许弩进行射箭。若没有传送至射击点位进行射击,则会提示需要传送至射击点位进行射击。

6、多摄像机

        游戏分为两个摄像机,一个为主摄像机Main Camera负责主页面的第一人称视角,一个为minimap摄像机,是一个鸟瞰视野,方便控制人物的移动。鸟瞰摄像机采用了Render Texture组件和Row Image组件。

7、声音

        1、场景背景音乐

                在场景内初始化的对象中添加Audio Source组件,将所需要的对应的音频资源拖入该组件的AudioClip中,再将Play On Wake打勾,即在场景初始化时便加载这一音乐对象,即可实现作为背景音乐。

        2、拉弓射箭音效。

                在ArrowController脚本中,设置AudioSource和AudioClip的音频源和音频信息,并在拉弓和射箭的时候调用其自带的Play()函数来播放音频。

        

8、游走

        游走分为三个部分的控制,第一是控制前后左右的人物角色移动move(),第二为控制视角的函数view(),最后为控制人物传送至指定射击地点的函数transport(),三个分为以键盘前后左右轴,鼠标移动和键盘键入1、2、3为输入参数来控制对应函数产生对应效果放入Fixedupdate()中,从而来控制人物的游走。

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

// 挂载在弩上,控制弩的移动和视角的旋转、以及传送到指定的射击位置
public class BowController : MonoBehaviour     
{
    public float moveSpeed = 10;    //速度:每秒移动6个单位长度
    public float angularSpeed = 90;    //角速度:每秒旋转90度
    public float jumpForce = 200f;    //跳跃参数
    public float horizontalRotateSensitivity = 2;    //水平视角灵敏度
    public float verticalRotateSensitivity = 3;    //垂直视角灵敏度
    private float xRotation = 0f;         // x旋转角度
    public bool canshoot = false;        // 是否可以射箭
    private Rigidbody rb;
    void Start()
    {
         rb = transform.parent.GetComponent<Rigidbody>();
    }
 
    void FixedUpdate()        
    {
        Move();
        View();
        transport();
    }
    public float jumpHeight = 10.0f;
    void Move()      // 移动
    {
        float v = Input.GetAxis("Vertical");
        float h = Input.GetAxis("Horizontal");
 
        transform.parent.Translate(Vector3.forward * v * Time.deltaTime * moveSpeed);
        transform.parent.Translate(Vector3.right * h * Time.deltaTime * moveSpeed);

         if (Input.GetKeyDown(KeyCode.Space))
         {
             rb.AddForce(Vector3.up * Mathf.Sqrt(jumpHeight * -2.0f * Physics.gravity.y), ForceMode.VelocityChange);
         }  
    }

    void SetCursorToCentre()   // 锁定鼠标到屏幕中心
    {
        //锁定鼠标后再解锁,鼠标将自动回到屏幕中心
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.lockState = CursorLockMode.None;
        //隐藏鼠标
        Cursor.visible = false;
    }

    void View()    // 控制视角
    {
        SetCursorToCentre();        //锁定鼠标到屏幕中心
        
        float mouseX = Input.GetAxis("Mouse X") * Time.deltaTime * angularSpeed* horizontalRotateSensitivity;
        transform.parent.Rotate(Vector3.up * mouseX);     // "人"水平旋转

        float mouseY = Input.GetAxis("Mouse Y") * Time.deltaTime * angularSpeed * verticalRotateSensitivity;
        xRotation -= mouseY;
        xRotation = Mathf.Clamp(xRotation, -45f, 45f);          // 限制上下视角
        transform.localRotation = Quaternion.Euler(xRotation, 0f, 0f);     // "人"不动,弓上下移动
    }

    void transport()     // 传送到指定的射击位置
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))            // 静态靶子区域
        {
            canshoot = true;
            transform.parent.rotation = Quaternion.Euler(0, 0, 0);
            transform.parent.position = new Vector3(120, 1, 70);          
            RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial1");
        }
        if (Input.GetKeyDown(KeyCode.Alpha2))            // 动态靶子区域
        {
            canshoot = true;
            transform.parent.rotation = Quaternion.Euler(0, 180, 0);
            transform.parent.position = new Vector3(-120, 1, -70);       
            RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial2");
        }
        if (Input.GetKeyDown(KeyCode.Alpha3))            // 静态大靶子区域
        {
            canshoot = true;
            transform.parent.rotation = Quaternion.Euler(0, 0, 0);
            transform.parent.position = new Vector3(-120, 1, 70);       
            RenderSettings.skybox = Resources.Load<Material>("Materials/SkyboxMaterial3");
        }
        if (Input.GetKeyDown(KeyCode.B))                // 回到原始位置
        {
            canshoot = false;
            transform.parent.position = new Vector3(0, 1, -8);
        }
    }
    
}

9、 射击效果

        射击的实现需要一个继承SSAction基类的射击动作CCShootAction,该类start()时将箭与弩绑定,并初始化其物理引擎的控制状态,当射击时调用GetSSAction函数来给箭施加一个冲量,从而实现物理引擎对其的控制,而在FixedUpdate()函数中动态判断箭是否落地或者上靶,动态更新销毁落地和上靶的箭。

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

// 射箭动作
public class CCShootAction : SSAction
{
    private Vector3 pulseForce;  //射箭提供的冲量
    private CCShootAction(){}

    public static CCShootAction GetSSAction(Vector3 impulseDirection,float power)      //获得一个射箭动作实例
    {
        CCShootAction shootarrow = CreateInstance<CCShootAction>();
        shootarrow.pulseForce = impulseDirection.normalized * power;          // 射箭提供的冲量(方向*力度)
        return shootarrow;
    }

    public override void Start()
    {
        gameobject.transform.parent = null;        // 摆脱跟随弓移动
        gameobject.GetComponent<Rigidbody>().useGravity = true;                  //  发射的时候才开启重力
        gameobject.GetComponent<Rigidbody>().velocity = Vector3.zero;               // 初速度为0
        gameobject.GetComponent<Rigidbody>().AddForce(pulseForce,ForceMode.Impulse);        //添加冲量
        //关闭运动学控制
        gameobject.GetComponent<Rigidbody>().isKinematic = false;      // 此时箭的运动由物理引擎控制
    }

    public  override void Update(){
        
    }

    public override void FixedUpdate(){   // 判断箭是否飞出场景、落在地上或者射中靶子
        if(!gameobject || this.gameobject.tag == "ground" || this.gameobject.tag == "onTarget"  )  
        {
            this.destroy = true;     // 摧毁动作
            this.callback.SSActionEvent(this);
        }
    }
}


        同时,还有一个射击动作的管理类CCShootManager,继承于SSActionManager ISSCallback负责将射击动作实例化并控制一些相关的飞行参数,从而管理射击动作的实现。

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

// 射箭动作的管理器
public class CCShootManager : SSActionManager, ISSCallback
{
    //箭飞行的动作
    private CCShootAction shoot;                                
    protected new void Start(){
    }

    public void SSActionEvent(SSAction source,  
        SSActionEventType events = SSActionEventType.Completed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null) {
    }

    //箭飞行
    public void ArrowShoot(GameObject arrow, Vector3 impulseDirection, float power)          // 游戏对象、力的方向、力的大小
    {
        shoot = CCShootAction.GetSSAction(impulseDirection, power);        //实例化一个射箭动作。
        RunAction(arrow, shoot, this);                  //调用SSActionmanager的方法运行动作。
    }
}


10、碰撞与计分

        碰撞计分机制由TargetController和ScoreRecoder两个共同实现,ScroreRecoder提供了一个变量score来记录分数,以及一个RecordScore()函数来加分。在TargetController中,在检测到碰撞后调用RecordScore()函数来加上对应的分数。现场修改游戏规则只需要在ArrowController中,加载靶子的实例对象时,对加载的靶子数目、不同靶子分数、不同环数的分数进行修改即可。

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

// 计分器类
public class ScoreRecorder : MonoBehaviour
{
    public int score;
    void Start()
    {
        score = 0;
    }

    public void RecordScore(int ringscore)
    {
       score += ringscore;        //增加新的值
    }
}

11、弩弓动画

        下载RyuGiKen资源包,利用其中的弩和制作好的动画来完成弩的动作。

        pull:

        hold:

三、整体实现

        1、ArrowController类

        控制整个游戏的运行。该脚本挂载在弓弩对象上,用于将场景、动作和用户交互的界面相关联。

        首先,LoadResources()中加载了主摄像机对象,弓弩对象,并将两者绑定,以获取射击的第一人称视角。然后构建靶子的实例对象和预制结构,其中静态靶子需要在构造时确定固定的位置,而动态的靶子对象因为动画中设置了动画的位置,所以构造时不需要指定其位置。在加载靶子的同时需要对不同靶子不同环数的得分进行设置(TargetController中初始都置0),通过这个赋值过程可以使得不同射中难度的靶子得分不同,从而在这部分代码中设置了游戏的得分规则。其中LoadTarget()函数为靶子对象构造的抽象函数,将预制的名字、分数、位置作为参数输入即可构造出对应需求的靶子实例对象于对应位置,并且添加对应的得分规则。

        start()中主要负责进行初始化操作,给对象添加各种需要的components。

        Shoot()函数用于触发射击的动作。首先需要确保箭矢队列里面还有箭,否则提示玩家按“R”键装载弓箭。在有箭的基础上,如果当前处在不可射击的区域时,当玩家点击鼠标时提示玩家到特定的区域射击。当箭矢队列中有箭并且玩家处在可射击区域时,触发射击的动作并通过射击动作控制器来执行动作。

        aniShoot()函数根据玩家不同的鼠标点击动作,执行蓄力、等待、射击的动画效果。这里引入了一个变量state(有值1、2、3)用来表示当前的状态来确保动作是按照蓄力、等待、射击的顺序来完成的,只有在正确状态下按下鼠标,才会执行相应的动画效果。对于“蓄力”操作,根据玩家按下鼠标左键的时间长短,弓箭的力也会改变,直到蓄力到达2秒时到达最大的力;不同大小的力量,对应的动画的效果和射箭的力度也会不同。当处于等待状态并按下鼠标右键时,执行“射击”状态,在播放动画的同时,从箭矢队列中取出一支箭,将其加入已发射flyed队列中,并通过射击动作管理器为这支箭执行射击动作。至此就完成了箭矢的射击动作和动画效果。

       LoadArrow()用于执行换弹(箭)操作,点击“R”后,先清空arrows队列中的箭,再通过箭的工厂添加10支箭进入队列,当要执行射击动作时,直接从队列中获取对应的箭即可。

        HitGround()用于判断是否和地面发生碰撞。通过射线检测的方法判断箭矢是否射中地面。如果箭矢和地面的距离小于设定的阈值(如代码中的2f)时,则认为箭矢碰撞到了地面,将箭矢的tag标记为ground,速度设置为0并取消物理引擎的控制。

        GetScore()、GetArrowNum()、GetMessage()函数分别用来获得当前得分,当前弹夹内剩余的弓箭数目以及获得当前需要显示在用户UI界面上的文字信息。

        Update()函数控制了一个射箭从换弹到蓄力射击最后箭射出后的后续处理的全过程。当用户按下"R"时调用LoadArrow()函数来获得箭,并同时更新当前的弹夹信息。调用Shoot()来执行射击的全过程动作,然后箭射出后调用HitGround()来检测当前的箭是否触碰到地面,并且检验维护记录已射出的箭的队列(flyed)来控制当飞出的箭数大于10或者箭飞出地图时,即使销毁对应的箭对象来释放空间,确保存在场上的箭数目最多为10.。

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

public class ArrowController : MonoBehaviour, ISceneController, IUserAction
{
    public AudioSource audioSource1;//射击音频源
    public AudioClip pullSound;//射击音频
    public AudioClip shootSound;//射击音频
    public CCShootManager arrow_manager;    //箭的动作管理者
    public ArrowFactory factory;    //箭的工厂
    public GameObject main_camera;       // 主相机
    public ScoreRecorder recorder;        // 计分对象
    public GameObject bow;     // 弓弩
    public GameObject target1, target2, target3, target4, large_target; // 四种不同的靶子和一个巨型靶子
    public GameObject arrow;       // 当前射出的箭
    public string message = "";   // 用于显示的信息
    private int arrow_num = 0;    // 装载的箭的数量
    public Animator ani;          // 动画控制器
    private List<GameObject> arrows = new List<GameObject>();    // 箭的队列
    private List<GameObject> flyed = new List<GameObject>();    // 飞出去的箭的队列

    public float longPressDuration = 2.0f; // 设置长按持续时间
    private bool isLongPressing = false;       // 是否正在长按
    private float pressTime = 0f;         // 长按的时间
    public int state = 0;  // 0-普通状态,1-拉弓状态,2-蓄力状态
    public float power = 0f;  // 拉弓的力度

    //加载箭和靶子
    public void LoadResources()
    {
        main_camera = transform.GetChild(5).gameObject;   // 获取摄像机
        bow = this.gameObject;
        // 加载靶子
        for(int i = 0; i < 3; ++i)    // 3 * 2 = 12个靶子
        {
            target1 = LoadTarget("target", 10 + i, new Vector3(100+20*i, 20, 140+5*i)); // 静态小靶子
            target2 = LoadTarget("big_target", 5-i, new Vector3(100+20*i, 10, 140-5*i)); // 静态大靶子
        }
        target3 = LoadTarget("target_move", 15, new Vector3(0,0,0)); // 慢速移动靶子, 使用动画,自带位置
        target4 = LoadTarget("target_quick", 20, new Vector3(0,0,0)); // 快速移动靶子,使用动画,自带位置
        large_target = LoadTarget("large_target", 6, new Vector3(-100, 25, 180)); // 静态大靶子
        for(int i = 0; i < 4; ++i)        // 给子对象添加TargetController组件
        {
            Transform child = large_target.transform.GetChild(i);
            child.gameObject.AddComponent<TargetController>();
            child.gameObject.GetComponent<TargetController>().RingScore = 7 + i;       // 不同的环数分数不同
        }
    }

    GameObject LoadTarget(string name, int score, Vector3 pos)    // 加载靶子
    {
        GameObject target = GameObject.Instantiate(Resources.Load("Prefabs/"+name, typeof(GameObject))) as GameObject;    // 从预制中获得靶子
        target.transform.position = pos;
        target.AddComponent<TargetController>();                 // 给靶子添加TargetController组件
        target.GetComponent<TargetController>().RingScore = score;
        return target;
    }

    //进行资源加载
    void Start()
    {
        arrow = null;   
        SSDirector director = SSDirector.getInstance();
        director.currentSceneController = this;
        director.currentSceneController.LoadResources();
        gameObject.AddComponent<ArrowFactory>();
        gameObject.AddComponent<ScoreRecorder>();
        gameObject.AddComponent<UserGUI>(); 
        gameObject.AddComponent<BowController>();
        factory = Singleton<ArrowFactory>.Instance;
        recorder = Singleton<ScoreRecorder>.Instance;
        arrow_manager = this.gameObject.AddComponent<CCShootManager>() as CCShootManager; 
        ani = GetComponent<Animator>();
    }

    void Update()
    {
        Shoot();              // 射击
        HitGround();           // 检测箭是否射中地面
        for(int i = 0; i < flyed.Count; ++i)         // 用于维护已经射出去的箭的队列
        {   
            GameObject t_arrow = flyed[i];
            if(flyed.Count > 10 || t_arrow.transform.position.y < -10)   // 箭的数量大于10或者箭的位置低于-10
            {   // 删除箭
                flyed.RemoveAt(i);
                factory.RecycleArrow(t_arrow);
            }
        }
        if(Input.GetKeyDown(KeyCode.R))         // 按R换弹
        {   //从工厂得到箭
            LoadArrow();
            message = "弩箭已填充完毕";
        }
    }

    public void Shoot()   // 射击
    {
        if(arrows.Count > 0){    // 确保有箭
            if(!gameObject.GetComponent<BowController>().canshoot && (Input.GetMouseButtonDown(0) || Input.GetButtonDown("Fire2"))){
                message = "请按1、2、3到指定地点射击";
            }
            else{
                aniShoot();
            } 
        }
        else{
            message = "请按R键以装载弓箭";
        }
    }
    
    public void aniShoot(){    // 射击的动画
        if (Input.GetMouseButtonDown(0) && state==0) // 监测鼠标左键按下
            {   
                message = "";
                transform.GetChild(4).gameObject.SetActive(true);           // 设置动作中的箭可见
                isLongPressing = true;
                ani.SetTrigger("pull");
                pressTime = Time.time;
                state = 1;
                PlaySound1();//播放声音
        }
            if (Input.GetMouseButtonUp(0) && state==1) // 监测鼠标左键抬起
            {
                isLongPressing = false;
                float duration = Time.time - pressTime;
                if (duration < longPressDuration){                  // 执行普通点击操作
                    power = duration/2;
                }
                else{                    // 拉满了
                    power = 1.0f;
                }
                ani.SetFloat("power", power);
                ani.SetTrigger("hold");
                state = 2;
            }
            if (isLongPressing && Time.time - pressTime > longPressDuration)   // 长按但是未抬起,且持续时间超过设定值
            {
                // 长按操作
                isLongPressing = false;
                power = 1.0f;
                ani.SetFloat("power", power);
                ani.SetTrigger("hold");
            }
            if (Input.GetButtonDown("Fire2") && state==2){       // 鼠标右键,攻击2
                transform.GetChild(4).gameObject.SetActive(false);          // 设置动作中的箭不可见
                ani.SetTrigger("shoot");
                arrow = arrows[0];
                arrow.SetActive(true);
                flyed.Add(arrow);
                arrows.RemoveAt(0);
                arrow_manager.ArrowShoot(arrow, main_camera.transform.forward,power);
                ani.SetFloat("power", 1.0f);        // 恢复力度
                arrow_num -= 1;
                state = 0;
                PlaySound2();//播放声音
        }
    }
    void PlaySound1()//拉弓
    {
        audioSource1.clip = pullSound; // 设置音频片段为拉弓音效
        audioSource1.Play(); // 播放音效
    }
    void PlaySound2()//射箭
    {
        audioSource1.clip = shootSound; // 设置音频片段为拉弓音效
        audioSource1.Play(); // 播放音效
    }

    public void LoadArrow(){          // 获得10支箭
        arrow_num = 10;
        while(arrows.Count!=0){     // 清空队列   
            factory.RecycleArrow(arrows[0]);
            arrows.RemoveAt(0);
        }
        for(int i=0;i<10;i++){
            GameObject arrow = factory.GetArrow();
            arrows.Add(arrow);
        }
    }

    public void HitGround(){               // 检测箭是否射中地面
        RaycastHit hit;
        if (arrow!=null && Physics.Raycast(arrow.transform.position, Vector3.down, out hit, 2f))
        {
            // 如果射线与地面相交
            if (hit.collider.gameObject.name == "Terrain")
            {
                arrow.tag = "ground";
                //将箭的速度设为0
                arrow.GetComponent<Rigidbody>().velocity = new Vector3(0,0,0);
                //使用运动学运动控制
                arrow.GetComponent<Rigidbody>().isKinematic = true;
            }
        }
    }

    //返回当前分数
    public int GetScore(){
        return recorder.score;
    }

    //得到剩余的箭的数量
    public int GetArrowNum(){
        return arrow_num;
    }

    // 显示的信息
    public string GetMessage(){
        return message;
    }
}

         2、ArrowFactory箭工厂。

                箭工厂用于维护箭创建和销毁的类似生命周期的过程。包含箭的控制器ArrowController、箭对象和箭的获取(GetArrow())和回收函数(RecycleArrow())。箭的获取包括了从资源中获取箭对象的预制结构,并将其放在弩上所需要的位置同时位置信息与弓弩进行绑定的过程。而箭的回收函数主要是对已有的箭对象进行销毁。

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

// 箭的工厂,用于创建箭和回收箭
public class ArrowFactory : MonoBehaviour
{
    public ArrowController arrowCtrl;
    public GameObject arrow;
    void Start()
    {
        arrowCtrl = (ArrowController)SSDirector.getInstance().currentSceneController;
    }

    void Update(){
    }

    public GameObject GetArrow(){    // 获取空闲的箭
        arrow = GameObject.Instantiate(Resources.Load("Prefabs/Arrow", typeof(GameObject))) as GameObject;
        //得到弓箭上搭箭的位置                       
        Transform bow_mid = arrowCtrl.bow.transform.GetChild(4);   // 获得箭应该放置的位置

        arrow.transform.position = bow_mid.transform.position;               //将箭的位置设置为弓中间的位置
        arrow.transform.rotation = arrowCtrl.bow.transform.rotation;    // 将箭的旋转角度设置为弓的旋转角度

        arrow.transform.parent = arrowCtrl.bow.transform;        //箭随弓的位置变化
        arrow.gameObject.SetActive(false);

        return arrow;
    }

    public void RecycleArrow(GameObject arrow)        // 回收箭
    {
        arrow.SetActive(false);
        DestroyImmediate(arrow);
    }
}


        3、UI界面

                首先,定义了UI界面所需接口,GetScore() 、GetArrowNum()、GetMessage()分别用来获得当前得分,当前弹夹内剩余的弓箭数目以及获得当前需要显示在用户UI界面上的文字信息,这些信息都是UI界面中需要显示的内容。

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

// 与用户交互的接口
public interface IUserAction
{
    int GetScore();
    int GetArrowNum();
    string GetMessage();
}

                UserGUI 用户交互界面 ,用于显示与用户交互相关的信息。首先start()函数内初始化定义了字体的style信息,并获得了用于用户交互的接口实例。update()函数用于实时获取游戏页面的宽度和高度。最后 OnGUI()函数用于显示玩家的得分信息、剩余箭矢数、以及用于提示玩家的信息(该信息由ArrowController类内代码控制,在需要时显示对应的信息)。同时这里还在屏幕中央加了一个圆作为射击的准星,以提高玩家游戏体验。

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

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    GUIStyle style1 = new GUIStyle();
    GUIStyle style2 = new GUIStyle();
    public string message = "";        // 显示的信息
    int screenWidth, screenHeight;

    void Start()
    {
        action = this.transform.gameObject.GetComponent<IUserAction>();

        style1.normal.textColor = Color.blue;
        style1.fontSize = 30;

        style2.normal.textColor = Color.red;
        style2.fontSize = 40;
    }

    void Update()
    {
        screenWidth = Screen.width;
        screenHeight = Screen.height;
    }

    private void OnGUI()
    {
        //显示各种信息
        GUI.Label(new Rect(20,10,150,50),"得分: ",style1);
        GUI.Label(new Rect(100,10,150,50),action.GetScore().ToString(),style2);
        
        GUI.Label(new Rect(20,50,150,50),"剩余箭矢数: ",style1);
        GUI.Label(new Rect(200,50,150,50),action.GetArrowNum().ToString(),style2);

        GUI.Label(new Rect(150,100,150,50),action.GetMessage(),style1);

        GUI.Label(new Rect(screenWidth/2+10,screenHeight/2,150,50),"o",style2);
    }
}

四、其他

        代码地址:wangP23/3D-Shoot

        参考文章:https://blog.csdn.net/weixin_70964175/article/details/134721093?ops_request_misc=%257B%2522request%255Fid%2522%253A%25221d4230eb9645dca52ca9a5f8ba6824e5%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=1d4230eb9645dca52ca9a5f8ba6824e5&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-3-134721093-null-null.nonecase&utm_term=%E7%AC%AC%E4%B8%80%E4%BA%BA%E7%A7%B0%E5%B0%84%E7%AE%AD&spm=1018.2226.3001.4450

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值