unity第一人称射箭游戏

游戏要求

  •  地形:使用地形组件,上面有草、树;
  •  天空盒:使用天空盒,天空可随玩家位置 或 时间变化 或 按特定按键切换天空盒;
  •  固定靶:有一个以上固定的靶标;
  •  运动靶:有一个以上运动靶标,运动轨迹,速度使用动画控制;
  •  射击位:地图上应标记若干射击位,仅在射击位附近可以拉弓射击,每个位置有 n 次机会;
  •  弩弓动画:支持蓄力半拉弓,然后 hold,择机 shoot;
  •  游走:玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;
  •  碰撞与计分:在射击位,射中靶标的相应分数,规则自定;

项目下载与展示

下载地址:

项目地址

游戏展示:

射击游戏视频展示

具体实现

人物控制

人物控制脚本

  1. 首先,脚本中声明了一些变量和引用:

    • controller:人物控制器,用于控制玩家角色的移动。
    • moveSpeed:人物移动速度。
    • gravity:模拟重力,用于在玩家角色下落时施加向下的速度。
    • mouseSpeed:鼠标控制角色旋转的速度。
    • xRot:角色绕X轴的旋转角度。
    • velocity:角色的当前速度向量。
    • playerFire:对应的弩箭发射脚本的引用。
  2. Start()方法中,获取角色的CharacterController组件,并将其赋值给controller变量。同时,锁定鼠标在屏幕中心并隐藏鼠标指针。

  3. Update()方法中,调用Move()方法和Rotation()方法来处理角色的移动和旋转。

  4. Move()方法用于处理角色的移动,具体操作如下:

    • 获取水平轴(左右键盘方向键)和垂直轴(上下键盘方向键)的输入。
    • 根据输入的值计算移动方向向量。
    • 使用人物控制器的Move()方法来移动角色,根据移动速度和时间间隔计算位移。
    • 根据模拟重力的值,更新角色的垂直速度。
    • 使用人物控制器的Move()方法再次移动角色,根据垂直速度和时间间隔计算位移。
  5. Rotation()方法用于处理角色的旋转,具体操作如下:

    • 获取鼠标在水平和垂直方向上的输入。
    • 根据鼠标输入和鼠标控制速度计算角色绕Y轴和X轴的旋转量。
    • 限制角色绕X轴的旋转角度在特定范围内。
    • 更新角色的旋转值,使其绕Y轴和X轴旋转。

射击区域实现

  1. OnTriggerEnter(Collider other)方法是当玩家角色进入触发器时调用的方法,具体操作如下:

    • 判断进入触发器的游戏对象的标签是否为"FirePoint"。
    • 如果是,则将playerFire脚本中的fire变量设置为true,表示可以发射箭。
  2. OnTriggerExit(Collider other)方法是当玩家角色退出触发器时调用的方法,具体操作如下:

    • 判断退出触发器的游戏对象的标签是否为"FirePoint"。
    • 如果是,则将playerFire脚本中的fire变量设置为false,表示不可发射箭。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    //人物控制器
    private CharacterController controller;
    //人物移动速度
    public float moveSpeed = 2f;
    //模拟重力
    public float gravity = -10f;
    public float mouseSpeed = 40f;
    float xRot = 0f;
    Vector3 velocity;

    public PlayerFire playerFire;

    private void Start()
    {
        controller = GetComponent<CharacterController>();
        Cursor.lockState = CursorLockMode.Locked; // 锁定鼠标在屏幕中心
        Cursor.visible = false; // 隐藏鼠标指针
    }

    void Update()
    {
        Move();
        Rotation();
    }

    //移动
    public void Move()
    {
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        //方向
        Vector3 dir = transform.right * x + transform.forward * z;
        controller.Move(dir * moveSpeed * Time.deltaTime);
        velocity.y += gravity * Time.deltaTime;
        controller.Move(velocity * Time.deltaTime);
    }

    //旋转
    public void Rotation()
    {
        //鼠标控制
        float mouseX = Input.GetAxis("Mouse X") * mouseSpeed * Time.deltaTime;
        float mouseY = Input.GetAxis("Mouse Y") * mouseSpeed * Time.deltaTime;
        xRot -= mouseY;
        //人物移动
        xRot = Mathf.Clamp(xRot, -20f, 10f);
        transform.rotation = Quaternion.Euler(xRot, transform.rotation.eulerAngles.y + mouseX, 0f);
    }


    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.tag == "FirePoint")
        {
            playerFire.fire = true;
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.gameObject.tag == "FirePoint")
        {
            playerFire.fire = false;
        }
    }
}

