[Unity3D] 3D贪吃蛇

目录

前言

一、整体设计思路

二、蛇体部分

2.1  蛇头(模仿传统贪吃蛇)

2.2  蛇身

2.3  贪吃蛇(改进版)

三、食物部分

3.1  旋转

3.2  触发器

3.3  生成食物

四、墙体部分

4.1  提供环境

五、胜利和失败的判定

5.1  屏幕显示分数

5.2  游戏胜利

5.3  游戏失败

六、界面设计

6.1  开始界面

6.2  结果界面(胜利/失败)

6.3  分数显示

6.4  退出界面


前言

最近在学习unity,计划做一个3D的贪吃蛇的小游戏,利用这个小游戏锻炼自己使用软件的熟练度。

一、整体设计思路

简单起见,并不进行建模美化操作,以直径为1的球体代替蛇头,直径为0.5的球体代替蛇身,用旋转的立方体代替食物,需要实现的功能有:

1. 蛇体部分

  • 蛇头:移动
  • 蛇身:增长、跟随蛇身运动
  • 视角:跟随

2. 食物部分

  • 旋转
  • 触发器(接触后销毁,并生成一个新的食物)
  • 食物出现在空间内的位置随机

3. 墙体部分

  • 提供食物生成的封闭空间

4. 胜利和失败的判定

  • 屏幕显示分数
  • 分数到达10即为胜利,可以选择继续游戏或结束本局游戏
  • 碰触墙壁或自身身体即为游戏失败,可以选择重新开始或退出游戏

5. 界面设计

  • 开始界面
  • 结果界面(胜利/失败)
  • 分数显示
  • 退出界面

二、蛇体部分

2.1  蛇头(模仿传统贪吃蛇)

蛇头的移动的实现思路是:获取当前相机的前向方向向量,通过按键检测控制移动方向,用一个Vector3 movement向量存储移动的增量,不断增加即可。

相机可以通过鼠标右键进行控制,具体控制代码见另一篇文章:

[Unity3D] 视角旋转学习笔记icon-default.png?t=N3I4https://blog.csdn.net/The___sky_/article/details/130249220?spm=1001.2014.3001.5502

    //蛇头移动+相机跟随
    private void Move()
    {
        // 跟随相机方向一起转动
        cameraRotation = Camera.main.transform.rotation;

        BodyNode.rotation = cameraRotation;
        SnakeRotation = cameraRotation;
        Snake.transform.rotation = SnakeRotation;

        // 获取当前相机的前向方向向量
        Vector3 cameraForward = Camera.main.transform.forward;
        cameraForward = cameraForward.normalized;
        Vector3 cameraRight = Camera.main.transform.right;
        cameraRight = cameraRight.normalized;

        // 计算移动方向
        Vector3 moveDirection = cameraForward * vertical + cameraRight * horizontal;
        Vector3 movement = moveDirection * moveSpeed * deltaTime;    
                
        if (Input.GetKeyDown(KeyCode.W))
        {
            movement = Vector3.forward;
        }
        else if (Input.GetKeyDown(KeyCode.A))
        {
            movement = Vector3.left;
        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
            movement = Vector3.back;
        }
        else if (Input.GetKeyDown(KeyCode.D))
        {
            movement = Vector3.right;
        }
        else if (Input.GetKeyDown(KeyCode.U))
        {
            movement = Vector3.up;
        }
        else if (Input.GetKeyDown(KeyCode.P))
        {
            movement = Vector3.down;
        }
        
        Snake.transform.position += movement;

        // 更新蛇身位置和旋转信息
        for (int i = 0; i < bodyList.Count; i++)
        {
            bodyPositions[i] = bodyList[i].transform.position;
            bodyRotations[i] = bodyList[i].transform.rotation;
        }
    }

2.2  蛇身

蛇身的增长,跟随蛇身移动

    private void Start()
    {
        // 尝试使用动画后的注释
        //bodyList = new List<Rigidbody>();
        //bodyList.Add(BodyNode);
        //bodyPositions = new Vector3[bodyList.Count];
        //bodyRotations = new Quaternion[bodyList.Count];
        CreateFood();
    }


    void LateUpdate()
    {
        bodyList[0].position += movement;

        // 更新蛇身位置和旋转信息
        for (int i = 0; i < bodyList.Count; i++)
        {
            bodyPositions[i] = bodyList[i].transform.position;
            bodyRotations[i] = bodyList[i].transform.rotation;
        }
    }

    private void CreateBody()
    {
        // 尝试使用动画后的注释
        Rigidbody newBody = Instantiate(BodyPrefabs, bodyList[bodyList.Count - 1].transform.position, Quaternion.identity);
        bodyList.Add(newBody);
        bodyPositions = new Vector3[bodyList.Count];
        bodyRotations = new Quaternion[bodyList.Count];      
    }

2.3  贪吃蛇(改进版)

在改进版中,对食物和蛇体导入了外部的3D模型和动画,对场景进行了一定程度的美化,在原有的贪吃蛇的规则上进行了如下改变:

  • 吃到食物后不会增长身体,而是体型增大。
  • 由于导入的外部模型和动画,故而无法实现自己碰到自己身体的条件,所以失败条件改为触碰到墙壁即判定为游戏失败。

