用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分别负载于青蛇和黑蛇上,并对检查器该脚本上的“玩家”设置为玩家,即可完成功能实现。
演示视频: