Unity我的世界地图第一人称射击游戏

一、游戏设计要求

  • 游戏场景(14分)
    •  地形(2分):使用地形组件,上面有山、路、草、树;(可使用第三方资源改造)
    • 天空盒(2分):使用天空盒,天空可随 玩家位置 或 时间变化 或 按特定按键切换天空盒;
    • 固定靶(2分):使用静态物体,有一个以上固定的靶标;(注:射中后状态不会变化)
       
    • 运动靶(2分):使用动画运动,有一个以上运动靶标,运动轨迹,速度使用动画控制;(注:射中后需要有效果或自然落下)
       
    • 射击位(2分):地图上应标记若干射击位,仅在射击位附近或区域可以拉弓射击,每个位置有 n 次机会;
    •  摄像机(2分):使用多摄像机,制作 鸟瞰图 或 瞄准镜图 使得游戏更加易于操控;
    •  声音(2分):使用声音组件,播放背景音 与 箭射出的声效;
  • 运动与物理与动画(8分)
    • 游走(2分):使用第一人称组件,玩家的驽弓可在地图上游走,不能碰上树和靶标等障碍;(注:建议使用 unity 官方案例)
    • 射击效果(2分):使用 物理引擎 或 动画 或 粒子,运动靶被射中后产生适当效果。
    •  碰撞与计分(2分):使用 计分类 管理规则,在射击位射中靶标得相应分数,规则自定;(注:应具有现场修改游戏规则能力)
    •  驽弓动画(2分):使用 动画机 与 动画融合, 实现十字驽蓄力半拉弓,然后 hold,择机 shoot;
  • 游戏与创新(不限项,每项 2 分)
    •  场景与道具类: 有趣的事物 或 模型等 (可以模仿 Unity 官方案例,在地形基础上搭建几何型场地)
    •  效果类:如显示箭的轨迹,特殊声效,等
    •  力场类: 如运用力场实现 ai 导航 与 捕获等
    •  玩法类:
    •  游戏感: 这是一个模糊的指标,有游戏感也许是某些人的天赋

 二、游戏源代码和视频

        水个游戏剧情:史蒂夫从地狱门回到主世界,发现白天也出现了不会燃烧的僵尸和不会动的小白,他手上只有一把弩和32支箭,好在弓弩附过魔,一箭可以击败敌人。

        游戏胜利目标:尽可能多击杀怪物,并到达村庄钻石傀儡处。

        源码:fang2368/unity

        视频:unity大作业-第一人称射击_哔哩哔哩_bilibili

 三、游戏设计过程

      1. 地形

            (1)使用Unity的Terrain组件,用笔刷的Raise or Lower Terrain功能刷出一定高度,作为背景;

          (2)使用MineWays程序,导出我的世界存档地图模型,导入unity,并进行渲染,具体教程参考:Unity导入我的世界模型,简单图解教程(包括调整材质贴图和光照和添加碰撞)_mineways-优快云博客

   

        2. 天空盒

             游戏内一共设置了3个天空盒:白天(daySkybox),黄昏(duskSkybox),夜晚(nightSkybox)。通过输入3条指令:/time set day, /time set dusk, /time set night分别切换。

              具体代码如下:

  SkyboxSwitcher 类

