Unity 3D射箭游戏

一、实现功能

 地形:使用地形组件,上面有草、树;
 天空盒:使用天空盒,天空可随时间变化
 固定靶:有一个以上固定的靶标;
 运动靶:有一个以上运动靶标,运动轨迹,速度使用动画控制;
 射击位:地图上应标记若干射击位,仅在射击位附近可以拉弓射击;
 驽弓动画:支持蓄力半拉弓,然后 hold,择机 shoot;
 游走:玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;
 碰撞与计分:在射击位,射中固定靶+10分,移动靶+20分。

二、代码介绍

2.1总体框架

导入了assets store的三个资源包,分别是天空盒、靶子、弓箭资源包,ArrowController用于射箭碰撞检测控制,BowController用于控制拉弓蓄力及射击,BowMovement用于控制弓箭(摄像头)移动及视角转动,SwitchSky用于控制天空盒随时间变化。

2.2天空盒变化

新建一个空物体挂载SwitchSky,配置mats数组为需要切换的天空盒材质,ChangeTime为天空盒切换时间,这里的三个天空盒Material均为资源包中下载的。

ChangeBox() 是一个公共方法。它在指定的时间间隔内被 InvokeRepeating() 方法重复调用。在这个方法中,它通过改变 currentIndexnextIndex 来更新当前和下一个天空盒的索引,并将 RenderSettings.skybox 设置为 mats 数组中的下一个天空盒材质。

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

public class SwitchSky : MonoBehaviour
{
    public Material[] mats;
    private int currentIndex = 0;
    private int nextIndex = 1;
    public int changeTime; // 更换天空盒子的秒数

    void Start()
    {
        RenderSettings.skybox = mats[currentIndex];
        InvokeRepeating("ChangeBox", 0, changeTime);
    }

    public void ChangeBox()
    {
        currentIndex = nextIndex;
        nextIndex = (nextIndex + 1) % mats.Length;
        RenderSettings.skybox = mats[currentIndex]; // 更换天空盒材质
    }
}

2.3地形配置

导入资源包后将任意Demo拖入对象列表即可获得对应天空盒和地形,根据游戏需要设置地形范围大小,环境中还有草、栅栏、树、石头等,这些物体的预制体中需要添加Collider,选择合适的Collider以保证玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍。

2.4固定靶、移动靶

直接拖入资源包中的靶子预制体即可使用,靶子数量根据个人游戏设计添加。

固定靶添加标签staticTarget,移动靶添加标签MovingTarget,地形添加标签Ground.

移动靶分为左右移动和上下移动两种,以上下移动的动画UpDownTarget为例,新建Animation——UpDownTarget,拉到需要做成移动靶的靶子对象上自动建立Animator组件,Animation通过编辑Curves或录制设计上下移动的动画。

2.5射击位

新建两个任意形状对象作为射击区域,这里选择建立两个圆形区域,白色圆形区域附近有两个移动靶,而绿色移动区域均为固定靶,两个射击区对象均添加标签Ground.后续代码实现仅在射击位附近可以拉弓射击。

2.6拉弓动画及拉弓射击控制

弓箭CrossBow的动画控制器CrossbowAni设置如下,Empty到EmptyPull的转移条件为isPulling = Ture,EmptyPull到Shoot的转移条件为Fire = True. 

射击控制由BowController实现,可以检测

1.Isinarea() 方法用于检查玩家是否处于特定区域内。

        检查当前玩家是否位于预设的两个区域中的任意一个内,通过计算玩家位置和两个区域点之间的距离来判断。

如果玩家在指定区域内:

  • 按下空格键时触发开始拉弓动作,设置 isPullingtrue
  • 持续按住空格键记录按键时间,并映射为拉弓的强度 pullStrength
  • 松开空格键时结束拉弓动作。
  • 点击鼠标左键触发射箭动作,通过 Fire() 方法发射箭矢,并根据 pullStrength 来设置箭矢的发射力度。

