Unity-Flappy Bird的学习笔记

说明

本文为泰课在线免费项目Flappy Bird的学习笔记,这个系统化的整理知识,对我来说很有用,所以我记录了下来。

  • 整理知识,学习笔记
  • 练习Markdown的书写方法
  • 可以和大家分享学习方法,互相进步,嘿嘿

教学视频跳转

正文


一、提取安卓安装包中的资源并处理

下载安装包之后修改后缀为zip就可以提取gfx中的图集和res中的文本资源文件
需要写个小程序来根据文本处理图片,如下

Visual Studio制作图集解析工具
  • 1.新建项目选择windows窗体应用程序---AtlasTool,vs会自动创建子文件夹,一个图片控件---PictureBox,按钮---Button。
  • 2.去掉放大按钮,和去掉拖拽功能,窗体属性中设置AutoSizeMode-GrowAndShrink这个是拖拽,MaximizeBox-False去掉放大,Text改名。
  • 3.PictureBox改名,picImage-组件加功能,Button改名,btnParse,text=解析。
  • 4.设计过程
    SpriteInfo-小图的信息,配置文件中一行信息,放回给Parser中数组
    Parser-解析配置信息,保持数据
    Loader-加载图集和配置文件
    Cutter-裁剪,给它大图,spriteInfo中配置,开始裁剪
    AtlasFacade-主角,


    1.图解解析流程.png

    2.图集解析类图.png
  • 5.编写
    设置拖拽事件,行为-allowdrop-true,设置事件,设置图片控件sizeMode-StretchImage。
//AtlasTool
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AtlasTool
{
    class SpriteInfo
    {
        public string Name { get; private set; }
        public int X{ get; private set; }
        public int Y { get; private set; }
        public int Width { get; private set; }
        public int Height { get; private set; }


        public SpriteInfo(string name, int x, int y, int width, int heigt)
        {
            this.Name = name;
            this.X = x;
            this.Y = y;
            this.Width = width;
            this.Height = heigt;
        }
    }
}

//AtlasTool
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AtlasTool
{
    class Parser
    {
        public SpriteInfo[] SpriteInfos { get; private set; }

        public void Parse(int width,int height, string config)
        {
            List<SpriteInfo> list = new List<SpriteInfo>();
            string[] lines= config.Split(new char[] { '\n' } ,StringSplitOptions.RemoveEmptyEntries);

            foreach (string line in lines)
            {
                //button_play 116 70 0.6855469 0.22851562 0.11328125 0.068359375
                string[] parts= line.Split(new char[] { ' ' });
                SpriteInfo info = new SpriteInfo(
                        parts[0],
                        
                        (int)(Convert.ToSingle(parts[3])* width),
                        (int)(Convert.ToSingle(parts[4]) * height),

                        Convert.ToInt32(parts[1]),
                        Convert.ToInt32(parts[2])
                    );

                list.Add(info);
            }
            SpriteInfos = list.ToArray();
        }
    }
}
//AtlasFacade
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
namespace AtlasTool
{
    class AtlasFacade
    {
        Loader m_Loader=null;
        Parser m_Parser=null;
        Cutter m_Cutter=null;

        public Bitmap[] Images { get; private set; }

        public AtlasFacade()
        {
            m_Loader = new Loader();
            m_Parser = new Parser();
            m_Cutter = new Cutter();
        }
        //验证
        public bool Valid(string imageFile)
        {
            return imageFile.ToLower().EndsWith(".png");
        }
        //处理
        public void Process(string imageFile)
        {
            //加载
            m_Loader.Load(imageFile);
            //大图
            Bitmap sourceImage = m_Loader.Image;
            //配置
            string config = m_Loader.Config;

            //-------解析
            m_Parser.Parse(sourceImage.Width, sourceImage.Height, config);
            SpriteInfo[] spriteInfos = m_Parser.SpriteInfos;

            //-------切割
            m_Cutter.Cut(sourceImage, spriteInfos);
            Images = m_Cutter.Images;

        }

        //保持
        public void Save(string path=null)
        {
            //默认用原来的路径目录
            if (path == null)
            {
                path = m_Loader.ResourceDir;
            }

            for (int i = 0; i < m_Parser.SpriteInfos.Length; i++)
            {
                SpriteInfo info = m_Parser.SpriteInfos[i];
                Bitmap image = Images[i];
                string fileName = path + "\\" + info.Name+".png";
                image.Save(fileName,ImageFormat.Png);

            }
        }
    }
}
//Cutter
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
namespace AtlasTool
{
    class Cutter
    {
        public Bitmap[] Images { get; private set; }