using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class SkyboxSwitcher : MonoBehaviour
{
    public Material daySkybox;   // 白天天空盒材质
    public Material duskSkybox;  // 黄昏天空盒材质
    public Material nightSkybox; // 夜晚天空盒材质

    public TMP_InputField commandInput; // 输入框,用于输入命令

    public bool entering = false;     // 标志位,输入时禁用wasd操控玩家运动
    public bool enterPressed = false; // 标志位,判断按下 Enter 键时是显示输入框还是执行命令,隐藏输入框

    private void Start()
    {
        // 初始化时禁用命令输入框
        commandInput.gameObject.SetActive(false);

        // 启动时不需要激活输入框
        commandInput.onEndEdit.AddListener(OnCommandEntered);
    }

    private void Update()
    {
        // 如果按下 Enter 键
        if (Input.GetKeyDown(KeyCode.Return))
        {
            Debug.Log("Press Enter"); 
            if (!enterPressed)
            {
                // 第一次按 Enter 时,显示输入框并激活
                commandInput.gameObject.SetActive(true);
                commandInput.ActivateInputField(); // 激活输入框,准备输入命令
                enterPressed = true;  // 标记为第一次按下 Enter
                entering = true;      //开启输入模式
            }
            else
            {
                // 第二次按 Enter 时,执行命令并切换天空盒
                ExecuteCommand(commandInput.text);
                ClearInputField();  // 清空输入框并隐藏
                enterPressed = false; // 重置标志位,等待下一次输入
                entering = false;     //退出输入模式,玩家可以活动
            }
        }
    }

    // 处理输入的命令
    private void ExecuteCommand(string input)
    {
        // 去除前后空格
        input = input.Trim();

        // 根据命令切换天空盒
        if (input == "/time set day")
        {
            SwitchSkybox(daySkybox); // 切换到白天天空盒
        }
        else if (input == "/time set dusk")
        {
            SwitchSkybox(duskSkybox); // 切换到黄昏天空盒
        }
        else if (input == "/time set night")
        {
            SwitchSkybox(nightSkybox); // 切换到夜晚天空盒
        }
        else
        {
            Debug.Log("未知命令: " + input); // 如果命令未知,输出调试信息
        }
    }

    // 切换天空盒的通用方法
    private void SwitchSkybox(Material skybox)
    {
        RenderSettings.skybox = skybox;  // 切换天空盒
        DynamicGI.UpdateEnvironment();  // 更新环境光
    }

    // 清空输入框并隐藏
    private void ClearInputField()
    {
        commandInput.text = "";  // 清空输入框
        commandInput.gameObject.SetActive(false); // 隐藏输入框
    }

    // 监听命令输入结束
    private void OnCommandEntered(string input)
    {
        // 由于按 `Enter` 键会调用这个方法,在 `Update` 中已经处理了执行命令逻辑,这里不再需要额外处理
    }
}

    3. 玩家的移动和视角旋转

           通过将玩家模型、摄像头和弓弩模型绑定,实现弓弩游走的效果,通过玩家动作类脚本进行水平、垂直方向上的移动,重力的设置以及摄像头视角随鼠标移动的旋转,同时还负责实现玩家被怪物攻击时的击退效果。

 PlayerMovementController 类:

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

public class PlayerMovementController : MonoBehaviour
{
    // 定义角色控制器(用于处理角色的物理碰撞和移动)
    CharacterController playerController;

    //玩家生命值管理器
    public PlayerDamageController playerDamageController;

    // 用于存储角色的移动方向
    Vector3 direction;

    //输入栏输入模式,禁用wasd移动
    public SkyboxSwitcher skyboxSwitcher;

    // 角色的基本移动速度
    public float speed = 6;

    // 跳跃的力量(影响跳跃的高度)
    public float jumpPower = 0.0001f;

    // 重力的大小,用于控制跳跃后的下落速度
    public float gravity = 10f;

    // 击退的速度(瞬时施加的力)
    public Vector3 knockbackVelocity;

    // 受伤后,击退持续时间
    public float knockbackDuration = 0.5f;
    public float knockbackTime = 0f;

    // 鼠标的移动速度,用于控制视角旋转的速度
    public float mousespeed = 5f;

    // 鼠标旋转的上下限制角度
    public float minmouseY = -90f;
    public float maxmouseY = 90f;

    // 记录当前视角的X和Y旋转角度
    float RotationY = 0f;
    float RotationX = 0f;

    // 第一人称摄像机
    public Transform agretctCamera;
    // 俯瞰摄像机
    public Transform topDownCamera;

    //获得音频
    private AudioSource audioSource;    
    public AudioClip hurtSound;

    // 在游戏开始时获取CharacterController组件
    void Start()
    {
        // 获取胶囊体的 CharacterController 组件
        playerController = this.GetComponent<CharacterController>();
    }

