教练 发表于 2016-12-19 11:37:31

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

   今天继续上一篇文章的分享,实现如何动态创建一个蛇的活动区域,以及控制蛇移动、游戏结束界面等。。。
   有兴趣的同学欢迎加我的Unity 学习交流群:575561285
   1.首先我们考虑贪食蛇的地图,其实可以通过一个大小一致的小方块循环生成,所以我们需要新建一个GridOrigin 对象 ,并且附加GameGrid.cs组件。


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



public class GameGrid : MonoBehaviour {

    ///
    /// 移动方式
    ///
    public enum MoveResult
    {
      MOVED, ATE, DIED, ROTATING, ERROR, NONE
    }

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

    private LinkedList snake;
    private List 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();
      cubes = new List();
      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();

                  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();
    }
    ///
    /// 初始化游戏
    ///
    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;
    }
    ///
    /// 获取当前的键盘键 (箭头)
    ///
    ///
    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;
    }

    ///
    /// 重新开始游戏
    ///
    public void RestartGame() {
      guiController.SetGameOverPanelActive(false);
      Initialize();
      playing = true;
      score = 0;
      guiController.SetScore(score);
    }
    ///
    /// 返回菜单
    ///
    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;
    }

    public void ShowNotification(string text) {
      RemoveNotifications();

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

      notificationObject.GetComponent().text = text;
      notificationObject.GetComponent().localPosition = new Vector3(0, 100);
      notificationObject.GetComponent().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();
      text = GetComponent();

      startTime = Time.time;
      maxY = transform.parent.GetComponent().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启动开始,看效果吧!




   


魔法禁书目录 发表于 2016-12-19 13:09:54

虽然不懂代码 不过这么一点代码就可以写成这样 确实不错

CWwwer 发表于 2016-12-20 08:37:42

66666666

376569522 发表于 2016-12-21 20:06:16

立刻提起了精神

376569522 发表于 2016-12-21 20:14:18

资源甚好,发帖艰辛,且阅且珍惜!

cowry 发表于 2017-2-9 15:58:35

文能提笔控萝莉,可以学习学习

神佛之上 发表于 2022-11-22 21:24:51

给力!元素有你更精彩
页: [1]
查看完整版本: Unity3D 贪食蛇小游戏Demo[3D版](二)