Unity游戏--射箭小游戏

一、游戏介绍

        使用Unity3D引擎制作一个射箭游戏。玩家在场景中,可以以第一人称方式在地图上游走,到达特定的射击位可以进行射击,命中静止的或者移动的靶子得到分数。每一次游戏有n次机会。

二、实现

        1、游走

        使用标准资源库Standard Assets 中的第一人称控制器FPSController,将内置的FirstPersonCharacter设为主摄像机。该控制器支持游走、跳跃。

        然后,创建一个脚本Controllers / CameraFollow.cs,挂载到 弓 这个预制件上,使之时刻跟随主摄像机。(弓CrossBow 使用资源包 RyuGiKen)。

using UnityEngine;

public class CameraFollow : MonoBehaviour
{
    public Transform cameraTransform; // 要跟随的物体
    // public Vector3 offset = new Vector3(0f, 0.5f, -1f); // 相对位置偏移量
    public Vector3 offset = new Vector3(0f, -0.4f, 0.2f); // 相对位置偏移量,x,y,z
    private Vector3 targetPosition; // 目标位置
    private Quaternion targetRotation;// 目标角度

    public void Start(){
        cameraTransform = Camera.main.transform;
    }
    private void LateUpdate()
    {
        if (cameraTransform != null)
        {
            // 获取相机的旋转角度
            targetRotation = cameraTransform.rotation;

            // 根据相机旋转角度计算弓的目标位置
            targetPosition = cameraTransform.position + (targetRotation * offset);

            // 设置弓的位置和旋转
            transform.position = targetPosition;
            transform.rotation = targetRotation;
        }
    }
}

        创建脚本mainController.cs,挂载到FPSController上,作用是在场景中创建物体 弓 。由于弓自身挂载了CameraFollow,所以一旦在游戏中创建,就会跟随摄像机。

using System.Diagnostics;
using UnityEngine;

public class mainController : MonoBehaviour
{
    public GameObject bow;
    //public TreeGenController treeGenController;
    void Start(){

        bow=Instantiate(Resources.Load("Prefabs/myCrossbow")) as GameObject;
        UnityEngine.Debug.Log("corssBow gen");
        //后面部分介绍
        gameObject.AddComponent<ShootScoreController>();
        gameObject.AddComponent<TreeGenController>();
    }
}
        2、射击

        首先,使用Animator动画控制 弓 的 蓄力拉弓、保持、射击 的动画变化过程。

        其中,Empty、Fill、Shoot均为资源包 RyuGiKen中弓的动画单元。Empty_Fill是 两个动画的混合。

        对应的,需要用脚本控制参数的变化进而控制动画。将下面的MouseController挂载到预制件 弓 (CrossBow)上。

using System.Diagnostics;
using UnityEngine;
using System;

public class MouseController : MonoBehaviour
{
    public Animator animator;
    private float clickStartTime;
    public ArrowFactory arrowFactory;
    public ShootScoreController shootScoreController;