    // 每一帧更新时调用,用于处理玩家的移动和视角控制
    void Update()
    {
        if ((skyboxSwitcher.entering))
        {
            return;
        }

        // 获取玩家输入的水平和垂直轴(WASD或箭头键)
        float _horizontal = Input.GetAxis("Horizontal");
        float _vertical = Input.GetAxis("Vertical");

        // 初始化方向为零向量
        direction = Vector3.zero;

        // 只有在玩家按下键时才更新移动方向
        if (_horizontal != 0 || _vertical != 0)
        {
            direction = new Vector3(_horizontal, 0, _vertical);
        }

        // 根据玩家是否按下Shift或Ctrl调整速度
        if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
        {
            speed = 3;  // 按下Shift时减慢速度
        }
        else if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
        {
            speed = 12;  // 按下Ctrl时加速
        }
        else
        {
            speed = 6;  // 默认速度
        }

        // 如果角色在地面上
        if (playerController.isGrounded)
        {
            // 如果按下空格键,则角色跳跃
            if (Input.GetKeyDown(KeyCode.Space))
                direction.y = jumpPower; // 设置跳跃力度
        }

        // 每帧更新重力,使角色逐渐下落
        direction.y -= gravity * Time.deltaTime;

        // 将计算出来的移动方向转换到世界坐标系下并执行移动
        playerController.Move(playerController.transform.TransformDirection(direction * Time.deltaTime * speed));

        // 在这里施加击退效果
        if (knockbackTime > 0)
        {
            // 施加击退力
            playerController.Move(knockbackVelocity * Time.deltaTime);
            knockbackTime -= Time.deltaTime; // 击退时间减少
        }

        // 获取并更新鼠标输入,控制玩家左右旋转(旋转角度累加)
        RotationX += Input.GetAxis("Mouse X") * mousespeed;

        // 获取鼠标上下移动输入,控制相机的上下旋转(限制在设定的范围内)
        RotationY -= Input.GetAxis("Mouse Y") * mousespeed;
        RotationY = Mathf.Clamp(RotationY, minmouseY, maxmouseY); // 限制相机的上下旋转角度在设定范围内

        // 更新玩家的水平旋转(绕Y轴旋转)
        this.transform.eulerAngles = new Vector3(0, RotationX, 0);

        // 更新相机的旋转,控制相机的上下视角旋转
        agretctCamera.transform.eulerAngles = new Vector3(RotationY, RotationX, 0);

        // 俯瞰视角:摄像机位于角色上方,并始终注视玩家
        //topDownCamera.LookAt(this.transform); // 始终注视玩家

    }

    public void PlayerDamaged(Vector3 knockbackDirection, float knockbackStrength)
    {
        Debug.Log("Attacked++!");

        // 计算击退效果,方向是击退方向,力度由传入的参数决定
        knockbackVelocity = knockbackDirection.normalized * knockbackStrength;

        // 设置击退时间
        knockbackTime = knockbackDuration;

        //播放音频
        AudioSource.PlayClipAtPoint(hurtSound, Vector3.zero);

        //扣一点血
        playerDamageController.TakeDamage(1);
    }
}

4. 射击位

        在游戏中,玩家开始不能自由游走射击,必须在规定的射击位击杀三个骷髅,才能获得自由射击的能力。

具体实现:

        比较 玩家与射击位中心的距离 和 射击位的半径,判断是否进入射击位;

        射杀3个骷髅,将布尔变量FinishedShootArea修改为true,允许自由射击。

代码如下:

  IfShootArea类

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

public class IfShootArea : MonoBehaviour
{
    public Transform player;        // 玩家对象
    public bool InShootArea = false; // 是否在射击位
    public Vector3 targetPosition = new Vector3(63f, 30f, -45f);  // 射击位圆心
    public float shootingAreaRadius = 2f;  // 射击位范围半径
    public bool FinishedShootArea = false; // 是否在射击位击杀三个骷髅
    public int SkeletonDeadCount = 3;          //骷髅数量   

    void Start()
    {
        
    }

    void Update()
    {
        
    }

    //判断是否在射击位内
    public void CheckIfInArea()
    {
        float distanceToTarget = Vector3.Distance(player.position, targetPosition);
        //Debug.Log("距离是?" + distanceToTarget);

        if (distanceToTarget <= shootingAreaRadius)
        {
            InShootArea = true;
        }
        else
            InShootArea = false;
    }

    //检查是否射杀三个骷髅
    void CheckIfShootSkeleton()
    {
        if (SkeletonDeadCount == 0)
            FinishedShootArea = true;
    }

    //骷髅被击杀
    public void changeDeadCount(int count)
    {
        SkeletonDeadCount -= count;
    }

