[Unity] 使用Unity创建2D游戏

查看:1176 |回复:16 | 2015-6-19 10:16:27

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

x
使用Unity创建2D游戏                        本文主要译自Create a 2D Platform Game with Unity and the Dolby Audio API
简介在这篇文章中,将讲解怎样使用C#和Unity创建移动2D游戏。将使用Unity杜比音频插件展示游戏音频体验。游戏很简单,到达另外一边,躲避敌人,收集金币。
Final.jpg
在这篇文章中,将学习Unity游戏开发的以下方面:
  • 在Unity中设置2D项目
  • 创建Prefabs(预设)
  • 移动和动作的按钮
  • 运用物理碰撞
  • 使用sprite sheet(精灵表)
  • 集成杜比音频API
步骤和说明以下一共34个步骤。
我使用的Unity版本是:4.3.4f1
1. 创建新的Unity项目打开Unity,在File菜单中选择New Project,打开新项目对话框:
  • 设置项目保存的路径
  • 设置为2D项目
Project_Wizard__4_3_4f1_.jpg
2. 构建设置创建项目后,会进入Unity用户界面。
在File菜单中选择Build Settings,然后选择Android作为目标平台。

3. 设备当开发2D游戏的时候,第一件事情是选择平台,之后就是选择艺术素材的尺寸。因为Android是开放的平台,因此市场上,有各种设备,它们有各不相同的屏幕分辨率和像素密度。比如以下是比较常见的:
  • Samsung Galaxy SIII: 720px x 1280px, 306 ppi
  • Asus Nexus 7 Tablet: 800px x 1280px, 216 ppi
  • Motorola Droid X: 854px x 480px, 228 ppi
虽然本文使用的是Android平台,但使用相同代码,可构建到Unity支持的其他目标平台上。
4. 导出图为了适应使用的设备,你可能需要将艺术素材转换为推荐的尺寸和像素密度。这件事情可以用你喜欢的图片编辑器完成。
我使用Mac OSX,预览就有这个功能,菜单项在这里:

然后在这个对话框里设置转换的参数:

5. Unity用户界面在开始之前,还需要调整一下Unity界面。
设置场景为2D(默认是3D的):

另外,可在Game控制面板里设置分辨率:

6. 游戏界面
游戏界面和直白。上面截图,可让你了解到艺术粗才最终在游戏界面的样子。后续文章将给出艺术素材的源文件。
7. 编程语言在Unity中,可选的编程语言有3种:
  • C#
  • UnityScript,一个JavaScript的变种
  • Boo
以上语言各有利弊,你需要根据自己喜好选择其中的一种。
文章作者选择了C#,我打算先按照作者的C#过一遍,然后再用UnityScript替代。
如果选择其他语言,可参阅Unity’s Script Reference
8. 2D图Unity是一个在不同平台上创建3D游戏的著名平台,比如微软的Xbox360,任天堂的Wii,Web,以及其他移动平台。
不过,在Unity4.3版本以后,它也可用于2D游戏开发。因为是2D游戏,我们将使用图片作为精灵表,而不是使用3D游戏中的材质。
9. 音效本文将使用一系列声音创建良好的游戏音效。本文中使用的音效来源于:
10. 导入Assets在写代码之前,需要加入asserts到Unity项目中。可使用以下几种方式之一:
  • 在Assets菜单中选择Import New Asset
  • 将assets条目加入到项目的assets文件夹中
  • 将assets拖拽到项目窗口中
我一般用最后一种。
这一步做好,就可在Project面板的Assets文件夹中看到这些assets。
11. 创建场景将对象拖拽到Hierachy面板或者Scene面板中,就会创建默认场景。

另外,只要在创建项目后command+s保存,也可以创建默认场景。
12. 背景将背景图片拖拽到Hierarchy面板中,会自动显示在Scene面板中。
我在操作的时候,怎么拽图片,也无法放在Hierarchy面板里去。
后来借助文章Unity 4.3 2D Tutorial: Getting Started,找到问题了:
The Default Behavior Mode defines the default import settings for your project’s art assets. When set to 3D, Unity assumes you want to create a Texture asset from an imported image file (e.g. a .PNG file); when set to 2D, Unity assumes you want an asset of type Sprite. You’ll read more details about Sprite assets and import settings throughout this tutorial.
需要设置:

然后,如果你之前就没有设置项目的这个属性,重新将图片拽到Assets面板中,然后再拖拽到Hierarchy面板中。
另外,需要将Main Camera在Inspector面板中的属性做下修改,透视投影改为正交投影,Size设置为1.58。

13. 地面将地面的素材图,导入到Assets,然后,拖拽到Scence面板中,再微调拼接好。
看起来效果是这样的:

14. 地面碰撞为了让人物在接触到地面时被检测到,我们需要为地面增加Component,Box Collider 2D。
在Scene面板中选择地面,然后打开Inspector面板,点击Add Component按钮,在Component列表中的Physics 2D中选择Box Collider 2D,见下面图:

15. 跳跃按钮我们将使用按钮控制游戏中的主要人物。拖拽跳跃按钮图到Scene面板,然后为它加上Circle Collider2D组件。
完成这步后,可在Game面板看到类似这样的效果:

做到这里碰到了问题,跳跃按钮会在运行时,会隐藏在当前图的后面,我现在的解决办法是:

Order in layer默认都是0,设置为1,就可见了。
16. 跳跃的声音在Scene面板里选择跳跃按钮,然后在Inspector面板里点击Add Component按钮。在Audio里选择Audio Source。
然后需要取消Play on Awake勾选项。再然后做如下操作,见图:

17. 跳跃脚本现在需要创建脚本来控制人物。选择跳跃按钮,点击Add Component按钮,选择New Script,命名脚本为Jump,不要忘记选择C#语言,默认的脚本语言是JavaScript。

系统将自动呼出MonoDevelop作为脚本的IDE。将下面代码粘贴进去:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using UnityEngine;
using System.Collections;
public class Jump : MonoBehaviour
{
        public float jumpForce;
        private GameObject hero; //used to reference our character (hero) on the scene

        // Use this for initialization
        void Start()
        {
                hero = GameObject.Find("Hero"); //gets the hero game object
        }

        // Update is called once per frame
        void Update()
        {
                /* Check if the user is touching the button on the device */

                if (Application.platform == RuntimePlatform.Android)
                {
                        if (Input.touchCount > 0)
                        {
                                if (Input.GetTouch(0).phase == TouchPhase.Began)
                                {
                                        CheckTouch(Input.GetTouch(0).position, "began"); // function created below
                                } else if (Input.GetTouch(0).phase == TouchPhase.Ended)
                                {
                                        CheckTouch(Input.GetTouch(0).position, "ended");
                                }
                        }
                }

                /* Check if the user is touching the button on the Editor, change OSXEditor value if you are on Windows */

                if (Application.platform == RuntimePlatform.OSXEditor)
                {
                        if (Input.GetMouseButtonDown(0))
                        {
                                CheckTouch(Input.mousePosition, "began");
                        }

                        if (Input.GetMouseButtonUp(0))
                        {
                                CheckTouch(Input.mousePosition, "ended");
                        }
                }
        }

        void CheckTouch(Vector3 pos, string phase)
        {
                /* Get the screen point where the user is touching */
                Vector3 wp = Camera.main.ScreenToWorldPoint(pos);
                Vector2 touchPos = new Vector2(wp.x, wp.y);
                Collider2D hit = Physics2D.OverlapPoint(touchPos);

                /* if button is touched... */

                if (hit.gameObject.name == "JumpButton" && hit && phase == "began")
                {
                        hero.rigidbody2D.AddForce(new Vector2(0f, jumpForce)); //Add jump force to hero
                        audio.Play(); // play audio attached to this game object (jump sound)
                }
        }
}
这部分代码很直白。获取hero对象,这是一个GameObject类的实例。我们后面会用到它。然后,我们检测用户是否点击了跳跃按钮,如果是这样,就给hero对象一个力,最后,我们播放跳跃的声音。
为了能立即执行这个代码,比如在andorid下,我屏蔽了部分还用不上的代码,比如创建hero对象。
做到这一步,我们就可以通过Build & Run部署到Android设备上运行了。或者直接在Game视图中运行。目前的一个问题是,只要点击了跳跃按钮,声音就开始不停的重复播放。需要做如下设置:

18. 移动按钮在界面中添加按钮asserts,左和右。

其实2个按钮是同一个图片asserts,需要将图形做一个转换:

19. 移动脚本创建一个新的脚本,名为MoveLeft,附加在向左移动的按钮上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using UnityEngine;
using System.Collections;
public class MoveLeft : MonoBehaviour
{
    public Vector3 moveSpeed = new Vector3();
    private bool moving = false;
    private GameObject[] scene;
    private GameObject bg;

    // Use this for initialization
    void Start()
    {
        scene = GameObject.FindGameObjectsWithTag("Moveable");
        bg = GameObject.Find("Background");
    }

    // Update is called once per frame
    void Update()
    {
        if (Application.platform == RuntimePlatform.Android)
        {
            if (Input.touchCount > 0)
            {
                if (Input.GetTouch(0).phase == TouchPhase.Began)
                {
                    CheckTouch(Input.GetTouch(0).position, "began");
                } else if (Input.GetTouch(0).phase == TouchPhase.Ended)
                {
                    CheckTouch(Input.GetTouch(0).position, "ended");
                }
            }
        }

        if (Application.platform == RuntimePlatform.OSXEditor)
        {
            if (Input.GetMouseButtonDown(0))
            {
                CheckTouch(Input.mousePosition, "began");
            }

            if (Input.GetMouseButtonUp(0))
            {
                CheckTouch(Input.mousePosition, "ended");
            }
        }

        // Move if button is pressed
        if (moving && bg.transform.position.x < 4.82f)
        {
            for (int i = 0; i < scene.Length; i++)
            {
                if (scene != null)
                {
                    scene .transform.position += moveSpeed;
                }
            }
        }
    }

    void CheckTouch(Vector3 pos, string phase)
    {
        Vector3 wp = Camera.main.ScreenToWorldPoint(pos);
        Vector2 touchPos = new Vector2(wp.x, wp.y);
        Collider2D hit = Physics2D.OverlapPoint(touchPos);

        if (hit.gameObject.name == "LeftButton" && hit && phase == "began")
        {
            moving = true;
        }

        if (hit.gameObject.name == "LeftButton" && hit && phase == "ended")
        {
            moving = false;
        }
    }
}
这里要提到,tag(标签),是操作多个GameObject(游戏对象)的方法。我们可以在Unity界面中给多个游戏对象,比如floor(地面)和Background(背景)对象标识为Moveable的标签。
这样,如果我们需要Hero(英雄)移动的时候,可以反方向移动floor(地面)和Background(背景)。
tag是需要添加的:

添加后就可以这样设置tag:

类似的,我们可以创建MoveRight脚本,将它附加在向右移动的按钮上。代码类似这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
using UnityEngine;
using System.Collections;
public class MoveRight : MonoBehaviour
{
    public Vector3 moveSpeed = new Vector3();
    private bool moving = false;
    private GameObject[] scene;
    private GameObject bg;
    public AudioClip completeSound;
    private GameObject[] buttons;
    private GameObject completeText;
    private bool ended = false;
    public Font goodDog;

    // Use this for initialization
    void Start()
    {
        scene = GameObject.FindGameObjectsWithTag("Moveable");
        bg = GameObject.Find("Background");
        buttons = GameObject.FindGameObjectsWithTag("Buttons");
    }

