请选择 进入手机版 | 继续访问电脑版

查看: 963|回复: 57

[技术] 使用Unity的2D功能开发弹球游戏

  [复制链接]

1193

主题

1894

帖子

2万

贡献

管理员

Rank: 9Rank: 9Rank: 9

积分
24860
QQ
发表于 2018-11-6 03:37:10 | 显示全部楼层 |阅读模式
本文将介绍使用Unity 2017中的2D功能开发经典的弹球游戏,游戏效果如下图所示。

01.gif


开发准备
本文适合具有一定的Unity使用经验,了解Unity的UI,并且具有C#编程的基本知识的用户。

本项目将使用Unity 2017.3进行制作,在Unity启动Starter项目,切换到场景视图,请确保使用2D模式。

下载并解压弹球项目素材文件:
游客,如果您要查看本帖隐藏内容请回复


探索项目
在Starter项目中,双击Assets\Scenes目录下的Starter Scene。我们在层级窗口可以看到游戏对象分为三种类型:
1、静态碰撞体(Static Colliders):它们是带有碰撞体的对象,用来组成弹球桌的基本结构。

02.png


2、静态面板(Static Panel):该分组的内容是隐藏的,若要显示该分组,请在层级窗口选中Static Panel 。然后点击Add  Component,选择Rendering分类中的Sorting Group。在新添加的Sorting Group组件中,找到Sorting Layer 下拉列表,选择Panel。

排序分组(Sorting Group)是一个组件分组,可以用来创建综合排序图层(Sorting Layers),相同预制件的实例可以作为不同对象处于不同的排序图层。

本项目中的排序图层按照如下顺序排列:Panel(最低一层)、Efx、Logo、Obstacles、Top、Cover(最高一层)。

03.gif


3、动画(Animations):在层级窗口选择Animations ,点击Add Component。然后选择Rendering分类的Sorting Group,在Sorting Layer下拉列表中选择Efx,然后运行游戏。

04.gif


让弹球滚动起来
现在,弹球游戏的大部分内容已经准备好了,我们开始添加动作。

首先,我们创建空白对象来保存项目。右键单击层级窗口的Starter Scene,依次选择GameObject -> Create Empty,然后将新对象命名为Interactive Parts。

在检视窗口中,点击Settings 图标,然后选择Reset来重置它的Transform数值。

接下来,创建七个空白子对象并重置这些子对象的Transform值。这些子对象分别是:ScrollingTrees,BillBoard,Plunger,Flippers,Float Piece Area,Road Tunnel Zone,Bumpy Parts。

05.png


滚动的树木
我们将通过使用精灵遮罩(Sprite Mask )仅展示动画的一部分,如下图所示。

06.gif


步骤如下:
1、打开项目窗口的Assets\Sprites目录,将Trees_circle拖动到层级窗口的ScrollingTrees上,命名精灵实例为TreeCircle。
2、设置检视窗口中Transform的Position为(X:-0.7 Y:2.2 Z:0) ,然后设置Rotation为(X:0 Y:0 Z:17)。
3、右键点击层级窗口中的ScrollingTrees,依次选择2D Objects ->Sprite Mask,命名为TreeMask。
4、设置Transform的Position为(X:-1.91 Y:2.58 Z:0) ,设置Scale为(X:1.48 Y:1.58 Z:1)。
5、在检视窗口中,找到Sprite Mask组件的Sprite,点击圆圈图标,打开精灵资源列表,然后选择Trees_mask。
6、选中层级窗口中的TreeCircle,在检视窗口找到Sprite Renderer,并在Sorting Layer下拉列表中选择Efx。在Mask Interaction下拉列表中选择Visible Inside Mask。

07.gif


编写动画脚本
准备好动画后,我们将添加脚本来控制动画和音效。

打开Assets\Scripts目录,双击其中的AnimateController脚本,在代码编辑器中打开它。

在代码中的//#1下,添加以下变量:
public GameObject treeObject;
private float turnspeed = 30f;


在//#2下,添加以下FixedUpdate函数:
void FixedUpdate()
{
    // 检查当前实例是否带有treeObject
    if (treeObject != null)
    {
        treeObject.transform.Rotate (new Vector3 (0, 0, turnspeed * Time.deltaTime));
    }
}

保存脚本后,返回Unity编辑器。

我们在层级窗口选中ScrollingTrees,点击Add Component,然后选择Scripts分类下的Animate Controller。

在检视窗口中,找到Animate Controller的Tree Object字段,点击圆圈图标打开游戏对象列表,选择TreeCircle ,运行游戏预览新动画。

08.gif


动画的脚本结构如下:
AnimateController.cs:保存所有特定动画需要的精灵。
ReactionController.cs:监听TriggerEvent,控制精灵和音效的播放。

这也是在拥有不同属性的多个游戏对象上重用代码最简单的方法。

添加计分板
玩弹球游戏的一项乐趣在于以最快速度打败自己的最高得分,所以我们要添加带有闪亮灯光的计分板。

打开项目窗口的Assets\Sprites\ani_billboard目录,将aniBillbord001 拖动到层级窗口的Billboard对象上,命名子实例为AniBillboard。

在Sorting Layer下拉列表中,选择Top。单击Add Component ,然后选择Miscellaneous分类下的Animator。

在检视窗口中,找到Animator的Controller字段,点击旁边的圆圈图标,打开Animator Controller列表。选择ani_Billboard。

点击菜单栏上Window的Animation窗口,运行游戏预览计分板效果。

09.gif


九宫格精灵(9-Slice Sprite)是一个实用的2D功能。在调整大小时,该功能会保留图形元素的边角和边框。

打开Assets\Sprites\ani_billboard目录,选择其中一个精灵。在检视窗口中,点击Sprite Editor图标,编辑精灵的可拉伸部分。本项目的动画中所使用的精灵已经具备九宫格精灵,我们可以随意调整大小。

10.gif


现在我们需要将计分板放到弹球机上。

在层级窗口中选中AniBillboard,将Transform的Position设为(X:1.5 Y:4.3 Z:0),将Scale设为(X:-3.5 Y:2 Z:1)。

在检视窗口中,将Sprite Renderer下的Draw Mode设为Tiled,将Size设为Width:1,Height:1。在Tile Mode下拉列表中,选择Adaptive。

打开Assets\PreFab目录,将ScoreText预制件拖到层级窗口的Billboard对象上,然后将其Scale改为(X:0.41 Y:0.4 Z:1)。

运行游戏,预览加入动态计分板的弹球游戏。如下图所示,我们将看到炫酷的方形计分板。

11.gif


添加挡板
画面效果弄好了,现在来添加挡板到游戏区域。

在层级窗口中,将BumperRamps和CurveRamp从Static Colliders移动到Interactive Parts对象中。

12.png


添加活塞
玩弹球游戏时,首先需要活塞来将弹球从管道中发射出来。在现实中,玩家要压缩连着弹簧圈的活塞。松手后,弹簧圈会将弹球推进游戏区域。

为了实现这些机制,在层级窗口选中Plunger。在检视窗口中,添加Rendering分类下的Sprite Renderer组件。

选择Sprite Renderer组件,点击Sprite 字段旁的圆圈图标,指定Cam资源。设置Sorting Layer为Top,将Transform的Position设为(X:2.66 Y:-4.6 Z:0)。

13.png


查看Plunger Physics,此时共使用2个刚体:
Rigidbody 1:用于锚定关节的活动部分,发出作用力。
Rigidbody 2:即关节的活动部分,接受作用力。

所有2D Joint组件都必须和Rigidbody 2D组件一起使用。

14.png


打开Assets\PreFab目录,将PlungerAnchor拖到层级窗口的Plunger对象上,创建子实例。在检视窗口中,添加Physics 2D分类的Rigidbody 2D组件,然后将其Body Type设为Kinematic来修复其位置。

15.png


弹簧关节
活塞已经制作好,现在加入弹簧关节。该关节功能和现实中的弹簧一样,它能启动活塞。

在层级窗口点击plungerAnchor旁边的箭头,显示其子对象。选中Plunger-springjoint,在其检视窗口添加Spring Joint 2D组件。

在Rigidbody 2D组件中,设置Body Type为Dynamic,将Mass设为2。展开Constraints部分,勾选Freeze Position的X和Freeze Rotation的Z。

16.png


在Spring Joint 2D组件中,勾选Enable Collision ,禁用Auto Configure Distance,将Distance设为1,将Frequency设为10。

设置Connected Anchor为(X:0 Y:-1) ,指定PlungerAnchor 为组件的Connected Rigid Body。

17.png


在层级窗口中,将PlungerEffects 预制件拖入Plunger对象上,这样会创建一个子对象实例。

活塞脚本
最后,添加物理效果到活塞和弹簧关节。

在检视窗口选中Plunger-springjoint ,点击Add Component,并选择Scripts分类的Launcher。双击Script字段中的脚本名,在代码编辑器打开该脚本。

在代码中的//#1下声明以下变量:
private SpringJoint2D springJoint;
private float force = 0f;          // 当前生成的作用力
public float maxforce = 90f;


在Start函数的//#2 下,添加以下代码:
springJoint = GetComponent<SpringJoint2D>();
springJoint.distance = 1f;


在Update函数中的//#3 下,添加以下代码:
// 计算当前施加的作用力
force = powerIndex * maxforce;


在//#4下,添加以下FixedUpdate 函数:
void FixedUpdate()
{
    //当作用力不为0时
    if (force != 0)
    {
        // 释放springJoint并加强作用力
        springJoint.distance = 1f;
        GetComponent<Rigidbody2D>().AddForce(Vector3.up * force);
        force = 0;
    }

    // 当活塞连接时
    if (pressTime != 0)
    {
        // 收缩springJoint距离并减小作用力
        springJoint.distance = .8f;
        GetComponent<Rigidbody2D>().AddForce(Vector3.down * 400);
    }         
}


保存脚本并返回Unity编辑器。

将PlungerEffects实例中的PlungerEfxZoom和PlungerEfxLight对象指定为Launcher脚本的游戏对象,将Maxforce 设为200。

18.png


测试
现在可以测试做好的弹球活塞了。

将Ball预制件拖到层级窗口来创建实例,设置其Transform的Position为(X:2.84 Y:-1.08)。运行游戏,按下和松开空格键来测试活塞,现在小球能够发射出来了。

19.gif


添加弹板
现在弹球可以从管道发射出来,进入游戏区域,然后落下去。下面我们来添加二个弹板。

在层级窗口中,右键单击Flippers,选择Create Empty。重命名空白游戏对象为FlipLeft-hingejoint。在检视窗口设置该对象Transform的Position为(X:-1.26 Y:-3.8 Z:0) ,将Scale设为(X:1.05 Y:1.05 Z:1)。

对于右边的弹板,按上面的方法创建新游戏对象,命名为FlipRgt-hingejoint。在其检视窗口中,将Transform的Position设为(X:0.46 Y:-3.8 Z:0) ,Scale设为(X:1.05 Y:1.05 Z:1)。

查看Flipper Set Up对象,它上面有二个刚体:
Rigidbody 1:在旋转点锚定可移动部分。
Rigidbody 2:即可移动部分。

接下来打开Assets\Prefab目录,将flipperLeft预制件拖入FlipLeft-hingejoint对象,将flipperRight预制件拖入FlipRgt-hingejoint对象。

20.png


Hinge Joints
在层级窗口选中FlipLeft-hingejoint对象,在其检视窗口添加Hinge Joint 2D组件,设置组件的Body Type为Kinematic,这样能让该关节成为弹板的旋转固定点。

在检视窗口的Hinge Joint 2D组件中,指定flipperLeft实例为它的Connected Rigid Body。

21.png

  
勾选Use Limits,然后将Angle Limits中Lower Angle设为-30,将Upper Angle设为30。

22.png


翻转弹板的脚本
现在添加脚本让弹板工作。在Assets\Scripts目录中,双击FlipControlLeft.cs,在代码编辑器打开该文件。

