Unity射箭游戏

本项目实现了基于Unity的射箭游戏,概况如下:

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

代码仓库:射箭游戏:github仓库

B站视频:射箭游戏_哔哩哔哩_bilibili​​​​​​ 

代码文件:

Arrow.cs:

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

public class Arrow : MonoBehaviour
{
    public Vector3 starting_point;
    Transform target;
    Vector3 delta;
    Rigidbody rb;
    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
        if(rb.isKinematic == true)
        {
            this.transform.position = target.position + delta;
        }
    }
    void OnCollisionEnter(Collision collision)
    {   if (rb.isKinematic == false)
        {
            rb.isKinematic = true;
            target = collision.gameObject.transform;
            delta = transform.position - target.position;
        }
    }

}

这段代码定义了一个名为 Arrow 的 Unity 组件,该组件控制一个游戏对象(预期为箭)的行为。

以下是这段代码的详细解释:

  • starting_point 用于存储箭的初始位置,但在此代码中未被使用。

  • target 用于存储箭碰撞的目标游戏对象的 Transform 组件。

  • delta 用于存储箭与目标游戏对象之间的初始距离。

  • rb 用于存储箭的 Rigidbody 组件,这是 Unity 中用于物理运算的组件。

在 Start 方法中,箭的 Rigidbody 组件被初始化。

在 Update 方法中,如果箭的 Rigidbody 组件被设置为 kinematic (即,不受物理引擎的影响,但可以通过代码控制其移动),那么箭的位置会被更新为目标游戏对象的位置加上初始距离。

在 OnCollisionEnter 方法中,当箭与其他游戏对象发生碰撞时,如果箭的 Rigidbody 组件未被设置为 kinematic,那么箭的 Rigidbody 组件会被设置为 kinematic,箭的目标会被设置为碰撞的游戏对象,初始距离会被设置为箭与目标游戏对象之间的距离。

CrossBow.cs:

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

public class CrossBow : MonoBehaviour
{
    public float mouseSensitivity = 100.0f;
    public Transform playerBody;
    private float xRotation = 0.0f;
    float force;
    const float maxForce = 0.5f;  // 最大力量
    const float chargeRate = 0.1f; // 每0.3秒蓄力的量
    Animator animator;
    float mouseDownTime;
    bool isCharging;
    public Slider Powerslider;
    public int speed;
    public bool ready_to_shoot;
    public int shots;
    void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;
        animator = GetComponent<Animator>();
        ready_to_shoot = false;
        shots = -1;
    }

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

        xRotation -= mouseY;
        xRotation = Mathf.Clamp(xRotation, 0f, 180f);

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

        //控制水平移动
        float H = Input.GetAxis("Horizontal");
        float V = Input.GetAxis("Vertical");
        Vector3 movement = new Vector3(H, 0.0f, V);
        movement = Camera.main.transform.rotation * movement * speed;
        Rigidbody rb = playerBody.GetComponent<Rigidbody>();
        //Debug.Log(movement);
        rb.velocity = new Vector3(movement.x, rb.velocity.y, movement.z);
        if(Input.GetKeyDown(KeyCode.Space))
        {
            rb.velocity = new Vector3(rb.velocity.x, rb.velocity.y + 3, rb.velocity.z);
        }
        if(!ready_to_shoot)
        {
            return;
        }
        //按照鼠标按下的时间蓄力,每0.3秒蓄0.1的力(最多0.5)加到animator的power属性上,并用相应的力射箭
        if (Input.GetMouseButtonDown(0)) // 0表示鼠标左键
        {
            mouseDownTime = Time.time;  // 记录鼠标按下的时间
            isCharging = true;  // 开始蓄力
            Powerslider.gameObject.SetActive(true);
        }

        if (isCharging)
        {
            float holdTime = Time.time - mouseDownTime; // 计算鼠标按下的时间
            force = Mathf.Min(holdTime / 0.3f * chargeRate, maxForce); // 计算蓄力的量,最大为0.5
            Powerslider.value = force / maxForce; // 更新力量条的值
            animator.SetFloat("Power", force + 0.5f);
        }

        if (Input.GetMouseButtonUp(0) && isCharging)
        {
            isCharging = false;  // 停止蓄力
            animator.SetTrigger("fire");
            Debug.Log("setrigger");
            float holdTime = Time.time - mouseDownTime;  // 计算鼠标按下的时间
            force = Mathf.Min(holdTime / 0.3f * chargeRate, maxForce);  // 计算蓄力的量,最大为0.5
            animator.SetFloat("Power", force + 0.5f);  // 将蓄力的量加到animator的power属性上
            StartCoroutine(DelayedFireCoroutine(force));
            Powerslider.value = 0;
            
        }
    }

    IEnumerator DelayedFireCoroutine(float f)
    {
        Debug.Log("Ready to fire!!");
        yield return new WaitForSeconds(0.5f);
        fire(f);
        shots--;
    }

    public void fire(float f)
    {
        // Your existing fire code
        GameObject arrow = Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Arrow"));
        Arrow aw = arrow.AddComponent<Arrow>();
        // 使用Find方法通过子对象的名字获取子对象
        Transform childTransform1 = transform.Find("mark");
        aw.transform.position = childTransform1.position;
        aw.transform.rotation = Quaternion.LookRotation(this.transform.up);
        Rigidbody arrow_db = arrow.GetComponent<Rigidbody>();
        aw.starting_point = aw.transform.position;
        arrow.tag = "Arrow";
        Debug.Log("starting_poing:" + aw.starting_point);
        arrow_db.velocity = 100 * f * this.transform.up;

        
    }
}

