游戏开发论坛

 找回密码
 立即注册
搜索
查看: 3738|回复: 0

Unity3D 贪食蛇小游戏Demo[3D版](二)

[复制链接]

15

主题

16

帖子

102

积分

注册会员

Rank: 2

积分
102
发表于 2016-12-20 12:53:52 | 显示全部楼层 |阅读模式
    继续上一篇文章的分享,实现如何动态创建一个蛇的活动区域,以及控制蛇移动、游戏结束界面等。。。
    有兴趣的同学可以加我的群:575561285,欢迎一起学习交流
    1.首先我们考虑贪食蛇的地图,其实可以通过一个大小一致的小方块循环生成,所以我们需要新建一个GridOrigin 对象 ,并且附加GameGrid.cs组件。
图1.png

     GameGrid.cs 代码如下:
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;



  4. public class GameGrid : MonoBehaviour {

  5.     /// <summary>
  6.     /// 移动方式
  7.     /// </summary>
  8.     public enum MoveResult
  9.     {
  10.         MOVED, ATE, DIED, ROTATING, ERROR, NONE
  11.     }

  12.     public int gridSize = 5;//地图的大小
  13.     public GameObject gridCubeClone;
  14.     public float rotationDuration = 5.0f;//3D 地图的旋转时间

  15.     private LinkedList<GridCube> snake;
  16.     private List<GridCube> cubes;
  17.     private bool rotationEnabled = false;

  18.     private bool isRotating = false;
  19.     private Vector3 rotationDirection;
  20.     private float startTime = 0;
  21.     private float lastVal = 0;


  22.     void Update() {
  23.         if (isRotating) {
  24.             float t = (Time.time - startTime) / rotationDuration;
  25.             float newVal = Mathf.SmoothStep(0, 90, t);
  26.             float diff = newVal - lastVal;
  27.             lastVal = newVal;

  28.             transform.Rotate(rotationDirection * diff, Space.World);

  29.             if (t >= 1) {
  30.                 isRotating = false;
  31.             }
  32.         }
  33.     }

  34.     public MoveResult MoveHead(GridCube.Direction direction) {
  35.         if (isRotating) {
  36.             return MoveResult.ROTATING;
  37.         }

  38.         bool changedSide = false;
  39.         GridCube next = SnakeHead().GetNextCube(direction, out changedSide);
  40.         if (next == null) {
  41.             return MoveResult.DIED;
  42.         }

  43.         if (next.IsSnake() || next.IsHole()) {
  44.             return MoveResult.DIED;
  45.         }

  46.         if (changedSide) {
  47.             bool ok = StartRotation(direction);
  48.             return ok ? MoveResult.ROTATING : MoveResult.ERROR;
  49.         }

  50.         bool ateApple = next.IsApple();

  51.         next.SetCubeState(GridCube.CubeState.SNAKE);
  52.         snake.AddFirst(next);

  53.         GridCube last = snake.Last.Value;
  54.         if (!ateApple) {
  55.             last.SetCubeState(GridCube.CubeState.EMPTY);
  56.             snake.RemoveLast();
  57.             return MoveResult.MOVED;
  58.         } else {
  59.             return MoveResult.ATE;
  60.         }
  61.     }

  62.     private bool StartRotation(GridCube.Direction direction) {
  63.         Vector3 rotation;
  64.         switch (direction) {
  65.             case GridCube.Direction.UP:
  66.                 rotation = new Vector3(-1, 0, 0);
  67.                 break;
  68.             case GridCube.Direction.DOWN:
  69.                 rotation = new Vector3(1, 0, 0);
  70.                 break;
  71.             case GridCube.Direction.LEFT:
  72.                 rotation = new Vector3(0, -1, 0);
  73.                 break;
  74.             case GridCube.Direction.RIGHT:
  75.                 rotation = new Vector3(0, 1, 0);
  76.                 break;
  77.             default:
  78.                 Debug.LogWarning("Unable to rotate grid!");
  79.                 return false;
  80.         }

  81.         rotationDirection = rotation;
  82.         startTime = Time.time;
  83.         lastVal = 0;
  84.         isRotating = true;
  85.         return true;
  86.     }

  87.     public float GetGridSizeWorld() {
  88.         return gridCubeClone.transform.transform.localScale.x * gridSize;
  89.     }

  90.     public void PlaceNewHole() {
  91.         // TODO: Avoid placing holes on edges
  92.         PlaceNewObject(GridCube.CubeState.HOLE);
  93.     }

  94.     public void PlaceNewApple() {
  95.         PlaceNewObject(GridCube.CubeState.APPLE);
  96.     }

  97.     private GridCube SnakeHead() {
  98.         return snake.First.Value;
  99.     }

  100.     private void PlaceNewObject(GridCube.CubeState state) {
  101.         bool done = false;
  102.         while (!done) {
  103.             GridCube cube = cubes.ElementAt(Random.Range(0, cubes.Count));
  104.             if (!cube.isEmpty() || (cube.SameSideAs(SnakeHead()) && rotationEnabled)) {
  105.                 continue;
  106.             }

  107.             cube.SetCubeState(state);
  108.             done = true;
  109.         }
  110.     }

  111.     public void SetupGrid(bool enableRotation, int appleCount) {
  112.         if (cubes != null) {
  113.             foreach (GridCube c in cubes) {
  114.                 Destroy(c.gameObject);
  115.             }
  116.         }

  117.         snake = new LinkedList<GridCube>();
  118.         cubes = new List<GridCube>();
  119.         isRotating = false;
  120.         rotationEnabled = enableRotation;

  121.         if (gridSize % 2 == 0) {
  122.             gridSize++;
  123.         }

  124.         gridSize = Mathf.Max(gridSize, 5);

  125.         float finalGridSize = GetGridSizeWorld();
  126.         float halfGridSize = finalGridSize / 2;

  127.         int zDepth = rotationEnabled ? gridSize : 1;

  128.         for (int i = 0; i < gridSize; i++) {
  129.             for (int j = 0; j < gridSize; j++) {
  130.                 for (int k = 0; k < zDepth; k++) {

  131.                     // Dont add cubes at center of 3d grid
  132.                     if ((k != 0 && k != gridSize - 1) && (j != 0 && j != gridSize - 1) && (i != 0 && i != gridSize - 1)) {
  133.                         continue;
  134.                     }

  135.                     GameObject cubeGameObject = Instantiate(gridCubeClone);
  136.                     cubeGameObject.transform.SetParent(transform);

  137.                     Vector3 size = cubeGameObject.transform.localScale;
  138.                     float offset = halfGridSize - size.x / 2;
  139.                     cubeGameObject.transform.Translate(i * size.x - offset, j * size.x - offset, k * size.x - offset);

  140.                     int centerPos = (int)halfGridSize;
  141.                     GridCube cube = cubeGameObject.GetComponent<GridCube>();

  142.                     if (i == centerPos && j == centerPos && k == 0) {
  143.                         // Set up starting cell
  144.                         cube.SetCubeState(GridCube.CubeState.SNAKE);
  145.                         snake.AddFirst(cube);
  146.                     } else {
  147.                         cube.SetCubeState(GridCube.CubeState.EMPTY);
  148.                     }

  149.                     if (i == 0) {
  150.                         cube.AddCubeSide(GridCube.CubeSide.LEFT);
  151.                     } else if (i == gridSize - 1) {
  152.                         cube.AddCubeSide(GridCube.CubeSide.RIGHT);
  153.                     }

  154.                     if (j == 0) {
  155.                         cube.AddCubeSide(GridCube.CubeSide.BOTTOM);
  156.                     } else if (j == gridSize - 1) {
  157.                         cube.AddCubeSide(GridCube.CubeSide.TOP);
  158.                     }

  159.                     if (k == 0) {
  160.                         cube.AddCubeSide(GridCube.CubeSide.FRONT);
  161.                     } else if (k == gridSize - 1) {
  162.                         cube.AddCubeSide(GridCube.CubeSide.BACK);
  163.                     }

  164.                     cubes.Add(cube);
  165.                 }
  166.             }
  167.         }
  168.          
  169.         for (int i = 0; i < appleCount; i++) {
  170.             PlaceNewApple();
  171.         }
  172.     }
  173. }
