【作业自存】用Unity完成简易开放世界游戏

用Unity完成一个简易开放世界游戏,能够实现地图动态加载,角色的跑跳、攀爬等,敌人的AI移动、攻击判定、玩家生命值等功能。

我负责的部分为两种种类的敌人的AI移动、攻击判定、玩家生命值,因此仅对此部分进行展示。

敌人种类分为青蛇和黑蛇,两种蛇在距离玩家较远时都会随机移动,玩家距离接近到一定范围内才会进行追逐,青蛇的追逐范围更广,速度更快,黑蛇的追逐范围较小,速度相对较慢,但青蛇攻击一次玩家会使其扣除一格生命值,而黑蛇一次可以扣除两格。

需要对两种类型的蛇都添加刚体和碰撞器

代码实现如下:

玩家生命值和UI显示:

设置玩家的初始生命值为3格,当生命值小于3格时,每隔60秒恢复1格生命值,并在左上角显示目前生命值和生命值恢复的剩余时间。当生命值等于0则游戏结束,显示战败面板,按R键可重新开始游戏。

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class HPController : MonoBehaviour
{
    public int maxHealth = 3; // 最大生命值
    private int currentHealth; // 当前生命值
    private float timeSinceLastHeal = 0f; // 上次恢复生命值的时间

    private Text healthText; // 用于显示生命值和恢复时间的 UI 文本
    private GameObject losePanel; // 战败提示 UI 面板
    private Text restartText; // 重新开始游戏的提示文本

    void Start()
    {
        currentHealth = maxHealth; // 初始化生命值为最大值
        CreateHealthUI(); // 创建 UI
    }

    void Update()
    {
        // 只有当生命值小于最大生命值时,才开始计时
        if (currentHealth < maxHealth)
        {
            timeSinceLastHeal += Time.deltaTime;

            // 每隔一分钟恢复一条生命值
            if (timeSinceLastHeal >= 60f)
            {
                currentHealth++;
                timeSinceLastHeal = 0f; // 重置计时器
                Debug.Log("生命值恢复,当前生命值: " + currentHealth);
            }
        }
        else
        {
            // 如果生命值已满,重置计时器
            timeSinceLastHeal = 0f;
        }

        // 每秒更新一次 UI
        UpdateHealthUI();

        // 检测是否按下 R 键重新开始游戏
        if (currentHealth <= 0 && Input.GetKeyDown(KeyCode.R))
        {
            RestartGame();
        }
    }

    public void TakeDamage()
    {
        if (currentHealth > 0)
        {
            currentHealth--;
            Debug.Log("受到伤害,当前生命值: " + currentHealth);

            if (currentHealth <= 0)
            {
                Die();
            }
        }
    }

    void Die()
    {
        ShowLoseUI(); // 显示战败提示 UI
        PauseGame(); // 暂停游戏
    }

    // 创建 UI
    void CreateHealthUI()
    {
        // 创建 Canvas
        GameObject canvasGO = new GameObject("HealthCanvas");
        Canvas canvas = canvasGO.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        canvasGO.AddComponent<CanvasScaler>();
        canvasGO.AddComponent<GraphicRaycaster>();

        // 创建 Text
        GameObject textGO = new GameObject("HealthText");
        textGO.transform.SetParent(canvasGO.transform);
        healthText = textGO.AddComponent<Text>();

        // 设置 Text 的样式和位置
        healthText.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
        healthText.fontSize = 32;
        healthText.color = Color.black;
        healthText.alignment = TextAnchor.UpperLeft;

        RectTransform rectTransform = textGO.GetComponent<RectTransform>();
        rectTransform.anchorMin = new Vector2(0, 1);
        rectTransform.anchorMax = new Vector2(0, 1);
        rectTransform.pivot = new Vector2(0, 1);
        rectTransform.anchoredPosition = new Vector2(10, -10);
        rectTransform.sizeDelta = new Vector2(500, 100); // 设置文本区域的大小
    }

    // 更新 UI 内容
    void UpdateHealthUI()
    {
        if (healthText != null)
        {
            // 如果生命值已满,显示“生命值已满”
            if (currentHealth >= maxHealth)
            {
                healthText.text = $"当前生命值: {currentHealth}\n生命值已满";
            }
            else
            {
                // 计算距离恢复生命值的时间
                float timeToNextHeal = Mathf.Max(0, 60f - timeSinceLastHeal);

                // 更新 UI 文本
                healthText.text = $"当前生命值: {currentHealth}\n距离恢复生命值: {timeToNextHeal:F0} 秒";
            }
        }
    }

    // 显示战败提示 UI
    void ShowLoseUI()
    {
        // 创建战败提示面板
        losePanel = new GameObject("LosePanel");
        losePanel.transform.SetParent(GameObject.Find("HealthCanvas").transform);

        // 设置面板背景
        Image background = losePanel.AddComponent<Image>();
        background.color = new Color(0, 0, 0, 0.5f); // 半透明黑色背景

        RectTransform panelRect = losePanel.GetComponent<RectTransform>();
        panelRect.anchorMin = new Vector2(0, 0);
        panelRect.anchorMax = new Vector2(1, 1);
        panelRect.pivot = new Vector2(0.5f, 0.5f);
        panelRect.anchoredPosition = Vector2.zero;
        panelRect.sizeDelta = Vector2.zero;

        // 添加战败提示文本
        GameObject loseTextGO = new GameObject("LoseText");
        loseTextGO.transform.SetParent(losePanel.transform);
        Text loseText = loseTextGO.AddComponent<Text>();
        loseText.text = "You Lose";
        loseText.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
        loseText.fontSize = 48;
        loseText.color = Color.white;
        loseText.alignment = TextAnchor.MiddleCenter;

        RectTransform textRect = loseTextGO.GetComponent<RectTransform>();
        textRect.anchorMin = new Vector2(0.5f, 0.5f);
        textRect.anchorMax = new Vector2(0.5f, 0.5f);
        textRect.pivot = new Vector2(0.5f, 0.5f);
        textRect.anchoredPosition = new Vector2(0, 50);
        textRect.sizeDelta = new Vector2(400, 100);

        // 添加“按R键重新开始游戏”提示文本
        GameObject restartTextGO = new GameObject("RestartText");
        restartTextGO.transform.SetParent(losePanel.transform);
        restartText = restartTextGO.AddComponent<Text>();
        restartText.text = "按 R 键重新开始游戏";
        restartText.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
        restartText.fontSize = 24;
        restartText.color = Color.white;
        restartText.alignment = TextAnchor.MiddleCenter;

        RectTransform restartTextRect = restartTextGO.GetComponent<RectTransform>();
        restartTextRect.anchorMin = new Vector2(0.5f, 0.5f);
        restartTextRect.anchorMax = new Vector2(0.5f, 0.5f);
        restartTextRect.pivot = new Vector2(0.5f, 0.5f);
        restartTextRect.anchoredPosition = new Vector2(0, -50);
        restartTextRect.sizeDelta = new Vector2(400, 50);
    }

    // 暂停游戏
    void PauseGame()
    {
        Time.timeScale = 0;
    }

    // 重新开始游戏
    void RestartGame()
    {
        Time.timeScale = 1; // 恢复游戏时间
        SceneManager.LoadScene(SceneManager.GetActiveScene().name); // 重新加载当前场景
    }
}