        public void Cut(Bitmap sourceImage, SpriteInfo[] spriteInfos)
        {
            List<Bitmap> list = new List<Bitmap>();

            foreach (SpriteInfo info in spriteInfos)
            {
                Bitmap smallImage = new Bitmap(info.Width, info.Height);

                Graphics g = Graphics.FromImage(smallImage);

                g.DrawImage(
                    sourceImage,
                    new Rectangle(0, 0, info.Width, info.Height),
                    new Rectangle(info.X, info.Y, info.Width, info.Height),
                    GraphicsUnit.Pixel);
                g.Dispose();

                list.Add(smallImage);
            }
            Images = list.ToArray();
        }
    }
}
//AtlasTool
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing.Imaging;
using System.Drawing;
using System.IO;
namespace AtlasTool
{
    class Loader
    {
        public Bitmap Image { get; private set; }
        public string Config { get; private set; }
        public string ResourceDir { get; private set; }

        public void Load(string imageFile)
        {
            //C:\Users\fjqmt\Desktop\Flappy Bird\Atlas\atlas.png
            //C:\Users\fjqmt\Desktop\Flappy Bird\Atlas
            ResourceDir = Path.GetDirectoryName(imageFile);
            Image = new Bitmap(imageFile);

            //C:\Users\fjqmt\Desktop\Flappy Bird\Atlas\atlas.png
            //atlas
            string fileName = Path.GetFileNameWithoutExtension(imageFile);
            string configFile = ResourceDir + "\\" + fileName + ".txt";
            
            StreamReader sr = new StreamReader(configFile);
            Config = sr.ReadToEnd();
            sr.Close();
            sr.Dispose();
        }
    }
}
//AtlasTool
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AtlasTool
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AtlasTool
{
    public partial class Form1 : Form
    {
        AtlasFacade m_Facade = new AtlasFacade();
        string m_File = null;
        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {

        }
        private void btnParse_Click(object sender, EventArgs e)
        {
            if (m_File == null)
            {
                MessageBox.Show("请拖入一张图片进行处理!");
                return;
            }
            bool isSuccess = true;
            try
            {
                //处理
                m_Facade.Process(m_File);
                //结束
                m_Facade.Save();
            }
            catch (Exception ex)
            {
                isSuccess = false;
                MessageBox.Show("解析失败!" + ex.Message);

            }
            if (isSuccess)
            {
                MessageBox.Show("解析成功!");
            }
        }
        private void Form1_DragDrop(object sender, DragEventArgs e)
        {
            //判断
            string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];
            string file = files[0];
            if (!m_Facade.Valid(file))
            {
                MessageBox.Show("需要图片文件");
                return;
            }
            //保存
            m_File = file;
            //显示
            picImage.Load(file);
        }

        private void Form1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Move;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

    }
}

二、Unity

1.流程和设计图
3.游戏流程.png

4.小鸟类图.png
2.操作流程
Game-挂摄像机上,GameUI挂ui上,游戏的状态-枚举,Global全局定义枚举enum GameState.
UI通过swith选择枚举状态来改变。
Game-导演不要有太多逻辑
单例
private static Game m_Instance=null;
public static Game Instance{get{return m_Instance;}}
void Awake(){m_Instance=this;}
类似于new
定义一个事件
public event Action<GameState> OnStateChanged
变量
GameState m_State=GameState.Init;
重构---封装字段
public GameState GameState
{
    get{return m_State;}
    private set{
        set 的时候事件不为空就触发事件
        m_State=value;
        if(OnStateChanged!=null)
            OnStateChanged(m_State);
    }
}
演员
public GameUI GameUI=null;
void Start()
{
    OnStateChanged+=Game_ OnStateChanged;
    初始进入Init
    this.GameState=GameState.Init;
   ------------------先建立连接让事件与函数挂钩,再设置属性,触发set方法中事件,触发了事件绑定的函数就会触发,
}
void Game_ OnStateChanged(GameState state)
{
    GameUI.UpdateUI(state);
}