    //判断能否射击
    public bool ifShoot()
    {
        CheckIfInArea();
        CheckIfShootSkeleton();

        //Debug.Log("可以射击吗?" + (InShootArea || FinishedShootArea));
        return (InShootArea || FinishedShootArea);
    }
}

5. 弓弩射击

(1)动画设计

            动画状态:empty(空膛),half(半拉弓和拉满),shoot(射击),

            通过改变布尔变量isPulling和isShooting,实现empty到half、half到shoot到empty的切换; 

            混合树half:empty为空膛,fill为拉满,混合两个动作实现半拉弓,

            通过浮点型变量PullStrength实现拉弓逐渐拉满的平滑过渡。

   

(2)脚本设计

          射击前提条件:箭支数量大于0,位于射击位或击杀三个骷髅;

          拉弓动作:右键按下开始计时,右键按压过程不断更新拉弓时间,右键放开完成记录;

                            设置射速和射程的最值,用拉弓时间和最大拉弓时间的比值进行对射速、射程、动画状态变量PullStrength进行线性映射

          射击动作:点击左键发射,创建箭支实例,箭支以摄像头方向即屏幕中间十字准星为方向进行匀速运动。

  ArcherController 类

using UnityEngine;
using System.Collections;  // 引入协程所需的命名空间

public class ArcherController : MonoBehaviour
{
    private Animator animator;      //动画组件
    public Transform player;        // 玩家对象
    public Transform firePoint;     //箭支射出点
    private bool Loaded = false;    // 是否正在装填
    private AudioSource audioSource;    //音频
    public AudioClip fillSound;
    public AudioClip shootSound;

    float arrowDistance;    //射程
    float minArrowDistance = 10f;  //最短射程
    float maxArrowDistance = 100f;  //最长射程

    float pullDuration = 0f;    //拉弓时间
    public float maxPullDuration = 2f; // 最长拉弓时间

    public float arrowSpeed;  // 箭速
    public float minArrowSpeed = 5f;  // 最小箭速
    public float maxArrowSpeed = 30f;  // 最大箭速

    private float rightDownTime;    //右键按下时间
    private float rightUpTime;      //右键松开时间
    private bool isRightMouseButtonPressed = false;    //右键是否正在按着

    public ArrowCountController arrowCountController;   //引用对象,修改箭的数量
    public IfShootArea ifShootArea;   //引用对象,获得射击条件

    //射杀三个骷髅作为可以在射击位外自由射击的条件

    void Start()
    {
        // 获取 Animator 组件
        animator = GetComponent<Animator>();

        //获取挂载音频
        audioSource = GetComponent<AudioSource>();  // 获取挂载的 AudioSource
    }

    void Update()
    {
        if (ifShootArea.ifShoot())
            shoot();
    }

