使用unity实现第一人称射箭小游戏

前言

这是中山大学软件工程学院2023年3D游戏编程与设计的第七次作业,也欢迎大家学习参考交流
github个人主页: innitinnit (github.com)

游戏规则以及操作

        这是一个第一人称的射击类小游戏。

        玩家需要通过键盘以及鼠标来控制角色射击目标,在有限的射击次数中达到目标分数则获得游戏胜利。游戏中的射击目标分为动态与静态两种,并且规定只有在变换区域后可以进行射击。单次填装后,射击次数为10次。

        -移动(wasd)

        -蓄力(长按鼠标左键进行拉弓,松开左键进行停止蓄力;根据长按时间长短来决定发射弓箭力度)

        -射击(点击鼠标右键)

        -进入静态靶区域(数字键1)

        -进入动态靶区域(数字2)

        -返回初始位置(字母B)

        -切换天空样式(数字键4)

游戏截图

游玩过程演示

1

逻辑与代码

0.0使用资源包及其下载地址

-Low-Poly Simple Nature Pack:Low-Poly Simple Nature Pack | 3D 风景 | Unity Asset Store

-Fantasy Skybox FREE:Fantasy Skybox FREE | 2D 天空 | Unity Asset Store

-十字弩:Classical Crossbow | 3D 武器 | Unity Asset Store

1.0 地形

2.0 天空盒

        玩家在游玩过程中可通过按下按键4可进行天空盒的切换。

2.0.1 代码

        编写一个SkyBoxController,挂载到场景中的空对象Sky Box中:

public class SkyBoxController : MonoBehaviour
{
    public Material[] skyboxes;  // 存储多个天空盒材质

    private int currentSkyboxIndex = 0;  // 当前天空盒的索引

    // Update is called once per frame
    void Update()
    {
        // 通过按键4触发天空盒切换
        if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            ChangeSkybox();
        }
    }

    void ChangeSkybox()
    {
        currentSkyboxIndex = (currentSkyboxIndex + 1) % skyboxes.Length;
        RenderSettings.skybox = skyboxes[currentSkyboxIndex];
    }
}

        设置参数,此处使用的是

3.0 射击靶

统一使用以下代码,完成箭矢与射击靶的互动:

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";            //标记箭为中靶
        }
    }
}
3.1 固定靶

见5.0中的代码

3.2 运动靶

同上

4.0 射击位

使用transport()函数,传送后可以进行射击。

5.0 驽弓动画

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

public class ArrowController : MonoBehaviour, ISceneController, IUserAction
{
    public CCShootManager arrow_manager;    //箭的动作管理者
    public ArrowFactory factory;    //箭的工厂
    public GameObject main_camera;       // 主相机
    public ScoreRecorder recorder;        // 计分对象
    public GameObject bow;     // 弓弩
    public GameObject target1, target2, target3, target4; // 四种不同的靶子和一个巨型靶子
    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)); // 快速移动靶子,使用动画,自带位置
    }

    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到指定地点射击\n按B返回";
            }
            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;
            }
            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;
            }
    }

    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;
    }
}

6.0 游走

        此处使用一个胶囊状3d物体作为玩家控制角色,因为是第一人称视角所以玩家无法看到自己的角色。

        这一部分中,我们需要完成:

-角色视角随鼠标移动(CameraController.cs)

-使用键盘可以操控角色移动(PlayerController.cs)

6.0.1 代码

CameraController.cs(挂载至Camera):

public class CameraController : MonoBehaviour
{
    public Transform player;//获取玩家旋转的值来进行视角旋转

    private float mouseX, mouseY;//获取鼠标移动的值
    public float mouseSensitivity;//鼠标灵敏度

    public float xRotation;

    private void Update()
    {
        mouseX=Input.GetAxis("Mouse X")*mouseSensitivity*Time.deltaTime;
        mouseY=Input.GetAxis("Mouse Y")*mouseSensitivity*Time.deltaTime;

        xRotation -= mouseY;
        xRotation = Mathf.Clamp(xRotation, -70, 70);//使玩家无法无限制地上下旋转视角

        player.Rotate(Vector3.up * mouseX);
        transform.localRotation=Quaternion.Euler(xRotation, 0,0);
        
    }
}

PlayerController.cs(挂载至Player):

public class PlayerController : MonoBehaviour
{
    private CharacterController cc;
    public float moveSpeed;
    public float jumpSpeed;

    private float horizontalMove, verticalMove;
    private Vector3 dir;

    public float gravity;
    private Vector3 velocity;

    //用于检测玩家是否在地面上
    public Transform groundCheck;
    public float checkRadius;
    public LayerMask groundLayer;
    public bool isGround;

    private void Start()
    {
        cc=GetComponent<CharacterController>();
    }

    private void Update()
    {
        isGround=Physics.CheckSphere(groundCheck.position,checkRadius,groundLayer);//玩家是否碰撞地面
        if (isGround && velocity.y < 0)
        {
            velocity.y = -2f;
        }

        horizontalMove = Input.GetAxis("Horizontal")*moveSpeed;
        verticalMove = Input.GetAxis("Vertical") * moveSpeed;

        dir=transform.forward*verticalMove+transform.right*horizontalMove;
        cc.Move(dir*Time.deltaTime);

        //跳跃
        if(Input.GetButtonDown("Jump")&& isGround)
        {
            velocity.y = jumpSpeed;
        }

        velocity.y-=gravity*Time.deltaTime;
        cc.Move(velocity*Time.deltaTime);
    }

}

设置参数:        

        Player:

        使用Character Controller组件,使角色具有碰撞体属性。

7.0 碰撞与计分

7.1碰撞

设置collider即可完成。

7.2计分

见5.0部分代码(record相关部分)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值