Bird类
隐藏于显示
public bool IsVisible
{
    get{return gameObject.activeInHierarchy;}
    set{gameObject.SetActive(value);}
}
商店下载
先登陆,uinty中的右上角第三个,搜索框里头输入Itween,下一行选择商店,free免费,inp什么导入
iTween.MoveBy(
gameObject,
iTween.Hash(
    "y",10,
    "easeType",iTween.EaseType.linear,
    "loopType",iTween.Looptype.pingPong,
    "time",3f
    )
)

2.场景和小鸟
图片数组
public Sprite[] Images;
随机显示
Random 在system和unity 中都有,如果用不到system就注释掉
public void  RandomShow()
{
    随机索引
    int randomIndex=Random.Range(0,Images.Length);[0,10)如果加了f都可以取到
    随机图片
    Sprite image=Images[randomIndex];
    获取组件
    SpriteRenderer renderer=GetComponent<SpriteRenderer>();
    设置图片
    renderer.sprite=image;
}
输入控制
inputController
//是否触发Tab事件
public bool Enale{get;set;}
//点击事件 加event起到保护作用,外部不能触发
public event Action Ontab=null;
void Update()
{
    if(Enale &&input.GetMouseButtonDown(0))
    {
       不为空的时候触发事件
            if(OnTab!=null)
                {
                    OnTab();
                }
    }
}
小鸟需要:跳跃的力,是否显示,是否受重力作用,事件---穿过管道----加分,事件---小鸟死掉
状态,活着,死亡,重新开始
rigidbody2d
播放动画不播放可以设置enabled=false;或者seep=0;

设置速度,获取刚体,.velocity=vector3.up*speed;速度值是一样的,加速度会与来越快

碰撞检测,那一层与那一层可以碰撞,physics里设置,Layers中的第一个是射线层什么的- -,

 解决靠近两个无缝物体之间的缝隙问题,选择贴近物体,按v,鼠标移动到要靠近的顶点,会出现一个小框,拖动,靠近其他顶点。松手。

预设体,按等级分类放置

const 常量

transform是可以foreach 因为transform有继承ienumerable
child in transform if child.name=="",child.gameObject.SetActive
设置2d隐藏,某个组件

遍历子对象,foreach(Transform child in obstacle.transform)
{
if(name=="")
       child.name和child.gameobject.name是一样的
        还有gameobject
}
for(int i=0;i<obstacle.transform.childcount;i++)
{
transform child= obstacle.transform.GetChile(i);
}
先获取localpostion,修改,再=
是否启用碰撞器,遍历所有子对象,如果是管道,就获取刚体
BoxCollider2D[] colliders=child.GetComponentsInChildren<BoxCollider2D>();
foreach(BoxCollider2D collider in colliders)
{
    collider.enabled=enable;
}
if(onScoreChanged!=null)
        onScoreChanged(m_Score);如果事件有人监听就触发

PlayerPrefs.GetInt();

private int m_Score=0;
public int Score
{
    get{return m_Score;}
    set{
            m_Score=value;
            if(onScoreChanged!=null)
                    onScoreChanged(m_Score);
        }
}
public int Best
{
    get{return PlayerPrefs.GetInt("Best",0)}
    set{PlayerPrefs.SetInt("Best",value);}
}

void Saver_OnScoreChanged(int Score)
{
    if(score>Best)
    {
        Best=score;
    }
}
这样自己监听自己,如果发现改变分数大于最大值,就保存,Score的代码都和Score,Best也是,低耦合

声音集合AudioClip[]
找到之后break之后就不用再找了
AudioSource.PlayClipAtPoint(clip,transform.position)世界坐标
声音放在摄像机上,声音会大一些些
穿过就触发事件saver.score++,加加就触发事件更新ui的text,

重新命名ctrl+r

Author : Jhin.hx
Time : 2016.11.29

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 158,608评论 24 689
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 7,315评论 4 39
  • 某朝,某国边界有敌寇侵犯。 大帅正拿树枝研究国家边界地图。然其已白发苍苍,厌倦了战争。哨兵打电话给大帅……嘟嘟嘟…...
    陌上冷阅读 46评论 0 0
  • 今天我亲自为爷爷奶奶拍了纪念照,81岁的爷爷,69岁的奶奶,年轻时候从来就没有奢望过能来一套婚纱照,因为在他们那个...
    我叫Arvin阅读 136评论 0 0
  • 山色隐薄雾,春晚正微凉。不知亭上栖燕,明日去潇湘?笑看江边竹泪,共把轻舟拭遍,执手泛清浪。蜀地真凄切,何计可归乡?...
    胡唐阅读 102评论 0 1