复制代码


    2.当我们设计好贪食蛇的区域后,我们新建一个游戏的控制器 GameController 对象[附加GameController .cs组件]控制贪食蛇在地图区域中利用按下键盘的上下左右箭头控制蛇移动的方向。
图2_0.png

图2_1.png    

     GameController .cs 代码如下:
  1. using UnityEngine;
  2. using System.Collections;
  3. using UnityEngine.SceneManagement;

  4. public class GameController : MonoBehaviour {
  5.     private const float DEFAULT_INPUT_COOLDOWN = 0.2f;
  6.     private const float COOLDOWN_STEP = 0.001f;
  7.     private const float MIN_INPUT_COOLDOWN = 0.05f;
  8.     private const float NEW_HOLE_PROBABILITY = 0.1f;

  9.     public GameGrid gameGrid;
  10.     public GUIController guiController;

  11.     private GridCube.Direction lastDirection = GridCube.Direction.RIGHT;
  12.     private GridCube.Direction lastMovedDirection = GridCube.Direction.NONE;
  13.     private GameGrid.MoveResult lastResult;
  14.     private float lastInputTime = 0;
  15.     private int score = 0;
  16.     private bool playing = true;
  17.     private bool rotationEnabled;
  18.     private float inputCoolDown = DEFAULT_INPUT_COOLDOWN;

  19.     void Start() {
  20.         Initialize();
  21.     }
  22.     /// <summary>
  23.     /// 初始化游戏
  24.     /// </summary>
  25.     private void Initialize() {
  26.         rotationEnabled = (PlayerPrefs.GetInt("3dMode", 1) == 1);
  27.         int appleCount = PlayerPrefs.GetInt("AppleCount", 20);

  28.         lastResult = GameGrid.MoveResult.NONE;
  29.         inputCoolDown = DEFAULT_INPUT_COOLDOWN;
  30.         guiController.SetTopScore(PlayerPrefs.GetInt("TopScore", 0));

  31.         gameGrid.SetupGrid(rotationEnabled, appleCount);
  32.         SetupCamera();
  33.     }

  34.     void Update() {
  35.         if (!playing) {
  36.             return;
  37.         }

  38.         GridCube.Direction dir = ReadInput();

  39.         if (dir == GridCube.Direction.NONE || AreOpposite(dir, lastMovedDirection)) {
  40.             dir = lastDirection;
  41.         }

  42.         if (lastResult == GameGrid.MoveResult.ROTATING) {
  43.             dir = lastMovedDirection;
  44.         }

  45.         lastDirection = dir;

  46.         lastInputTime += Time.deltaTime;
  47.         if (lastInputTime > inputCoolDown) {
  48.             
  49.             lastInputTime = 0;

  50.             GameGrid.MoveResult result = gameGrid.MoveHead(dir);

  51.             if (result == GameGrid.MoveResult.MOVED || result == GameGrid.MoveResult.ATE) {
  52.                 lastMovedDirection = dir;
  53.             }

  54.             switch (result) {
  55.                 case GameGrid.MoveResult.DIED:
  56.                     playing = false;

  57.                     int topScore = PlayerPrefs.GetInt("TopScore", 0);
  58.                     if (score > topScore) {
  59.                         PlayerPrefs.SetInt("TopScore", score);
  60.                     }

  61.                     guiController.RemoveNotifications();
  62.                     guiController.SetGameOverPanelActive(true);
  63.                     break;
  64.                 case GameGrid.MoveResult.ERROR:
  65.                     Debug.Log("An error occured.");
  66.                     gameObject.SetActive(false);
  67.                     break;
  68.                 case GameGrid.MoveResult.ATE:
  69.                     gameGrid.PlaceNewApple();
  70.                     if (rotationEnabled && Random.value < NEW_HOLE_PROBABILITY) {
  71.                         gameGrid.PlaceNewHole();
  72.                     }

  73.                     //TODO: Win if no more space is available

  74.                     score++;                    
  75.                     guiController.SetScore(score);

  76.                     inputCoolDown -= COOLDOWN_STEP;
  77.                     if (inputCoolDown < MIN_INPUT_COOLDOWN) {
  78.                         inputCoolDown = MIN_INPUT_COOLDOWN;
  79.                     }

  80.                     break;
  81.                 case GameGrid.MoveResult.ROTATING:
  82.                 default:
  83.                     // pass
  84.                     break;
  85.             }

  86.             lastResult = result;
  87.         }
  88.     }

  89.     void SetupCamera() {
  90.         float frustumHeight = gameGrid.GetGridSizeWorld();
  91.         float distance = frustumHeight / Mathf.Tan(Camera.main.fieldOfView * 0.5f * Mathf.Deg2Rad);
  92.         Camera.main.transform.position = new Vector3(0, 0, -distance);
  93.     }

  94.     private bool AreOpposite(GridCube.Direction a, GridCube.Direction b) {
  95.         if ((a == GridCube.Direction.DOWN && b == GridCube.Direction.UP) ||
  96.             (a == GridCube.Direction.UP && b == GridCube.Direction.DOWN)) {
  97.             return true;
  98.         }

  99.         if ((a == GridCube.Direction.RIGHT && b == GridCube.Direction.LEFT) ||
  100.             (a == GridCube.Direction.LEFT && b == GridCube.Direction.RIGHT)) {
  101.             return true;
  102.         }

  103.         return false;
  104.     }
  105.     /// <summary>
  106.     /// 获取当前的键盘键 (箭头)
  107.     /// </summary>
  108.     /// <returns></returns>
  109.     private GridCube.Direction ReadInput() {
  110.         if (Input.GetKey(KeyCode.UpArrow)) {
  111.             return GridCube.Direction.UP;
  112.         } else if (Input.GetKey(KeyCode.DownArrow)) {
  113.             return GridCube.Direction.DOWN;
  114.         } else if (Input.GetKey(KeyCode.RightArrow)) {
  115.             return GridCube.Direction.RIGHT;
  116.         } else if (Input.GetKey(KeyCode.LeftArrow)) {
  117.             return GridCube.Direction.LEFT;
  118.         }

  119.         return GridCube.Direction.NONE;
  120.     }

  121.     /// <summary>
  122.     /// 重新开始游戏
  123.     /// </summary>
  124.     public void RestartGame() {
  125.         guiController.SetGameOverPanelActive(false);
  126.         Initialize();
  127.         playing = true;
  128.         score = 0;
  129.         guiController.SetScore(score);
  130.     }
  131.     /// <summary>
  132.     /// 返回菜单
  133.     /// </summary>
  134.     public void BackToMenu() {
  135.         SceneManager.LoadScene("Menu");
  136.     }
  137. }
