Unity3D拉弓射箭

游戏要求:

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

素材

弩弓使用素材Classical Crossbow 资源

弩弓动画机

其中empty-pull为Blend Tree,参数power(0~1)控制蓄力拉弓程度。

其他三个参数为bool变量,start为true时为弓箭上弦,预备蓄力;

shoot_ready为true表示蓄力完毕,可以准备射击;

shoot为true时射击。

弩弓的组件:

characterController组件用来控制弩弓的基本移动和处理一些简单的碰撞

shijiaoyidong脚本控制弩弓行走和视角旋转

shoot脚本处理射箭的相关逻辑

弓箭(arrow)的组件:

Rigidbody刚体组件为弓箭添加重力等物理特性

Arrow_target脚本处理弓箭与靶子的碰撞

MeshCollider为碰撞体,碰撞检测的必要条件

靶子(target):

靶子预制件的制作比较简单,把两个圆柱体放到一起就好了。

Mesh Collider组件是与弓箭检测碰撞的必要条件

得分要点及代码说明:

地形:

直接使用地形编辑器编辑,无代码,效果可参考游戏视频。

值得一提的是树木的碰撞问题,要想实现人物与树木的碰撞检测,除了人物要添加characterController组件外,树木在作为素材添加到地形Terrain前还得添加碰撞器Collider

天空盒:

本代码的设计是按“k”可以切换天空盒,代码如下:

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

public class changeSkyBox : MonoBehaviour
{
    public Material[] skyBoxes;
    private int currentSkyboxIndex = 0;
    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        // 检测是否按下K键
        if (Input.GetKeyDown(KeyCode.K))
        {
            // 切换到下一个天空盒
            currentSkyboxIndex = (currentSkyboxIndex + 1) % skyBoxes.Length;

            // 设置RenderSettings中的skybox属性
            RenderSettings.skybox = skyBoxes[currentSkyboxIndex];
        }
    }
}
固定靶与移动靶:

主要是移动靶的固定点来回移动逻辑,通过代码实现,如下:

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

public class target_move : MonoBehaviour
{

    public float minX = 12f; // 最小X坐标
    public float maxX = 20f; // 最大X坐标
    public float moveSpeed = 2f; // 移动速度

    void Update()
    {
        // 计算X坐标
        float newX = Mathf.PingPong(Time.time * moveSpeed, maxX - minX) + minX;

        // 更新靶的位置
        transform.position = new Vector3(newX, transform.position.y, transform.position.z);
    }
}

射击位:

射击位是两个圆柱体做成的圆台,逻辑是将两个圆台放置在地面上,当人物走到圆台上面时,检测到与圆台的碰撞,将shoot_pos置为true,即可开始射箭,当检测不到与射击位的碰撞时,shoot_pos为false,无法射击。

每次进入射击位时有五次射击机会,当射击机会为0时无法射击。

离开射击位时得分置0,射击机会置0

代码如下:

void OnControllerColliderHit(ControllerColliderHit hit)
{
    // 检测是否与圆台碰撞
    if (hit.gameObject.CompareTag("shoot_pos"))
    {
        Debug.Log("进入射击位");
        shoot_pos = true;
    }
    else
    {
        arrow_target.score = 0;
        shoot_pos = false;
        arrowCount = 5;
        UpdateArrowCountText();
        for(int i = 0; i < arrowCount; i++)
        {
            if (arrows[i] != null)
            {
                Destroy(arrows[i]);
            }
        }
    }
}
弩弓动画:

前面已经介绍了一部分,在此补上操作逻辑:

按住鼠标右键,代表射击力度的参数power逐渐增加,同时蓄力拉弓动画播放;

松开右键,power不变,同时shoot_ready变为true;

当shoot_ready变为true后,按下鼠标左键可以射击

射击代码如下:

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class shoot : MonoBehaviour
{
    public Transform crossbow;
    private Transform arrow;
    private Animator shoot_animator;
    public Transform arrowbegin; // 箭射出的位置
    public Transform arrowPrefab;    // 箭的预制体
    public float maxPower = 1.0f;
    public float powerIncreaseSpeed = 0.1f;
    private bool shoot_pos = false;
    private float currentPower = 0.0f;

    public TextMeshProUGUI arrowCountText; // 引用显示剩余射箭次数的 UI Text 元素
    private int arrowCount = 5; // 初始射箭次数
    private GameObject[] arrows = new GameObject[5];
    /*    private bool isCharging = false;*/

    private void Start()
    {
        shoot_animator = GetComponent<Animator>();
        arrow = crossbow.Find("箭");
        arrow.gameObject.SetActive(false);

    }
    void Update()
    {
        if (currentPower == 0.0f && Input.GetMouseButtonDown(1))
        {
            shoot_animator.SetBool("start", true);
        }

        // 检测鼠标右键按住
        if (shoot_pos && Input.GetMouseButton(1) && arrowCount > 0)
        {
            arrow.gameObject.SetActive(true);
            ChargePower();
        }
        // 检测鼠标右键松开
        else if (shoot_pos && currentPower > 0 && Input.GetMouseButtonUp(1) && arrowCount > 0)
        {
            ReleasePower();
        }

        // 检测鼠标左键按下
        if (shoot_pos && Input.GetMouseButtonDown(0) && currentPower > 0)
        {
            ShootArrow();
            currentPower = 0.0f;
        }

        if (shoot_pos && Input.GetMouseButtonUp(0))
        {
            shoot_animator.SetBool("shoot", false);
            arrow.gameObject.SetActive(false);
        }
    }

    void OnControllerColliderHit(ControllerColliderHit hit)
    {
        // 检测是否与圆台碰撞
        if (hit.gameObject.CompareTag("shoot_pos"))
        {
            Debug.Log("进入射击位");
            shoot_pos = true;
        }
        else
        {
            arrow_target.score = 0;
            shoot_pos = false;
            arrowCount = 5;
            UpdateArrowCountText();
            for(int i = 0; i < arrowCount; i++)
            {
                if (arrows[i] != null)
                {
                    Destroy(arrows[i]);
                }
            }
        }
    }

    void ChargePower()
    {
        // 逐渐增加power值,最大不超过1
        currentPower = Mathf.Min(currentPower + powerIncreaseSpeed * Time.deltaTime, maxPower);
        shoot_animator.SetFloat("power", currentPower);
    }

    void ReleasePower()
    {
        // 设置shoot_ready为true
        shoot_animator.SetBool("shoot_ready", true);
        shoot_animator.SetBool("start", false);
    }

    void ShootArrow()
    {
        // 设置shoot为true,并重置power和shoot_ready
        shoot_animator.SetBool("shoot_ready", false);
        shoot_animator.SetBool("shoot", true);
        
        // 实例化箭对象
        Transform newArrow = Instantiate(arrowPrefab, arrowbegin.position, arrowbegin.rotation);
        arrows[5 - arrowCount] = newArrow.gameObject;
        // 计算射出的方向
        Vector3 shootDirection = arrowbegin.forward;

        // 获取弓的力度(power)作为箭的速度
        float arrowSpeed = currentPower * 20;

        // 为箭添加力
        newArrow.GetComponent<Rigidbody>().velocity = shootDirection * arrowSpeed;

        shoot_animator.SetFloat("power", 0.0f);

        arrowCount--;
        UpdateArrowCountText();
    }

    void UpdateArrowCountText()
    {
        arrowCountText.text = "Remain shoot counts: " + arrowCount.ToString();
    }

}