    //射击动作
    void shoot()
    {
        if (arrowCountController.arrowcount > 0)
        {
            // 右键点击,进行装填
            if (Input.GetMouseButtonDown(1) && !Loaded)
            {
                //重置力度
                pullDuration = 0f;

                //改变动画状态变量
                animator.speed = 1;
                animator.SetBool("isLoading", true);
                animator.SetBool("isShooting", false);
                animator.SetFloat("PullStrength", 0);

                //播放音频
                AudioSource.PlayClipAtPoint(fillSound, Vector3.zero);

                //记录右键按下
                isRightMouseButtonPressed = true;

                //箭数-1
                arrowCountController.changeArrow(-1);
            }

            //右键按压,计算拉弓力度
            if (isRightMouseButtonPressed)
            {
                // 更新拉弓时间
                if (pullDuration < maxPullDuration)
                    pullDuration += Time.deltaTime;

                //线性映射拉弓时间至射速
                arrowSpeed = Mathf.Lerp(minArrowSpeed, maxArrowSpeed, pullDuration / maxPullDuration);

                //线性映射拉弓时间至射程
                arrowDistance = Mathf.Lerp(minArrowDistance, maxArrowDistance, pullDuration / maxPullDuration);

                //映射拉弓时间到动画
                float pullStrength = Mathf.Clamp01(pullDuration / maxPullDuration);
                animator.SetFloat("PullStrength", pullStrength);

                // 输出射速和射程用于调试
                //Debug.Log("Arrow Speed: " + arrowSpeed);
                //Debug.Log("Arrow Distance: " + arrowDistance);
            }

            //右键松开,装填完毕
            if (Input.GetMouseButtonUp(1))
            {
                //记录右键松开
                isRightMouseButtonPressed = false;

                //装填完毕
                Loaded = true;

                //改变动画状态变量
                animator.SetBool("isLoading", false);
            }
        }


        // 左键点击,进行射击
        if (Input.GetMouseButtonDown(0) && Loaded)
        {
            //空膛状态
            Loaded = false;

            //播放音频
            AudioSource.PlayClipAtPoint(shootSound, Vector3.zero);

            // 播放射击动画
            animator.speed = 2;
            animator.SetBool("isLoading", false);
            animator.SetBool("isShooting", true);

            //创建箭头实例
            GameObject arrowPrefab = Resources.Load<GameObject>("RyuGiKen/Crossbow/Prefabs/Arrow");
            if (arrowPrefab != null)
            {
                // 实例化箭头预设(从Resources文件夹中加载的箭头预设)
                GameObject arrow = Instantiate(arrowPrefab);

                // 为新创建的箭头对象动态添加ArrowController组件
                arrow.AddComponent<ArrowHitMonster>();

                // 获取箭头的ArrowController组件,以便后续操作
                ArrowHitMonster arrowHitMonster = arrow.GetComponent<ArrowHitMonster>();

                // 将箭头的位置设置为firePoint的当前位置
                // firePoint应该是发射箭头的起始点,例如弓箭的弦的位置
                arrow.transform.position = firePoint.position;

                // 设置箭头的旋转,使其朝向当前物体(弓)的正前方
                // Quaternion.LookRotation(this.transform.forward) 会将箭头朝向当前物体(弓)的前方方向
                arrow.transform.rotation = Quaternion.LookRotation(Camera.main.transform.forward);

                // 使用协程让箭矢匀速移动
                StartCoroutine(MoveArrow(arrow));
            }
            else
            {
                Debug.LogError("Arrow prefab could not be loaded from Resources. Please check the file path.");
            }
        }

    }

    // 使用协程实现匀速移动
    private IEnumerator MoveArrow(GameObject arrow)
    {
        float distanceTraveled = 0f;
        Vector3 startPosition = arrow.transform.position;
        Vector3 direction = arrow.transform.forward;

        while (distanceTraveled < arrowDistance)  // 假设箭的最大飞行距离是 100 单位
        {
            float moveDistance = arrowSpeed * Time.deltaTime;
            arrow.transform.Translate(direction * moveDistance, Space.World);  // 让箭矢匀速前进

            distanceTraveled += moveDistance;
            yield return null;
        }

        // 飞行结束后,可以销毁箭矢
        Destroy(arrow);
    }
}

6. 怪物(靶子)

(1)靶子设置

        静态靶:骷髅;动态靶:僵尸

        僵尸自动寻路实现:使用Unity提供的NavMeshAgent自动寻路组件,烘培地图,挂载脚本,自动追踪玩家。

(骷髅打金服)

(NavMesh Surface地图烘培)

僵尸行动相关代码如下:

MonsterBehaviour 类

using UnityEngine;
using UnityEngine.AI;

public class MonsterBehaviour : MonoBehaviour
{
    public Transform player;  // 玩家的位置 
    public float detectionRange = 15f;  // 怪物的追击范围
    public float attackRange = 2f;  // 怪物的攻击范围
    public float moveSpeed = 3.5f;  // 怪物移动速度
    public float damage = 1;  // 怪物攻击伤害
    public float knockbackForce = 12f;  // 击退力
    public float attackInterval = 1f;  // 攻击间隔时间

    public PlayerMovementController playerMovementController;   // 玩家移动组件,用于击退玩家
    public PlayerDamageController playerDamageController;   // 玩家受到伤害组件
    private NavMeshAgent navAgent;  // 怪物的 NavMeshAgent 组件
    private Animator animator;  // 怪物的动画控制器

    private float lastAttackTime = 0f;  // 上次攻击的时间

    void Start()
    {
        // 获取NavMeshAgent组件
        navAgent = GetComponent<NavMeshAgent>();

        // 设置 NavMeshAgent 的移动速度
        if (navAgent != null)
        {
            navAgent.speed = moveSpeed;
        }
    }

