UnityでLWFを使ってみた

# はじめに
Unity上でLWFを再生するにあたって
実際にあれこれと試してみたことについて書いていきます。

# 環境

| system | version |
|:-----------|------------:|
| Mac OS X | 10.9 |
| Unity | 4.3.1 |
| LWF | 1.0.2 |
| LWFS | 20131119-1217 |
| Adobe Flash CS6 | 12.0.2.529 |
| TexturePacker | 3.2.1 |

# 準備する
## LWF
本体。これがないと始まらない。

### ダウンロード
https://github.com/gree/lwf
ここから `git clone` もしくはZIPファイルで取得する。

### ビルド
ソースコードのままでも問題ないけど
私は *dll* にしたものを使用しているのでビルドします。

```sh
$ cd lwf/csharp/unity/build
$ ls

Packager
Rakefile

$ rake
$ ls

Packager
Rakefile
lwf.dll
lwf.dll.mdb
lwf_editor.dll
lwf_editor.dll.mdb
```

### Unityプロジェクトにセット

`lwf.dll` `lwf_editor.dll` `lwf/csharp/unity/shaders` をそれぞれ下記のように配置する。

```
Assets/
   Editor/
      lwf_editor.dll
   Plugins/
      lwf.dll
   Resources/
      shaders/*.shader
```

以前のバージョンでは
*lwf.dll* -> *lwf_editor.dll* の順番でimportしないとUnityがクラッシュしていたが
今は大丈夫そう。

ソースコードのまま使用する場合は下記にある *unitypackage* を利用するとインポートが楽。

```
lwf/csharp/unity/dist/lwf.unitypackage
```

## LWFS
LWFへの変換と確認を簡単にしてくれるツール。

### ダウンロード
http://code.google.com/p/lwfs/downloads/list
ここから環境にあったものを取得する

私はMacを使用しているので *LWFS-osx-20131119-1217.zip* を選択。

### インストール

Applicationディレクトリに置くだけ。

```sh
$ unzip LWFS-osx-20131119-1217.zip
$ mv LWFS /Applications/
```

*README* に書かれていますが
*OS X Marvericks* を使用している場合は下記を実行する必要があるようです。

```sh
$ xattr -d -r com.apple.quarantine /Applications/LWFS
```

これをしないと壊れている的なメッセージが出ました。

### 起動

`start.app`をダブルクリック。もしくは下記コマンドを実行する。

```sh
open /Applications/LWFS/start.app
```

デスクトップに変換元と変換後の出力ディレクトリが作成される。

```
~/Desktop/LWFS_work			# 変換元ファイル置き場
~/Desktop/LWFS_work_output	# LWF変換後の出力先
```

また、起動と同時にLWF用のパブリッシュが可能な
`Publish for LWF`コマンドがFlashにインストールされる。

インストール先

```
~/Library/Application\ Support/Adobe/Flash\ CS6/ja_JP/Configuration/Commands/Publish\ for\ LWF.jsfl
```

## アニメーションデータ作成
### Flashムービー作成
下記点に気をつけながら作成する。

* Flashのパブリッシュターゲットは *Flash Player 7* にする

 > *Publish for LWF* が調整してくれるようです！

* シェイプは使えないので画像ファイルにする

* <del>ムービークリップの回転はできるがムービークリップ内の画像を回転させるとLWF変換時にエラーとなる</del>
 > 現在の *swf2lwf.rb* では改善されているようです！

* ActionScriptは使用不可(stop,playなどの簡単なアクションは使用可能)
   * 但し `mc.gotoAndStop()` のようにムービークリップを指定した操作はできない

 > `tellTarget()` が使えるそうです！これで代替可能ですね。
 > 
 > ```as
tellTarget("mc") {
      gotoAndStop();
}
tellTarget("..") {
      gotoAndStop();
}
```

* トゥイーンはクラシックトゥイーンのみ使用可能
* マスクは使用できない
* ビットマッププロパティの圧縮は「劣化なし」にする
   * 「劣化なし」でない場合 ERROR: Bitmap is as JPEG. Bitmap should be as 'Loss-less' となる

 > *Publish for LWF* が調整してくれるようです！

* ムービークリップへの着色はできない。明度の設定は可能。
   * 詳細カラー設定の乗算値によって代用はできるかもしれない。但し、加算値は'0'としておくこと
      * 使用できるようにする方法はある


### パブリッシュ
コマンド -> `Publish for LWF`

* パブリッシュ前

 ```
 test/
 └─ test.fla
 ```

* パブリッシュ後

 ```
 test/
 ├─ test.fla
 ├─ test.swf
 └─ test.bitmap/*.png
 ```

## FlashをLWFに変換
### LWFSを使用する場合

swfやbitmapが入ったディレクトリを `~/Desktop/LWFS_work` に置くと
`~/Desktop/LWFS_work_output/unity` にLWFデータが出力される。

```sh
$ mv test ~/Desktop/LWFS_work
$ ls ~/Desktop/LWFS_work_output/unity

test
```

### 手動で行う場合
現バージョンでは *ruby 1.9.3* 以降が必要。
要求rubyバージョンは今後変わる可能性が高いので *rbenv* かなんかで
複数バージョン扱えるようにしておくと良いかも。

```sh
# LWFに変換
$ ruby lwf/tools/swf2lwf/swf2lwf.rb test.swf


# 不要なファイルを削除（情報としては有用）
$ cd test.lwfdata/
$ ls |grep -v -E "\.lwf|\.png" |xargs rm

# 拡張子変更
$ mv test.lwf test.bytes

# Publish時に作成された画像ファイルをもってくる
$ mv ../test.bitmap/*.png ./
```

## LWFデータをUnityプロジェクトにインポートする
下記のように *Resources* の中に入れる。
管理しやすいように *LwfData* を用意したがResources直下でも可。

```
Assets/
   Resources/
      LwfData/
         test/
```

# 動かしてみる
## Unity上で動かす
LWFObjectを継承したクラスを用意する。

```C#:LwfSampleObject.cs
using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class LwfSampleObject : LWFObject {
	void Start() {
		string dir = System.IO.Path.GetDirectoryName(lwfName);
		if (dir.Length > 0) {
			dir += "/";
		}
		if (Application.isEditor) {
			UseDrawMeshRenderer();
		}

		Load(lwfName, dir);
	}
}
```
空のGameObjectを作成し上記スクリプトを `Add Component` する。
*Lwf Name* パラメータにbytesファイル名 `LwfData/test/test` を拡張子なしでセット。

再生用のスクリプトを用意し、GameObjectに `Add Component` すると再生されます。

```C#:Play.cs
using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
public class Play : MonoBehaviour {
	public LwfSampleObject lwfObject;

	void Start() {
		lwfObject.GotoAndPlayMovie("_root", 1, true);
	}
}
```

### Unity上での表示位置について
Flashのステージサイズを640x960とした場合、GameObjectのx,yはそれぞれ半分の-320,480とするとカメラの中心に表示される

## Unity側からムービークリップを操作する
* フレーム番号で指定

 ```C#:LwfSampleObject.cs
this.GotoAndPlayMovie("_root", 1, true);
```

* ラベルで指定

 ```C#:LwfSampleObject.cs
this.GotoAndPlayMovie("_root", "label", true);
```

Flash側の組み方として
*_root* には1フレームにアニメーションの1シーンを置く形の方が扱いやすいので
下記のような呼び方をよく使用しています。

```C#:LwfSampleObject.cs
	public void PlayScene() {
		this.GotoAndPlayMovie("_root", "scene1", true);
		this.GotoAndPlayMovie("scene1", "part1", true);
	}
```

## ムービークリップからUnityに通知する

アニメーションの特定の位置でUnity側に処理をさせたい場合
例えば指定の位置で音を鳴らす等
その場合、ムービークリップ内に `fscommand()` を入れることで通知できます。

```actionscript
// "event”は固定。第二引数にイベント通知名を入れる。
fscommand("event", "sendMessage");
```

Unity側でイベント通知を受けるには `SetEventHandler()` を使い
第一引数に受信したいイベント通知名を入れる。

```C#:LwfSampleObject.cs
	void Start() {
		// …
		Load(lwfName, dir);

		SetEventHandler("sendMessage", (movie, button) => {
			// ここにイベント通知された時に行いたい処理を書く
		});
		SetEventHandler("animationEndMessage", (movie, button) => {
			// 受信したいイベントが複数ある場合はその分定義する。
		});
	}
```

これらイベント通知機能とラベルによるフレーム移動があれば大抵のことは実現できると思います。

## テクスチャ入れ替え
Unity側からLWFのテクスチャを入れ替えるために *LwfSampleObject* に
*ChangeTexture* メソッドを追加する。

```C#:LwfSampleObject.cs
	public void ChangeTexture(Texture2D newTexture, string targetTextureName) {
		var cache = LWF.UnityRenderer.ResourceCache.SharedInstance().textureCache;
		if (cache != null) {
			if (cache.ContainsKey(targetTextureName)) {
				var cache_item = cache[targetTextureName];
				var tex_context = cache_item.Entity();
				tex_context.material.mainTexture = newTexture;
			}
		}
	}
```

使い方: *sample.png* を別のテクスチャに入れ替える場合

```c#
ChangeTexture(newTexture, "test/LwfData/test/sample");
```
第二引数: LWF名 + テクスチャファイルパス（拡張子なし）

## テクスチャ入れ替え時の注意点
同じLWFを複数箇所で同時に再生している場合
テクスチャを入れ替えるとすべてのLWFのテクスチャが入れ替わります。
特定のLWFのテクスチャのみ入れ替えたい場合はFlash側で予めテクスチャを分けておいてあげる必要があります。

## 画像をTexturePackerでまとめる
### 1. パブリッシュ
コマンド -> `Publish for LWF`

### 2. テクスチャアトラス作成
*TexturePacker* で画像をまとめてjson形式で出力する。

コマンドラインでやる場合は下記

```sh
/Applications/TexturePacker.app/Contents/MacOS/TexturePacker --scale 0.93750 --format json --data test.json --sheet "texture.png" test/test.bitmap/*.png
```

*scale* については各自調整してください。

### 3. LWFデータ作成
jsonファイルを指定して変換処理を行う。

```sh
# LWFに変換
$ ruby lwf/tools/swf2lwf/swf2lwf.rb test.swf test.json
```

後の処理は以前と同じです。

## 複数テクスチャアトラスを使用する
1枚に収まりきらなかった場合は複数のjsonを指定することで可能。

```sh
# LWFに変換
$ ruby lwf/tools/swf2lwf/swf2lwf.rb test.swf test1.json test2.json
```

## テクスチャアトラス使用時のテクスチャ入れ替え
テクスチャアトラスを使用すると以前のやり方でのテクスチャ入れ替えができないため
入れ替えを行うテクスチャだけアトラスとは別に個別で持たせました。

その場合特に何か処理をする必要はなく
jsonに含まれていないテクスチャは勝手に個別扱いにしてくれます。

### 入れ替え用テクスチャについて
そのまま使用はしないため何も描かれていない画像を使用。
さらに無駄を省くため下記もかけています。

```sh
pngquant --ext .png --force 256 ./*.png
```

## 加算合成が使いたい
アニメーションのクオリティを上げるためにやっぱり加算合成がしたい！
と言われたのでデフォルトでは使えない加算合成をできるようにしてみます。
とはいっても、LWFには元々加算合成の機能が組み込まれているのでそれを利用するだけですが！
（速度が遅くなるためデフォルトではオフになっているそうです。）

### 加算合成機能を有効にする
機能を有効にしたdllに作り直す。

```sh
$ cd lwf/csharp/unity/build
$ vim Rakefile
$ rake
```

Rakefileの変更箇所

```diff:Rakefile
 desc "build"
 task :build do
   puts "lwf.dll:"
-  system "#{MONO_BIN}/gmcs #{LIBS} #{MONO_OPTS} -out:lwf.dll #{SRCS.join(' ')}"
+  system "#{MONO_BIN}/gmcs #{LIBS} #{MONO_OPTS} -out:lwf.dll #{SRCS.join(' ')} -define:LWF_USE_ADDITIONALCOLOR"
   puts "lwf_editor.dll:"
-  system "#{MONO_BIN}/gmcs #{EDITOR_LIBS} #{MONO_OPTS} -out:lwf_editor.dll #{EDITOR_SRCS.join(' ')}"
+  system "#{MONO_BIN}/gmcs #{EDITOR_LIBS} #{MONO_OPTS} -out:lwf_editor.dll #{EDITOR_SRCS.join(' ')} -define:LWF_USE_ADDITIONALCOLOR"
 end
```

### 加算合成用にシェーダーを書き換える
既存のシェーダーでは加算値は反映されないので
*LWFPreMultipliedAlpha.shader* をいじります。

```diff:LWFPreMultipliedAlpha.shader
Shader "LWF/PreMultipliedAlpha" {
	Properties {
+		_AdditionalColor ("AdditionalColor", Color) = (0, 0, 0, 0)
		_Color ("Color", Color) = (1, 1, 1, 1)
		_MainTex ("Texture", 2D) = "white" {}
	}

	SubShader {
		Tags {
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
		}
		Cull Off
		ZWrite Off
		Blend One OneMinusSrcAlpha
		Pass {
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			sampler2D _MainTex;
			uniform half4 _MainTex_ST;
			fixed4 _Color;
+			fixed4 _AdditionalColor;
			struct v2f {
				float4 pos: SV_POSITION;
				float2 uv: TEXCOORD0;
				fixed4 color: COLOR;
			};
			v2f vert(appdata_full v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
				o.color = v.color * _Color;
				return o;
			}
			fixed4 frag(v2f i): COLOR0
			{
+				fixed4 color_result = tex2D(_MainTex, i.uv.xy) * i.color;
+				if (color_result[0] != 0) {
+					color_result += _AdditionalColor;
+				}
+				return color_result;
-				return tex2D(_MainTex, i.uv.xy) * i.color;
			}
			ENDCG
		}
	}
}
```

# おわりに
LWFは簡単にアニメーションを複数プラットフォームで動かすことができる良いツールだと思います。
ただ情報がまだまだ少なくて結構大変でした。
この記事が少しでもこれから使う方への参考になれば嬉しいです。

ちなみにどこかおかしい点等あればご指摘頂けると助かります！
色々試しながら使っていたので使い方が違うものもあるかもしれません。。

# 謝辞
@splhack さんにアドバイス頂きました。ありがとうございます！
https://gist.github.com/splhack/8006982