我的第一个Unity的2D小游戏(Flappy Bird)

前言

兜兜转转跑来学习unity了,学习利用的是unity2017.2版本,在看过网上所谓的一堆零基础入门的视频后(确实0基础,无外乎都从界面开始介绍,然后是脚本基础几个API的介绍,然后讲解了下UGUI的一些应用,然后就没了),终于选定 Flappy Bird 作为第一个熟悉界面操作的作品。
根据视频内容分为以下

  1. 场景构建
  2. 根据需求添加相应的组件
  3. 功能脚本的分析及编写

过程

1.场景需求及构建

我的第一个Unity的2D小游戏(Flappy Bird)_第1张图片
最终结果大概如图,该过程通过构建使人熟悉了对于GameObject类的创建,以及稍微科普了下贴图与材质球的关系。以及通过对物体的position的z轴移动调整物体的的一个视觉关系,让人初步了解关于创建之后的视角关系。在创建之后,根据场景的从属关系或者同质关系将object进行层级的从属管理,以最高级的场景做最上级元素,将从属于场景的地面、水管等元素作为子类拖曳在场景下方,又由于水管为多个同质存在,通过创建空的GameObject类做父类进行统一管理。

2.根据需求添加相应的组件

在场景构建完毕后,由于构建基础只是在3D Object的Squd上涂上一层材质,并未具有更多的物理属性以支持我们进行对应事件的触发,我们要分析出最基本的碰撞事件会在什么之间发生,然后是他们的碰撞会触发什么后果,哪些物体需要刚体属性。
当物体需要到重力或者外力因素的时候即考虑使用riligbody组件,而如果只是需要使用其进行碰撞判定,则只添加collider组件即可,根据object的种类及需求选择,这里选择使用box collider来做碰撞判定。有如下分析:

  1. Bird——玩家操作本身,具有碰撞特性、受重力影响的特性,不能作为触发器使用;
  2. Pipe——游戏中的障碍,具有碰撞特性,却并不需要刚体特性,由于每过一根柱子我们要进行一次计分,而pipe本身为一个组合(上下两根),因此在该元素组合下增添一个empty object,添加碰撞特性做触发器,以触发事件;
  3. Land——游戏中的土地,在Bird坠落地面后不至于由于重力因素无限向下坠落,同样的只需要一个碰撞组件以承载Bird,并不需要接受外力。

    3.功能脚本的分析及编写

    在组件的添加的时候就考虑过对应要实现的相应功能,在根据原视频的教导下,基本能实现到这个小游戏需要的功能,但是代码却并不具备着良好的概念,这里写一下自己的体会,在过去的java学习中,遵循MVC模式,视图层可以直接调用改变模型业务层数据,而为了保持一个整洁清晰的开发过程,MVP模式的概念更适用于unity开发之中,通过编程过程中对象的泛型化、工厂化的形式进行编写,以节省更多的效率。

场景块管理类概念

设计场景管理模块的目的是在无较大程度视觉差异,不影响真实性效果的情况下人为的减少三角面数量,从而保证场景渲染的实时性,流畅性。

即通过更底层的功能拆分进行代码编写,在上级场景的管理类中进行整合,增大代码的复用性。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// 
/// 关于物体图片的材质球平移造成连续播放效果
/// 
public class MainTexOffset : MonoBehaviour {

    private int frameNum;//帧率
    private float timer; //计数器
    private int frameCounter =0;//当前图片帧数
    public int frameTotal;//图片总帧数
    private float perFramePosition;//每帧的移动幅度
    [SerializeField]
    private MeshRenderer renderer;
    private float count;
    private Vector2 vec;

    private void Start()
    {
        Init();
    }

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


        PlayThePages();

    }
    /// 
    /// 初始化程序
    /// 
    public void Init()
    {
        frameTotal = 3;
        frameNum = 10;
        count = 1.0f / frameNum;
        vec = new Vector2(0.3333333f * frameCounter, 0);
        perFramePosition = 1 / frameTotal;
    }
    /// 
    /// 播放图片的方法
    /// 
    void PlayThePages()
    {
        timer += Time.deltaTime;
        if (timer >= count)
        {
            frameCounter++;
            frameCounter %= 3;//图片层数
            timer -= count;
            renderer.material.mainTextureOffset = vec;
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// 
/// 二维物体的移动工具
/// 
public class 2DMove : MonoBehaviour {
    [SerializeField]
    private Rigidbody rb;
    private float speedX;//移动速度X轴
    private float speedY;//移动速度Y轴
    private bool triggerType;//触发类型

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

    public void Init()
    {

        if (null == rb)  rb = GetComponent(); //检测要是没被创建就自己实例一个
        rb.velocity = new Vector2(2.0f, 0);//给予初始速度
        triggerType = Input.GetMouseButtonDown(0);//给予触发事件
    }
    /// 
    /// 触发后事件
    /// 
    // Update is called once per frame
    void Update () {
        if (triggerType)
        {
            rb.velocity = new Vector2(2.0f, 2.0f);
        }
    }
}

至此Bird相关的脚本已经编写完毕,实现了其图片的播放及移动的功能。然后是场景的初始化事件——柱子的随机生成。

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

public class RandomApp : MonoBehaviour {

    [SerializeField]
    private GameObject pipe;
    private Transform tf;

    // Use this for initialization
    void Start () {
        if (pipe == null) pipe = GetComponent();
        tf = GetComponent();
    }

    /// 
    /// 场景初始化引用
    /// 


    public void RandomY()
    {
        float ranY =  Random.Range(-0.18f, 0.065f);
        tf.localPosition = new Vector2(transform.localPosition.x,ranY);
    }
}

这里设置了一个固定X轴在一定范围随机移动Y轴的类,但并没有在start函数调用时添加,他仅作为提供参数的类,在其上级管理场景中统一调用:

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

public class PipesMan : MonoBehaviour {
    [SerializeField]
    private RandomApp [] ras;

    // Use this for initialization
    void Start () {
        RandomAllAppPos();

    }

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

    }

    public void RandomAllAppPos()
    {
        for (int i = 0; i < ras.Length; i++)
        {
            ras[i].RandomY();
        }
    }
}

然后是场景的变化,在视频中,通过将场景作为预设物prefab,在通过对应位置的触发器后进行Instantiate生成,但是考虑到无限生成的场景将极大考验电脑的运算能力,也没法通过Destory方法指定删除某一个场景,作为熟悉预设物的生成的练习还凑合,但是考虑到实用性就是极为不智的,于是有了以下的场景管理代码:

public class blockS : MonoBehaviour {

    [SerializeField]
    private PipesMan rapps;
    [SerializeField]
    private Transform tf;
    // Use this for initialization

    void Start () {
        if (tf == null) tf = gameObject.GetComponent();
        if (rapps == null) Debug.Log("there is no Management");
    }

    /// 
    /// 移动场景块到下一个位置
    /// 
    public void Move()
    {
        tf.position += new Vector3(54f, 0, 0);
        rapps.RandomAllAppPos();
    }
}

在移动的过程中再度调用随机生成功能,以作为一个新生成的关卡。而move方法则通过绑定触发器使用,触发器获取当前所在的上级元素tag来获取位置,进行对指定目标的移动。

后续还有计分、游戏结束等,通过简单的判定及api调用即可完成。

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