在代码的//#1下,声明以下变量:
public float speed = 0f;
private HingeJoint2D myHingeJoint;
private JointMotor2D motor2D;


在Start函数中的//#2 下,添加:
myHingeJoint = GetComponent<HingeJoint2D>();
motor2D = myHingeJoint.motor;


在FixedUpdate函数的//#3 下,添加代码以控制该关节的运动:
// 将转速设为最大
motor2D.motorSpeed = speed;
myHingeJoint.motor = motor2D;


在FixedUpdate函数中的//#4 下,添加以下else语句部分,解除关节的运动:
else
{
    // 降低转速
    motor2D.motorSpeed = -speed;
    myHingeJoint.motor = motor2D;
}


将这部分代码在FlipControlRight.cs中重复添加给右边弹板。
1、声明需要的变量,用于访问对象的HingeJoint2D和JointMotor2D组件。
2、创建变量,保存运动的速度值。
3、在FixedUpdate 函数中检测用户交互,并相应地触发其转速。

我们保存文件并返回Unity编辑器。

23.png


现在将FlipControlLeft脚本组件附加到FlipLeft-hingejoint对象,将FlipControlRight 脚本组件附加到FlipRgt-hingejoint对象,将它们的Speed值设为1700。

24.gif


运行游戏,尝试使用方向键左键和右键控制弹板。

通道区域
游戏区域顶部的曲线轨道能提高弹球进入CurveRamp的机会,弹球进入该部分时会获得更多积分。

这一步将制作入口的旋涡,它将弹球吸入通道,并将其从另一端发射出来。

带有Area Effector 2D组件的力场
将Ball实例的Transform Position设为(X:-2.2 Y:0.8 Z:0)。在层级窗口中,右键点击Road Tunnel Zone,选择Create Empty,命名该对象为ForceField。单击Add Component ,选择Physics2D下的Polygon Collider 2D组件。

在检视窗口中,点击Edit Collider图标,移动向量到通道入口外,勾选Is Trigger和Used By Effector。

25.gif


点击Add Component,选择Physics 2D下的Area Effector 2D组件,勾选Use Global Angle。然后将Force Angle设为90,Force Magnitude设为20。在Force Target 下拉列表中选择Rigidbody。

运行游戏并测试新的旋涡。

26.gif


将Force Magnitude提升为200足够使弹球通过该通道,但这样不会使用到Surface Effectors 2D组件,所以我们要让旋涡的作用力弱一点。

带有Surface Effector 2D组件的引导路径
“弱一点”的旋涡只有足够的作用力将弹球推到通道入口。现在我们创建路径,引导弹球通过通道。
1、右键点击层级窗口的Road Tunnel Zone,选择Create Empty,将新对象命名为Road Surface。
2、在该对象检视窗口点击Add Component,选择Physics 2D分类的Edge Collider 2D组件。
3、点击Edit Collider图标,相应地移动向量。
4、勾选Used By Effector,将Edge Radius提高为0.03。

27.gif


接下来点击Add Component,选择Physics 2D分类的Surface Effector 2D组件,将Force Speed设为15,Force Scale设为1。

运行游戏,效果如下图所示。

28.gif


在Unity编辑网格
现在运行游戏时,我们发现弹球的行为似乎有点奇怪。

在层级窗口,选中Static Colliders下的WallBoard对象,在检视窗口勾选Sprite Renderer,在这里我们可以找到问题的原因。

我们找到Polygon Collider 2D组件,点击Edit Collider图标,移开有问题的向量,不要让间距太宽。

29.gif


我们再次运行游戏,现在就正常了,效果如下图所示。

30.gif


使用Platform Effector 2D组件处理挡板
现在ForceField会将弹球推到公路表面,然后引导弹球通过通道,回到发射器,过程如下图所示。

31.gif


但这并不是正确的效果。我们需要添加碰撞体,防止弹球回到活塞,但是同时还要在发射时允许弹球通过发射器,我们可以使用Platform Effector 2D 组件来进行处理。

右键点击层级窗口的Road Tunnel Zone对象,选择Create Empty ,命名新对象为BlockPlatform。