这段代码定义了一个名为 CrossBow 的 Unity 组件,用于控制玩家角色(预计为拿着弩的角色)的行为。具体包括角色移动、视角旋转、跳跃、弩的蓄力射击等。

以下是这段代码的详细解释:

  • Start 方法中,游戏开始时,鼠标被锁定在屏幕中央,初始化弩的 Animator 组件,设置弩未准备好射击,弩的射击次数为-1。

  • Update 方法中,首先根据鼠标移动设置角色视角旋转,然后根据输入控制角色水平移动和跳跃,接着如果弩未准备好射击则返回。如果弩准备好射击,当鼠标左键按下时开始计算蓄力时间和力量,当鼠标左键松开时结束蓄力并射出箭。

  • DelayedFireCoroutine 方法是一个协程,它会在鼠标左键松开后半秒进行射击。

  • fire 方法中,实例化一个箭的游戏对象,设置箭的位置、方向和速度,并射出箭。

Player.cs

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

public class Player : MonoBehaviour
{
    public GameObject CrossBow;
    private bool atSpot = false;
    private Spot spot;

    void Start()
    {

    }

    void Update()
    {   if (atSpot && CrossBow.GetComponent<CrossBow>().shots >= 0)
        {
            spot.shots = CrossBow.GetComponent<CrossBow>().shots;
        }

        if(spot.shots == 0)
        {
            CrossBow.GetComponent<CrossBow>().ready_to_shoot = false;
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.tag == "Spot")
        {
            spot = collision.gameObject.GetComponent<Spot>();
            atSpot = true;
            if (spot.shots > 0)
            {
                CrossBow.GetComponent<CrossBow>().ready_to_shoot = true;
                CrossBow.GetComponent<CrossBow>().shots = spot.shots;
            }
            
            
            spot = collision.gameObject.GetComponent<Spot>();
        }
        else if(collision.gameObject.name == "Terrain")
        {
            
                CrossBow.GetComponent<CrossBow>().ready_to_shoot = false;
                atSpot = false;
            
        }
    }

    private void OnCollisionExit(Collision collision)
    {
        if (collision.gameObject.name == "Spot")
        {

            CrossBow.GetComponent<CrossBow>().ready_to_shoot= false;
            atSpot = false;

        }
    }

    void OnGUI()
    {
        if (atSpot)
        {
            // Create a new GUIStyle
            GUIStyle style = new GUIStyle(GUI.skin.label);
            style.fontSize = 30; // Set the font size to 30

            // Draw the message with the new GUIStyle
            GUI.Label(new Rect(10, 50, 500, 100), "您已到达射击位,剩余射击次数:"+spot.shots, style);
        }
    }
}

以下是这段代码的详细解释:

  • Update 方法中,如果角色在射击位并且弩还有剩余的射击次数,那么更新射击位的剩余射击次数。如果射击位的剩余射击次数为0,那么设置弩未准备好射击。

  • OnCollisionEnter 方法中,当角色碰撞到一个带有 "Spot" 标签的对象时,获取该对象的 Spot 组件,设置角色在射击位,如果射击位有剩余的射击次数,那么设置弩准备好射击并更新弩的剩余射击次数。当角色碰撞到一个名为 "Terrain" 的对象时,设置弩未准备好射击,设置角色不在射击位。

  • OnCollisionExit 方法中,当角色离开一个名为 "Spot" 的对象时,设置弩未准备好射击,设置角色不在射击位。

  • OnGUI 方法中,如果角色在射击位,那么显示一个消息,告诉玩家他们已经到达射击位,和剩余的射击次数。

