3D射箭游戏

目录

一、游戏介绍

1、游戏要求

2、游戏规则

二、游戏实现

1、地形

2、天空盒

3、固定靶和运动靶

4、射击位

5、弩弓管理

6、游走        

7、碰撞与计分

三、游戏视频

四、代码地址


一、游戏介绍

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

        每个射击位有5次射箭机会,只有在射击位上方才能射箭。当箭射在靶上时,靶心得6分,之后每向外一环得分减少一份,靶子有固定靶和运动靶。射箭时还会根据鼠标点击的时间长短来决定蓄力的大小,从而控制箭射出去的力量。

二、游戏实现

为了实现游戏要求,步骤如下:

1、地形

        在GameObject-3D Object-Terrain上创建地形,然后在Asset Store上下载合适的树资源包,在Unity中导入资源包之后将树和草预制对象放在创建的Terrain上,即可形成想要的地形。

2、天空盒

        为了实现随时间自动切换天空盒,在Asset Store上导入资源包后,在摄像机上添加组件SkyBox Materials,并将想要进行切换的天空盒添加在组件中,然后在使用脚本控制,可以实现没过5秒切换一次天空盒背景,代码如下:

public class changeskybox : MonoBehaviour
{
    public Material[] skyboxMaterials;
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(ChangeSkybox());
    }

    IEnumerator ChangeSkybox()
    {
        while (true)
        {
            // 随机选择一个天空盒子材质
            int index = Random.Range(0, skyboxMaterials.Length);
            RenderSettings.skybox = skyboxMaterials[index];

            // 等待一段时间后再切换天空盒子
            yield return new WaitForSeconds(5);
        }
    }
}
3、固定靶和运动靶

        可以先创建并生成一个靶子的对象,使用不同大小的Cylinder分别作为靶子的不同环,并在上面添加Mesh Collider作为碰撞器,同时添加脚本来为每一环来设置分数

public class TargetData : MonoBehaviour
{
    // 挂载到Target上的每一环,用来返回该环对应的分数。
    public int GetScore()
    {
        string name = this.gameObject.name;
        int score = 7 - (name[0] - '0');
        return score;
    }

}

        将创建的靶子作为预制,然后生成更多靶子。对于运动靶,需要先添加一个Animator,然后在Animation中设置靶子的运动轨迹就可以完成运动靶的设计,同时为了处理箭射中靶子之后的事件,还需要在每个靶子上添加一个触发的脚本。

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

public class TriggerController : MonoBehaviour
{
    int score = 0;
    public int scores = 0;
    void Start()
    {
        scoreController = Singleton<ScoreController>.Instance;
    }
    // 当有箭射中某一环后触发
    void OnCollisionEnter(Collision collider)
    {
        //得到箭
        GameObject arrow = collider.gameObject;
        Rigidbody rb = arrow.GetComponent<Rigidbody>();
        if (arrow == null)
        {
            return;
        }
        if (collider.gameObject.tag == "Arrow")
        {
            arrow.GetComponent<Rigidbody>().isKinematic = true;
            // 分数控制器
            score = this.gameObject.gameObject.GetComponent<TargetData>().GetScore();
            scores += score;
            Debug.Log(this.gameObject.name);
        }
    }
}
4、射击位

同样使用Cylinder来完成射击位的设计,然后需要设置每个射击位的射击次数,添加的脚本代码如下:

public class SpotController : MonoBehaviour
{
    public int shots;
    // Start is called before the first frame update
    void Start()
    {
        shots = 5;
    }
}
5、弩弓管理

在弩和箭上,需要实现的功能如下:

1、对弩弓是否到达射击位进行判断,并且完成射击次数的管理

2、支持蓄力半拉弓,鼠标点击时蓄力,在松开鼠标时箭释放,同时蓄力大小用Slider显示出来

3、防止弩弓被碰撞时飞走

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