青蛇行为逻辑:

当玩家距离青蛇20f外时,青蛇随机移动,当玩家在青蛇20f内时,青蛇追逐玩家,移动速度为2.5f。当与玩家距离小于2f时,青蛇触发攻击动画。当青蛇与玩家碰撞时,玩家扣除一格生命值。

using UnityEngine;

public class GreenSnake : MonoBehaviour
{
    public float moveSpeed = 2.5f; // 移动速度
    public float rotationSpeed = 5f; // 旋转速度
    public float changeDirectionInterval = 2f; // 改变方向的时间间隔
    public float chaseRange = 20f; // 追逐玩家的范围
    public float attackRange = 2f; // 攻击玩家的范围
    public Transform player; // 玩家目标

    private Vector3 targetDirection; // 目标移动方向
    private float timeSinceLastDirectionChange = 0f; // 上次改变方向的时间
    private bool isChasing = false; // 是否正在追逐玩家
    private Animator animator; // 动画器组件

    void Start()
    {
        // 初始化随机方向
        SetRandomDirection();

        // 获取动画器组件
        animator = GetComponent<Animator>();
        if (animator == null)
        {
            Debug.LogError("Animator 组件未找到!");
        }
    }

    void Update()
    {
        // 计算敌人与玩家的距离
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);