    void Update()
    {
        if (player == null) return;  // 如果没有指定玩家对象,跳过更新

        // 计算玩家与怪物之间的距离
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);

        // 如果玩家在追击范围内,怪物追击玩家
        if (distanceToPlayer <= detectionRange)
        {
            // 设置 NavMeshAgent 的目标为玩家的位置
            navAgent.SetDestination(player.position);

            // 如果怪物距离玩家很近,并且达到了攻击间隔(1s),进行攻击
            if (distanceToPlayer <= attackRange && Time.time - lastAttackTime >= attackInterval)
            {
                //Debug.Log("怪物和玩家距离:" + distanceToPlayer);
                //Debug.Log("Attacked!");

                // 更新上次攻击时间
                lastAttackTime = Time.time;

                // 进行攻击
                AttackPlayer();
            }
        }
    }

    // 攻击玩家的逻辑
    private void AttackPlayer()
    {
        //Debug.Log("Attacked+!");

        // 计算击退方向(怪物到玩家的方向)
        Vector3 knockbackDirection = player.position - transform.position;

        // 调用玩家移动控制器来处理击退
        playerMovementController.PlayerDamaged(knockbackDirection, knockbackForce);
    }
}
(2)碰撞检测

         箭头进行boxCast检测,若碰撞到父物体标签为monsters的物体(怪物),则怪物死亡。

(boxCast检测效果)

   ArrowHitMonster 类

using UnityEngine;
using System.Collections;  // 引入协程所需的命名空间

public class ArrowHitMonster : MonoBehaviour
{
    public float speed = 30f;  // 箭矢速度
    public float maxDistance = 100f;  // 最大射程
    public Transform firePoint;     // 箭矢射出点
    public Vector3 boxSize = new Vector3(1f, 1f, 1f);  // 设置BoxCast的大小(这可以根据需要调整)

    private Vector3 startPosition;

    void Start()
    {
        startPosition = transform.position;
    }

    void Update()
    {
        // 箭矢飞行方向
        Vector3 direction = transform.forward;

        // 使用 BoxCast 检测
        RaycastHit hit;
        
        // BoxCast的中心点是箭矢当前的位置,检测方向是箭矢的前方(方向),检测的最大距离是箭矢的速度 * deltaTime
        if (Physics.BoxCast(transform.position, boxSize, direction, out hit, Quaternion.identity, 30f * Time.deltaTime))
        {
            // 检测到碰撞,判断是否击中怪物
            if (hit.collider.transform.parent != null && hit.collider.transform.parent.CompareTag("monsters"))
            {
                Debug.Log("Hit Monster!");

                // 父物体是 Zombies,调用 ZombieDead 脚本
                if (hit.collider.transform.parent.name == "Zombies")
                {
                    //Debug.Log("射中僵尸了!");

                    ZombieDead zombieDead = hit.collider.transform.GetComponent<ZombieDead>();
                    if (zombieDead != null)
                    {
                        Debug.Log("射中僵尸了!");
                        zombieDead.OnHit();  // 调用怪物死亡的逻辑
                    }
                }

                // 父物体是 Skeletons,调用 SkeletonDead 脚本
                if (hit.collider.transform.parent.name == "Skeletons")
                {
                    //Debug.Log("射中小白了!");
                    SkeletonDead skeletonDead = hit.collider.transform.GetComponent<SkeletonDead>();
                    if (skeletonDead != null)
                    {
                        Debug.Log("射中小白了!");
                        skeletonDead.OnHit();  // 调用怪物死亡的逻辑
                    }
                }
            }
        }
    }

    // 可选:绘制调试用的 BoxCast 范围(便于调试和测试)
    void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawWireCube(transform.position + transform.forward * (speed * Time.deltaTime), boxSize);
    }
}

(3)怪物击杀与计分管理

     识别怪物所属父物体名称判断怪物种类,击杀调用ScoreManager 类,击杀僵尸+2分,击杀骷髅+1分并增加2支箭。

ZombieDead 类

using UnityEngine;
using System.Collections;

public class ZombieDead : MonoBehaviour
{
    public ScoreManager scoreManager; // 引用 ScoreManager 脚本,用于增加分数
    public ParticleSystem smokeEffect;  // 引入烟雾粒子系统

    private bool isHit = false; // 判断怪物是否被击中

    private AudioSource audioSource;    //音频
    public AudioClip deadSound;