    // Update is called once per frame
    void Update()
    {
        if (Application.platform == RuntimePlatform.Android)
        {
            if (Input.touchCount > 0)
            {
                if (Input.GetTouch(0).phase == TouchPhase.Began)
                {
                    CheckTouch(Input.GetTouch(0).position, "began");
                } else if (Input.GetTouch(0).phase == TouchPhase.Ended)
                {
                    CheckTouch(Input.GetTouch(0).position, "ended");
                }
            }
        }
        if (Application.platform == RuntimePlatform.OSXEditor)
        {
            if (Input.GetMouseButtonDown(0))
            {
                CheckTouch(Input.mousePosition, "began");
            }
            if (Input.GetMouseButtonUp(0))
            {
                CheckTouch(Input.mousePosition, "ended");
            }
        }

        // Move if button is pressed && stage is not over
        if (moving && bg.transform.position.x > -4.8f)
        {
            for (int i = 0; i < scene.Length; i++)
            {
                if (scene != null)
                {
                    scene .transform.position -= moveSpeed;
                }
            }
        }
        // Stage Completed
        if (bg.transform.position.x <= -4.8f && ended == false)
        {
            Alert("complete");
        }
    }
    void CheckTouch(Vector3 pos, string phase)
    {
        Vector3 wp = Camera.main.ScreenToWorldPoint(pos);
        Vector2 touchPos = new Vector2(wp.x, wp.y);
        Collider2D hit = Physics2D.OverlapPoint(touchPos);

        if (hit.gameObject.name == "RightButton" && hit && phase == "began")
        {
            moving = true;
        }

        if (hit.gameObject.name == "RightButton" && hit && phase == "ended")
        {
            moving = false;
        }
    }
    public void Alert(string action)
    {
        ended = true;
        completeText = new GameObject();
        completeText.AddComponent("GUIText");
        completeText.guiText.font = goodDog;
        completeText.guiText.fontSize = 50;
        completeText.guiText.color = new Color(255, 0, 0);

        if (action == "complete")
        {
            AudioSource.PlayClipAtPoint(completeSound, transform.position);
            completeText.guiText.text = "Level Complete!";
            completeText.guiText.transform.position = new Vector3(0.24f, 0.88f, 0);
        } else
        {
            completeText.guiText.text = "Game Over";
            completeText.guiText.transform.position = new Vector3(0.36f, 0.88f, 0);
        }
        bg.GetComponent<AudioSource>().Stop();

        for(int i = 0; i < buttons.Length; i++)
        {
            buttons.renderer.enabled = false;
            Invoke("restart", 2);
        }
    }
    void restart()
    {   
        Application.LoadLevel(Application.loadedLevel);
    }
}
这里的Alert函数用来创建提示玩家的信息,并播放附加在对应背景对象的声音。然后让所有按钮隐藏,然后间隔2秒后重新开始游戏。
TODO 这里播放声音需要解释
20. 精灵表我们将使用精灵表(sprite sheet)展现游戏的其他元素。Unity有一个精灵表编辑器,使得设置精灵表变得很容易。
精灵表是一张图:

这张图来源于:OpenGameArt.org
需要将它导入到Unity,成为asserts。然后,在Inspector面板中设置它为Multiple,默认是Single:

然后,点击Sprite Editor按钮,在弹出的对话框里点击Slice,这是自动识别精灵表的每个元素:

这之后,精灵表看起来就是这样的了:

21. 英雄然后,我们就可以从精灵表中将英雄拖拽到Scene视图中:

然后,给它加上Collider 2D组件,这是原文的写法,不过有多种Collider 2D组件,我先使用Box Collidar 2D组件,如果以后发现不妥再修改。
22. 英雄2D刚体为了能对英雄检测碰撞,需要至少给碰撞的一方设置位RigidBody 2D组件。也就是2D刚体。我们给英雄设置一下这个组件。
我们不希望英雄在碰撞后发生角度变化,因此需要勾选Fixed Angle:

如果地面当时忘记设置Box Collider 2D,那么设置RigidBody 2D后,英雄会掉落而不是站在地面上。
这时候,可以运行一下这个游戏,看是否能借助前面跳跃的脚本,让英雄跳跃起来。
23. 英雄的声音当英雄被敌人击中,我们要为游戏玩家播放另外一个声音。首先需要加入这个声音,和跳跃按钮添加声音类似。
24. 收集金币收集金币,是很多2D游戏有的需求。我们需要在游戏中多次使用金币。后面我们会将金币转换为Prefab(预设),这样就可以在所有需要的地方添加它了。

在这一步,我们还是将金币的图,从Assets中拖拽到游戏场景中,然后像上面类似的为它添加Box Collider2D组件。
25. 金币的声音和添加英雄声音类似,给金币设置声音。
26. 金币的脚本和Prefab(预设)和跳跃脚本的添加方式类似,代码是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;
using System.Collections;
public class GrabCoin : MonoBehaviour
{   
        void OnTriggerEnter2D(Collider2D other)
        {
                Debug.Log("play sound....");
                if (other.gameObject.name == "Hero")
                {
                        audio.Play();
                        Destroy(gameObject.collider2D);
                        gameObject.renderer.enabled = false;
                        Destroy(gameObject, 0.47f);// Destroy the object -after- the sound played
                }
        }
}
另外,别忘记给金币设置为trigger:

否则当英雄碰撞金币的时候不会触发OnTriggerEnter2D方法。
然后是将这个金币生成为Prefab,操作很简单,只需要将Hierarchy面板中的coin拖拽到下面的Assets中即可。

