开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:1969

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 1546|回复: 13

[海莉zhe] 【知识摘录】 Unity3D 贪食蛇小游戏Demo[3D版]-动态创建贪食蛇活动的地图 & 控制蛇移动功能(...

[复制链接]  [移动端链接]
排名
952
昨日变化
8

57

主题

191

帖子

2246

积分

Rank: 9Rank: 9Rank: 9

UID
76890
好友
54
蛮牛币
3234
威望
0
注册时间
2015-3-4
在线时间
776 小时
最后登录
2017-11-22

专栏作家

发表于 2016-12-14 23:02:56 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
本帖最后由 海莉zhe 于 2016-12-18 21:20 编辑

       今天小编继续上一篇文章的分享,实现如何动态创建一个蛇的活动区域,以及控制蛇移动、游戏结束界面等。。

   
     1.首先我们考虑贪食蛇的地图,是通过一个大小一致的小方块循环生成,所以我们需要新建一个GridOrigin 对象 ,并且附加GameGrid.cs组件。

            图1.png

          GameGrid.cs 代码如下:

                 
[C#] 纯文本查看 复制代码
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组件]控制贪食蛇在地图区域中利用按下键盘的上下左右箭头键控制蛇移动的方向。
   
       图2_0.png

      图2_1.png

      GameController .cs 代码如下:

        
[C#] 纯文本查看 复制代码
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 制作游戏分数显示。

      图3.png

     4.GUI 的控制类GUIController.cs ,并且游戏结束界面功能实现。

      图4.png

    GUIController.cs 代码如下:

     
[C#] 纯文本查看 复制代码
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 组件]。

    图5_0.png

    图5_1.png

       NotificationFade.cs 代码如下:
     
[C#] 纯文本查看 复制代码
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启动开始,看效果吧!
      
       图6_0.png

       图6_1.png

       图6_2.png

       图6_3.png
   
    学习交流群:575561285

评分

参与人数 1鲜花 +2 收起 理由
AnimeKing + 2 很给力!

查看全部评分


回复

使用道具 举报

7日久生情
2017/5000
排名
612
昨日变化
4

1

主题

531

帖子

2017

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
111260
好友
0
蛮牛币
7310
威望
0
注册时间
2015-6-30
在线时间
343 小时
最后登录
2017-10-17
发表于 2016-12-15 09:29:12 | 显示全部楼层
谢谢分享!

回复

使用道具 举报

6蛮牛粉丝
1244/1500
排名
5295
昨日变化
7

2

主题

775

帖子

1244

积分

Rank: 6Rank: 6Rank: 6

UID
92518
好友
1
蛮牛币
2464
威望
0
注册时间
2015-4-15
在线时间
277 小时
最后登录
2017-4-6
发表于 2016-12-15 11:17:18 | 显示全部楼层

谢谢分享!

回复

使用道具 举报

5熟悉之中
696/1000
排名
2710
昨日变化
12

0

主题

54

帖子

696

积分

Rank: 5Rank: 5

UID
23794
好友
0
蛮牛币
1886
威望
0
注册时间
2014-5-4
在线时间
216 小时
最后登录
2017-11-22
发表于 2016-12-15 22:21:47 | 显示全部楼层
有点意思 谢谢分享

回复 支持 反对

使用道具 举报

排名
1616
昨日变化
1

12

主题

218

帖子

1590

积分

Rank: 9Rank: 9Rank: 9

UID
18798
好友
6
蛮牛币
621
威望
0
注册时间
2014-3-24
在线时间
644 小时
最后登录
2017-11-16

专栏作家VIP

QQ
发表于 2016-12-16 15:40:51 | 显示全部楼层
不错,休礼拜练习一下!感谢

回复 支持 反对

使用道具 举报

排名
12711
昨日变化
9

2

主题

13

帖子

93

积分

Rank: 2Rank: 2

UID
191006
好友
0
蛮牛币
66
威望
0
注册时间
2016-12-13
在线时间
29 小时
最后登录
2017-4-29
发表于 2016-12-16 20:39:08 | 显示全部楼层
那个SceneManager类主要是作何功能的

回复 支持 反对

使用道具 举报

排名
952
昨日变化
8

57

主题

191

帖子

2246

积分

Rank: 9Rank: 9Rank: 9

UID
76890
好友
54
蛮牛币
3234
威望
0
注册时间
2015-3-4
在线时间
776 小时
最后登录
2017-11-22

专栏作家

 楼主| 发表于 2016-12-18 11:27:39 | 显示全部楼层
huga 发表于 2016-12-16 20:39
那个SceneManager类主要是作何功能的

SceneManager.LoadScene("Menu"); 加载场景 里面有两个场景一个Menu.unity/Main.unity 一般是在Main.unity中游戏失败, 返回菜单Menu.unity场景。

回复 支持 反对

使用道具 举报

7日久生情
1525/5000
排名
885
昨日变化
5

2

主题

87

帖子

1525

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
66098
好友
2
蛮牛币
5078
威望
0
注册时间
2015-1-5
在线时间
484 小时
最后登录
2017-11-21
发表于 2016-12-19 14:25:13 | 显示全部楼层
6666~真赞!!

回复

使用道具 举报

排名
12711
昨日变化
9

2

主题

13

帖子

93

积分

Rank: 2Rank: 2

UID
191006
好友
0
蛮牛币
66
威望
0
注册时间
2016-12-13
在线时间
29 小时
最后登录
2017-4-29
发表于 2016-12-19 16:16:48 | 显示全部楼层
海莉zhe 发表于 2016-12-18 11:27
SceneManager.LoadScene("Menu"); 加载场景 里面有两个场景一个Menu.unity/Main.unity 一般是在Main.unit ...

那这个SceneManager类是场景跳转对不

回复 支持 反对

使用道具 举报

7日久生情
3402/5000
排名
132
昨日变化

0

主题

187

帖子

3402

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
5681
好友
1
蛮牛币
7764
威望
0
注册时间
2013-10-15
在线时间
1275 小时
最后登录
2017-11-22

社区QQ达人

QQ
发表于 2016-12-19 17:48:36 | 显示全部楼层
按照楼主的文章做了一遍,为什么按键没反应。

dir:NONE
UnityEngine.MonoBehaviour:print(Object)
GameController:Update() (at Assets/Scripts/GameController.cs:50)

回复 支持 反对

使用道具 举报

5熟悉之中
928/1000
排名
3090
昨日变化
15

3

主题

257

帖子

928

积分

Rank: 5Rank: 5

UID
159631
好友
1
蛮牛币
1515
威望
0
注册时间
2016-7-30
在线时间
290 小时
最后登录
2017-11-22
发表于 2017-1-23 11:48:32 | 显示全部楼层
学习学习,谢谢分享

回复 支持 反对

使用道具 举报

3偶尔光临
174/300
排名
12020
昨日变化
6

1

主题

61

帖子

174

积分

Rank: 3Rank: 3Rank: 3

UID
197202
好友
0
蛮牛币
639
威望
0
注册时间
2016-12-29
在线时间
58 小时
最后登录
2017-11-8
发表于 2017-1-30 16:26:40 | 显示全部楼层
谢谢分享

回复

使用道具 举报

5熟悉之中
889/1000
排名
2859
昨日变化
3

3

主题

167

帖子

889

积分

Rank: 5Rank: 5

UID
51046
好友
3
蛮牛币
1596
威望
0
注册时间
2014-10-25
在线时间
313 小时
最后登录
2017-11-10
发表于 2017-3-15 11:22:44 | 显示全部楼层
66666666666666666666666666666

回复 支持 反对

使用道具 举报

排名
53168
昨日变化
21

0

主题

8

帖子

17

积分

Rank: 1

UID
185454
好友
0
蛮牛币
22
威望
0
注册时间
2016-11-21
在线时间
7 小时
最后登录
2017-9-15
发表于 2017-9-13 22:32:20 | 显示全部楼层
多谢分享啊

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

关闭

站长推荐 上一条 /1 下一条

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