    void Start(){
        animator = GetComponent<Animator>();
        UnityEngine.Debug.Log(animator);
        gameObject.AddComponent<ArrowFactory>();
        arrowFactory = gameObject.GetComponent<ArrowFactory>();
        shootScoreController = Singleton<ShootScoreController>.Instance;

        GameObject place = GameObject.Find("shootPlace");
        UnityEngine.Debug.Log(place.transform.position.x);
    }
    private void Update()
    {
        // 当鼠标点击时设置触发器为 true
        if (Input.GetMouseButtonDown(0))
        {
            // 设置触发器为 true
            animator.SetBool("prepared",true);
            UnityEngine.Debug.Log(animator.GetBool("prepared"));

            clickStartTime = Time.time;
        }

        if (Input.GetMouseButton(0))
        {
            float elapsedTime = Time.time - clickStartTime;

            // 将经过的时间映射到0到1的范围
            float PowerValue = Mathf.Clamp01(elapsedTime / 1.5f);

            // 设置Animator Controller中的浮点数参数
            animator.SetFloat("Power", PowerValue);
        }

        if (Input.GetMouseButtonUp(0))
        {
            // 将参数A重置为0
            //animator.SetFloat("Power", 0f);
        }

        if (Input.GetMouseButtonDown(1))
        {
            if(animator.GetFloat("Power") == 0f) return;
            if(!InShootPlace()) return;

            // 设置触发器为 true
            animator.SetTrigger("Fire");
            UnityEngine.Debug.Log("shoot");
            animator.SetBool("prepared",false);


            //箭神,启动!!!!
            GameObject arrow = arrowFactory.GetArrow();

            UnityEngine.Debug.Log("启动");
            //初始化箭的基本参数
            // 初始位置
            Vector3 offset = new Vector3(0f, 0f, 1.5f); 
            Transform cameraTransform = Camera.main.transform;
            arrow.transform.position = cameraTransform.position + (transform.rotation * offset);
            arrow.transform.rotation = cameraTransform.rotation;
            // arrow.transform.position = transform.position + (transform.rotation * offset);
            // arrow.transform.rotation = transform.rotation;
            //  力
            float maxShootForce = 1f;
            // 获取Power
            float power = animator.GetFloat("Power");
            float shootForce = power * maxShootForce;
            Vector3 shootDirection = arrow.transform.forward;
            arrow.GetComponent<Rigidbody>().AddForce(shootForce * shootDirection, ForceMode.Impulse);
            arrow.AddComponent<ArrowStop>();
            animator.SetFloat("Power", 0f);
        }

        if (Input.GetKey(KeyCode.O) && Input.GetKey(KeyCode.K))
        {
            // "O" 和 "K" 同时按下的处理逻辑
            UnityEngine.Debug.Log("按下了 O 和 K 键");
            shootScoreController.ResetAll();
        }
            
    }

    public bool InShootPlace(){

        GameObject place = GameObject.Find("shootPlace");

        // 判断 damnObject 是否存在,并获取其位置
        if(place != null){
            Vector3 placePosition = place.transform.position;
            if(gameObject.transform.position.y>=placePosition.y + 3.5 && 
                Mathf.Abs(gameObject.transform.position.x - placePosition.x)<=3 &&
                Mathf.Abs(gameObject.transform.position.z - placePosition.z)<=3)
                {
                    shootScoreController.DelCount(0);
                    return true;
                }
                
        }

        GameObject placeMoving = GameObject.Find("shootPlaceMoving");

        if(placeMoving != null){
            Vector3 placeMovingPosition = placeMoving.transform.position;
            if(gameObject.transform.position.y>=placeMovingPosition.y + 10 && 
                Mathf.Abs(gameObject.transform.position.x - placeMovingPosition.x)<=10 &&
                Mathf.Abs(gameObject.transform.position.z - placeMovingPosition.z)<=10)
                {
                    shootScoreController.DelCount(1);
                    return true;
                }
        }

        return false;
    }

}

上面实现了弓的动画,下面要在弓射击时,实现箭的发射。

        创建脚本ArrowFactory.cs,(弓箭工厂),生产 箭。

