海量编程文章、技术教程与实战案例

网站首页 > 技术文章 正文

Unity游戏开发示例1.地面球游戏

yimeika 2025-05-22 15:19:54 技术文章 4 ℃

示例1.地面球游戏

10.1 游戏设计

地面球游戏中玩家身处舞台中, 4个角的4个球门颜色不同,地面上有4只对应颜色的球,另外有多只黄色的球称为混乱球,它们比4只彩球小一些,但它们的移动速度更快。

游戏规则:

通过白色的控制球碰撞彩球让彩球移动。

当彩球到达正确的角落将导致不活动的状态。4只球全部位于正确的角落时游戏结束。

10.2 舞台

10.2.1 创建场景

  1. 创建新项目 groundball,添加地形 Terrain,位置为(-25,0,-25),选中地形,单击 Terrain Settings,设置 Resolution 中的宽度和高度为 50。
  2. 设置 Main Camera 的位置为 (0,60,-60),旋转为(30,0,0)。
  3. 在场景中添加一个立方体,设置位置为(25,1.5,0),缩放为(1.5,3,51),重命名为 Wall。
  4. 保存场景,并重命名为 MainScene。

10.2.2 纹理化

1.墙壁纹理化

  1. 在 Assets 下创建 Textures 和 Materials 文件夹。
  2. 将纹理图片 WallTexture.png 推拽到 Textures 文件夹中。
  3. 右击 Meterials 文件夹并选择 Create -> Material,重命名为 WallMeterial,在 Inspector 窗口中设置 Shade 为 Diffuse,单击纹理中的 Select,在弹出窗口中选择 WallTexture,修改材质 WallMeterial 的 Tiling 的 X 值为 40。
  4. 将 WallMeterial 拖拽到场景中的 Wall 上。

2.地面纹理化

  1. 将草地图片 GrassTexture.png 拖拽到 Textures 文件夹中。
  2. 右击 Meterials 文件夹并选择 Create -> Material,重命名为 GrassMeterial,在 Inspector 窗口中设置 Shade 为 Diffuse,单击纹理中的 Select,在弹出窗口中选择 GrassTexture,修改材质 GrassMeterial 的 Tiling 的 X 值为 50,Y 值为 50。
  3. 选中 Terrain,将 GrassTexture 拖拽到 Inspector 视图中 Terrain 的 Meterial 上。

10.2.3 物理材质

  1. 在该游戏中,我们希望墙壁具有弹性,所以需要创建一种具有超级弹性的物理材质。
  2. 右击 Materials 文件夹,选择 Create -> Physic Material,将物理材质重命名为 SuperBouncyMaterial,设置属性:
  3. Dynamic Friction :0
  4. Static Friction :0
  5. Bounciness :1
  6. Friction Combine :最大值
  7. Bounce Combine :平均值
  8. 将 SuperBouncyMaterial 拖拽到场景中的 Wall 上。

10.2.4 完成舞台

将墙壁复制三次,设置属性如下:

  1. 位置 (-25,1.5,0)
  2. 位置 (0,1.5,-25),旋转 (0,90,0)
  3. 位置 (0,1.5,25),旋转 (0,90,0)

效果如下图所示:

10.3 游戏对象

10.3.1 彩球

彩球的颜色分别为红、蓝、绿、棕。通过制作一只球复制即可。由于球门也具有颜色,并且将来必须将同颜色的球体移动到同颜色的球门才终止球体的移动,所以需要使用标签来在脚本中进行识别。

1.需要创建标签:

选择 Edit -> Project Settings -> Tags and Layers,展开 Tags,单击加号,添加 Blue、Red、Green、Brown 和 Yellow(混乱球使用)。

2.创建材质如下:

  • BlueMaterial,颜色为 0000FF(蓝色)
  • RedMaterial,颜色为 FF0000(红色)
  • GreenMaterial,颜色为 00FF00(绿色)
  • BrownMaterial,颜色为 A52A2A(棕色)
  • YellowMaterial,颜色为 FFFF00(黄色),混乱球使用

3.在场景中添加一个球体,命名为 Blue,位置为(0,1,5),缩放为(2,2,2),将 BlueMaterial 拖拽到该球体上,然后将超级弹性材质推拽到该球体上。

4.给球体添加刚体,设置角阻力为 0,取消勾选 Use Gravity,Constraints 中冻结 y 轴移动。

5.设置球体的标签为 Blue。

6.复制蓝色球体三次,分别重命名为 Red、Green 和 Brown,设置位置分别为 (5,1,0)、(-5,1,0)、(5,1,-5)。

7.将颜色材质拖拽到对应的球体上,并设置对应的标签。

10.3.2 混乱球

混乱球是在舞台上随机移动的黄色球体,目的是在游戏中随处移动,以达到混乱彩球的目的。所以,需要脚本加以控制。

1.在场景中添加球体 Yellow,位置为(15,0.5,20),缩放为(1,1,1),设置标签为 Yellow。

2.将颜色材质 YellowMaterial 和超级弹性材质拖拽到球体上。

3.添加刚体,设置角阻力为 0,取消勾选 Use Gravity,Constraints 中冻结 y 轴移动,将 Collision Detection 属性改为 Continuous。

