Unity笔记 2D ROGUELIKE 实例详解

这是unity官方的一个视频教程。比较完整的一个2D小游戏,共14讲。
http://unity3d.com/learn/tutorials/projects/2d-roguelike
这里整理了一些tips。
游戏控制一个2D帧动画的人物在tile地图上行走,目的是走到出口。地图上有食物、敌人、阻挡。移动消耗食物,碰到敌人会自动攻击。

Unity把这个项目定义为中级,可以先看这个

第一部分 准备资源
1 介绍
tag、layer、sortinglayer的用法:
tag 是标识gameobject用的。
layer 一共32个不能扩展,类似分类的概念。射线选择和渲染可以指定感兴趣的layer。
sortinglayer 在render里选择的,决定2D排序用的。

2 player和enemy的prefab
制作prefab:
prefab就是gameobject的预置模板,建立好后方便clone建立gameobject无论是在编辑时和脚本中。
player的prefab涉及加精灵动画、做AnimationController(AC)、选tag、layer、sortinglayer、加Rigibody控制用。

加精灵动画:
这个游戏是简单的帧动画,美术资源已经按照固定大小做好了每帧画面。
选中一个动作的所有帧拖到gameobject就会生成一个动画文件(这时有对话框要起文件名和选路径)和一个配套的AnimationController。

AnimationController(AC):
例子里用AC调整了动画速度。
2种敌人公用一个AC使用Create Animator Override Controller 这种AC会要求指定一个AC状态机和选择override动画。

添加rigidbody来控制:
选中IsKinematic这样就不会被物理系统本身影响,但可以使用其控制gameobject。

3 Tile(地形块)prefab
为每个Tile元素建立perfab:
Tile prefab没有动画的情况(只使用单帧资源)添加一个SpiriteRender然后从资源里拖一帧上去。
注意和有动画的tile(可以打碎的tile)区分。
trigger:
Exit、Food之类的要交互,加上Box Collider并设置trigger这样就可以重叠而不受物理系统约束了。
wall之类的trigger就不设置了,要利用物理系统达到Player不能重叠墙的效果。

第二部分 关卡和整体结构
1 board manager
管理关卡场景的脚本。
生成地图、放置元素等功能。

2 game manager
编辑状态数组批量赋值:
可以点击右边图标lock inspector,多选prefab拖动到脚本public的gameobject数组上。

singleton的一个实现:
singleton简单说就是个全局变量。

public static GameManager instance = null; 
//Awake is always called before any Start functions
void Awake()
{
    //Check if instance already exists
    if (instance == null)
        //if not, set instance to this
        instance = this;
    //If instance already exists and it's not this:
    else if (instance != this)
        //Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
        Destroy(gameObject);    

    //Sets this to not be destroyed when reloading scene
    DontDestroyOnLoad(gameObject);
}

建立各种singleton的manager合适地方:
Loader脚本组织各种Singleton,把loader脚本放在maincamera上,awake时建立singleton(gamemanager、soundmanager)
代码如下:

void Awake ()
{
    //Check if a GameManager has already been assigned to static variable GameManager.instance or if it's still null
    if (GameManager.instance == null)
        //Instantiate gameManager prefab
        Instantiate(gameManager);
}

第三部分 游戏逻辑实现
1 移动基类
建立一个MovingObject的基类:
abstract virutal override 关键字的使用。
使用StartCoroutine建立协程来,移动object。
generic泛型方法定义了尝试移动的过程,由子类来决定自己关心的地形物体和处理方法。

本集是整个教程难点,涉及c#的相关知识比较多,看过这些就没问题啦 c#笔记 系列

限制碰撞涉及的layer建立一个layerMask:
public LayerMask blockingLayer;

2 wall的脚本
没有动画的gameobject改变外观:
gameobject通过spriterenderer替换sprite
//Set spriteRenderer to the damaged wall sprite.
spriteRenderer.sprite = dmgSprite;
区别于有动画的方式,有动画的直接AC改变动画。

3 Player AC
改变动画:
添加一个 trigger类型的参数用于变换动画(transition)的条件。

idle to chop :
transition to 0,通常帧动画处理方式,没有过渡。
has exit time,不勾选,在idle过程中随时插入chop动画。

chop to idle:
transition to 0,通常帧动画处理方式,没有过渡。
has exit time,勾选,idle不可打断chop。
exit time,设置1,完成1次才可播放idle。

4 Player的脚本
利用singleton保存数据:
GameManager.instance.playerFoodPoints = food;

重载2个泛型方法完成move,调用并强转为感兴趣的交互类型Wall:
protected override void AttemptMove (int xDir, int yDir)
protected override void OnCantMove (T component)
AttemptMove (horizontal, vertical);
Wall hitWall = component as Wall;

播放动画:
animator.SetTrigger (“playerChop”);

用tag配合这个回调来处理游戏逻辑:
private void OnTriggerEnter2D (Collider2D other)

延迟触发一个函数调用:
Invoke (“Restart”, restartLevelDelay);

5 Enemy的脚本
和player差不多,多了个自动找player的简单算法MoveEnemy。

6 Enemy AC 和 GameManager相关修改
Enemy AC:
和Player很像。由于用了override AC 2种enemy都ok了。

GameManager Update里Start协程完成移动enemy功能。

GameManager添加EnemyList,每个enemy 在Start的时候利用GameManagerSingleton把自己加进list:
GameManager.instance.AddEnemyToList (this);

第四部分 收尾
1 UI和level
UGUI系统:
canvas是UI元素容器。各种Anchor(居中、贴底等)可以用于对齐UI元素。
得到UI的gameobject设置其显示或隐藏。
得到UI的Component, Text levelText,直接设置它的值。

OnLevelWasLoaded:
当Scene被装载后调用。

2 音乐和音效
建立一个singleton的SoundManager
加2个audiosource,一个是audio(efx音效)一个是music(音乐)

在player加上public audioclip(在编辑器指定),在需要的时候播放。

3 手机控制
intput touch的使用见代码:

private Vector2 touchOrigin = -Vector2.one;

//Check if Input has registered more than zero touches
            if (Input.touchCount > 0)
            {
                //Store the first touch detected.
                Touch myTouch = Input.touches[0];

                //Check if the phase of that touch equals Began
                if (myTouch.phase == TouchPhase.Began)
                {
                    //If so, set touchOrigin to the position of that touch
                    touchOrigin = myTouch.position;
                }

                //If the touch phase is not Began, and instead is equal to Ended and the x of touchOrigin is greater or equal to zero:
                else if (myTouch.phase == TouchPhase.Ended && touchOrigin.x >= 0)
                {
                    //Set touchEnd to equal the position of this touch
                    Vector2 touchEnd = myTouch.position;

                    //Calculate the difference between the beginning and end of the touch on the x axis.
                    float x = touchEnd.x - touchOrigin.x;

                    //Calculate the difference between the beginning and end of the touch on the y axis.
                    float y = touchEnd.y - touchOrigin.y;

                    //Set touchOrigin.x to -1 so that our else if statement will evaluate false and not repeat immediately.
                    touchOrigin.x = -1;

                    //Check if the difference along the x axis is greater than the difference along the y axis.
                    if (Mathf.Abs(x) > Mathf.Abs(y))
                        //If x is greater than zero, set horizontal to 1, otherwise set it to -1
                        horizontal = x > 0 ? 1 : -1;
                    else
                        //If y is greater than zero, set horizontal to 1, otherwise set it to -1
                        vertical = y > 0 ? 1 : -1;
                }
            }

            #endif //End of mobile platform dependendent compilation section started above with #elif

你可能感兴趣的:(unity)