public class CrossbowController : MonoBehaviour
{
    public float mouseSensitivity = 100.0f;
    //public Transform playerBody;
    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;
    private bool atSpot = false;
    public SpotController[] spots;
    private SpotController inspot;
    string message;
    void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;
        animator = GetComponent<Animator>();
        ready_to_shoot = false;
        inspot = null;
        message = "";
    }

    void Update()
    {
        foreach(SpotController s in spots)
        {
            // 获取两个物体在 X 和 Z 轴上的位置
            Vector2 position1 = new Vector2(s.gameObject.transform.position.x, s.gameObject.transform.position.z);
            Vector2 position2 = new Vector2(this.gameObject.transform.position.x, this.gameObject.transform.position.z);

            // 计算在 X 和 Z 轴上的距离
            float distance = Vector2.Distance(position1, position2);   
            if (distance<=0.5)//位于射击位上方
            {
                atSpot = true;
                inspot = s;
                if (s.shots > 0)
                {
                    ready_to_shoot = true;
                }
                else
                {
                    ready_to_shoot = false;
                }
                break;
            }
            else
            {
                atSpot = false;
                //shots = 0;
                inspot = null;
                ready_to_shoot = false;
            }
        }
        
        if (ready_to_shoot)
        {
            //按照鼠标按下的时间蓄力,每0.3秒蓄0.1的力(最多0.5)加到animator的power属性上,并用相应的力射箭
            if (Input.GetMouseButtonDown(0)) // 0表示鼠标左键
            {
                mouseDownTime = Time.time;  // 记录鼠标按下的时间
                isCharging = true;  // 开始蓄力
                Powerslider.gameObject.SetActive(true);
                animator.SetTrigger("pull");
            }

            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("hold");
                float holdTime = Time.time - mouseDownTime;  // 计算鼠标按下的时间
                force = Mathf.Min(holdTime / 0.3f * chargeRate, maxForce);  // 计算蓄力的量,最大为0.5
                animator.SetFloat("hold_power", force + 0.5f);  // 将蓄力的量加到animator的power属性上
                StartCoroutine(DelayedFireCoroutine(force));
                Powerslider.value = 0;
                animator.SetTrigger("shoot");
                Debug.Log("setrigger");

            }
        }
        else
        {
            return;
        }
    }

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

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

    }

    void OnCollisionEnter(Collision collider)
    {
        this.gameObject.GetComponent<Rigidbody>().isKinematic = true;
    }
    void OnCollisionExit(Collision collision)
    {
        this.gameObject.GetComponent<Rigidbody>().isKinematic = false;
    }
    void OnGUI()
    {
        GUIStyle style = new GUIStyle(GUI.skin.label);
        style.fontSize = 20; // Set the font size to 30
        if (atSpot)
        {
            // Draw the message with the new GUIStyle
            message = "您已到达射击位,剩余射击次数:" + inspot.shots;
        }
        else
        {
            message = "";
        }
        GUI.Label(new Rect(300, 350, 500, 100), message, style);
    }
}
6、游走        

        为了在鼠标移动的时候弩弓能跟着旋转,需要代码如下:

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

public class Rotate : MonoBehaviour
    private float rotationSpeed = 5f;
    public float maxRotationX = 60f; // 上下旋转的最大角度

    private float currentRotationX = 0f; // 当前上下旋转角度
    void Update()
    {
        FollowMouse();
    }
    void FollowMouse()
    {
        // 获取鼠标在水平和垂直方向的移动距离
        float mouseX = Input.GetAxis("Mouse X");
        float mouseY = Input.GetAxis("Mouse Y");

        // 根据鼠标移动距离计算旋转角度
        float rotationX = mouseY * rotationSpeed;
        float rotationY = mouseX * rotationSpeed;

        // 左右旋转围绕 Y 轴
        transform.Rotate(Vector3.up, rotationY, Space.World);
        // 计算上下旋转角度并限制范围
        currentRotationX -= rotationX;
        currentRotationX = Mathf.Clamp(currentRotationX, -maxRotationX, maxRotationX);
        // 应用上下旋转围绕局部 X 轴
        Quaternion targetRotation = Quaternion.Euler(currentRotationX, transform.localEulerAngles.y, transform.localEulerAngles.z);
        transform.localRotation = targetRotation;
    }
}

        为了能够使用键盘实现弩弓位置的改变,从而使弩弓能够到达射击位,代码如下:

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

public class Move : MonoBehaviour
{
    //public float speed = 1f;    // 允许设置速度
    public float moveSpeed = 3.0f;
    public float rotateSpeed = 90f;
    void Update()
    {
        float x = Input.GetAxis("Horizontal");
        float z = Input.GetAxis("Vertical");
        transform.localPosition = this.transform.position + this.transform.forward * z * Time.fixedDeltaTime+ this.transform.right * x * Time.fixedDeltaTime;
    }
}
7、碰撞与计分

        为了最后统计总得分,需要将所有靶子上的TriggerController中的得分加在一起,并在页面中显示出来,代码如下:

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

public class UserGUI : MonoBehaviour
{
    int totalScore;
    public TriggerController[] controller;
    // Start is called before the first frame update
    void Start()
    {
        totalScore = 0;
    }
    // Update is called once per frame
    void OnGUI()
    {
        int tScore = 0;
        foreach (var con in controller)
            tScore += con.scores;
        // Create a new GUIStyle
        GUIStyle style = new GUIStyle(GUI.skin.label);
        style.fontSize = 20; // Set the font size to 30
        // Draw the score label with the new GUIStyle
        GUI.Label(new Rect(10, 10, 200, 60), "Score: " + tScore, style);
        totalScore = tScore;
    }
}

三、游戏视频

四、代码地址

射击游戏 · 张菁/3D游戏编程与设计 - 码云 - 开源中国 (gitee.com)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值