在改进版的贪吃蛇中,对蛇体的控制是另外的代码,如果大家感兴趣可以私信我,这里就不做展示了。

三、食物部分

3.1  旋转

新建一个脚本,作用对象是食物,将Update中的代码进行修改。

    void Update()
    {
        //Vector3.up = (0,1,0)
        //Space.World:相对于世界坐标系
        //即绕y轴旋转
        transform.Rotate(Vector3.up, Space.World);
    }

3.2  触发器

按照2D贪吃蛇的逻辑,食物在被蛇头接触后应对其进行销毁,并生成一个新的食物。

注意,如果想要某个游戏物体能够触发触发器,该游戏物体需要带有刚体(Rigidbody)组件。 

首先将其作为触发器,而不是碰撞器,应勾选Is Trigger.

对其新建一个标签,命名为Food,方便后续检测碰撞对象

 在OnTriggerEnter中进行碰撞对象的销毁和新食物的创建(CreateFood)

    private void OnTriggerEnter(Collider other)
    {
        //检测触发器的对象是否是食物
        if (other.tag == ("Food"))
        {
            //是食物则销毁当前碰撞的游戏物体
            Destroy(other.gameObject);
            CreateFood();
        }

    }

3.3  生成食物

食物出现在空间内的随机位置分为三步:

  1. 将预先设置好的食物预制体实例化
  2. 生成x,y,z的随机数
  3. 将生成的随机数作为新生成的食物的位置参数
    void CreateFood()
    {
        //将预制体实例化
        GameObject Foods = Instantiate(FoodPrefabs);

        //随机生成位置
        int x = Random.Range(-9, 9);
        int y = Random.Range(1, 19);
        int z = Random.Range(-9, 9);

        Foods.transform.position = new Vector3(x, y, z);
    }

四、墙体部分

4.1  提供环境

提供食物生成的封闭空间

设置一个预制体,对其进行整体操作。

当前游戏设计中游戏空间为矩形大小,规格为20*20*20,坐标为:x:[-10,10],y:[0,20],z:[-10,10],因此在生成食物时也需要注意不要让食物产生在无法到达的边界。

五、胜利和失败的判定

5.1  屏幕显示分数

新建Text控件,用来显示当前分数。

    // 分数
    private int Score;
    public Text ScoreText;

    // 分数变化
    void ScoreChange()
    {
        Score++;
        ScoreText.text = "Score:" + Score;
    }

5.2  游戏胜利

分数到达10即为胜利。

新建Text控件,用来显示当前分数,一个用来显示结果,显示结果的Text先不给予显示。

    public GameObject Result;
    public Text ResultText;

    // 胜利界面
    void WinInterface()
    {
        Result.SetActive(true);
        if (Score == 10)
        {
            ResultText.text = "You win!!!";
        }
    }

5.3  游戏失败

碰触墙壁或自身身体即为游戏失败,键盘不再可以继续操控小球运动。

    private void Update()
    {
        switch(InterfaceFlag)
        {
            // 胜利
            case 1:
                WinnerInterface();
                break;
            
            // 失败
            case 2:
                GameOverInterface();
                Direction = 0;
                break;
           
             // 默认
            default:
                break;
        }

        if(InterfaceFlag == 0)
        {
            Move();
        }
    }

    // 失败界面
    void GameOverInterface()
    {
        Result.SetActive(true);
        ResultText.text = "Game Over!";
    }

可以选择重新开始或退出游戏

六、界面设计

6.1  开始界面

开始界面和界面的跳转建议读下面这篇文章,写的非常详细,一步一步来就好了,我做的非常简略,就不在这里献丑啦!

unity——通过点击按钮进行场景切换

6.2  结果界面(胜利/失败)

结果界面通过一个公用的Text控件实现,根据不同的游戏结果呈现出不同的文字。

    // 胜利界面
    void WinnerInterface()
    {
        // 激活控件
        Result.SetActive(true);
        ResultText.text = "You win!!!";

        // 停止所有
        Time.timeScale = 0;
        
    }

    // 失败界面
    void GameOverInterface()
    {
        Result.SetActive(true);
        ResultText.text = "Game Over!";        
        
        // 停止所有
        Time.timeScale = 0;
    }

6.3  分数显示

将分数放在右上角,用Text控件实现

6.4  退出界面

将EscInterface放在Update()中,通过GetKeyDown检测Esc键是否按下。

    public GameObject Menu;
    [SerializeField] private bool MenuKeys = true;

    // Esc界面
    void EscInterface()
    {
        if (MenuKeys)
        {
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                Menu.SetActive(true);
                MenuKeys = false;
                Time.timeScale = 0;
            }
        }
        else if (Input.GetKeyDown(KeyCode.Escape))
        {
            Menu.SetActive(false);
            MenuKeys = true;
            Time.timeScale = 1;
        }
    }

关闭程序可以用下行代码

    public void ExitGame()
    {
        Application.Quit();
    }

猜你喜欢

转载自blog.csdn.net/The___sky_/article/details/130031678