|
继续上一篇文章的分享,实现如何动态创建一个蛇的活动区域,以及控制蛇移动、游戏结束界面等。。。
有兴趣的同学可以加我的群:575561285,欢迎一起学习交流
1.首先我们考虑贪食蛇的地图,其实可以通过一个大小一致的小方块循环生成,所以我们需要新建一个GridOrigin 对象 ,并且附加GameGrid.cs组件。
GameGrid.cs 代码如下:
- using UnityEngine;
- using System.Collections.Generic;
- using System.Linq;
-
-
-
- public class GameGrid : MonoBehaviour {
-
- /// <summary>
- /// 移动方式
- /// </summary>
- public enum MoveResult
- {
- MOVED, ATE, DIED, ROTATING, ERROR, NONE
- }
-
- public int gridSize = 5;//地图的大小
- public GameObject gridCubeClone;
- public float rotationDuration = 5.0f;//3D 地图的旋转时间
-
- private LinkedList<GridCube> snake;
- private List<GridCube> cubes;
- private bool rotationEnabled = false;
-
- private bool isRotating = false;
- private Vector3 rotationDirection;
- private float startTime = 0;
- private float lastVal = 0;
-
-
- void Update() {
- if (isRotating) {
- float t = (Time.time - startTime) / rotationDuration;
- float newVal = Mathf.SmoothStep(0, 90, t);
- float diff = newVal - lastVal;
- lastVal = newVal;
-
- transform.Rotate(rotationDirection * diff, Space.World);
-
- if (t >= 1) {
- isRotating = false;
- }
- }
- }
-
- public MoveResult MoveHead(GridCube.Direction direction) {
- if (isRotating) {
- return MoveResult.ROTATING;
- }
-
- bool changedSide = false;
- GridCube next = SnakeHead().GetNextCube(direction, out changedSide);
- if (next == null) {
- return MoveResult.DIED;
- }
-
- if (next.IsSnake() || next.IsHole()) {
- return MoveResult.DIED;
- }
-
- if (changedSide) {
- bool ok = StartRotation(direction);
- return ok ? MoveResult.ROTATING : MoveResult.ERROR;
- }
-
- bool ateApple = next.IsApple();
-
- next.SetCubeState(GridCube.CubeState.SNAKE);
- snake.AddFirst(next);
-
- GridCube last = snake.Last.Value;
- if (!ateApple) {
- last.SetCubeState(GridCube.CubeState.EMPTY);
- snake.RemoveLast();
- return MoveResult.MOVED;
- } else {
- return MoveResult.ATE;
- }
- }
-
- private bool StartRotation(GridCube.Direction direction) {
- Vector3 rotation;
- switch (direction) {
- case GridCube.Direction.UP:
- rotation = new Vector3(-1, 0, 0);
- break;
- case GridCube.Direction.DOWN:
- rotation = new Vector3(1, 0, 0);
- break;
- case GridCube.Direction.LEFT:
- rotation = new Vector3(0, -1, 0);
- break;
- case GridCube.Direction.RIGHT:
- rotation = new Vector3(0, 1, 0);
- break;
- default:
- Debug.LogWarning("Unable to rotate grid!");
- return false;
- }
-
- rotationDirection = rotation;
- startTime = Time.time;
- lastVal = 0;
- isRotating = true;
- return true;
- }
-
- public float GetGridSizeWorld() {
- return gridCubeClone.transform.transform.localScale.x * gridSize;
- }
-
- public void PlaceNewHole() {
- // TODO: Avoid placing holes on edges
- PlaceNewObject(GridCube.CubeState.HOLE);
- }
-
- public void PlaceNewApple() {
- PlaceNewObject(GridCube.CubeState.APPLE);
- }
-
- private GridCube SnakeHead() {
- return snake.First.Value;
- }
-
- private void PlaceNewObject(GridCube.CubeState state) {
- bool done = false;
- while (!done) {
- GridCube cube = cubes.ElementAt(Random.Range(0, cubes.Count));
- if (!cube.isEmpty() || (cube.SameSideAs(SnakeHead()) && rotationEnabled)) {
- continue;
- }
-
- cube.SetCubeState(state);
- done = true;
- }
- }
-
- public void SetupGrid(bool enableRotation, int appleCount) {
- if (cubes != null) {
- foreach (GridCube c in cubes) {
- Destroy(c.gameObject);
- }
- }
-
- snake = new LinkedList<GridCube>();
- cubes = new List<GridCube>();
- isRotating = false;
- rotationEnabled = enableRotation;
-
- if (gridSize % 2 == 0) {
- gridSize++;
- }
-
- gridSize = Mathf.Max(gridSize, 5);
-
- float finalGridSize = GetGridSizeWorld();
- float halfGridSize = finalGridSize / 2;
-
- int zDepth = rotationEnabled ? gridSize : 1;
-
- for (int i = 0; i < gridSize; i++) {
- for (int j = 0; j < gridSize; j++) {
- for (int k = 0; k < zDepth; k++) {
-
- // Dont add cubes at center of 3d grid
- if ((k != 0 && k != gridSize - 1) && (j != 0 && j != gridSize - 1) && (i != 0 && i != gridSize - 1)) {
- continue;
- }
-
- GameObject cubeGameObject = Instantiate(gridCubeClone);
- cubeGameObject.transform.SetParent(transform);
-
- Vector3 size = cubeGameObject.transform.localScale;
- float offset = halfGridSize - size.x / 2;
- cubeGameObject.transform.Translate(i * size.x - offset, j * size.x - offset, k * size.x - offset);
-
- int centerPos = (int)halfGridSize;
- GridCube cube = cubeGameObject.GetComponent<GridCube>();
-
- if (i == centerPos && j == centerPos && k == 0) {
- // Set up starting cell
- cube.SetCubeState(GridCube.CubeState.SNAKE);
- snake.AddFirst(cube);
- } else {
- cube.SetCubeState(GridCube.CubeState.EMPTY);
- }
-
- if (i == 0) {
- cube.AddCubeSide(GridCube.CubeSide.LEFT);
- } else if (i == gridSize - 1) {
- cube.AddCubeSide(GridCube.CubeSide.RIGHT);
- }
-
- if (j == 0) {
- cube.AddCubeSide(GridCube.CubeSide.BOTTOM);
- } else if (j == gridSize - 1) {
- cube.AddCubeSide(GridCube.CubeSide.TOP);
- }
-
- if (k == 0) {
- cube.AddCubeSide(GridCube.CubeSide.FRONT);
- } else if (k == gridSize - 1) {
- cube.AddCubeSide(GridCube.CubeSide.BACK);
- }
-
- cubes.Add(cube);
- }
- }
- }
-
- for (int i = 0; i < appleCount; i++) {
- PlaceNewApple();
- }
- }
- }
复制代码
2.当我们设计好贪食蛇的区域后,我们新建一个游戏的控制器 GameController 对象[附加GameController .cs组件]控制贪食蛇在地图区域中利用按下键盘的上下左右箭头控制蛇移动的方向。
GameController .cs 代码如下:
- using UnityEngine;
- using System.Collections;
- using UnityEngine.SceneManagement;
-
- public class GameController : MonoBehaviour {
- private const float DEFAULT_INPUT_COOLDOWN = 0.2f;
- private const float COOLDOWN_STEP = 0.001f;
- private const float MIN_INPUT_COOLDOWN = 0.05f;
- private const float NEW_HOLE_PROBABILITY = 0.1f;
-
- public GameGrid gameGrid;
- public GUIController guiController;
-
- private GridCube.Direction lastDirection = GridCube.Direction.RIGHT;
- private GridCube.Direction lastMovedDirection = GridCube.Direction.NONE;
- private GameGrid.MoveResult lastResult;
- private float lastInputTime = 0;
- private int score = 0;
- private bool playing = true;
- private bool rotationEnabled;
- private float inputCoolDown = DEFAULT_INPUT_COOLDOWN;
-
- void Start() {
- Initialize();
- }
- /// <summary>
- /// 初始化游戏
- /// </summary>
- private void Initialize() {
- rotationEnabled = (PlayerPrefs.GetInt("3dMode", 1) == 1);
- int appleCount = PlayerPrefs.GetInt("AppleCount", 20);
-
- lastResult = GameGrid.MoveResult.NONE;
- inputCoolDown = DEFAULT_INPUT_COOLDOWN;
- guiController.SetTopScore(PlayerPrefs.GetInt("TopScore", 0));
-
- gameGrid.SetupGrid(rotationEnabled, appleCount);
- SetupCamera();
- }
-
- void Update() {
- if (!playing) {
- return;
- }
-
- GridCube.Direction dir = ReadInput();
-
- if (dir == GridCube.Direction.NONE || AreOpposite(dir, lastMovedDirection)) {
- dir = lastDirection;
- }
-
- if (lastResult == GameGrid.MoveResult.ROTATING) {
- dir = lastMovedDirection;
- }
-
- lastDirection = dir;
-
- lastInputTime += Time.deltaTime;
- if (lastInputTime > inputCoolDown) {
-
- lastInputTime = 0;
-
- GameGrid.MoveResult result = gameGrid.MoveHead(dir);
-
- if (result == GameGrid.MoveResult.MOVED || result == GameGrid.MoveResult.ATE) {
- lastMovedDirection = dir;
- }
-
- switch (result) {
- case GameGrid.MoveResult.DIED:
- playing = false;
-
- int topScore = PlayerPrefs.GetInt("TopScore", 0);
- if (score > topScore) {
- PlayerPrefs.SetInt("TopScore", score);
- }
-
- guiController.RemoveNotifications();
- guiController.SetGameOverPanelActive(true);
- break;
- case GameGrid.MoveResult.ERROR:
- Debug.Log("An error occured.");
- gameObject.SetActive(false);
- break;
- case GameGrid.MoveResult.ATE:
- gameGrid.PlaceNewApple();
- if (rotationEnabled && Random.value < NEW_HOLE_PROBABILITY) {
- gameGrid.PlaceNewHole();
- }
-
- //TODO: Win if no more space is available
-
- score++;
- guiController.SetScore(score);
-
- inputCoolDown -= COOLDOWN_STEP;
- if (inputCoolDown < MIN_INPUT_COOLDOWN) {
- inputCoolDown = MIN_INPUT_COOLDOWN;
- }
-
- break;
- case GameGrid.MoveResult.ROTATING:
- default:
- // pass
- break;
- }
-
- lastResult = result;
- }
- }
-
- void SetupCamera() {
- float frustumHeight = gameGrid.GetGridSizeWorld();
- float distance = frustumHeight / Mathf.Tan(Camera.main.fieldOfView * 0.5f * Mathf.Deg2Rad);
- Camera.main.transform.position = new Vector3(0, 0, -distance);
- }
-
- private bool AreOpposite(GridCube.Direction a, GridCube.Direction b) {
- if ((a == GridCube.Direction.DOWN && b == GridCube.Direction.UP) ||
- (a == GridCube.Direction.UP && b == GridCube.Direction.DOWN)) {
- return true;
- }
-
- if ((a == GridCube.Direction.RIGHT && b == GridCube.Direction.LEFT) ||
- (a == GridCube.Direction.LEFT && b == GridCube.Direction.RIGHT)) {
- return true;
- }
-
- return false;
- }
- /// <summary>
- /// 获取当前的键盘键 (箭头)
- /// </summary>
- /// <returns></returns>
- private GridCube.Direction ReadInput() {
- if (Input.GetKey(KeyCode.UpArrow)) {
- return GridCube.Direction.UP;
- } else if (Input.GetKey(KeyCode.DownArrow)) {
- return GridCube.Direction.DOWN;
- } else if (Input.GetKey(KeyCode.RightArrow)) {
- return GridCube.Direction.RIGHT;
- } else if (Input.GetKey(KeyCode.LeftArrow)) {
- return GridCube.Direction.LEFT;
- }
-
- return GridCube.Direction.NONE;
- }
-
- /// <summary>
- /// 重新开始游戏
- /// </summary>
- public void RestartGame() {
- guiController.SetGameOverPanelActive(false);
- Initialize();
- playing = true;
- score = 0;
- guiController.SetScore(score);
- }
- /// <summary>
- /// 返回菜单
- /// </summary>
- public void BackToMenu() {
- SceneManager.LoadScene("Menu");
- }
- }
复制代码
3.利用GUI 制作游戏分数显示。
4.GUI 的控制类GUIController.cs ,并且游戏结束界面功能实现。
GUIController.cs 代码如下:
- using UnityEngine;
- using UnityEngine.UI;
-
- public class GUIController : MonoBehaviour {
-
- public Canvas gameCanvas;
- public GameObject notificationPrefab;
- public Text score;
- public Text topScore;
- public GameObject gameOverPanel;
-
- public readonly string[] congratulationMessages = {
- "Nice!",
- "Congratulations!",
- "Great!",
- "Cool!",
- "Super!",
- "Sweet!",
- "Excellent!",
- "Eggcellent!",
- "Dude!",
- "Noice!",
- "Incredible!",
- "Amazing!",
- "OMG!",
- "en.messages.Congratulate!",
- "Good!",
- "Pretty good!",
- "Not bad!",
- "Life has no intrinsic meaning!",
- "NullReferenceException",
- "Terrific!",
- "Alright!",
- "Whaaaat",
- "Yeaaah!"
- };
-
- private GameObject lastNotification = null;
-
- public string RandomCongratulationMessage() {
- return congratulationMessages[Random.Range(0, congratulationMessages.Length)];
- }
-
- public void ShowNotification(string text) {
- RemoveNotifications();
-
- GameObject notificationObject = Instantiate(notificationPrefab);
- lastNotification = notificationObject;
- notificationObject.transform.SetParent(gameCanvas.transform);
-
- notificationObject.GetComponent<Text>().text = text;
- notificationObject.GetComponent<RectTransform>().localPosition = new Vector3(0, 100);
- notificationObject.GetComponent<NotificationFade>().SetupAnimation();
- }
-
- public void RemoveNotifications() {
- if (lastNotification != null) {
- Destroy(lastNotification);
- }
- }
-
- public void SetScore(int s) {
- score.text = s.ToString();
- }
-
- public void SetTopScore(int s) {
- topScore.text = s.ToString();
- }
-
- public void SetGameOverPanelActive(bool active) {
- gameOverPanel.SetActive(active);
- }
- }
复制代码
5.为了通知显示更好,并且新建一个Notification.prefab[附加NotificationFade.cs 组件]。
NotificationFade.cs 代码如下:
- using UnityEngine;
- using System.Collections;
- using UnityEngine.UI;
-
- public class NotificationFade : MonoBehaviour {
- public float duration = 1;
- private float startTime = 0;
-
- private float maxY = 0;
- private float initialY = 0;
- private RectTransform rectTransform;
- private Text text;
-
- public void SetupAnimation() {
- rectTransform = GetComponent<RectTransform>();
- text = GetComponent<Text>();
-
- startTime = Time.time;
- maxY = transform.parent.GetComponent<RectTransform>().rect.height / 2;
- initialY = rectTransform.localPosition.y;
- }
-
- void Update() {
- float t = (Time.time - startTime) / duration;
- float val = (t * t) * (maxY - initialY);
- float alpha = 1 - (t * t);
- val += initialY;
- rectTransform.localPosition = new Vector3(0, val);
-
- Color prev = text.color;
- prev.a = alpha;
- text.color = prev;
-
- if (t >= 1) {
- Destroy(gameObject);
- }
- }
- }
复制代码
6.好吧!一切都做好后,我们直接从Menu.unity启动开始,看效果吧!
|
|