复制代码



    3.利用GUI 制作游戏分数显示。
图3.png
    4.GUI 的控制类GUIController.cs ,并且游戏结束界面功能实现。
图4.png

      GUIController.cs 代码如下:
  1. using UnityEngine;
  2. using UnityEngine.UI;

  3. public class GUIController : MonoBehaviour {

  4.     public Canvas gameCanvas;
  5.     public GameObject notificationPrefab;
  6.     public Text score;
  7.     public Text topScore;
  8.     public GameObject gameOverPanel;

  9.     public readonly string[] congratulationMessages = {
  10.         "Nice!",
  11.         "Congratulations!",
  12.         "Great!",
  13.         "Cool!",
  14.         "Super!",
  15.         "Sweet!",
  16.         "Excellent!",
  17.         "Eggcellent!",
  18.         "Dude!",
  19.         "Noice!",
  20.         "Incredible!",
  21.         "Amazing!",
  22.         "OMG!",
  23.         "en.messages.Congratulate!",
  24.         "Good!",
  25.         "Pretty good!",
  26.         "Not bad!",
  27.         "Life has no intrinsic meaning!",
  28.         "NullReferenceException",
  29.         "Terrific!",
  30.         "Alright!",
  31.         "Whaaaat",
  32.         "Yeaaah!"
  33.     };

  34.     private GameObject lastNotification = null;

  35.     public string RandomCongratulationMessage() {
  36.         return congratulationMessages[Random.Range(0, congratulationMessages.Length)];
  37.     }

  38.     public void ShowNotification(string text) {
  39.         RemoveNotifications();

  40.         GameObject notificationObject = Instantiate(notificationPrefab);
  41.         lastNotification = notificationObject;
  42.         notificationObject.transform.SetParent(gameCanvas.transform);

  43.         notificationObject.GetComponent<Text>().text = text;
  44.         notificationObject.GetComponent<RectTransform>().localPosition = new Vector3(0, 100);
  45.         notificationObject.GetComponent<NotificationFade>().SetupAnimation();
  46.     }

  47.     public void RemoveNotifications() {
  48.         if (lastNotification != null) {
  49.             Destroy(lastNotification);
  50.         }
  51.     }

  52.     public void SetScore(int s) {
  53.         score.text = s.ToString();
  54.     }

  55.     public void SetTopScore(int s) {
  56.         topScore.text = s.ToString();
  57.     }

  58.     public void SetGameOverPanelActive(bool active) {
  59.         gameOverPanel.SetActive(active);
  60.     }
  61. }