4.在 Assets 下创建 Scripts 文件夹,并创建 BallController 脚本,编写球体控制脚本并挂载到 Yellow 上,脚本如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallController : MonoBehaviour
{
    private float max = 100;

    private Rigidbody _rigidbody;
    // Start is called before the first frame update
    void Start()
    {
        _rigidbody = GetComponent<Rigidbody>();
        _rigidbody.velocity = new Vector3(Random.Range(0, max), 0, Random.Range(0, max));
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

上面的代码中,使用 Random.Range() 方法给黄色球体提供沿 x 轴和 z 轴的 0~50 之间的随机初始速度。

运行游戏,可以看到混乱球在地面上移动。

5.在 Assets 下创建 Prefabs 文件夹,将球体 Yellow 拖拽到 Project 视图中的 Prefabs 文件夹中,使其成为预设。

6.在 Scripts 文件夹中创建游戏初始化脚本 GameController,编写代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameController : MonoBehaviour
{
    public GameObject ball;

    public int ballCount;
    // Start is called before the first frame update
    void Start()
    {
        for (int i = 0; i < ballCount; i++) Instantiate(ball);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

7.在场景中添加空物体,并将脚本 GameInit 挂载到该空物体上,并将 Prefabs 下的 Yellow 拖拽到空物体的脚本上,设置 ballCount 的值为 200。

运行游戏,查看效果如下图所示:

10.4 控制对象

在该游戏中,球门用于控制彩球是否可运动,同时,用户可以通过拖拽地面上的控制球碰撞彩球来运动。

10.4.1 球门

球门的设计思想是在 4 个角落放置 4 个球门,其标签与球的颜色相同,如果同颜色的彩球进入球门(通过标签来识别),则将球体设置为不活动状态。

1.在场景中添加一个立方体,重命名为 BlueDoor,设置标签为 Blue,位置为(23,1.5,23),缩放为(2.5,3,2.5)。

2.在立方体上添加点光源组件,设置强度 Intensity 为 10。

3.将 BlueMaterial 拖拽到立方体上。

4.设置立方体的 Shared 为 Legacy
Shared/Transparent/Diffuse,设置其 Main Color 为 0000FF,RGBA 中的 A 为 150(透明度)。

5.设置立方体的碰撞器,选中 Is Trigger。

6.在 Scripts 中创建脚本 DoorController ,代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DoorController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void OnTriggerEnter(Collider other)
    {
        if (CompareTag(other.tag))
        {
            other.GetComponent<Rigidbody>().isKinematic = true;
        }
    }
}

上面的脚本在发生碰撞时检查标签是否与门的标签相同,如果相同则设置彩球处于不活动的状态。

7.复制球门三次,设置位置如下:

  • (-23,1.5,23)
  • (-23,1.5,-23)
  • (23,1.5,-23)

分别设置标签、材质、灯光等(参照蓝色球门)

10.4.1 控制球

控制球通过用户拖拽来控制,首先复制一个彩球,位置为(10,1,10),然后设置标签、材质等(参考彩球的设置)。

在 Scripts 文件夹下创建 WhiteBallController 并挂载到控制球,编写代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design.Serialization;
using UnityEngine;

public class WhiteBallController : MonoBehaviour
{
    private bool isSelected = false;
    private Vector3 startPos;
    
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        // 按下鼠标时检测是否在彩球表面,并记录是否选中及单击的位置
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity) && hit.collider.gameObject.name == "White")
            {
                isSelected = true;
                startPos = hit.point;
            }
        }
        // 选中的情况下,松开鼠标时检测单击的位置
        if (isSelected && Input.GetMouseButtonUp(0))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                isSelected = false;
                Vector3 endPos = hit.point;
                this.GetComponent<Rigidbody>().velocity =
                    new Vector3(endPos.x - startPos.x, 0, endPos.z - startPos.z) * 5.0f;
            }
        }
    }
}

上面的代码当按下鼠标时,通过射线来检测是否碰撞到白色球自己,如果碰撞到自己则标记并记录鼠标的位置,当松开鼠标时,如果已经被标记为碰撞到,则给刚体施加力。

10.5 结束场景

至此,游戏功能基本开发完成,只需要添加检测到 4 个彩球都进入对应颜色的球门代表游戏结束的显示。游戏结束的显示可以通过增加结束场景来实现。在结束场景中还可以添加一个重新开始的按钮让画面回到舞台。

1.新建一个场景 EndScene,并在其中添加 UI -> Text 和 Button。

2.设置 Text 的文字为 Game Over,字号为 72,宽度 600,高度 200,居中显示。

3.设置 Button 的文字为 Restart。

4.在 Scripts 中创建脚本 RestartButton,并挂载到 Button 上,编写脚本如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class RestartButton : MonoBehaviour
{
    private Button restartButton;
    // Start is called before the first frame update
    void Start()
    {
        restartButton = GetComponent<Button>();
        restartButton.onClick.AddListener(RestartButtonClicked);
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void RestartButtonClicked()
    {
        SceneManager.LoadScene("Scenes/MainScene");
    }
}

5.修改 GameController 脚本,实现检测彩球与 4 个球门的关系,当彩球进入对应的球门时,我们设置彩球为不活动状态,所以,只需要检测 4 个彩球的活动状态即可。脚本如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameController : MonoBehaviour
{
    public GameObject ball;

    private GameObject blueBall, redBall, greenBall, brownBall;

    public int ballCount;
    // Start is called before the first frame update
    void Start()
    {
        for (int i = 0; i < ballCount; i++) Instantiate(ball);
        blueBall = GameObject.Find("Blue");
        redBall = GameObject.Find("Red");
        greenBall = GameObject.Find("Green");
        brownBall = GameObject.Find("Brown");
    }

    // Update is called once per frame
    void Update()
    {
        if (blueBall.GetComponent<Rigidbody>().isKinematic
            && redBall.GetComponent<Rigidbody>().isKinematic
            && greenBall.GetComponent<Rigidbody>().isKinematic
            && brownBall.GetComponent<Rigidbody>().isKinematic)
        {
            print("Game over");
            SceneManager.LoadScene("Scenes/EndScene");
        }
    }
}



Tags:

最近发表
标签列表