    public void OnHit()
    {
        if (isHit) return; // 如果怪物已经被击中,则不再执行任何操作
        isHit = true;

        //获取挂载音频
        audioSource = GetComponent<AudioSource>();  // 获取挂载的 AudioSource
        AudioSource.PlayClipAtPoint(deadSound, Vector3.zero);  //播放音频

        //播放烟雾粒子效果
        smokeEffect.transform.position = transform.position;  // 将烟雾位置设置为怪物位置
        smokeEffect.Play();  // 播放烟雾粒子效果

        //僵尸+2分
        int scoreToAdd = 2; 

        // 增加分数
        scoreManager.AddScore(scoreToAdd);

        // 销毁怪物
        Destroy(gameObject, 0.2f);
    }
    
}

SkeletonDead 类

using UnityEngine;
using System.Collections;

public class SkeletonDead : MonoBehaviour
{
    public ScoreManager scoreManager; // 引用 ScoreManager 脚本,用于增加分数
    public ParticleSystem smokeEffect;  // 引入烟雾粒子系统

    //引用 ArrowController 脚本,用于修改箭数
    public ArrowCountController arrowCountController;

    //引用 IfShootArea 脚本,用于修改骷髅击杀数
    public IfShootArea ifShootArea;

    private bool isHit = false; // 判断怪物是否被击中

    private AudioSource audioSource;    //音频
    public AudioClip deadSound;

    public void OnHit()
    {
        if (isHit) return; // 如果怪物已经被击中,则不再执行任何操作
        isHit = true;

        //获取挂载音频
        audioSource = GetComponent<AudioSource>();  // 获取挂载的 AudioSource
        AudioSource.PlayClipAtPoint(deadSound, Vector3.zero);  //播放音频

        //播放烟雾粒子效果
        smokeEffect.transform.position = transform.position;  // 将烟雾位置设置为怪物位置
        smokeEffect.Play();  // 播放烟雾粒子效果

        //小白+1分
        int scoreToAdd = 1;

        // 增加分数
        scoreManager.AddScore(scoreToAdd);

        //增加箭数
        arrowCountController.changeArrow(2);

        //小白击杀+1
        ifShootArea.changeDeadCount(1);

        // 销毁怪物
        Destroy(gameObject, 0.2f);
    }

}

ScoreManager 类

using UnityEngine;
using UnityEngine.UI;  // 如果使用 Text 组件的话
using TMPro;  // 如果使用 TextMeshPro 的话

//分数显示
public class ScoreManager : MonoBehaviour
{
    public TMP_Text scoreText;  // 如果使用 TextMeshPro,用这个来显示UI
    private int score = 0;      // 分数变量

    void Start()
    {
        ResetScore();
    }

    void UpdateScoreDisplay()
    {
        // 更新 UI 中的文本显示分数
        scoreText.text = "Score: " + score.ToString();
    }

    public void AddScore(int points)
    {
        score += points;
        UpdateScoreDisplay();
    }

    public void ResetScore()
    {
        score = 0;
        UpdateScoreDisplay();
    }
}
(4)粒子效果

        创建粒子播放脚本,怪物死亡后绑定的粒子效果传送到死亡位置并开始播放。代码见上面的ZombieDead 类和SkeletonDead 类。

7. 小地图

新建一个摄像机,镜头固定在鸟瞰游戏地图的方向,摄像机设置为玩家模型的子物体,位置随玩家位置变化而变化;使用Render Texture搭载摄像机,显示在游戏画面左上角。

8. 玩家生命值

僵尸靠近玩家会进行攻击,将玩家击退,并扣除一点生命值。

生命值初始有5点,在游戏界面用5颗心表示,生命值清零后,游戏结束。

(UI设计)

代码如下:

PlayerDamageController类

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

public class PlayerDamageController : MonoBehaviour
{
    public int maxHealth = 5;    // 玩家最大生命值
    public int currentHealth = 5; // 当前生命值
    public GameObject[] hearts;   // 存储心形图标的数组
    public TextMeshProUGUI gameOverText;  // 死亡文本

    void Start()
    {
        // 初始化心形图标的显示
        UpdateHearts();

        // 游戏开始时隐藏死亡信息
        gameOverText.gameObject.SetActive(false); 
    }