复制代码



    5.为了通知显示更好,并且新建一个Notification.prefab[附加NotificationFade.cs 组件]。
图5_0.png

图5_1.png

    NotificationFade.cs 代码如下:
  1. using UnityEngine;
  2. using System.Collections;
  3. using UnityEngine.UI;

  4. public class NotificationFade : MonoBehaviour {
  5.     public float duration = 1;
  6.     private float startTime = 0;

  7.     private float maxY = 0;
  8.     private float initialY = 0;
  9.     private RectTransform rectTransform;
  10.     private Text text;

  11.     public void SetupAnimation() {
  12.         rectTransform = GetComponent<RectTransform>();
  13.         text = GetComponent<Text>();

  14.         startTime = Time.time;
  15.         maxY = transform.parent.GetComponent<RectTransform>().rect.height / 2;
  16.         initialY = rectTransform.localPosition.y;
  17.     }

  18.     void Update() {
  19.         float t = (Time.time - startTime) / duration;
  20.         float val = (t * t) * (maxY - initialY);
  21.         float alpha = 1 - (t * t);
  22.         val += initialY;
  23.         rectTransform.localPosition = new Vector3(0, val);

  24.         Color prev = text.color;
  25.         prev.a = alpha;
  26.         text.color = prev;

  27.         if (t >= 1) {
  28.             Destroy(gameObject);
  29.         }
  30.     }
  31. }
复制代码



    6.好吧!一切都做好后,我们直接从Menu.unity启动开始,看效果吧!
图6_0.png

图6_1.png

图6_2.png

图6_3.png
   


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-2-25 04:16

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表