using System.IO;
using System.Diagnostics;
using System.Security.AccessControl;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class ArrowFactory : MonoBehaviour
{
    List<GameObject> arrow_used;
    List<GameObject> arrow_free;
    // Start is called before the first frame update
    void Start()
    {  
        arrow_used = new List<GameObject>();
        arrow_free = new List<GameObject>();
    }
    public GameObject GetArrow() {
        GameObject arrow;
        if (arrow_free.Count != 0) {
            arrow = arrow_free[0];
            arrow_free.Remove(arrow);
        }
        else {
            //
            arrow = Instantiate(Resources.Load("Prefabs/Arrow")) as GameObject;
        }
        arrow_used.Add(arrow);
        arrow.SetActive(true);
        UnityEngine.Debug.Log("generate arrow");
        return arrow;
    }

    

}

        箭的发射在上面的MouseController中实现。

            //箭神,启动!!!!
            GameObject arrow = arrowFactory.GetArrow();

            UnityEngine.Debug.Log("启动");
            //初始化箭的基本参数
            // 初始位置
            Vector3 offset = new Vector3(0f, 0f, 1.5f); 
            Transform cameraTransform = Camera.main.transform;
            arrow.transform.position = cameraTransform.position + (transform.rotation * offset);
            arrow.transform.rotation = cameraTransform.rotation;
            // arrow.transform.position = transform.position + (transform.rotation * offset);
            // arrow.transform.rotation = transform.rotation;
            //  力
            float maxShootForce = 1f;
            // 获取Power
            float power = animator.GetFloat("Power");
            float shootForce = power * maxShootForce;
            Vector3 shootDirection = arrow.transform.forward;
            arrow.GetComponent<Rigidbody>().AddForce(shootForce * shootDirection, ForceMode.Impulse);
            arrow.AddComponent<ArrowStop>();

        箭在触碰到其他物体时,需停下来。将下面的ArrowStop.cs挂载到 箭 预制件上。

using UnityEngine;
public class ArrowStop : MonoBehaviour
{
    private Rigidbody arrowRigidbody;
    private ShootScoreController shootScoreController;

    private void Start()
    {
        arrowRigidbody = GetComponent<Rigidbody>();
        shootScoreController = Singleton<ShootScoreController>.Instance;
    }

    private void OnCollisionEnter(Collision collision)
    {

        // 停止箭的运动
        arrowRigidbody.velocity = Vector3.zero;
        arrowRigidbody.isKinematic = true;

        
        // 启用或禁用其他需要的组件或脚本
        // ...
        // if (collision.gameObject.CompareTag("targetMid")
        //     || collision.gameObject.CompareTag("targetLeft")
        //     || collision.gameObject.CompareTag("targetRight")){
        //     UnityEngine.Debug.Log("hit");
        // }
        if (collision.gameObject.name == "targetMid"
            || collision.gameObject.name == "targetLeft"
            || collision.gameObject.name == "targetRight"){
            UnityEngine.Debug.Log("hit");

            if(shootScoreController.GetCount(0)>0)
                shootScoreController.AddScore(1);
        }

        if (collision.gameObject.name == "targetMovingUp" || collision.gameObject.name == "targetMovingDown" || collision.gameObject.name == "targetMovingRound") {
            UnityEngine.Debug.Log("hitMove");
            transform.SetParent(collision.transform);
            
            if(shootScoreController.GetCount(1)>0)
                shootScoreController.AddScore(3);

        }
    }
}
        3、分数、次数、射击位

        实现碰撞检测,当弓箭命中靶子时,分数增加。同时,每一次发射消耗一次次数。

        其中,碰撞检测在ArrowStop.cs

using UnityEngine;

public class ShootScoreController : MonoBehaviour
{
    private int score;
    private ShootGUI shootGUI;

    void Start(){
        score = 0;
        shootGUI = Singleton<ShootGUI>.Instance;
    }

    void Update(){
        shootGUI.SetScore(score);

    }
    public void AddScore(int s){
        score += s;
    }
    public int GetCount(int type){
        return shootGUI.GetCount(type);
    }
    public void ResetAll(){
        score = 0;
        shootGUI.ResetAll();
    }
    public void DelCount(int type){
        shootGUI.DelCount(type);
    }
}

        射击位:到达射击位处才能射击。在MouseController.cs实现。原理为弓和射击位之间的坐标对比。

        4、场景--天空盒、树木

        地形、天空 使用 资源 Fantasy Skybox FREE中的地形。树木同样适使用现有资源包。

        实现天空随着时间变化:SkyboxController.cs挂载到主摄像机:

using UnityEngine;
using System.Collections;
public class SkyboxController : MonoBehaviour
{
    public string skyboxMaterialPath_Sunrise = "SkyBox/FS017_Sunrise";
    public string skyboxMaterialPath_Day = "SkyBox/FS000_Day_01";  // Skybox材质的资源路径
    public string skyboxMaterialPath_Night = "SkyBox/FS000_Night_02";
    public string skyboxMaterialPath_Sunset = "SkyBox/FS002_Sunset_Cubemap";
    public string skyboxMaterialPath_Sunset2 = "SkyBox/FS017_Sunset";

    public float changeInterval = 7f;  // 更换材质的时间间隔
    private Material[] skyboxMaterials;
    private int currentMaterialIndex = 0;

    void Start()
    {
        // 使用资源路径加载Skybox材质
        Material skyboxMaterial_Sunrise = Resources.Load<Material>(skyboxMaterialPath_Sunrise);
        Material skyboxMaterial_Day = Resources.Load<Material>(skyboxMaterialPath_Day);
        Material skyboxMaterial_Night = Resources.Load<Material>(skyboxMaterialPath_Night);
        Material skyboxMaterial_Sunset = Resources.Load<Material>(skyboxMaterialPath_Sunset);
        Material skyboxMaterial_Sunset2 = Resources.Load<Material>(skyboxMaterialPath_Sunset2);
        skyboxMaterials = new Material[] { skyboxMaterial_Sunrise, skyboxMaterial_Day, skyboxMaterial_Sunset, skyboxMaterial_Sunset2, skyboxMaterial_Night };

        
        for(int i=0;i<skyboxMaterials.Length;i++){
            if (skyboxMaterials[i] == null)
            {
                Debug.LogError("Failed to load Skybox material at path: " + skyboxMaterials[i]);
                return;
            }
                
        }
        StartCoroutine(ChangeSkyboxMaterial());

    }

    IEnumerator ChangeSkyboxMaterial()
    {
        while (true)
        {
            yield return new WaitForSeconds(changeInterval);

            currentMaterialIndex++;
            if (currentMaterialIndex >= skyboxMaterials.Length)
            {
                currentMaterialIndex = 0;
            }

            RenderSettings.skybox = skyboxMaterials[currentMaterialIndex];
        }
    }
}

        地图中随机生成树木:TreeGenController挂载到FPSController

using UnityEngine;

public class TreeGenController : MonoBehaviour
{
    private int treeNums = 120;
    private Terrain terrain;
    private void Start()
    {
        terrain = Terrain.activeTerrain;
        GenerateTrees();
        UnityEngine.Debug.Log(terrain.transform.position.x);
        UnityEngine.Debug.Log(terrain.terrainData.size.x);
    }

    private void GenerateTrees(){
        for(int i = 0;i<treeNums;i++){
            Vector3 randomPosition = GetRandomPosition();
            bool isPositionValid = IsPositionValid(randomPosition);

            // If the position is not valid, find a new position within the valid area
            while (!isPositionValid)
            {
                randomPosition = GetRandomPosition();
                isPositionValid = IsPositionValid(randomPosition);
            }
            Instantiate(Resources.Load("Prefabs/Tree9_2"), randomPosition, Quaternion.identity);
        }

        for(int i = 0;i<treeNums;i++){
            Vector3 randomPosition = GetRandomPosition();
            bool isPositionValid = IsPositionValid(randomPosition);

            // If the position is not valid, find a new position within the valid area
            while (!isPositionValid)
            {
                randomPosition = GetRandomPosition();
                isPositionValid = IsPositionValid(randomPosition);
            }
            Instantiate(Resources.Load("Prefabs/Tree9_3"), randomPosition, Quaternion.identity);
        }

        for(int i = 0;i<treeNums;i++){
            Vector3 randomPosition = GetRandomPosition();
            bool isPositionValid = IsPositionValid(randomPosition);

            // If the position is not valid, find a new position within the valid area
            while (!isPositionValid)
            {
                randomPosition = GetRandomPosition();
                isPositionValid = IsPositionValid(randomPosition);
            }
            Instantiate(Resources.Load("Prefabs/Tree9_2"), randomPosition, Quaternion.identity);
        }
    }