标靶控制

标靶控制脚本

  1. 首先,定义了一个枚举类型MoveDirection,用于表示移动的方向,包括前后、左右和上下三个方向。

  2. 在脚本中声明了一些变量和引用:

    • isMovingTarget:是否为移动靶,用于控制靶子是否需要移动。
    • distance:反复移动的距离。
    • speed:移动速度。
    • moveDirection:移动方向,根据枚举类型MoveDirection来确定。
    • startPos:初始位置,用于记录靶子的初始位置。
    • isReverseDirection:是否反向移动,用于控制靶子的移动方向。
  3. Start()方法中,将靶子的初始位置赋值给startPos变量。

  4. Update()方法中,如果isMovingTargettrue,则执行靶子的移动逻辑。

    • 根据移动方向确定移动向量dir
    • 如果需要反向移动,则将移动向量取反。
    • 计算物体下一帧的位置nextPos,根据移动速度和时间间隔计算位移。
    • 判断物体是否超出移动范围,如果超出则改变移动方向。
    • 更新物体的位置。
  5. OnCollisionEnter(Collision collision)方法中,当箭与靶子发生碰撞时调用的方法,具体操作如下:

    • 判断碰撞的游戏对象的标签是否为"Arrow"。
    • 将碰撞的刚体设为运动学,使其不受物理引擎的影响。
    • 获取碰撞点的位置collisionPoint,通过GetContact(0)获取第一个碰撞点的位置。
    • 将箭的父物体设置为靶子,使箭跟随靶子移动。
    • 计算碰撞点和靶子中心点之间的距离。
    • 根据距离的范围进行逻辑判断,根据是否为移动靶子和距离的不同,设置不同的文本内容即所得分数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum MoveDirection
{
    Forward,
    Left,
    Up
}

public class TargetController : MonoBehaviour
{
    public bool isMovingTarget;//是否为移动靶
    public float distance = 5f; // 反复移动的距离
    public float speed = 2f; // 速度
    public MoveDirection moveDirection; // 移动方向
    private Vector3 startPos; // 初始位置
    private bool isReverseDirection; // 是否反向移动

    private void Start()
    {
        startPos = transform.position; // 记录初始位置
    }

