使用Unity做一个3D吃豆人小游戏
目标
用Unity完成一个3D吃豆人小游戏,实现玩家吃豆子,鬼魂追逐玩家的功能。
制作步骤
制作模型
在右上角的Layout选择2×3,方便操作。
在层级界面右键选择3D对象,添加模型对象。
选择平面,构建迷宫底,可以在右边的Transform调整位置和大小。
同理可以新建立方体构建迷宫的围墙。
若要更换颜色等材质可以在Assets栏右键,选择创建→材质(左图)。
选择基础贴图,选取颜色(右图),再将材质拖到想改变颜色的模型对象上,即可完成颜色更换。
左下角的游戏视角可以通过选择Main Camera并进行修改。
设计完地图后大概是这样(迷宫内部可以自行修改)此处我用了黄色圆球表示吃豆人,浅黄色小球表示豆子,三个椭球表示鬼。
至此3D部分的模型就差不多做好了。
再新建一个画布,并在画布上右键选中UI→面板,命名为winPanel,再新建一个文本作为游戏胜利 的显示。
同理,也设计出一个GameOver界面。
之后再在这两个界面之上设置一个Botton,Botton之内添加一个文本,自定义按键中的文字。
整体的架构和效果大概分别如下图:
现在模型部分基本都已完成,可以开始进行之后的设计。
逻辑设计
选中所有墙壁,在检查器的标签处添加Wall标签,并将Collider开启。
吃豆人的标签设为Player,同样也将Collider开启,并添加Rigidbody组件,以便完成移动功能。
在Rigidbody组件中,取消“是运动学的”选项,并冻结位置和旋转,以防飞出地图外或乱动。
鬼的标签设为Ghost,Collider和Rigidbody的配置和吃豆人一样。
选中所有豆子,豆子的标签设为Bean,不需要Rigidbody组件,但同样需要开启Collider,且需选中“是触发器”选项。
要实现鬼魂自动追逐玩家的效果,需要使用组件Nav Mesh Agent,导入组件后可在组件内部设置各个鬼的移动速度等。(三个鬼都需要)
PS:为防止鬼挤在一起,可以给它们设置不同的速度。
之后进行鬼魂移动区域的规划,需要在工具栏的窗口处选中AI→导航(过时)进行导入
(可能需要在包管理器处下载AI Navigation才能显示)
导入后选择Plane,选中Navigation Static。
再选择烘培窗口,点击Bake。
之后就可以完成自动追踪吃豆人的功能。
但要实现游戏的运行还有最重要的代码部分。
代码设计
PacmanController.cs:
using UnityEngine;
public class PacmanController : MonoBehaviour
{
public float moveSpeed = 30f; // 设置吃豆人的移动速度
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
MovePacman();
}
// 控制吃豆人的移动
void MovePacman()
{
float moveX = Input.GetAxis("Horizontal"); // 读取左右键输入
float moveZ = Input.GetAxis("Vertical"); // 读取上下键输入
// 保持吃豆人在平面上移动
Vector3 movement = new Vector3(moveX, 0, moveZ) * moveSpeed * Time.deltaTime;
rb.MovePosition(transform.position + movement);
}
// 吃豆子逻辑
void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Bean"))
{
Destroy(other.gameObject); // 移除豆子
// 告知GameManager豆子已被吃掉
FindObjectOfType<GameManager>().AddCollectedBean();
}
}
}
在这个代码内我们设置了吃豆人的逻辑,如设置移动速度为30,设定其移动由键盘的上下左右控制,设置吃豆人碰到豆子就将豆子移除并告知GameManager以达成吃豆子的效果。
GhostController.cs:
using UnityEngine;
using UnityEngine.AI;
public class GhostController : MonoBehaviour
{
public Transform player; // 玩家(吃豆人)的Transform
private NavMeshAgent navMeshAgent;
void Start()
{
navMeshAgent = GetComponent<NavMeshAgent>(); // 获取鬼的NavMeshAgent组件
}
void Update()
{
// 让鬼始终追逐玩家
navMeshAgent.SetDestination(player.position);
}
// 当鬼碰到玩家时触发游戏结束
void OnCollisionEnter(Collision collision)
{
// 检查碰到的物体是否是玩家
if (collision.gameObject.CompareTag("Player"))
{
Debug.Log("鬼碰到玩家");
FindObjectOfType<GameManager>().GameOver(false); // 显示游戏结束界面
}
}
}
此代码用于实现鬼的逻辑,使其自动追逐玩家,并在玩家被鬼碰到时触发GameManager的GameOver函数以达到游戏结束的效果。
GameManager.cs:
using UnityEngine;
using UnityEngine.SceneManagement; // 用于重新加载场景
public class GameManager : MonoBehaviour
{
private bool isGameOver = false;
private int beansCollected = 0; // 吃掉的豆子数量
public int totalBeans = 48; // 需要吃掉的豆子总数
private UIManager uiManager; // 引用UIManager
void Start()
{
uiManager = FindObjectOfType<UIManager>(); // 获取UIManager实例
}
// 每次吃到一个豆子时调用这个方法
public void AddCollectedBean()
{
beansCollected++; // 增加豆子计数器
Debug.Log("吃掉的豆子数: " + beansCollected);
// 如果吃满48个豆子,触发胜利
if (beansCollected >= totalBeans)
{
GameOver(true); // 显示胜利界面
}
}
// 游戏结束处理
public void GameOver(bool isWin)
{
if (isGameOver) return; // 防止重复触发
isGameOver = true;
if (isWin)
{
Debug.Log("胜利!");
uiManager.ShowWin(); // 调用UIManager显示胜利界面
}
else
{
Debug.Log("游戏结束");
uiManager.ShowGameOver(); // 调用UIManager显示游戏结束界面
}
Time.timeScale = 0f; // 暂停游戏
}
// 重新开始游戏
public void RestartGame()
{
Time.timeScale = 1f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); // 重新加载当前场景
}
}
此代码对游戏的数据进行了管理,也设置了当吃豆人吃完了界面上所有豆子后就调用GameOver函数触发胜利界面的胜利条件。
UIManager.cs:
using UnityEngine;
using UnityEngine.UI;
using TMPro; // 引入TextMeshPro命名空间
public class UIManager : MonoBehaviour
{
public Button restartButton; // 重新开始按钮
public GameObject gameOverPanel; // 游戏结束面板
public GameObject winPanel; // 胜利面板
private GameManager gameManager;
private TMP_Text restartButtonText; // 使用TextMeshPro
void Start()
{
gameManager = FindObjectOfType<GameManager>();
// 绑定重新开始按钮的点击事件
restartButton.onClick.AddListener(RestartGame);
// 初始隐藏结束和胜利面板,以及重新开始按钮
gameOverPanel.SetActive(false);
winPanel.SetActive(false);
restartButton.gameObject.SetActive(false); // 隐藏按钮
}
// 显示游戏结束界面
public void ShowGameOver()
{
gameOverPanel.SetActive(true); // 显示游戏结束面板
winPanel.SetActive(false); // 确保胜利面板隐藏
restartButton.gameObject.SetActive(true); // 显示重新开始按钮
// 确保重新开始按钮位于UI层级的最上方
restartButton.transform.SetAsLastSibling();
}
// 显示胜利界面
public void ShowWin()
{
winPanel.SetActive(true); // 显示胜利面板
gameOverPanel.SetActive(false); // 确保游戏结束面板隐藏
restartButton.gameObject.SetActive(true); // 显示重新开始按钮
// 确保重新开始按钮位于UI层级的最上方
restartButton.transform.SetAsLastSibling();
}
// 重新开始游戏
public void RestartGame()
{
gameManager.RestartGame(); // 调用 GameManager 的重新开始方法
// 隐藏所有面板和按钮
gameOverPanel.SetActive(false);
winPanel.SetActive(false);
restartButton.gameObject.SetActive(false); // 隐藏重新开始按钮
}
}
此代码用于管理UI界面,在游戏运行时将胜利、结束界面及重新开始按钮隐藏,在显示游戏胜利或结束时显示对应的UI画面。
实现完以上代码后,需要将代码拖入对象中完成代码的使用。
选中吃豆人对象,打开其检查器,将PacmanController代码拖入其中,效果如下,可以在Move Speed处修改吃豆人运行速度。
鬼同理,将GhostController代码拖入三个鬼当中,但需要将“玩家”指向的对象修改为吃豆人(三个都要)
UIManager代码则拖入UI对象当中,同样需要修改指向对象。
至于GameManager代码,则需要新建一个空对象,再将代码拖入。
至此已经完成了游戏的所有内容,可以进行游玩。