在新对象的检视窗口点击Add Component ,选择Physics 2D下的Edge Collider 2D组件。然后点击Edit Collider来移动阻挡出口的向量。勾选Used By Effector ,将Edge Radius设为0.03。

32.png


接下来,点击Add Component,选择Physics 2D下的Platform Effector 2D组件。禁用Use Collider Mask,将Rotational Offset设为90,将Surface Arc设为130,勾选Surface Arc。

运行游戏,该问题就顺利解决,效果如下图所示。

33.gif


悬浮区域
另一个能让玩家获得更多分数的位置是悬浮区域,实现该区域的步骤如下:
1、将ball实例的Transform Position设为(X:-2.2 Y:-0.5 Z:0)。
2、打开Assets\Sprites目录,将floatpiece拖到层级窗口的Float Piece Area对象上。
3、将该子实例命名为Float Area。
4、在检视窗口将Transform的Position设为(X:-2.63 Y:0.18 Z:0),选择Logo作为其Sorting Layer。
5、点击Add Component ,选择Physics2D下的Polygon Collider 2D组件,勾选Is Trigger和Used By Effector。
6、添加Physics 2D分类的Buoyancy Effector 2D组件。将Density设为50,在Damping部分将Linear Drag设为10,Angular Drag设为10。
7、将Flow Variation 设为-5,移动悬浮效果。

34.png


运行游戏,查看如下图所示的悬浮效果。

35.gif


悬浮效果的代码
现在设置悬浮持续时间。在Assets\Scripts目录中,双击Floatpiece.cs,在代码编辑器打开文件。

在//#1下,声明以下变量:
private BuoyancyEffector2D floatEffector;
public float floatingTime = 0f; // 悬浮持续时间
private float runningTime = 0f; //从startTime开始的当前持续时间
private float startTime = 0f;


在Start函数的//#2 下,添加:
floatEffector = GetComponent<BuoyancyEffector2D>(); // 为该实例指定组件

在OnTriggerEnter2D函数的//#3 下,替换为以下代码:
// 进入区域时,开始悬浮
floatEffector.density = 50;
// 启动计时器
if (startTime == 0f)
{
    startTime = Time.time;
    sound.bonus.Play();
    scoreBoard.gamescore = scoreBoard.gamescore + 10;
    golightRenderer.sprite = golightAniController.spriteSet[0];
    StartCoroutine(BeginFloat());
}


在//#4下,用以下代码替换BeginFloat 函数:
IEnumerator BeginFloat()
{
    while (true)
    {
        // 计算当前持续时间
        runningTime = Time.time - startTime;

        // 播放动画循环
        int index = (int)Mathf.PingPong(handcamAniController.fps *
                    Time.time, handcamAniController.spriteSet.Length);
        handcamRenderer.sprite = handcamAniController.spriteSet[index];
        yield return new WaitForSeconds(0.1f);

        // 当时间结束时
        if (runningTime >= floatingTime)
        {
            // 停止悬浮,重置计时器
            floatEffector.density = 0;   
            runningTime = 0f;
            startTime = 0f;

            // 停止音效和动画
            sound.bonus.Stop();
            golightRenderer.sprite = golightAniController.spriteSet[1];
            handcamRenderer.sprite = handcamAniController.spriteSet
                          [handcamAniController.spriteSet.Length - 1];
            break;
        }
    }
}


保存文件并回到Unity编辑器。

打开Assets\PreFab目录,将floatEffects拖到层级窗口的Float Piece Area 对象上,创建子实例,点击floatEffects 旁边的箭头展开内容。

选择层级窗口中的Float Area ,点击Add Component 并选择Scripts分类的Floatpiece。在检视窗口将floatEffects对象指定到Handcam Obj,将StaticLight指定到Golight Obj,将Floating Time 设为2。

36.png


运行游戏来测试效果。

37.gif


三角形反弹部分
弹球游戏就快要完成了,剩下的工作是使用Point Effectors 2D组件和物理材质提高游戏性。