    private void Update()
    {
        if (isMovingTarget)
        {
            // 根据移动方向确定移动向量
            Vector3 dir = Vector3.zero;
            if (moveDirection == MoveDirection.Forward)
            {
                //前后
                dir = transform.forward;
            }
            else if (moveDirection == MoveDirection.Left)
            {
                //左右
                dir = -transform.right;
            }
            else if (moveDirection == MoveDirection.Up)
            {
                //上下
                dir = transform.up;
            }

            // 如果需要反向移动,则将移动向量取反
            if (isReverseDirection)
            {
                dir = -dir;
            }
            // 计算物体下一帧的位置
            Vector3 nextPos = transform.position + dir * speed * Time.deltaTime;
            // 判断物体是否超出移动范围,如果超出则改变移动方向
            if (Vector3.Distance(startPos, nextPos) > distance)
            {
                isReverseDirection = !isReverseDirection; // 反向移动
            }
            // 更新物体的位置
            transform.position = nextPos;
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Arrow")
        {
            collision.rigidbody.isKinematic = true;
            // 获取第一个碰撞点的位置
            Vector3 collisionPoint = collision.GetContact(0).point;
            //设置父物体为靶子
            collision.transform.parent = transform;
            // 计算碰撞点和物体中心点之间的距离
            float distance = Vector3.Distance(collisionPoint, transform.position);
            // 在这里进行与中心点距离逻辑判断 小于0.25f为靶心
            if (distance <= 0.25f)
            {
                if (isMovingTarget)
                {
                    SetText.Instance.SetStr("移动靶靶心区域", 10);
                }
                else
                {
                    SetText.Instance.SetStr("固定靶靶心区域", 5);
                } 
            }
            else if(distance > 0.25f && distance < 0.4f) 
            {
                if (isMovingTarget)
                {
                    SetText.Instance.SetStr("移动靶中间区域", 6);
                }
                else
                {
                    SetText.Instance.SetStr("固定靶中间区域", 3);
                }
            }
            else
            {
                if (isMovingTarget)
                {
                    SetText.Instance.SetStr("移动靶边缘区域", 3);
                }
                else
                {
                    SetText.Instance.SetStr("固定靶边缘区域", 1);
                }
           }
        } 
    }
}


弩弓发射与蓄力

弩箭发射脚本

  1. 首先,脚本中声明了一些变量和引用:

    • animator:用于获取角色的动画组件。
    • arrowObj:箭的预制体,即用于实例化箭的游戏对象。
    • arrowSpeed:箭的移动速度。
    • holdTime:蓄力时间,用于记录玩家按住鼠标右键的时间。
    • fire:控制是否能发射箭的布尔变量。
  2. Start()方法中,获取角色的Animator组件,并将其赋值给animator变量。

  3. Update()方法中,如果fire为真,则进行以下操作:

    • 如果玩家按下鼠标右键(Input.GetMouseButtonDown(1)),则触发角色的蓄力动画(通过设置动画控制器中的触发器)。
    • 如果玩家一直按住鼠标右键(Input.GetMouseButton(1)),则累加蓄力时间,并将蓄力时间传递给动画控制器中的浮点数参数。
    • 如果玩家松开鼠标右键(Input.GetMouseButtonUp(1)),则销毁场景中的所有箭对象、触发角色的发射动画,并调用Fire()方法发射箭,并重置蓄力时间为0。
  4. Fire(float holdForce)方法用于发射箭,具体操作如下:

    • 实例化箭对象,位置为当前对象的子对象的位置,旋转为当前对象的子对象的旋转。
    • 获取箭对象的刚体组件。
    • 根据蓄力时间和箭的速度,设置箭的初始速度(方向为当前对象的前方向)。
  5. DestroyArrows()方法用于删除场景中所有的箭对象,具体操作如下:

    • 通过标签查找所有具有"Arrow"标签的游戏对象。
    • 循环遍历找到的箭对象数组,并销毁每个箭对象。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 弩箭发射脚本
/// </summary>
public class PlayerFire : MonoBehaviour
{
    private Animator animator;
    //箭的预制体
    public GameObject arrowObj;
    //箭的移动速度
    public float arrowSpeed = 25;
    //蓄力时间
    float holdTime;
    //是否能发射
    public bool fire;
    void Start()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        if (fire)
        {
            //鼠标右键发射
            if (Input.GetMouseButtonDown(1))
            {
                animator.SetTrigger("hold");
            }
            else if (Input.GetMouseButton(1))
            {
                holdTime += Time.deltaTime;
                animator.SetFloat("holdTime", holdTime);
            }
            else if (Input.GetMouseButtonUp(1))
            {
                DestroyArrows();
                animator.SetTrigger("shoot");
                Fire(holdTime);
                //清空上次蓄力时间
                holdTime = 0;
            }
        } 
    }