2.Fire() 方法:

  • firePoint 位置实例化箭矢预制体,并设置箭矢的初始位置和方向。
  • 给箭矢添加了 Rigidbody 组件,并根据 holdForce(拉弓力度)施加力,使箭矢飞行
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class BowController : MonoBehaviour
{

    Animator animator;
    bool isPulling = false;
    float pullDuration = 0f;
    public float maxPullDuration = 2f; // 最长拉弓时间
    public float arrowSpeed = 1f; // 箭矢速度
    public GameObject arrowObj;
    float pullStrength;
    public Transform firePoint;
    public Camera maincam;
    void Start()
    {
        // 获取弓上的Animator组件
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        if (Isinarea())
        {
            Debug.Log("在区域内");
            // 当按下空格键时触发状态切换
            if (Input.GetKeyDown(KeyCode.Space))
            {
                pullDuration = 0;
                animator.SetBool("Fire", false);
                animator.SetFloat("Power", 0);
                isPulling = true;
                animator.SetBool("isPulling", true);
                //animator.SetBool("Holding", false);
            }

            // 持续按下空格键时记录按下时间,决定拉弓的强度
            if (isPulling)
            {
                pullDuration += Time.deltaTime;
                // 将按下的时间映射到0到1的范围,作为拉弓强度的参数
                pullStrength = Mathf.Clamp01(pullDuration / maxPullDuration);
                animator.SetFloat("Power", pullStrength);
            }


            if (Input.GetKeyUp(KeyCode.Space))
            {

                isPulling = false;
                //animator.SetBool("Holding", true);
                animator.SetBool("isPulling", false);

            }
            // 当点击鼠标左键时触发射击
            if (Input.GetMouseButtonDown(0))
            {
                animator.SetBool("Fire", true);
                animator.SetFloat("Power", 0);
                // animator.SetBool("Holding", false);
                Fire(pullStrength);
            }
        }
    }
    public void Fire(float holdForce)
    {
        GameObject arrow = Instantiate<GameObject>(Resources.Load<GameObject>("prefabs/Arrow"));
        arrow.AddComponent<ArrowController>();
        ArrowController arrowController = arrow.GetComponent<ArrowController>();
        arrowController.cam = maincam;
        arrow.transform.position = firePoint.transform.position;
        arrow.transform.rotation = Quaternion.LookRotation(this.transform.forward);
        Rigidbody rd = arrow.GetComponent<Rigidbody>();
        if (rd != null)
        {
            rd.AddForce(this.transform.forward * 60 * holdForce);
            Debug.Log(arrow.transform.rotation);
        }
    }
    bool Isinarea()
    {
        Vector3 currentPosition = transform.position;
        Vector3 area1 = new Vector3(-99.12f, 14.86f, 153.65f);
        Vector3 area2 = new Vector3(-93.8506f, 15.3659f, 83.4803f);
        float distance1 = Vector3.Distance(currentPosition, area1); // 计算目标点和玩家位置之间的距离
        float distance2 = Vector3.Distance(currentPosition, area2);
        float radius = 10f; // 圆的半径

        if (distance1 <= radius || distance2 <= radius)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

}

2.7游走控制

1.变量定义:

  • public Transform tourCamera;:用于存储游览相机的 Transform 组件。
  • moveSpeed, rotateSpeed, shiftRate:控制相机移动和旋转的速度参数。
  • minDistance:控制相机与不可穿透表面的最小距离。

2.Update() 方法:

  • GetDirection():检测玩家输入,并获取相机移动的方向。
  • 通过 Physics.Raycast() 方法检查是否离不可穿透表面过近,如果是,消除垂直于不可穿透表面的运动速度分量,以防止相机穿透表面。
  • 限制相机的最大和最小高度。
  • 使用 Translate() 方法移动相机。

3.GetDirection() 方法:

  • 根据玩家输入的键盘按键来设置相机在各个方向上的移动速度,实现弓箭在地图上游走。
  • 通过 Input.GetMouseButton(1) 检测鼠标右键是否按下,如果按下,根据鼠标移动来控制相机的旋转。

4.V3RotateAround() 方法:

  • 用于计算一个 Vector3 绕旋转中心旋转指定角度后所得到的向量。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BowMovement : MonoBehaviour
{
    // 在场景中游览的相机(不要给相机加碰撞器!)
    public Transform tourCamera;
    #region 相机移动参数
    public float moveSpeed = 20.0f;
    public float rotateSpeed = 150.0f;
    public float shiftRate = 2.0f;// 按住Shift加速
    public float minDistance = 0.5f;// 相机离不可穿过的表面的最小距离(小于等于0时可穿透任何表面)
    #endregion
    #region 运动速度和其每个方向的速度分量
    private Vector3 direction = Vector3.zero;
    private Vector3 speedForward;
    private Vector3 speedBack;
    private Vector3 speedLeft;
    private Vector3 speedRight;
    private Vector3 speedUp;
    private Vector3 speedDown;
    #endregion
    void Start()
    {
        if (tourCamera == null) tourCamera = gameObject.transform;

    }
    void Update()
    {
        GetDirection();
        // 检测是否离不可穿透表面过近ss
        RaycastHit hit;
        while (Physics.Raycast(tourCamera.position, direction, out hit, minDistance))
        {
            // 消去垂直于不可穿透表面的运动速度分量
            float angel = Vector3.Angle(direction, hit.normal);
            float magnitude = Vector3.Magnitude(direction) * Mathf.Cos(Mathf.Deg2Rad * (180 - angel));
            direction += hit.normal * magnitude;
        }
        if (tourCamera.localPosition.y > 25.0f)
        {
            tourCamera.localPosition = new Vector3(tourCamera.localPosition.x, 25.0f, tourCamera.localPosition.z);
        }
        if (tourCamera.localPosition.y < 12.0f)
        {
            tourCamera.localPosition = new Vector3(tourCamera.localPosition.x, 12.0f, tourCamera.localPosition.z);
        }
        tourCamera.Translate(direction * moveSpeed * Time.deltaTime, Space.World);
    }
    private void GetDirection()
    {
        #region 加速移动
        if (Input.GetKeyDown(KeyCode.LeftShift)) moveSpeed *= shiftRate;
        if (Input.GetKeyUp(KeyCode.LeftShift)) moveSpeed /= shiftRate;
        #endregion
        #region 键盘移动
        // 复位
        speedForward = Vector3.zero;
        speedBack = Vector3.zero;
        speedLeft = Vector3.zero;
        speedRight = Vector3.zero;
        speedUp = Vector3.zero;
        speedDown = Vector3.zero;
        // 获取按键输入
        if (Input.GetKey(KeyCode.W)) speedForward = tourCamera.forward;
        if (Input.GetKey(KeyCode.S)) speedBack = -tourCamera.forward;
        if (Input.GetKey(KeyCode.A)) speedLeft = -tourCamera.right;
        if (Input.GetKey(KeyCode.D)) speedRight = tourCamera.right;
        if (Input.GetKey(KeyCode.E)) speedUp = Vector3.up;
        if (Input.GetKey(KeyCode.Q)) speedDown = Vector3.down;
        direction = speedForward + speedBack + speedLeft + speedRight + speedUp + speedDown;
        #endregion
        #region 鼠标旋转
        if (Input.GetMouseButton(1))
        {
            // 转相机朝向
            tourCamera.RotateAround(tourCamera.position, Vector3.up, Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime);
            tourCamera.RotateAround(tourCamera.position, tourCamera.right, -Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime);
            // 转运动速度方向
            direction = V3RotateAround(direction, Vector3.up, Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime);
            direction = V3RotateAround(direction, tourCamera.right, -Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime);
        }
        #endregion
    }
    /// <summary>
    /// 计算一个Vector3绕旋转中心旋转指定角度后所得到的向量。
    /// </summary>
    /// <param name="source">旋转前的源Vector3</param>
    /// <param name="axis">旋转轴</param>
    /// <param name="angle">旋转角度</param>
    /// <returns>旋转后得到的新Vector3</returns>
    public Vector3 V3RotateAround(Vector3 source, Vector3 axis, float angle)
    {
        Quaternion q = Quaternion.AngleAxis(angle, axis);// 旋转系数
        return q * source;// 返回目标点
    }

}

2.8碰撞与计分

碰撞逻辑在ArrowController实现,分数改变传递到ui.score变量显示。

1.变量定义:

  • private Rigidbody rb;:箭矢的刚体组件,用于控制箭矢的物理行为。
  • public float Score = 0;:箭矢的得分,可能是在箭矢击中目标时增加的分数。
  • public Camera cam;:用于获取相机以访问 Main 脚本。
  • private Main ui;:对游戏界面的引用,用于更新得分。

2.Start() 方法:

  • 获取箭矢的刚体组件。
  • 获取相机上的 Main 脚本。

3.OnCollisionEnter(Collision collision) 方法:

  • 当箭矢与其他物体碰撞时触发。
  • 如果是 "staticTarget" 标签,分数增加 10 分,箭矢销毁。
  • 如果是 "MovingTarget" 标签,分数增加 20 分,箭矢销毁。
  • 如果是 "Ground" 标签,箭矢直接销毁。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SocialPlatforms.Impl;

public class ArrowController : MonoBehaviour
{
    // Start is called before the first frame update
    private Rigidbody rb;
    public float Score = 0;

    public Camera cam;
    private Main ui;
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        ui = cam.GetComponent<Main>();
    }

    // Update is called once per frame
    void Update()
    {

    }
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("staticTarget"))
        {
            rb.velocity = Vector3.zero;
            rb.angularVelocity = Vector3.zero;
            ui.Score += 10;
            Destroy(gameObject);

        }
        else if (collision.gameObject.CompareTag("MovingTarget"))
        {
            rb.velocity = Vector3.zero;
            rb.angularVelocity = Vector3.zero;
            ui.Score += 20;
            Destroy(gameObject);
        }
        else if (collision.gameObject.CompareTag("Ground"))
        {
            rb.velocity = Vector3.zero;
            rb.angularVelocity = Vector3.zero;
            Destroy(gameObject);
        }

    }
}

Main实现显示分数和游戏提示,挂载在主摄像机Cam_fps(1)上。

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

public class Main : MonoBehaviour
{
    // Start is called before the first frame update
    public float Score = 0;
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {

    }
    private void OnGUI()
    {
        GUIStyle style = new GUIStyle();
        style.fontSize = 15;
        style.normal.textColor = Color.white;

        // 定义游戏介绍文本内容
        string introText = "天空10s自动切换\nwasd --移动弓弩\nSpace --蓄力拉弓\n鼠标左键 --射箭\n鼠标右键 --调整角度";

        // 在屏幕左上角绘制游戏介绍文本
        GUI.Label(new Rect(10, 10, 300, 100), introText, style);
        style.fontSize = 24;
        style.normal.textColor = Color.white;
        // 在屏幕右上角显示分数
        GUI.Label(new Rect(Screen.width - 150, 20, 150, 30), "Score: " + Score, style);
    }
}

三、游戏演示

参考博客:unity射箭小游戏-CSDN博客

演示视频:Unity3D射箭小游戏_哔哩哔哩_bilibili

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值