如果弹球击中反弹部分的话,它会朝相反方向弹出。为了制作这些部分,打开Assets\PreFab目录,将bumperTriangleLeft拖到层级窗口的Bumpy Parts。将Ball实例放到反弹器前面。

运行游戏,了解弹球如何在斜坡滚动。

38.gif


添加动量的方法
在Unity中,为了在碰撞时给动态对象添加动量,我们有Physics Material 2D材质和Point Effector 2D组件二种方法:
1、Physics Material 2D材质:无论作用点如何,速度在该材质的整个对象表面都是一致的。

39.gif


2、Point Effector 2D组件:速度取决于对象的中心和其作用点。

40.gif


我们可以随意使用这二种方法,尝试不同的设置来了解不同碰撞元素的最佳效果。

在层级窗口选择bumperTriangleLeft,在检视窗口Polygon Collider 2D组件上,勾选Is Trigger和Used By Effector。

接下来点击Add Component ,选择Physics 2D下的Point Effector 2D组件,将Force Magnitude设为500,调整Damping部分,将Drag设为5000,将Angular Drag设为1000。

41.png


对于右边的反弹器,将BumperTriangleRgt从Assets\PreFab目录拖动到Bumpy Parts对象。在检视窗口的Polygon Collider 2D组件中,勾选Is Trigger和Used By Effector。

添加Point Effector 2D组件,将Force Magnitude设为500。调整Damping部分,将Drag设为5000,Angular Drag设为1000。

现在将Ball实例的Transform Position设为(X:-1.7 Y:-1.6 Z:0)。运行游戏,测试新的反弹器,如下图所示,弹球的速度超快,甚至看不清它。

42.gif


更多反弹效果
我们还可以尝试2D物理引擎的其它预制件:BumperCircles和BumperDividers。添加它们到弹球游戏时,请将它们拖到层级窗口的Bumpy Parts部分。

通过处理Point Effectors 2D组件或Physics Material 2D材质,调整碰撞的强度。

现在将Ball实例的Transform Position设为(X:2.8 Y:-1 Z:0)。运行游戏,享受游戏的乐趣吧!



小结
使用Unity的2D功能开发弹球游戏就分享到这里,你可以通过点击[阅读原文]下载项目素材,查看本教程项目的最终版本。更多Unity教程尽在Unity官方中文论坛(UnityChina.cn)!

0

主题

1

帖子

95

贡献

初级UU族—2级

Rank: 2

积分
95
发表于 2018-11-6 05:00:50 | 显示全部楼层
使用Unity的2D功能开发弹球游戏

0

主题

3

帖子

35

贡献

初级UU族—1级

Rank: 1

积分
35
发表于 2018-11-6 05:22:07 | 显示全部楼层
谢谢分享,学习下

0

主题

1

帖子

25

贡献

初级UU族—1级

Rank: 1

积分
25
发表于 2018-11-6 05:44:58 | 显示全部楼层

0

主题

1

帖子

30

贡献

初级UU族—1级

Rank: 1

积分
30
发表于 2018-11-6 05:49:57 | 显示全部楼层
感谢分享!

0

主题

2

帖子

35

贡献

初级UU族—1级

Rank: 1

积分
35
发表于 2018-11-6 05:53:44 | 显示全部楼层
感谢分享,最近想看看2d方面的教程

0

主题

3

帖子

35

贡献

初级UU族—1级

Rank: 1

积分
35
发表于 2018-11-6 06:05:49 | 显示全部楼层
提示: 该帖被管理员或版主屏蔽

0

主题

3

帖子

50

贡献

初级UU族—2级

Rank: 2

积分
50
发表于 2018-11-6 06:07:10 | 显示全部楼层
谢谢分享

0

主题

3

帖子

25

贡献

初级UU族—1级

Rank: 1

积分
25
发表于 2018-11-6 06:10:30 | 显示全部楼层
通过公众号看了感觉不错

0

主题

21

帖子

170

贡献

初级UU族—2级

Rank: 2

积分
170
发表于 2018-11-6 06:15:37 | 显示全部楼层
很想知道当年这个游戏开发了多久
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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