    /// <summary>
    /// 发射
    /// </summary>
    /// <param name="holdForce"></param>
    public void Fire(float holdForce)
    {
        //实例化箭
        GameObject arrow = Instantiate(arrowObj, transform.GetChild(0).position, transform.GetChild(0).rotation);
        //获得刚体
        Rigidbody arrowRigidbody = arrow.GetComponent<Rigidbody>();
        //根据蓄力大小发射弩箭
        arrowRigidbody.velocity = transform.forward * holdForce * arrowSpeed;
    }


 


    /// <summary>
    /// 删除所有箭
    /// </summary>
    public void DestroyArrows()
    {
        GameObject[] arrows = GameObject.FindGameObjectsWithTag("Arrow");
        for (int i = 0; i < arrows.Length; i++)
        {
            Destroy(arrows[i]);
        }
    }
}

动画控制

天空盒切换

  1. 在脚本中声明了一些变量和引用:

    • skyboxMaterials:天空盒材质的数组,用于存储多个天空盒材质。
    • currentIndex:当前天空盒的索引,用于记录当前正在使用的天空盒材质。
    • timer:计时器,用于计算时间。
    • countDownTime:倒计时时间,表示在多长时间后切换到下一个天空盒材质。
  2. Start()方法中,将初始的天空盒材质设置为数组中的第一个材质。

  3. Update()方法中,每帧更新计时器的值,累加时间。

  4. 如果计时器的值超过等于倒计时时间,表示时间已经达到了切换天空盒的条件,执行以下操作:

    • 将当前天空盒索引加1,切换到下一个天空盒材质。
    • 如果当前天空盒索引超过等于天空盒材质数组的长度,即索引越界,将当前天空盒索引重置为0,重新从第一个天空盒材质开始循环。
    • 将新的天空盒材质设置为渲染设置中的天空盒材质。
    • 将计时器重置为0,重新开始计时。
using UnityEngine;

public class SkyBox : MonoBehaviour
{
    public Material[] skyboxMaterials; // 天空盒材质数组
    private int currentIndex = 0; // 当前天空盒索引
    private float timer = 0f; // 计时器
    public float countDownTime = 10f; //倒计时时间


    private void Start()
    {
        RenderSettings.skybox = skyboxMaterials[currentIndex]; // 设置初始天空盒材质
    }

    private void Update()
    {
        timer += Time.deltaTime; // 计时器累加

        if (timer >= countDownTime) // 当计时器达到倒计时时间
        {
            currentIndex++; // 切换到下一个天空盒材质
            if (currentIndex >= skyboxMaterials.Length)//索引越界判断
            {
                currentIndex = 0;
            }
            RenderSettings.skybox = skyboxMaterials[currentIndex]; // 设置新的天空盒材质
            timer = 0f; // 重置计时器
        }
    }
}

场景管理

  1. 脚本中声明了两个公共方法:

    • GotoScene(int i):用于加载场景。接收一个整数参数i作为要加载的场景的索引。
    • QuitGame():用于退出游戏。
  2. GotoScene(int i)方法中,调用SceneManager.LoadScene(i)来加载指定索引的场景。SceneManager.LoadScene()是Unity提供的静态方法,用于加载场景。通过传入场景的索引,可以加载对应的场景。

  3. QuitGame()方法中,调用Application.Quit()来退出游戏。Application.Quit()是Unity提供的静态方法,用于退出应用程序。

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

public class SceneMgr : MonoBehaviour
{
    //加载场景
    public void GotoScene(int i)
    {
        SceneManager.LoadScene(i);
    }


    //退出游戏
    public void QuitGame()
    {
        Application.Quit();
    }
}

时间控制与文本显示