游走:

代码如下:

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

public class shijiaoyidong : MonoBehaviour
{
    public Transform bow;        // 弓箭的Transform组件
    public Transform cameraPivot; // 摄像机的Transform组件,作为摄像机的父对象
    public float rotationSpeed = 2.0f;
    public float moveSpeed = 5.0f;
    private float gravity = 9.8f; // 重力加速度
    private float verticalSpeed = 0.0f;

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


    void Update()
    {
        ApplyGravity();

        // 应用垂直速度
        Vector3 verticalMovement = Vector3.up * verticalSpeed * Time.deltaTime;
        characterController.Move(verticalMovement);

        // 弓箭的上下左右旋转
        RotateBow();

        // 地面上行走
        MoveOnGround();

    }

    void RotateBow()
    {
        // 获取鼠标水平和垂直移动的增量
        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        // 弓箭绕垂直轴旋转(左右移动)
        bow.Rotate(Vector3.up, mouseX * rotationSpeed, Space.World);

        // 弓箭绕水平轴旋转(上下移动),限制旋转角度在一定范围内
        float newRotationX = bow.rotation.eulerAngles.x - mouseY * rotationSpeed;
        newRotationX = Mathf.Clamp(newRotationX, 0, 90);
        bow.rotation = Quaternion.Euler(newRotationX, bow.rotation.eulerAngles.y, 0);
    }

    void MoveOnGround()
    {
        float horizontalInput = Input.GetAxis("Horizontal");
        float verticalInput = Input.GetAxis("Vertical");

        Vector3 moveDirection = new Vector3(horizontalInput, 0, verticalInput).normalized;
        moveDirection = Camera.main.transform.TransformDirection(moveDirection);
        moveDirection.y = 0;

        characterController.Move(moveDirection * moveSpeed * Time.deltaTime);
    }

    void ApplyGravity()
    {
        // 如果角色不在地面上,应用重力
        if (!characterController.isGrounded)
        {
            verticalSpeed -= gravity * Time.deltaTime;
        }
        else
        {
            // 如果角色在地面上,重置垂直速度
            verticalSpeed = -0.5f; // 一个小的负值,防止角色陷入地面
        }
    }

}

碰撞与得分:

分数Score定义成静态全局变量,每次检测到弓箭与靶子的碰撞时增加分数。

射中外圈白色部分+1分,射中内圈红色部分+3分

代码如下:

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEditor.VersionControl;
using UnityEngine;
using UnityEngine.UI;

public class arrow_target : MonoBehaviour
{
    private bool isEmbedded = false; // 标记箭是否嵌入靶子
    public static int score = 0;
    void OnCollisionEnter(Collision collision)
    {
        Debug.Log(collision.gameObject.name);
        // 检测箭与靶子的碰撞
        if (collision.gameObject.CompareTag("target") && !isEmbedded)
        {
            // 将箭嵌入靶子
            EmbedArrow(collision.transform);
            score += 1;
        }
        
        else if (collision.gameObject.CompareTag("target2") && !isEmbedded)
        {
            // 将箭嵌入靶子
            EmbedArrow(collision.transform);
            score += 3;
        }

    }

    void EmbedArrow(Transform target)
    {
        // 将箭的父对象设置为靶子,使箭随着靶子的移动
        transform.parent = target;

        // 将箭的碰撞器设置为触发器,使其不再参与物理碰撞
        GetComponent<Collider>().isTrigger = true;

        // 将箭的刚体组件禁用,使其不再受到物理影响
        GetComponent<Rigidbody>().isKinematic = true;

        // 标记箭已经嵌入靶子
        isEmbedded = true;

        Debug.Log("箭嵌入靶子!");
    }

}

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

public class Score : MonoBehaviour
{

    public TextMeshProUGUI scoreText;
    int score;
    void Update()
    {
        score = arrow_target.score;
        UpdateScore();
    }
    void UpdateScore()
    {
        scoreText.text = "Score:" + score;
    }

}

游戏视频:

射箭

代码地址:gitee地址

  • 16
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值