总的来说,这段代码实现了一个游戏角色与射击位的交互,包括角色到达射击位时弩可以射击,角色离开射击位或射击位的剩余射击次数为0时弩不能射击,以及在界面上显示角色是否在射击位和剩余的射击次数。

Spot.cs

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

public class Spot : MonoBehaviour
{
    public int shots;
    // Start is called before the first frame update
    void Start()
    {
        shots = 5;
    }

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

这段代码定义了射击位的属性,即允许的射击次数为5

Target.cs

using UnityEngine;
using System.Collections;
public class Target : MonoBehaviour
{   public int basepoint;
    public float pace;
    public bool is_moving;
    int sign;
    Vector3 ori;
    public int scores;
    // Use this for initialization
    void Start()
    {   
        if(is_moving == false)
        {
            pace = 0;
        }
        else
        {
            sign = 1;
            ori = this.transform.position;
        }
    }

    // Update is called once per frame
    void Update()
    {
        if(is_moving == true)
        {
            this.transform.position += new Vector3(sign * 0.01f, 0, 0);
            if(transform.position.x - ori.x > 5 || transform.position.x - ori.x < -5 )
            {
                sign *= -1;
            }
        }
    }

    // OnCollisionEnter is called when this collider/rigidbody has begun touching another rigidbody/collider
    private void OnCollisionEnter(Collision collision)
    {
        // Get the first contact point
        ContactPoint contact = collision.contacts[0];

        // Get the starting_point property from the arrow's script
        // Replace "ArrowScript" with the name of the script that contains the starting_point property
        if (collision.gameObject.tag == "Arrow")
        {
            Vector3 starting_point = collision.gameObject.GetComponent<Arrow>().starting_point;

            // Calculate the distance
            float distance = Vector3.Distance(contact.point, starting_point);
            Debug.Log("hitting point:" + contact.point);
            // Print the distance
            Debug.Log("Distance: " + distance);
            Transform center = transform.Find("center");
            int factor = 0;
            if(Vector3.Distance(contact.point,center.position)<0.09)
            {
                factor = 5;
            }
            else if(Vector3.Distance(contact.point, center.position) < 0.37)
            {
                factor = 3;
            }
            else
            {
                factor = 1;
            }

            if(distance > 5)
            {
                scores += basepoint + factor * (int)distance;
            }
        }
    }
}

这段代码定义了靶子的行为,靶子可以在一定范围内来回移动,在被射中时,可以根据弓箭飞行的距离以及命中点离靶心的位置进行算分,具体来说,命中点越接近靶心,分数翻的倍数越高,而基数是箭飞行的距离(5米以内不得分)。另外,击中移动的靶子可以额外增加10分。

main.cs

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

public class Main : MonoBehaviour
{
    public GameObject t1;
    public GameObject t2;

    private int totalScore;
    private int previousScore;
    private Color scoreColor = Color.white;

    void Start()
    {
        totalScore = 0;
        previousScore = 0;
    }

    IEnumerator ChangeColorBack()
    {
        // Wait for 1 second
        yield return new WaitForSeconds(1);

        // Change the color back to white
        scoreColor = Color.white;
    }

    void Update()
    {
        int t1Score = t1.GetComponent<Target>().scores;
        int t2Score = t2.GetComponent<Target>().scores;

        previousScore = totalScore;
        totalScore = t1Score + t2Score;

        if (totalScore != previousScore)
        {
            // Change the score color to red when the score changes
            scoreColor = Color.red;

            // Call a coroutine to change the color back to white after 1 second
            StartCoroutine(ChangeColorBack());
        }
    }

    void OnGUI()
    {
        // Set the GUI color to scoreColor
        GUI.color = scoreColor;

        // Create a new GUIStyle
        GUIStyle style = new GUIStyle(GUI.skin.label);
        style.fontSize = 30; // Set the font size to 30

        // Draw the score label with the new GUIStyle
        GUI.Label(new Rect(10, 10, 200, 60), "Score: " + totalScore, style);
    }
}

该代码实现了计算两个靶子的分数之和,并显示在屏幕上,在得分时,分数会短暂变成红色。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值