2019年7月19日 学习日记

唉,今天(2019年7月19日 22:40:43)的事情好多,压得我喘不过气,亲人的生日父母的思念哥们生病朋友的烦心事和组织内的人员动荡。影响了学习进度,让我脑袋晕晕的。这里我简单的对今天的学习做一下总结。
今天我学习做的是一个轻量版的flappy bird,从最开始的3d到如今的2d。感觉今天竟然比昨天简单,应该是有了基础并且真的是横版的入门。首先是3d背景的删除,是在window中的lighting选项,之后就要创建我们的游戏背景,分为背景和地板两个部分。由于游戏背景需要循环调用,这里直接创建了两份背景进行设置。由于单个背景也涉及到地板和天空的问题,天空是不需要添加bollider的,因此为了让地板可视,直接把地板设置为foreground,天空则反之。当然,挂上也没什么毛病,直接把常用脚本挂上,该调用的时候调用。之后就是角色的设置,拉入之后就要考虑到一系列的动作。写入controller和character脚本,对小鸟进行控制。由于小鸟是依附于刚体进行运动,直接在其脚本中实例化Rigidbody2D变量,得到本脚本方便进行调用。由于这次的游戏可以看做小鸟可以无限连跳,因此设置每次跳跃初始速度都为0rigid2d.velocity = Vector2.zero;而新速度的给予rigid2d.AddForce(new Vector2(0, upForce)); 这样,只是有了跳跃方法,还没进行调用。 在controller中的
刷新函数中:

    private void Update()
    {
        if (!character.isAlive)//后
        {
            return;
        }
        if (Input.GetMouseButtonDown(0))//先
        {
            character.Up();
        }
    }

需要先判断小鸟是否存活,之后再进行相应操作,即活着才能跳。而存活是角色的属性,因此需要在character脚本中进行声明。初始是活的。因此要写死亡方法,判断成活。

    public void Die()
    {
        isAlive = false;
        animator.SetTrigger("Die");
        rigid2d.velocity = Vector2.zero;
        GameMode.instance.GameOver();
    }

isAlive变为false,可我们知道,只有碰到特定砖块才能断定为死亡。这个时候就要想到触发器,触发器是鸟儿碰撞地板或者管子的时候触发。

    private void OnCollisionEnter2D(Collision2D collision)
    {
        Die();
    }

今天看到代码就心烦,明天更新。

2019年7月20日 08:31:41,好了,恶心好了一点了。昨天说到了Die方法的调用方法,利用了碰撞器,在触碰任何collider后都执行死亡方法。现在小鸟已经会自己跳动了,缺的是它和天空的相对移动。这里我们直接对背景进行移动,角色进行x轴的固定。这样就可以看作小鸟和天空进行了相对移动。自然,为了让天空动起来就要实行一定的方法。这里是重点:
-----前面我们创建了两个ground拼接形成整个背景,我们的方法就是在背景的Transforn.position.x(组件最左上角点的坐标)移动了一个组件长度后,直接将这个组件整体移动到下一个组件的后面,进行循环拼接:

public class BackGroundRepeat : MonoBehaviour
{
    BoxCollider2D groundCollider; //需要调用boxcollider的位置方法,就要实例化并取到这个控件
    float groundHorizontalLength;    //用浮点变量接收控件的长度(由于组件整体同意,这个collider的长度就是一块背景的长度)
    void Start() //初始化函数
    {
        groundCollider = GetComponent();//得到BoxCollider2D控件,以便于调用它的方法,取到它的长度
        groundHorizontalLength = groundCollider.size.x;
    }
    void Update()//刷新函数(每一帧)
    {
        if (transform.position.x <  -groundHorizontalLength)//当移动后的坐标小于长度的相反数,就执行拼接
        {
            Vector2 offset = new Vector2(groundHorizontalLength * 2f, 0);//拼接位置的取到,两倍的长度,相当于隔开一个背景,因为有两个背景,隔着一个背景,transform.position又取到了左上点。这个时候就相当于隔了两个组件。
            transform.position = (Vector2)transform.position + offset;//直接对坐标进行操作,原来的坐标在横坐标加上两个组件长度后直接位移成功
        }
    }
}

这个地方出现过一个问题,就是位移时候取到的transform.position不是直接取到了背景组件的位置,如果不把他们的根节点位置归零,就会出现一些奇怪的现象。这个之后会在基础内在进行复习,因此,根节点的位置初始化是一个好习惯。-----那好,现在已经有了拼接方法,我们该怎么让背景动起来呢。其实很简单,给背景一个rigid控件,利用其velocity方法对速度进行初始化,但是大小就要在另一个组件内声明。这样有利于实现方法的模块化,一个写基础函数,一个写调用和影响,就和controller和character关系差不多:

public class ScrollingObject : MonoBehaviour
{
    Rigidbody2D rigid2d;    //调用刚体脚本
    void Start()//初始化方法
    {
        rigid2d = GetComponent();//得到刚体控件以便于调用其方法
        rigid2d.velocity = new Vector2(GameMode.instance.scrollSpeed, 0);//对速度进行初始化,x轴速度由GameMode中的float类型变量决定      
    }    
    void Update()//刷新函数(每帧)
    {
        if (GameMode.instance.gameOver==true)//若游戏已经结束
        {
            rigid2d.velocity = Vector2.zero;//背景不再运动
        }
    }
}//屏幕滚动而鸟儿不动,这个很特殊。

这里的GameMode.instance.scrollSpeed其实并不必要,不过为了实现程序的模块化,这个方法很标准,有利于更多地方调用这个模块的速度。这里我们要仔细分析一下很重要的GameMode脚本:

public class GameMode : MonoBehaviour
{
    public static GameMode instance;//自己取到自己,这个其实在别的地方用FindObjectOfType也可以,取到这个就是在别的地方不用设置接口
    public bool gameOver = false;//初始化游戏开始的时候游戏没有结束
    public float scrollSpeed = -1.5f;//游戏初始速度为固定的1.5f,且在控件内可以自由调控
    public Text scoreText;//这个是显示分数改变的UI,和我们后面要加的柱子有关
    public GameObject gameOverUI;//GameObject 是一个还没有得到对象的组件,下面由详细的说明
    int score = 0; //分数,在后面才用到
    private void Awake()//唤醒方法
    {
        instance = this; //得到自己,可以在别的脚本中直接进行调用
    }
    private void Update()//刷新方法
    {
        if(gameOver&&Input.GetMouseButton(0))//游戏结束且鼠标左键进行了点击
        {
            //UnityEngine.SceneManagement.SceneManager.LoadScene("Start");这个是重新打开场景,而这里可以直接用当前已经激活的场景,老方法,会对游戏场景进行重置,也会加大系统的负荷
            SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);//直接用激活的场景来载入,这自己百度。上面调用了命名空间:`using UnityEngine.SceneManagement;`
        }
    }
     public void ScoreUp()//分数上升,这个方法还没完成
    {
        if (gameOver)//游戏结束
        {
            return;//结束
        }
        score++;//否则分数增加
        scoreText.text = "Score:" + score.ToString();
    }
    public void GameOver()//游戏结束
    {
    gameOver = true;//bool游戏结束
    gameOverUI.setActive(true);//跳出游戏结束的UI界面
    }
}
    

这里涉及到一些知识拓展,摘自ask网的大佬:GameObject是从Object派生出来的,是Object的子类,根据替换规则,只能向下替换,即子类可以替换基类,如果基类想变成子类,必须要通过显示转化。这个是对***层次结构与显隐性***的分析。

百度的大佬对其***需不需要实例化的本质<***进行了分析:这两个相比,gameObject好理解一点,就是你脚本挂着的那个物体。这个实例化过程是Unity帮你实现的,不用在写代码实例化。this.gameObject默认函数,脚本一创建直接就get到了。
例如,有一个A物体。你给它挂载一个脚本里写this.gameObject。那就等于是直接获取(实例化)A这个物体了,你直接可以引用它下面挂载的属性。
GameObject不是对象,通常需要获取一个对象,就像你定义一个public GameObject A;
那么属性里就会出现一个可托选的框,那就是Unity告诉你,你定义的这个物体是哪个物体要你选择,无论你拖拽也好,脚本里获取也好,都是要给A赋予对象的。

最后,对游戏的Animator进行说明。动作全部集中在了Player鸟上,因此只在character中进行控制即可。当然也需要创建animation脚本进行添加。选定鸟儿后点击window中的animation,将各个动作拖入,将每秒帧数变少,每个动作都占一帧率。之后就是对Animator的调整。先把死连接在Any State上,只有死的时候才Die,因此创建触发器Die。将Idle连接在Entry(进入)上,由于是发呆动作,不需要通过状态的迁移来确定动作。由Idle到Up的转变需要由Up来判断,飞起来用Up动作,下落不需要,且下落的时候可以等上升的动作结束,即Exit Time可以勾选,但是要是想连续飞翔,若动作不变会有些尴尬不人性化,因此在每一次飞跃开始我们都么有Exit Time。
之后就差了加管子了,我会在今天的另外一篇博客中仔细写管子模块的随机生成,以及对这个游戏还需要哪些优化。

你可能感兴趣的:(学习日记)