27. 敌人和上面操作类似,添加敌人:
  • 需要为敌人创建2个Collider 2D,下面的是检测敌人碰到英雄的(失败),另外一个放在敌人头上,用于检测英雄从上面下来碰撞到敌人(敌人被杀死)
  • 不要忘记勾选Is Trigger
  • 然后是添加脚本,见下面
  • 最后,将敌人设置为Prefab
脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
    public Vector3 moveSpeed;
    public AudioClip hitSound;
    public GameObject alertBridge;

    // Use this for initialization
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
        transform.position -= moveSpeed; //Move the enemy to the left
    }

    void OnCollisionEnter2D(Collision2D other) //Hero jumps on enemy
    {
        if (other.gameObject.name == "Hero")
        {
            AudioSource.PlayClipAtPoint(hitSound, transform.position);
            Destroy(gameObject);
        }

    }

    void OnTriggerEnter2D(Collider2D other) //hero hits side of enemy
    {
        if (other.gameObject.name == "Hero")
        {
            other.gameObject.audio.Play(); //Play audio
            Destroy(other.gameObject.collider2D); //Remove collider to avoid audio replaying
            other.gameObject.renderer.enabled = false; //Make object invisible
            Destroy(other.gameObject, 0.626f); //Destroy object when audio is done playing, destroying it before will cause the audio to stop
            alertBridge.GetComponent().Alert("gameover");
        }
    }
}
28. 砖头砖头的逻辑比较简单,无非是:
  • 添加Collider 2D组件
  • 转换为Prefab
29. 结束最后,我们需要创建一个重点游戏对象。当英雄碰到它触发完成游戏。
30. 杜比音频插件略。
31. 测试略。
32. 播放器设置略。
33. 图标和开场画面略。
34. 构建和播放略。
         


评分

参与人数 2元素币 +40 展开 理由
元素界王神 + 20 组织需要你!
成林 + 20 分享快乐~

查看全部评分

2015-6-19 10:16:27  
 赞 赞 1

使用道具 登录

16个回答,把该问题分享到群,邀请大神一起回答。
2#
高端大气上档次,低调奢华有内涵!
回复 收起回复
2015-6-19 10:46:39   回复
 赞 赞 1

使用道具 登录

3#
{:1_146:}
回复 收起回复
2015-6-19 10:51:50   回复
 赞 赞 1

使用道具 登录

5#
想要成大触,天天上元素!
回复 收起回复
2015-6-19 11:18:20   回复
 赞 赞 1

使用道具 登录

6#
支持一下~~~
回复 收起回复
2015-6-19 11:34:33   回复
 赞 赞 1

使用道具 登录

7#
给力!元素有你更精彩
回复 收起回复
2015-6-19 12:01:39   回复
 赞 赞 1

使用道具 登录

8#
为了元素币,拼了!
回复 收起回复
2015-6-19 13:13:47   回复
 赞 赞 1

使用道具 登录

9#
带你赚币带你飞,元素里面有正妹!
回复 收起回复
2015-6-19 18:13:07   回复
 赞 赞 1

使用道具 登录

10#
看完楼主的帖子,感觉自己萌萌哒~!
回复 收起回复
2015-6-19 22:22:31   回复
 赞 赞 1

使用道具 登录

11#
为毛楼主不做成视频发布附件捏?这样还可以赚点元素币哈
回复 收起回复
2015-6-20 23:34:45   回复
 赞 赞 1

使用道具 登录

12#
资源发布哪家强?元素首发称大王!
回复 收起回复
2015-6-22 17:32:57   回复
 赞 赞 1

使用道具 登录

13#
资源发布哪家强?元素首发称大王!
回复 收起回复
2015-7-9 15:26:00   回复
 赞 赞 1

使用道具 登录

14#
带你赚币带你飞,元素里面有正妹!
回复 收起回复
2015-7-12 18:48:10   回复
 赞 赞 1

使用道具 登录

15#
给力!元素有你更精彩
回复 收起回复
2015-7-23 12:48:33   回复
 赞 赞 1

使用道具 登录

16#
高端大气上档次,低调奢华有内涵!
回复 收起回复
2015-7-23 22:37:09   回复
 赞 赞 1

使用道具 登录

17#
元素帖子强,满满正能量!
回复 收起回复
2018-10-17 09:53:38   回复
 赞 赞 1

使用道具 登录

CG 游戏行业专业问题

Unity3D技术手机游戏引擎手游引擎
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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