时间控制脚本

  1. 在脚本中声明了一些变量和引用:

    • countdownTime:倒计时的总时间。
    • countdownText:倒计时时间的UI文本组件。
    • currentTime:当前剩余时间。
    • over:游戏结束面板的游戏对象。
    • scoreText:得分的UI文本组件。
  2. Start()方法中,将时间缩放设置为1,即正常时间流逝的状态。初始化当前剩余时间为倒计时时间。

  3. Update()方法中,每帧更新当前剩余时间。

    • 使用Time.deltaTime来减去流逝的时间,更新剩余时间。
    • 使用Mathf.Max()函数将剩余时间限制在大于等于0的范围内。
    • 如果剩余时间小于等于0,调用GameOver()方法,游戏结束。
    • 更新倒计时UI文本的显示。
  4. FormatTime(float time)方法用于将时间格式化为分:秒的形式。

    • 将剩余时间转换为整数的分钟数和秒数。
    • 使用字符串插值将分钟数和秒数格式化为"00:00"的形式。
  5. GameOver()方法用于处理游戏结束的逻辑。

    • 激活游戏结束面板。
    • 将得分显示在UI文本组件中,通过访问SetText.Instance.score来获取得分值。
    • 将鼠标锁定模式设置为None,显示鼠标光标。
    • 将时间缩放设置为0,即暂停游戏。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class TimeController : MonoBehaviour
{
    //设置倒计时时间
    public float countdownTime = 1000f;
    //倒计时ui文本
    public Text countdownText;
    //当前时间
    private float currentTime;
    public GameObject over;

    public Text scoreText;

    void Start()
    {
        Time.timeScale = 1;
        //初始化倒计时时间
        currentTime = countdownTime;
    }

    void Update()
    {
        currentTime -= Time.deltaTime;
        // 使用Mathf.Max函数将currentTime限制在0以上
        currentTime = Mathf.Max(currentTime, 0);
        if (currentTime <= 0)
        {
            GameOver();
        }
        countdownText.text = FormatTime(currentTime);
    }

    // 更改时间显示模式
    private string FormatTime(float time)
    {
        int minutes = (int)(time / 60);
        int seconds = (int)(time % 60);
        return $"{minutes:00}:{seconds:00}";
    }

    //游戏结束
    public void GameOver()
    {
        over.SetActive(true);
        scoreText.text = SetText.Instance.score.ToString();
        Cursor.lockState = CursorLockMode.None;
        Cursor.visible = true; 
        Time.timeScale = 0;
    }
}

文本设置脚本

  1. 在脚本中声明了一些变量和引用:

    • score:分数的整数值。
    • scoreText:分数的UI文本组件。
    • Instance:用于实现单例模式的静态引用。
    • tipsText:游戏提示文本的UI文本组件。
  2. Start()方法中,将Instance设置为当前脚本实例,用于实现单例模式。获取游戏提示文本的UI文本组件。

  3. SetStr(string str, int sc)方法用于设置游戏提示文本和更新分数。

    • 接收字符串参数str和整数参数sc,表示射中的目标和得分。
    • 将得分加上射中目标的得分。
    • 使用字符串插值将射中目标和得分格式化为提示文本,并显示在游戏提示文本的UI文本组件中。
    • 将分数转换为字符串,并更新分数的UI文本组件显示。
    • 使用Invoke()方法在1.5秒后调用EmptyStr()方法,清空游戏提示文本。
  4. EmptyStr()方法用于清空游戏提示文本。

    • 将游戏提示文本设置为空字符串,清空提示文本的显示。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SetText : MonoBehaviour
{
    public int score = 0;
    public Text scoreText;

    public static SetText Instance;
    Text tipsText;


    private void Start()
    {
        Instance = this;
        tipsText = GetComponent<Text>();
    }

    public void SetStr(string str , int sc)
    {
        score += sc;
        tipsText.text = "射中" + str + "+" + sc + "分";
        scoreText.text = score.ToString();
        Invoke("EmptyStr", 1.5f);
    }

    public void EmptyStr()
    {
        tipsText.text = string.Empty;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

We3le7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值