    void Update()
    {
        // 检查玩家是否死亡
        if (currentHealth <= 0 && !gameOverText.gameObject.activeSelf)
        {
            GameOver(); // 触发游戏结束
        }
    }

    // 处理玩家受伤
    public void TakeDamage(int damage)
    {
        currentHealth -= damage;
        if (currentHealth < 0)
        {
            currentHealth = 0;  // 生命值不能小于0
        }
        UpdateHearts();  // 更新显示的心形图标
    }

    // 更新心形图标的显示
    void UpdateHearts()
    {
        for (int i = 1; i <= maxHealth; i++)
        {
            // 显示当前生命值颗心
            if (i <= currentHealth)
            {
                hearts[i-1].SetActive(true); // 显示心形图标
            }
            else
            {
                hearts[i-1].SetActive(false); // 隐藏心形图标
            }
        }
    }

    // 游戏结束时调用此方法
    void GameOver()
    {
        gameOverText.text = "Wasted";  // 设置显示文本为 "Wasted"
        gameOverText.gameObject.SetActive(true); // 显示文本

        // 启动协程,在2秒后结束游戏
        StartCoroutine(EndGameWithDelay(2f));
    }

    // 协程:延迟2秒后结束游戏
    IEnumerator EndGameWithDelay(float delay)
    {
        // 等待 2 秒
        yield return new WaitForSeconds(2f);

        // 退出游戏
        Debug.Log("Game Over! You Win!");
        Application.Quit();  // 退出游戏

        // 在编辑器中退出
        UnityEditor.EditorApplication.isPlaying = false;  // 仅在编辑器中有效
    }
}

9. 音频

导入音频后,使用Audio Source组件进行播放,游戏里搭载有背景音乐,以及拉弓、玩家受伤、怪物死亡等音效。

backgroudmusic类

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

public class backgroudmusic : MonoBehaviour
{
    private AudioSource audioSource;    //音频
    public AudioClip backgroundmusic;

    // Start is called before the first frame update
    void Start()
    {
        //获取挂载音频
        audioSource = GetComponent<AudioSource>();  // 获取挂载的 AudioSource
        AudioSource.PlayClipAtPoint(backgroundmusic, Vector3.zero);  //播放音频
    }
    
    // Update is called once per frame
    void Update()
    {
        
    }
}

10. 游戏胜利条件

到达村庄钻石傀儡处,游戏胜利,自动结束。

控制代码如下:

GameControllerScript 类

using UnityEngine;
using TMPro;  // 如果使用 TextMeshPro 的话
using UnityEngine.SceneManagement;  // 引入场景管理
using System.Collections;  // 引入协程所需的命名空间

public class GameControllerScript : MonoBehaviour
{
    public Vector3 targetPosition = new Vector3(-34f, 25f, 51f);  // 目标位置
    public Transform player;         // 玩家对象
    public TMP_Text win;        // 显示胜利文本的UI组件

    public float winDistance = 10f;    // 到达目标位置的距离阈值

    void Start()
    {
        // 在游戏开始时确保胜利文本不可见
        win.enabled = false;

        // 隐藏鼠标
        Cursor.visible = false;

        // 锁定鼠标到屏幕中心
        Cursor.lockState = CursorLockMode.Locked;
    }

    void Update()
    {
        // 检查玩家与目标位置之间的距离
        float distanceToTarget = Vector3.Distance(player.position, targetPosition);

        // 如果距离小于设定的阈值,显示胜利消息并结束游戏
        if (distanceToTarget <= winDistance)
        {
            ShowWinMessage();
            // 启动协程
            if (!isGameOver) // 防止重复启动协程
            {
                StartCoroutine(EndGameWithDelay());
                isGameOver = true;
            }
        }
    }

    // 显示胜利文本
    void ShowWinMessage()
    {
        win.enabled = true;  // 显示文本
    }

    private bool isGameOver = false;

    // 协程:等待 2 秒后结束游戏
    IEnumerator EndGameWithDelay()
    {
        // 等待 2 秒
        yield return new WaitForSeconds(2f);

        // 退出游戏
        Debug.Log("Game Over! You Win!");
        Application.Quit();  // 退出游戏

        // 在编辑器中退出
        UnityEditor.EditorApplication.isPlaying = false;  // 仅在编辑器中有效
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值