    private Vector3 GetRandomPosition()
    {
        // Generate a random position within the terrain bounds
        float x = Random.Range(terrain.transform.position.x, terrain.transform.position.x + terrain.terrainData.size.x);
        float z = Random.Range(terrain.transform.position.z, terrain.transform.position.z + terrain.terrainData.size.z);
        float y = terrain.SampleHeight(new Vector3(x, 0, z));

        return new Vector3(x, y, z);
    }

    private bool IsPositionValid(Vector3 position)
    {
        // Check if the position is within the restricted areas or too close to them
        // You can use any method that suits your needs to define and check the restricted areas

        // Example: Check distance from restricted areas

        // Collider[] colliders = Physics.OverlapSphere(position, minDistanceFromRestrictedAreas);
        // foreach (Collider collider in colliders)
        // {
        //     if (collider.CompareTag("RestrictedArea"))
        //     {
        //         return false;
        //     }
        // }



        return true;
    }
}
        4、GUI

        显示分数、次数、提示、准心,提供函数以实时修改分数等参数

using UnityEngine;

public class ShootGUI : MonoBehaviour
{
    private int score;
    private int count_static;
    private int count_Moving;
    void Start(){
        score = 0;
        count_static = 10;
        count_Moving = 10;
    }
    public void SetScore(int score){
        this.score = score;
    }
    public void DelCount(int type){
        if(type == 0){
            if(count_static > 0)
            count_static-=1;
        }
        else if(type == 1){
            if(count_Moving > 0)
            count_Moving-=1;
        }
    }
    public int GetCount(int type){
        if(type == 0){
            return count_static;
        }
        else if(type == 1){
            return count_Moving;
        }
        return 0;
    }
    public void ResetAll(){
        score = 0;
        count_static = 10;
        count_Moving = 10;
    }
    private void OnGUI()
    {
        // 获取屏幕中心的位置
        Vector3 screenCenter = new Vector3(Screen.width / 2f, Screen.height / 2f, 0f);
        
        // 设置GUI样式
        GUIStyle style = new GUIStyle(GUI.skin.label);
        style.fontSize = 20;
        style.fontStyle = FontStyle.Bold;
        style.alignment = TextAnchor.MiddleCenter;
        style.normal.textColor = Color.red;
        
        // 在屏幕中心绘制字母 "O"
        GUI.Label(new Rect(screenCenter.x - style.fontSize/2, screenCenter.y - style.fontSize/2, style.fontSize, style.fontSize), "o", style);
        // 在屏幕左上方绘制分数文本
        GUI.Label(new Rect(10f, 10f, 100f, 30f), "Score: " + score.ToString(), style);
        GUI.Label(new Rect(10f, 30f, 150f, 30f), "Case_1: " + count_static.ToString(), style);
        GUI.Label(new Rect(10f, 50f, 150f, 30f), "Case_2: " + count_Moving.ToString(), style);
        // 在屏幕右上方绘制三行文本
        style.fontStyle = FontStyle.Normal;
        GUI.Label(new Rect(Screen.width - 200f, 10f, 200f, 30f), "左键长按蓄力", style);
        GUI.Label(new Rect(Screen.width - 200f, 40f, 200f, 30f), "左键点击取消", style);
        GUI.Label(new Rect(Screen.width - 200f, 70f, 200f, 30f), "右键点击射击", style);
        GUI.Label(new Rect(Screen.width - 200f, 100f, 200f, 30f), "ok键重置", style);
    }
}

三、视频链接:unity小游戏-射箭游戏_单机游戏热门视频

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值