        // 如果玩家在追逐范围内,开始追逐玩家
        if (distanceToPlayer <= chaseRange)
        {
            isChasing = true;
        }
        else
        {
            isChasing = false;
        }

        // 如果玩家在攻击范围内,触发攻击动画
        if (distanceToPlayer <= attackRange)
        {
            animator.SetTrigger("Attack"); // 触发攻击动画
        }
        else
        {
            animator.SetTrigger("Normal"); // 触发正常动画
        }

        if (isChasing)
        {
            // 追逐玩家
            ChasePlayer();
        }
        else
        {
            // 随机移动
            RandomMove();
        }

        // 移动对象
        MoveAnimal();

        // 旋转对象
        RotateAnimal();

        // 强制设置 Y 轴位置
        LockYPosition();
    }

    void MoveAnimal()
    {
        transform.Translate(targetDirection * moveSpeed * Time.deltaTime, Space.World); // 添加 Time.deltaTime
    }

    void RotateAnimal()
    {
        // 计算当前朝向和目标方向的旋转差异
        if (targetDirection != Vector3.zero)
        {
            Quaternion targetRotation = Quaternion.LookRotation(targetDirection);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotationSpeed);
        }
    }

    // 设置随机方向
    void SetRandomDirection()
    {
        // 设置一个新的随机方向(仅在 X 和 Z 轴上随机)
        targetDirection = new Vector3(Random.Range(-1f, 1f), 0f, Random.Range(-1f, 1f)).normalized;
    }

    // 随机移动逻辑
    void RandomMove()
    {
        // 每隔一段时间改变方向
        timeSinceLastDirectionChange += Time.deltaTime;

        if (timeSinceLastDirectionChange >= changeDirectionInterval)
        {
            SetRandomDirection();
            timeSinceLastDirectionChange = 0f; // 重置计时器
        }
    }

    // 追逐玩家逻辑
    void ChasePlayer()
    {
        // 计算敌人与玩家的方向
        targetDirection = (player.position - transform.position).normalized;
    }

    // 强制设置 Y 轴位置
    void LockYPosition()
    {
        Vector3 currentPosition = transform.position;
        currentPosition.y = 20.7f; // 强制设置 Y 轴位置
        transform.position = currentPosition;
    }

    // 碰撞检测
    void OnCollisionEnter(Collision collision)
    {
        // 检查碰撞对象是否是玩家
        if (collision.gameObject.CompareTag("Player"))
        {
            // 获取玩家的 HPController 组件并调用 TakeDamage 方法
            HPController playerHealth = collision.gameObject.GetComponent<HPController>();
            if (playerHealth != null)
            {
                playerHealth.TakeDamage();
            }
        }
    }
}

黑蛇行为逻辑:

当玩家距离黑蛇15f外时,黑蛇随机移动,当玩家在黑蛇15f内时,黑蛇追逐玩家,移动速度为2f。当与玩家距离小于2f时,黑蛇触发攻击动画。当黑蛇与玩家碰撞时,玩家扣除两格生命值。

using UnityEngine;

