寫在前頭
這篇僅挑重點寫,若對Unity或Futile完全沒碰過的,請先看完上一篇再來。
另外,這篇也是參考Futile作者的教學影片來的,有興趣的話也可過去看看。
Texture打包
使用Texture Packer打包所需的圖形,設定上的重點是:
輸出Data Format選Unity3D
Layout的Algorithm選Basic, Trim Mode選None
然後按Publish輸出,本範例輸出命名為:textures.png及textures.txt
另外字型圖也可包進同一張Texture圖裏。我是用bmGlyph這套軟體做字型圖,我選"Impact"這個字型來做,產生了ImpactFont.png及ImpactFont.txt兩個檔案,然後ImpactFont.png這個也可拉進Texture Packer裏一起輸出。如下圖
所以最後會得到texture.png, texture.txt及ImpactFont.txt三個檔,就一併放到Unity專案路徑裏的/Assets/Resources/Atlases/裏吧
專案設定
本範例長寬設為960x640,其他就沒有特別做什麼設定了
Pixel Style
因為我個人滿偏好Pixel Style的,所以特別想知道Futile在開發Pixel Style時的設定方式,尤其Unity這種3D環境經常預設就會幫你做一些反鋸齒之類的效果,但這反而會把銳利的Pixel給模糊掉,因此首先要知道如何設定texture才能得到我們想要的效果。本範例對Texture的設定如下
Textures Atlas的載入與使用
上一篇只單純載入一張png圖,這次是把一堆圖及字型,都拼成一塊再載入,不過用法是差不多的:
Futile.atlasManager.LoadAtlas("Atlases/textures");
Futile.atlasManager.LoadFont("ImpactFont","ImpactFont.png","Atlases/ImpactFont");
//使用圖形時:
_background = new FSprite("background.png");
//使用字型時:
_label = new FLabel("ImpactFont","Tap the BaoZi to Start!!");
FSprite與FContainer
FSprite不能再包別的FSprite,更不能包FContainer。它只有一層,不像Flash那樣可以層層包。
FSprite在生成時直接就可以塞一張圖進去,FContainer不行。
只有FContainer可以包東西,除了可包FSprite外,也可包FContainer。
FContainer有scale, scaleX, scaleY等屬性,但卻沒有width, height。
FButton與 FLabel
//Button用法
_myBtn = new FButton("原本的圖.png", "壓下去的圖.png ", "音效名");
//event是用+的方式偵聽
_myBtn.SignalRelease += onTapFunction;
private void onTapFunction(FButton obj){
Main.instance.gotoTitle();
}
//Label
_myLabel = new FLabel("字型名稱","文字內文");
_myLabel.text = "改字的內容"
//Label本身沒有width, height,要用textRect
Debug.Log(_myLabel.textRect.width)
Debug.Log(_myLabel.textRect.height)
TouchManager
//要先加入MultiTouch的介面
public class PageGame : FContainer, FMultiTouchableInterface {
override public void HandleAddedToStage(){
//啟用
Futile.touchManager.AddMultiTouchTarget(this);
base.HandleAddedToStage();
}
override public void HandleRemovedFromStage(){
//停用
Futile.touchManager.RemoveMultiTouchTarget(this);
base.HandleRemovedFromStage();
}
//touches包含了目前所有動作中的點的資訊
public void HandleMultiTouch(FTouch[] touches){
foreach(FTouch _touch in touches){
if(_touch.phase == TouchPhase.Began){
//do something
}
}
}
}
一些Event的設置
//把某個function加入到Futile系統的Update事件中,效果類似於Flash的EnterFrame
Futile.instance.SignalUpdate += myFunction;
private void myFunction () {
//do something
}
//取消就用減的
Futile.instance.SignalUpdate -= myFunction;
//加入跟移出場景時的事件,名稱及效果都與Flash的類似,FSprite, FContainer, FButton這些都有這個事件
override public void HandleAddedToStage(){
}
override public void HandleRemovedFromStage(){
}
FRenderLayer發生GameObject.active的問題
目前這個版本(0.67 alpha)在Unity 4.1的環境下,輸出時會出現類似這樣的錯誤訊息:
Assets/Plugins/Futile/Core/FRenderLayer.cs(110,29): warning CS0618: UnityEngine.GameObject.active' is obsolete:GameObject.active is obsolete. Use GameObject.SetActive(), GameObject.activeSelf or GameObject.activeInHierarchy.'
作者有在github上說明問題點,並已在dev版本修正了。所以下一版的Futile release時這問題應該就不會有了。
目前的話,可自行修改,改的地方只有三行,很容易。
打開FRenderLayer.cs這個檔,可找到三行 _gameObject.active = XXX; 的程式,自行改成 _gameObject.SetActive(XXX); 即可。
(這問題應該在下一版的Futile release後就不會吧)
GoKit的使用
GoKit是一個類似AS3裏的Tween Library,用法也類似,Futile有把GoKit包進來,所以可直接用
//基本用法,寫法跟TweenLite很像
Go.to( someTransform, 4f, new GoTweenConfig().position( new Vector3( 10, 10, 10 ) ) );
//由於GoKit不是只設計給Futile用的,而是設計給Unity用的,所以上一行會用3d的座標
//在Futile裏用的話,可以這樣寫
Go.to( instanceOfMyClass, 3f, new TweenConfig().floatProp("scale",scaleTarget*0.25f) );
//同時要變多個屬性的話,可以一直接在後面:
Go.to( instanceOfMyClass, 3f, new TweenConfig().floatProp("scale",scaleTarget*0.25f).floatProp("y",100) );
//也可以用TweenChain一次設定一整串Tween
var tween1 = new Tween(this, 0.1f, new TweenConfig().floatProp("scaleY",scaleTarget*0.3f).floatProp("scaleX",scaleTarget*1.4f).floatProp("y",_yTarget));
var tween2 = new Tween(this, 0.1f, new TweenConfig().floatProp("scaleY",scaleTarget*1.4f).floatProp("scaleX",scaleTarget*0.3f).floatProp("y",_yTarget + height));
var tween3 = new Tween(this, 0.1f, new TweenConfig().floatProp("scale",scaleTarget*0.25f).floatProp("y",_yTarget));
var chain = new TweenChain();
chain.append(tween1).append(tween2).append(tween3);
chain.setOnCompleteHandler(C=>{
//do something
}
);
chain.play();
其他更詳細的用法,請自己上官網去看。
最後是原始碼大爆發
Main.cs:
using UnityEngine;
using System.Collections;
public class Main : MonoBehaviour {
private FContainer _currentPage;
public static Main instance;
// Use this for initialization
void Start () {
instance = this;
FutileParams futileParams = new FutileParams (true,true,false,false);
futileParams.AddResolutionLevel(960,1,1,"");
futileParams.origin = new Vector2(0.5f,0.5f);
Futile.instance.Init(futileParams);
Futile.atlasManager.LoadAtlas("Atlases/textures");
Futile.atlasManager.LoadFont("ImpactFont","ImpactFont.png","Atlases/ImpactFont");
//FSprite _bg = new FSprite("background.png");
//Futile.stage.AddChild(_bg);
gotoTitle();
}
public void gotoTitle(){
if(_currentPage != null)_currentPage.RemoveFromContainer();
_currentPage = new PageTitle();
Futile.stage.AddChild(_currentPage);
}
public void gotoGame(){
if(_currentPage != null)_currentPage.RemoveFromContainer();
_currentPage = new PageGame();
Futile.stage.AddChild(_currentPage);
}
// Update is called once per frame
void Update () {
}
}
PageTitle.cs:
using UnityEngine;
using System.Collections;
public class PageTitle : FContainer, FSingleTouchableInterface {
private FSprite _background;
private FSprite _mainLogo;
private FLabel _label;
public PageTitle(){
_background = new FSprite("background.png");
AddChild(_background);
_mainLogo = new FSprite("mainLogo.png");
AddChild(_mainLogo);
_label = new FLabel("ImpactFont","Tap the BaoZi to Start!!");
_label.color = Color.white;
_label.y = -260;
_label.scale = 2;
AddChild(_label);
doBreath();
doShake ();
FContainer _test = new FContainer ();
AddChild (_test);
//var bb = new FButton(
//bb.SignalRelease += onRelease;
}
override public void HandleAddedToStage(){
Futile.touchManager.AddSingleTouchTarget(this);
base.HandleAddedToStage();
}
override public void HandleRemovedFromStage(){
Futile.touchManager.RemoveSingleTouchTarget(this);
base.HandleRemovedFromStage();
}
public bool HandleSingleTouchBegan(FTouch touch)
{
//Debug.Log("Touch");
return true;
}
public void HandleSingleTouchMoved(FTouch touch)
{
//Debug.Log("Move");
}
public void HandleSingleTouchEnded(FTouch touch)
{
//Debug.Log("Ended");
Vector2 pos = _mainLogo.GlobalToLocal(touch.position);
if(_mainLogo.textureRect.Contains(pos)){
//Debug.Log("Oh");
Main.instance.gotoGame();
}
}
public void HandleSingleTouchCanceled(FTouch touch)
{
//Debug.Log("Canceled");
}
private void doBreath(){
//Debug.Log("doBreath");
var tween1 = new Tween(_mainLogo, 2.5f, new TweenConfig().floatProp("scale",1.05f).setEaseType(EaseType.QuadInOut));
var tween2 = new Tween(_mainLogo, 2.5f, new TweenConfig().floatProp("scale",1).setEaseType(EaseType.QuadInOut));
var chain = new TweenChain();
chain.append(tween1).append(tween2);
chain.setOnCompleteHandler(C=>{
doBreath();
}
);
chain.play();
}
private void doShake () {
//Debug.Log("doShake");
var r = RXRandom.Float()*16 - 8;
var tween1 = new Tween(_mainLogo, 0.05f, new TweenConfig().floatProp("rotation",r).setEaseType(EaseType.BackOut));
var tween2 = new Tween(_mainLogo, 0.1f, new TweenConfig().floatProp("rotation",-r).setEaseType(EaseType.BackOut));
var tween3 = new Tween(_mainLogo, 0.1f, new TweenConfig().floatProp("rotation",r).setEaseType(EaseType.BackOut));
var tween4 = new Tween(_mainLogo, 0.1f, new TweenConfig().floatProp("rotation",-r).setEaseType(EaseType.BackOut));
var tween5 = new Tween(_mainLogo, 0.05f, new TweenConfig().floatProp("rotation",0).setEaseType(EaseType.BackOut));
var chain = new TweenChain();
chain.appendDelay(RXRandom.Float()*5+3).append( tween1).append(tween2).append(tween3).append(tween4).append(tween5);
chain.setOnCompleteHandler(C=>{
doShake();
}
);
chain.play();
}
// Update is called once per frame
void Update () {
}
}
PageGame.cs:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class PageGame : FContainer, FMultiTouchableInterface {
private FSprite _background;
private FButton _exitBtn;
private int _frameCount;
private int _createBaoziTime;
private int _baoziMaxCount;
private int _liveTimeLimit;
private FContainer _baozisContainer;
private FContainer _baozisCrushContainer;
private List _baoziList;
private FLabel _scoreLabel;
private FLabel _lifeLabel;
private FLabel _overLabel;
private int _score;
private int _life ;
private bool _isPlaying;
//private List
// Use this for initialization
public PageGame () {
//Debug.Log("OOOKKK");
_background = new FSprite("background.png");
AddChild(_background);
init();
}
private void setDiffcult(){
if(_score < 10){
_createBaoziTime = 45;
_baoziMaxCount = 5;
_liveTimeLimit = 300;
}else if(_score < 30 && _score >=10){
_createBaoziTime = 40;
_baoziMaxCount = 10;
_liveTimeLimit = 250;
}else if(_score < 70 && _score >=30){
_createBaoziTime = 30;
_baoziMaxCount = 15;
_liveTimeLimit = 250;
}else if(_score < 100 && _score >=70){
_createBaoziTime = 25;
_baoziMaxCount = 20;
_liveTimeLimit = 200;
}else if(_score >=100){
_createBaoziTime = 20;
_baoziMaxCount = 25;
_liveTimeLimit = 150;
}
}
private void init(){
_baoziList = new List();
_baozisContainer = new FContainer();
AddChild(_baozisContainer);
_baozisCrushContainer = new FContainer();
AddChild(_baozisCrushContainer);
_scoreLabel = new FLabel("ImpactFont","SCORE: 000");
_scoreLabel.y = Futile.screen.halfHeight-32;
_scoreLabel.scale = 2;
_scoreLabel.x = -Futile.screen.halfWidth+_scoreLabel.textRect.width;
AddChild(_scoreLabel);
_lifeLabel = new FLabel("ImpactFont","LIFE: 010");
_lifeLabel.y = Futile.screen.halfHeight-32;
_lifeLabel.scale = 2;
_lifeLabel.x = Futile.screen.halfWidth-_lifeLabel.textRect.width;
AddChild(_lifeLabel);
_overLabel = new FLabel("ImpactFont","GAME OVER");
_overLabel.scale = 5;
AddChild(_overLabel);
_exitBtn = new FButton("exit.png");
AddChild(_exitBtn);
_exitBtn.SignalRelease += onClickExit;
_exitBtn.x = Futile.screen.halfWidth-_exitBtn.sprite.width/2 -8;
_exitBtn.y = -Futile.screen.halfHeight + _exitBtn.sprite.height/2 + 8;
}
private void onClickExit(FButton obj){
Main.instance.gotoTitle();
}
override public void HandleAddedToStage(){
_frameCount = 0;
_score = 0;
setDiffcult();
_life = 10;
_isPlaying = true;
_overLabel.isVisible = false;
Futile.instance.SignalUpdate += signalUpdate;
Futile.touchManager.AddMultiTouchTarget(this);
base.HandleAddedToStage();
}
override public void HandleRemovedFromStage(){
Futile.instance.SignalUpdate -= signalUpdate;
Futile.touchManager.RemoveMultiTouchTarget(this);
base.HandleRemovedFromStage();
}
private void addScore(){
_score ++;
setDiffcult();
string _str = _score.ToString();
while(_str.Length < 3){
_str = "0"+_str;
}
_scoreLabel.text = "SCORE: "+_str;
}
public void HandleMultiTouch(FTouch[] touches){
if(!_isPlaying)return;
foreach(FTouch _touch in touches){
if(_touch.phase == TouchPhase.Began){
for(int i = _baoziList.Count -1; i>=0; i--){
Baozi _baozi = _baoziList[i];
Vector2 _touchPos = _baozi.GlobalToLocal(_touch.position);
if(_baozi.textureRect.Contains(_touchPos)){
crushBaozi(_baozi);
addScore();
break;
}
}
}
}
}
private void decLife(){
_life --;
string _str = _life.ToString();
while(_str.Length < 3){
_str = "0"+_str;
}
_lifeLabel.text = "LIFE: "+_str;
if(_life<=0){
gameOver();
}
}
private void gameOver(){
_overLabel.isVisible = true;
_isPlaying = false;
}
// Update is called once per frame
private void signalUpdate () {
if(!_isPlaying)return;
if(_frameCount % _createBaoziTime ==0 && _baoziList.Count<_baoziMaxCount){
createBaozi();
}
_frameCount++;
for(int i = _baoziList.Count -1; i>=0; i--){
Baozi _baozi = _baoziList[i];
_baozi.liveTime ++;
if(_baozi.liveTime >= _liveTimeLimit){
_baoziList.Remove(_baozi);
_baozi.doHiding();
decLife ();
//_baozi.RemoveFromContainer();
}
//Vector2 _touchPos = _baozi.GlobalToLocal(_touch.position);
//if(_baozi.textureRect.Contains(_touchPos)){
//crushBaozi(_baozi);
//break;
//}
}
}
private void crushBaozi(Baozi _baozi){
BaoziCrush _baoziCrush = new BaoziCrush();
//_baoziCrush.scale = _baozi.scale;
_baoziCrush.x = _baozi.x;
_baoziCrush.y = _baozi.y-_baozi.height*0.3f;
_baoziCrush.setCrush(_baozi.scaleTarget);
_baozisCrushContainer.AddChild(_baoziCrush);
_baoziList.Remove(_baozi);
_baozi.RemoveFromContainer();
}
private void createBaozi(){
Baozi _baozi = new Baozi();
//_baozi.x = RXRandom.Range(-Futile.screen.halfWidth,Futile.screen.halfWidth);
//_baozi.y = RXRandom.Range(-Futile.screen.halfHeight,Futile.screen.halfHeight);
_baozisContainer.AddChild(_baozi);
_baozi.doIntro();
_baoziList.Add(_baozi);
}
}
Baozi.cs:
using UnityEngine;
using System.Collections;
public class Baozi : FSprite {
public float scaleTarget;
public int liveTime;
private float _yTarget;
// Use this for initialization
public Baozi():base ("baozi.png"){
x = RXRandom.Range(-Futile.screen.halfWidth + width/2,Futile.screen.halfWidth - width/2);
_yTarget = y = RXRandom.Range(-Futile.screen.halfHeight + 100,Futile.screen.halfHeight - height/2-32);
scaleTarget = scale = (1-(_yTarget+Futile.screen.halfHeight)/Futile.screen.height) + 0.3f;
//scaleTarget = scale = RXRandom.Float()*0.5f + 0.5f;
}
public void doIntro(){
//_scaleTarget *=1.05f;
this.scale = scaleTarget*0.25f;
var tween1 = new Tween(this, 0.1f, new TweenConfig().floatProp("scaleY",scaleTarget*1.2f).floatProp("y",_yTarget + height*2));
var tween2 = new Tween(this, 0.1f, new TweenConfig().floatProp("scaleY",scaleTarget*0.3f).floatProp("scaleX",scaleTarget*1.4f).floatProp("y",_yTarget));
var tween3 = new Tween(this, 0.1f, new TweenConfig().floatProp("scale",scaleTarget).setEaseType(EaseType.BackOut));
var chain = new TweenChain();
chain.append(tween1).append(tween2).append(tween3);
chain.setOnCompleteHandler(C=>{
liveTime = 0;
}
);
chain.play();
}
public void doHiding(){
var tween1 = new Tween(this, 0.1f, new TweenConfig().floatProp("scaleY",scaleTarget*0.3f).floatProp("scaleX",scaleTarget*1.4f).floatProp("y",_yTarget));
var tween2 = new Tween(this, 0.1f, new TweenConfig().floatProp("scaleY",scaleTarget*1.4f).floatProp("scaleX",scaleTarget*0.3f).floatProp("y",_yTarget + height));
var tween3 = new Tween(this, 0.1f, new TweenConfig().floatProp("scale",scaleTarget*0.25f).floatProp("y",_yTarget));
var chain = new TweenChain();
chain.append(tween1).append(tween2).append(tween3);
chain.setOnCompleteHandler(C=>{
this.RemoveFromContainer();
}
);
chain.play();
}
// Update is called once per frame
void Update () {
}
}
BaoziCrush.cs:
using UnityEngine;
using System.Collections;
public class BaoziCrush : FSprite {
// Use this for initialization
public BaoziCrush():base ("baoziCrush.png"){
//scale = RXRandom.Float()*0.5f + 0.5f;
//x = RXRandom.Range(-Futile.screen.halfWidth + width/2,Futile.screen.halfWidth - width/2);
//y = RXRandom.Range(-Futile.screen.halfHeight + 100,Futile.screen.halfHeight - height/2);
}
public void setCrush(float _scaleTarget){
_scaleTarget *=1.05f;
this.scale = _scaleTarget/2;
var tween1 = new Tween(this, 0.1f, new TweenConfig().floatProp("scale",_scaleTarget).setEaseType(EaseType.BackOut));
var tween2 = new Tween(this, 1.0f, new TweenConfig().floatProp("alpha",0f).setEaseType(EaseType.QuadInOut));
var chain = new TweenChain();
chain.append(tween1).append(tween2);
chain.setOnCompleteHandler(C=>{
this.RemoveFromContainer();
//Debug.Log("Killed");
}
);
chain.play();
}
//private void destory(){
// this.RemoveFromContainer();
//}
// Update is called once per frame
void Update () {
}
}
寫在後面
接下來的目標是音效音樂的使用,以及近期會被導入的Spine動畫,還有一些gitHub上別人開發給Futile的小外掛。謝謝收看。