兜兜转转跑来学习unity了,学习利用的是unity2017.2版本,在看过网上所谓的一堆零基础入门的视频后(确实0基础,无外乎都从界面开始介绍,然后是脚本基础几个API的介绍,然后讲解了下UGUI的一些应用,然后就没了),终于选定 Flappy Bird 作为第一个熟悉界面操作的作品。
根据视频内容分为以下
最终结果大概如图,该过程通过构建使人熟悉了对于GameObject类的创建,以及稍微科普了下贴图与材质球的关系。以及通过对物体的position的z轴移动调整物体的的一个视觉关系,让人初步了解关于创建之后的视角关系。在创建之后,根据场景的从属关系或者同质关系将object进行层级的从属管理,以最高级的场景做最上级元素,将从属于场景的地面、水管等元素作为子类拖曳在场景下方,又由于水管为多个同质存在,通过创建空的GameObject类做父类进行统一管理。
在场景构建完毕后,由于构建基础只是在3D Object的Squd上涂上一层材质,并未具有更多的物理属性以支持我们进行对应事件的触发,我们要分析出最基本的碰撞事件会在什么之间发生,然后是他们的碰撞会触发什么后果,哪些物体需要刚体属性。
当物体需要到重力或者外力因素的时候即考虑使用riligbody组件,而如果只是需要使用其进行碰撞判定,则只添加collider组件即可,根据object的种类及需求选择,这里选择使用box collider来做碰撞判定。有如下分析:
Land——游戏中的土地,在Bird坠落地面后不至于由于重力因素无限向下坠落,同样的只需要一个碰撞组件以承载Bird,并不需要接受外力。
在组件的添加的时候就考虑过对应要实现的相应功能,在根据原视频的教导下,基本能实现到这个小游戏需要的功能,但是代码却并不具备着良好的概念,这里写一下自己的体会,在过去的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调用即可完成。