public class BlackSnake : MonoBehaviour
{
    public float moveSpeed = 2f; // 移动速度
    public float rotationSpeed = 5f; // 旋转速度
    public float changeDirectionInterval = 2f; // 改变方向的时间间隔
    public float chaseRange = 15f; // 追逐玩家的范围
    public float attackRange = 2f; // 攻击玩家的范围
    public Transform player; // 玩家目标

    private Vector3 targetDirection; // 目标移动方向
    private float timeSinceLastDirectionChange = 0f; // 上次改变方向的时间
    private bool isChasing = false; // 是否正在追逐玩家
    private Animator animator; // 动画器组件

    void Start()
    {
        // 初始化随机方向
        SetRandomDirection();

        // 获取动画器组件
        animator = GetComponent<Animator>();
        if (animator == null)
        {
            Debug.LogError("Animator 组件未找到!");
        }
    }

    void Update()
    {
        // 计算敌人与玩家的距离
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);

        // 如果玩家在追逐范围内,开始追逐玩家
        if (distanceToPlayer <= chaseRange)
        {
            isChasing = true;
        }
        else
        {
            isChasing = false;
        }

        // 如果玩家在攻击范围内,触发攻击动画
        if (distanceToPlayer <= attackRange)
        {
            animator.SetTrigger("Attack"); // 触发攻击动画
        }
        else
        {
            animator.SetTrigger("Normal"); // 触发正常动画
        }

        if (isChasing)
        {
            // 追逐玩家
            ChasePlayer();
        }
        else
        {
            // 随机移动
            RandomMove();
        }

        // 移动对象
        MoveAnimal();

        // 旋转对象
        RotateAnimal();

        // 强制设置 Y 轴位置
        LockYPosition();
    }

    void MoveAnimal()
    {
        transform.Translate(targetDirection * moveSpeed * Time.deltaTime, Space.World); // 添加 Time.deltaTime
    }

    void RotateAnimal()
    {
        // 计算当前朝向和目标方向的旋转差异
        if (targetDirection != Vector3.zero)
        {
            Quaternion targetRotation = Quaternion.LookRotation(targetDirection);
            transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * rotationSpeed);
        }
    }

    // 设置随机方向
    void SetRandomDirection()
    {
        // 设置一个新的随机方向(仅在 X 和 Z 轴上随机)
        targetDirection = new Vector3(Random.Range(-1f, 1f), 0f, Random.Range(-1f, 1f)).normalized;
    }

    // 随机移动逻辑
    void RandomMove()
    {
        // 每隔一段时间改变方向
        timeSinceLastDirectionChange += Time.deltaTime;

        if (timeSinceLastDirectionChange >= changeDirectionInterval)
        {
            SetRandomDirection();
            timeSinceLastDirectionChange = 0f; // 重置计时器
        }
    }

    // 追逐玩家逻辑
    void ChasePlayer()
    {
        // 计算敌人与玩家的方向
        targetDirection = (player.position - transform.position).normalized;
    }

    // 强制设置 Y 轴位置
    void LockYPosition()
    {
        Vector3 currentPosition = transform.position;
        currentPosition.y = 20.7f; // 强制设置 Y 轴位置
        transform.position = currentPosition;
    }

    // 碰撞检测
    void OnCollisionEnter(Collision collision)
    {
        // 检查碰撞对象是否是玩家
        if (collision.gameObject.CompareTag("Player"))
        {
            // 获取玩家的 HPController 组件并调用 TakeDamage 方法
            HPController playerHealth = collision.gameObject.GetComponent<HPController>();
            if (playerHealth != null)
            {
                playerHealth.TakeDamage();
                playerHealth.TakeDamage();
            }
        }
    }
}

将HPController负载于玩家上,并对玩家添加Player标签。将GreenSnake和BlackSnake分别负载于青蛇和黑蛇上,并对检查器该脚本上的“玩家”设置为玩家,即可完成功能实现。

演示视频:

Unity附加作业演示_单机游戏热门视频