大学の時にブラウザ上で3Dモデルに絵を描くアプリケーションを作りました。
長い間ブログを放置していたので、それの紹介でもしたいと思います。
まずブラウザで3Dを扱うというのはあまりない話です。重いですし。
下のリンクでは描いたテクスチャを3Dモデルに貼り付けるようなことをやっていました。
http://labs.logosware.com/archives/21
なんやかんやあってFlashで作るのがいいだろうって話になって、そこからは自分がいろいろ試して作っていくことになりました。
Flashもそんな詳しいわけではなかったし、3Dは全く分からないというぐらいだったので開発には苦労しました。
ブログの過去記事などを見ると試行錯誤の跡が少し見られると思います。
まず3Dモデルをどうするかという話でしたが、開発メンバーが有料のモデリングソフトもってない、Flashの3Dライブラリでサポートされてるといった点などを考えた結果、Collada(.xml)形式にしました。
で、3Dライブラリは当時(今でも?)一番メジャーなPapervision3Dにしようと考えていました。
http://blog.r3c7.net/?p=333
上の記事を参考にサンプルを作っていきました。3Dモデルの作成や、マウスイベントなど苦労しましたがなんとかそれっぽいものはできました。
じゃあ次はシェーダーを試そうと思った時に問題が起きました。
http://aaharu.blogspot.com/2009/10/pv3duv.html
UVマッピングを使用したモデルにシェーダーを適用すると黒い線が出てきました。
これの解決方法は結局わからず、Papervision3D以外のライブラリではどうなのか試してみることにしました。
で、試したのがAway3Dです。
まあFlashの3Dライブラリといってもメジャーなのは、Papervision3DとAway3DとAlternativa3Dぐらいでしたし、Papervision3Dから派生したものだと聞いて今までのが移植しやすいかなーと。
それで試した結果、Away3Dは問題なく動作することがわかり、3DライブラリはAway3Dを使用することに決めました。
Away3Dにして良かった点はいくつかありました。
・上の黒い線の問題が解決したこと
・3Dマウスイベントが豊富なこと
・シェーダーなども豊富で、きれいであったこと
絵を描くということを考えると3Dのオブジェクトのマウスイベントがあるのはとてもよかったです。
悪いと思った点もいくらかあります。
・重い
・そんなに移植しやすいわけではなかった
とそんな感じでぐだぐだと作りました。
せっかくなので、FlashDevelopで書き直した、一部機能削除版をソースコードごと公開します。(開発時はFlashBuilderを使いました。学生だったので無料ライセンスで)
今見るといろいろと直したくなりますがそこらへんは許してやってください。
Flexアプリ http://www.aaharu.com/data/UruLabCreater.swf
ソースコード(Flex) http://www.aaharu.com/data/srcview_urulabcreater/index.html
ソースコード(ライブラリ)http://www.aaharu.com/data/srcview_urulab/index.html
ソースコード書き出し後に修正行ってるのでソースコード最新じゃないですけどだいたいあってます。
2012年1月8日日曜日
2010年8月4日水曜日
Flex4本
Flex4になってそれなりに時間がたったのでいくつか書籍がでてきました。
まだじっくりとは読んでいないけど簡単な感想など。
FlashBuilder4とかFlex4の新機能について詳しく書いてある感じ。Flex4プログラミング入門ってタイトルだけど、内容は初心者向けというよりは、4より前のFlexを知っている人向けだと感じた。
よく知らない人が読んでも、「これで何ができるの?」と疑問を持ってしまうかも。
また、FB4とサンプルソースが入ったCDがついている。けどCDがついている本って好きじゃないんだ、開きにくくなって読みづらいし。
CDつけるよりは、ウェブからダウンロードできるようなのがいい。
不満点挙げたけど、別にこの本そんなひどいわけじゃないよ。
こちらの本はまだちらっとしか読んでないけど、結構いい感じだった。欲しい。
わかりやすいと思いますし、初心者にも勧められそうな感じ。
Tour de Flexってこの本で知った。便利っすね。
Flex4のStateとかよくわかっていないので、これらの本で勉強して使えるようになりたいなー。
まだじっくりとは読んでいないけど簡単な感想など。
FlashBuilder4とかFlex4の新機能について詳しく書いてある感じ。Flex4プログラミング入門ってタイトルだけど、内容は初心者向けというよりは、4より前のFlexを知っている人向けだと感じた。
よく知らない人が読んでも、「これで何ができるの?」と疑問を持ってしまうかも。
また、FB4とサンプルソースが入ったCDがついている。けどCDがついている本って好きじゃないんだ、開きにくくなって読みづらいし。
CDつけるよりは、ウェブからダウンロードできるようなのがいい。
不満点挙げたけど、別にこの本そんなひどいわけじゃないよ。
こちらの本はまだちらっとしか読んでないけど、結構いい感じだった。欲しい。
わかりやすいと思いますし、初心者にも勧められそうな感じ。
Tour de Flexってこの本で知った。便利っすね。
Flex4のStateとかよくわかっていないので、これらの本で勉強して使えるようになりたいなー。
2010年1月3日日曜日
[Flex]CanvasのaddChild
この間、FlexでCanvasにSimpleButtonをaddChildしようとしたらエラーが出た。
ググるといろいろ対策が出てきます。
Canvas に Sprite や Shape が addChild できない件 - kagamihogeのblog
Flex 3で直接Spriteを使用することはできない? - Memorize
というかなぜaddChild出来ないんでしょうかね。
flash.display以下のものは直接addChildできないのかなー。
上であげたリンク先はUIComponentを使ってこの問題を解決しています。
この方法でも解決出来ますが、Twitterでつぶやいてたら先輩に他の方法を教えてもらった。
CanvasのrawChildrenプロパティにaddChildすれば、Spriteでもエラーはでないとのこと。
これは地味に便利。
rawChildren便利だよ、rawChildren。
なんで解決したのかがよくわかってないのが気持ち悪いですが。
というかこの方法、以前教えてもらったのに忘れてた気がする。
ググるといろいろ対策が出てきます。
Canvas に Sprite や Shape が addChild できない件 - kagamihogeのblog
Flex 3で直接Spriteを使用することはできない? - Memorize
というかなぜaddChild出来ないんでしょうかね。
flash.display以下のものは直接addChildできないのかなー。
上であげたリンク先はUIComponentを使ってこの問題を解決しています。
この方法でも解決出来ますが、Twitterでつぶやいてたら先輩に他の方法を教えてもらった。
CanvasのrawChildrenプロパティにaddChildすれば、Spriteでもエラーはでないとのこと。
これは地味に便利。
rawChildren便利だよ、rawChildren。
なんで解決したのかがよくわかってないのが気持ち悪いですが。
というかこの方法、以前教えてもらったのに忘れてた気がする。
2009年11月21日土曜日
[Away3D]ライトとか。
Away3DでCollada(DAE)オブジェクトにお絵かき、みたいなことをやろうとしたけどやり方がわからなかったので違うものを作ってみました。
Away3Dでクリックした座標をどうやってとってくるのかがよくわからないんだよなー。Pv3DだとVirtualMouseとか使ってたけどどうやるんだろ?
今回はライトの3つの値(ambient, diffuse, specular)をスライダーで変更してみました。
こちら。
いつものようにソースコードは右クリック→「ソースの表示」で見られます。
自分も大学のCG論のOpenGLで少しやったぐらいなのであまり詳しくないですが、ambientは環境光、diffuseは散乱光、specularは反射です。
次はまだよく理解していないカメラの動きを勉強する。
Away3Dでクリックした座標をどうやってとってくるのかがよくわからないんだよなー。Pv3DだとVirtualMouseとか使ってたけどどうやるんだろ?
今回はライトの3つの値(ambient, diffuse, specular)をスライダーで変更してみました。
こちら。
いつものようにソースコードは右クリック→「ソースの表示」で見られます。
自分も大学のCG論のOpenGLで少しやったぐらいなのであまり詳しくないですが、ambientは環境光、diffuseは散乱光、specularは反射です。
次はまだよく理解していないカメラの動きを勉強する。
2009年10月24日土曜日
[Pv3D]UVマッピングの問題

UVマッピングを使用したテクスチャに、Shaderを適用すると出てくる例の黒い線を消すべくいろいろやってみた。
日本語のページを探してもこの問題の解決策を見つけられなかったので、英語で調べてみるとこんなものを見つけました。
まさに自分と同じ現象、しかも解決策書いてある!というわけで早速試してみました。
解決方法が書いてあるのはこの一文。
a fix is to change the shadercomposition mode to per_tri on the shader. This will be slower but remove the seams.意訳すると、「シェーダーのshadercomposition modeをper_triにすれば、重くなるけど線消えるよ。」って感じでしょうか?
shadercomposition modeってのは、ShadedMaterialのプロパティにshaderCompositeModeというのがあるので多分これのことでしょう。
shaderCompositeModeのデフォルトの値は0。
また、ShaderCompositeModesに次のような記述があります。
public static var PER_LAYER:int = 0; public static var PER_TRIANGLE_IN_BITMAP:int = 1;per_triというのは、おそらくこのPER_TRIANGLE_IN_BITMAPのこと。
というわけで、
var material:ShadedMaterial = new ShadedMaterial(bitmapmaterial, shader, 1);という風に記述しました。3つ目の引数がshaderCompositeModeです。
こちらが作ったswfファイルです。重いので反応遅れます。
ソースコードは右クリックで見られます。
他の目的で作ったものをブログ用に適当に改変してるので非効率的な書き方になってます。
上のファイルを見てもらえればわかりますが、この方法を試しても駄目でした。
例の線は消えてはいるようですが、それとはまた別のものが表示されるようになってしまいました。
原因はよくわかりません。
自分は解決しませんでしたが、これで解決する場合もあるかもしれません。
2009年9月8日火曜日
[Flex]Flexでスライドパズル
2009年9月3日木曜日
[Pv3D]Collada(.dae)にShader試してみた[AS3.0]
Papervision3DでShader試してみました。
Shaderってのは、3Dで光、それによって発生する影を表現するもの、と思っておけばいいです。自分も詳しくは知りません。
やはり影がないと3Dって感じがしないですよね。ベタ塗りだと奥行きとかわからないですし。
このようになりました。
ソースは右クリックで見てください。
おー、一気に3Dっぽくなったー。
PhongShaderだと重いので、今回はGouraudShaderを使用しました。マシンによっては重いかも。
まぁ見ればわかりますが、問題があります。
表面に黒い線のツギハギができてしまっています。
原因はテクスチャマッピング関係だと思います。
このサンプルのモデル、テクスチャ画像は下のようになっています。

つまりこの一枚の画像を、テクスチャマッピング(割り当て)をして、モデルに対応させて表示しています。
おそらく、この問題の原因は、テクスチャの継ぎ目のところに影を生成してしまってるからだと思います。
この問題を解決して綺麗に表示したいのですが方法がわかりません。
さてこれはどうしたものか・・・
--------------------------------------
追記
解決しませんでしたが、こちらで黒い線の問題を取り上げました。
さらに追記
別のライブラリを使って問題を解決してみました。
こちら。
Shaderってのは、3Dで光、それによって発生する影を表現するもの、と思っておけばいいです。自分も詳しくは知りません。
やはり影がないと3Dって感じがしないですよね。ベタ塗りだと奥行きとかわからないですし。
このようになりました。
ソースは右クリックで見てください。
おー、一気に3Dっぽくなったー。
PhongShaderだと重いので、今回はGouraudShaderを使用しました。マシンによっては重いかも。
まぁ見ればわかりますが、問題があります。
表面に黒い線のツギハギができてしまっています。
原因はテクスチャマッピング関係だと思います。
このサンプルのモデル、テクスチャ画像は下のようになっています。

つまりこの一枚の画像を、テクスチャマッピング(割り当て)をして、モデルに対応させて表示しています。
おそらく、この問題の原因は、テクスチャの継ぎ目のところに影を生成してしまってるからだと思います。
この問題を解決して綺麗に表示したいのですが方法がわかりません。
さてこれはどうしたものか・・・
--------------------------------------
追記
解決しませんでしたが、こちらで黒い線の問題を取り上げました。
さらに追記
別のライブラリを使って問題を解決してみました。
こちら。
2009年8月14日金曜日
ちょっと遊び
Parpervision3Dの2.1が公開されてますね。
DAEまわりが改善されてるようで嬉しいです。
変えてみたら以前はエラーが出てたものが動くようになったりしました。
というわけでちょっと更新。
内容自体は前のとほとんど変わりません。
初音ミクのモデルデータに落書きをできるようにしてみた。→■
かなーり適当です。左右対称に描かれてしまうところが多いです。
ちなみに、このプログラムはPv3Dが2.0だとエラーが出て動きません。2.1だと動いたので公開してみました。
モデルのデータはこちらからいただきました。謝々。
DAEまわりが改善されてるようで嬉しいです。
変えてみたら以前はエラーが出てたものが動くようになったりしました。
というわけでちょっと更新。
内容自体は前のとほとんど変わりません。
初音ミクのモデルデータに落書きをできるようにしてみた。→■
かなーり適当です。左右対称に描かれてしまうところが多いです。
ちなみに、このプログラムはPv3Dが2.0だとエラーが出て動きません。2.1だと動いたので公開してみました。
モデルのデータはこちらからいただきました。謝々。
2009年7月16日木曜日
[Pv3D][Flex]Collada(.dae)にお絵かき[AS3.0]
これまた前回の続きです。
こんな感じです。
現時点で不具合は多いですが、基本の動作としてはこんな感じかな~と・・・
右下の「編集する」を選択すると編集モードに入ります。
[PV3D2.0] virtualMouse axis on DAE
Flash Player 10でPV3D上の落書きを保存してみる
↑2つを参考にし、matsu4512さんに助けてもらいながら作りました。
ソースコード
demo.mxml World3D.as
現時点でお絵かきが上手くいってない原因はここらへんが原因かなー。
---
お絵かきをするにはクリックしたマテリアルの情報を得る必要があるのですが次のように行ってます。
mesh1っていうDisplayObject3Dからマテリアルの名前をどうやって取得するかわからなかったので、Flex Builderの補完機能をつかってそれっぽいのを片っ端から試してたら、
『e.renderHitData.material.name』で取得することができることが判明。
この"renderHitData"って調べてもあまり情報がないんですよね~。
まあ問題なく(?)動いてるので大丈夫かな。
ちなみに、「getChildByName("Model").getChildByName("mesh1")」の"Model"と"mesh1"は
daeファイルのこの部分の『node』の名前からきてます。
mesh1のほかにもmesh2とかがありますが、マテリアルが含まれてるのはmesh1のほうなのでそっちです。
書き出したソフトによって多少違いがあるとは思いますが他も同じようなもののはず。
こんな感じです。
現時点で不具合は多いですが、基本の動作としてはこんな感じかな~と・・・
右下の「編集する」を選択すると編集モードに入ります。
[PV3D2.0] virtualMouse axis on DAE
Flash Player 10でPV3D上の落書きを保存してみる
↑2つを参考にし、matsu4512さんに助けてもらいながら作りました。
ソースコード
demo.mxml World3D.as
現時点でお絵かきが上手くいってない原因はここらへんが原因かなー。
material = new MovieMaterial(movieClip, false, true, false, new Rectangle(0, 0, bmpData.width, bmpData.height));
---
お絵かきをするにはクリックしたマテリアルの情報を得る必要があるのですが次のように行ってます。
private function modelComplete(e:FileLoadEvent):void {
var dae_rootNode:DisplayObject3D = dae.getChildByName("COLLADA_Scene");
var targetMesh:DisplayObject3D = dae_rootNode.getChildByName("Model").getChildByName("mesh1");
targetMesh.addEventListener(InteractiveScene3DEvent.OBJECT_MOVE, drawModel);
}
private function drawModel(e:InteractiveScene3DEvent):void {
if(isModelOver) {
targetMaterial = int(e.renderHitData.material.name);
}
}
mesh1っていうDisplayObject3Dからマテリアルの名前をどうやって取得するかわからなかったので、Flex Builderの補完機能をつかってそれっぽいのを片っ端から試してたら、
『e.renderHitData.material.name』で取得することができることが判明。
この"renderHitData"って調べてもあまり情報がないんですよね~。
まあ問題なく(?)動いてるので大丈夫かな。
ちなみに、「getChildByName("Model").getChildByName("mesh1")」の"Model"と"mesh1"は
<library_visual_scenes>
<visual_scene id="SketchUpScene" name="SketchUpScene">
<node id="Model" name="Model">
<node id="mesh1" name="mesh1">
<instance_geometry url="#mesh1-geometry">
<bind_material>
<technique_common>
<instance_material symbol="material0" target="#material0ID">
<bind_vertex_input semantic="UVSET0" input_semantic="TEXCOORD" input_set="0"/>
</instance_material>
<instance_material symbol="material1" target="#material1ID">
<bind_vertex_input semantic="UVSET0" input_semantic="TEXCOORD" input_set="0"/>
</instance_material>
中略
<instance_material symbol="material6" target="#material6ID">
<bind_vertex_input semantic="UVSET0" input_semantic="TEXCOORD" input_set="0"/>
</instance_material>
</technique_common>
</bind_material>
</instance_geometry>
</node>
<node id="mesh2" name="mesh2">
<instance_geometry url="#mesh2-geometry">
<bind_material>
<technique_common>
<instance_material symbol="ForegroundColor" target="#ForegroundColorID"/>
</technique_common>
</bind_material>
</instance_geometry>
</node>
中略
</visual_scene>
</library_visual_scenes>
daeファイルのこの部分の『node』の名前からきてます。
mesh1のほかにもmesh2とかがありますが、マテリアルが含まれてるのはmesh1のほうなのでそっちです。
書き出したソフトによって多少違いがあるとは思いますが他も同じようなもののはず。
2009年7月3日金曜日
[Pv3D][Flex]Collada(.dae)のマテリアル張り替え
前回の続き?
前回3DギャラリーからとってきたCollada形式のデータはテクスチャの枚数が多かったので今回は自分でGoogle SketchUpを使ってモデルを作ってみました。形はかなり適当ですが・・・
動作サンプル
ソースコードは右クリックで表示できます。
背景が白いところでドラッグ&ドロップで視点移動、マウスホイールでズームイン&アウトします。
ズームは正確にはズームではなく、カメラごと対象に近づく(or遠ざける)ようになってます。
「ロード」ボタンを押してから、「はりかえ」ボタンを押すとテクスチャがランダムに変わります。
エラーが出ると思うので、「ロード」を押す前に「はりかえ」を押さないでください。
DAEに渡すマテリアルリストの作成は次のようにやってます。
myMaterialsの中身の『material0: material0』ですが、
これは、『Colladaファイルのマテリアルの名前: 上で宣言しているBitmapMaterial型の変数material0』です。名前同じでわかりづらいですね、すみません。
で、「Colladaファイルのマテリアルの名前」はCollada(DAE)ファイルを調べればわかります。
ColladaってのはXMLで書かれているので、適当なテキストエディタで開くと、次のようになっています。
書き出したソフトによって記述に多少の違いはありますが、『<material id="material0ID" name="material0">』のnameがマテリアルの名前になります。
あとは、
と書けばロードされます。
普通はCollada自体でもテクスチャを指定してありますが、これで渡したマテリアルリストのほうが優先されるっぽいです。
マテリアルの張り替えには「replaceMaterialByName」を使ってます。こんな感じで。
今回参考にさせてもらったサイト
http://pv3d.org/2008/12/26/switching-a-moviematerial-on-the-face-of-a-cube-replacematerialbyname/
http://d.hatena.ne.jp/matsu4512/20090701
前回3DギャラリーからとってきたCollada形式のデータはテクスチャの枚数が多かったので今回は自分でGoogle SketchUpを使ってモデルを作ってみました。形はかなり適当ですが・・・
動作サンプル
ソースコードは右クリックで表示できます。
背景が白いところでドラッグ&ドロップで視点移動、マウスホイールでズームイン&アウトします。
ズームは正確にはズームではなく、カメラごと対象に近づく(or遠ざける)ようになってます。
「ロード」ボタンを押してから、「はりかえ」ボタンを押すとテクスチャがランダムに変わります。
エラーが出ると思うので、「ロード」を押す前に「はりかえ」を押さないでください。
DAEに渡すマテリアルリストの作成は次のようにやってます。
var material0:BitmapMaterial = new BitmapFileMaterial("model3/images/texture0.jpg");
var material1:BitmapMaterial = new BitmapFileMaterial("model3/images/texture1.jpg");
var material2:BitmapMaterial = new BitmapFileMaterial("model3/images/texture2.jpg");
var material3:BitmapMaterial = new BitmapFileMaterial("model3/images/texture3.jpg");
var material4:BitmapMaterial = new BitmapFileMaterial("model3/images/texture4.jpg");
var material5:BitmapMaterial = new BitmapFileMaterial("model3/images/texture5.jpg");
var material6:BitmapMaterial = new BitmapFileMaterial("model3/images/texture6.jpg");
var material7:BitmapMaterial = new BitmapFileMaterial("model3/images/texture7.jpg");
var material8:BitmapMaterial = new BitmapFileMaterial("model3/images/texture8.jpg");
var material9:BitmapMaterial = new BitmapFileMaterial("model3/images/texture9.jpg");
var myMaterials:Object = {
material0: material0,
material1: material1,
material2: material2,
material3: material3,
material4: material4,
material5: material5,
material6: material6,
material7: material7,
material8: material8,
material9: material9
}
texture = new MaterialsList(myMaterials);
myMaterialsの中身の『material0: material0』ですが、
これは、『Colladaファイルのマテリアルの名前: 上で宣言しているBitmapMaterial型の変数material0』です。名前同じでわかりづらいですね、すみません。
で、「Colladaファイルのマテリアルの名前」はCollada(DAE)ファイルを調べればわかります。
ColladaってのはXMLで書かれているので、適当なテキストエディタで開くと、次のようになっています。
<library_images>
<image id="material0-image" name="material0-image">
<init_from>../images/texture0.jpg</init_from>
</image>
<image id="material1-image" name="material1-image">
<init_from>../images/texture1.jpg</init_from>
</image>
中略
<image id="material8-image" name="material8-image">
<init_from>../images/texture8.jpg</init_from>
</image>
<image id="material9-image" name="material9-image">
<init_from>../images/texture9.jpg</init_from>
</image>
</library_images>
<library_materials>
<material id="material0ID" name="material0">
<instance_effect url="#material0-effect"/>
</material>
<material id="FrontColorNoCullingID" name="FrontColorNoCulling">
<instance_effect url="#FrontColorNoCulling-effect"/>
</material>
<material id="material1ID" name="material1">
<instance_effect url="#material1-effect"/>
</material>
<material id="material2ID" name="material2">
<instance_effect url="#material2-effect"/>
</material>
中略
<material id="material8ID" name="material8">
<instance_effect url="#material8-effect"/>
</material>
<material id="material9ID" name="material9">
<instance_effect url="#material9-effect"/>
</material>
<material id="ForegroundColorID" name="ForegroundColor">
<instance_effect url="#ForegroundColor-effect"/>
</material>
</library_materials>
書き出したソフトによって記述に多少の違いはありますが、『<material id="material0ID" name="material0">』のnameがマテリアルの名前になります。
あとは、
dae.load("○○.dae", 作成したマテリアルリスト);
と書けばロードされます。
普通はCollada自体でもテクスチャを指定してありますが、これで渡したマテリアルリストのほうが優先されるっぽいです。
マテリアルの張り替えには「replaceMaterialByName」を使ってます。こんな感じで。
var material:BitmapMaterial = new BitmapFileMaterial("model3/images/texture" + Math.round(Math.random()*9) + ".jpg");
dae.replaceMaterialByName(material, "material0");
今回参考にさせてもらったサイト
http://pv3d.org/2008/12/26/switching-a-moviematerial-on-the-face-of-a-cube-replacematerialbyname/
http://d.hatena.ne.jp/matsu4512/20090701
2009年5月17日日曜日
[Flex][AIR]ポケベル暗号変換Ver.0.1
2009年5月3日日曜日
[Flex][AIR]15パズルVer.0.15

前回の続きです。
Ver.0.1にエフェクトを追加しました。連打しまくるとおかしくなるかも?
それと、ピース(Piece)の綴りを間違えていたのを修正しました。
白く塗りつぶしていた最後のピースを横のほうに表示させるようにしました。
イメージとしては、おもちゃの「できるんです」をイメージ。
次回あたりに大きい画像を縮小して表示させるようにする予定。
ソースコード
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" nativeDragEnter="onDragEnter(event);" nativeDragDrop="onDragDrop(event);" width="600" height="500" viewSourceURL="srcview/index.html">
<mx:MenuBar labelField="@label" itemClick="useFile(event)" width="100%" id="myMenu">
<mx:dataProvider>
<mx:XMLList>
<Menu label="操作">
<MenuItem label="スタート" />
</Menu>
</mx:XMLList>
</mx:dataProvider>
</mx:MenuBar>
<mx:Image id="myImage" width="100%" height="100%" />
<mx:Move id="myMove" easingFunction="Back.easeInOut" duration="350" />
<mx:Script>
<![CDATA[
import mx.effects.easing.*;
import mx.controls.Alert;
import mx.events.MenuEvent;
// 画像のロード判定用
private var loadEnd:Boolean = false;
private var file_ary:Array;
// 1ピースの幅
private var pieceWidth:Number;
// 1ピースの高さ
private var pieceHeight:Number;
// 空ピースの番号
private var empty:uint;
// 完成確認用配列
private var order:Array = new Array();
private var base:Image;
// シャッフルしたかどうか
private var random:Boolean = false;
// メニュー用の関数
private function useFile(e:MenuEvent):void {
if(e.label == "スタート") {
shuffle();
}
}
// ドラッグ&ドロップでロード
private function onDragEnter(event:NativeDragEvent):void {
trace("entered");
var cb:Clipboard = event.clipboard;
if(cb.hasFormat(ClipboardFormats.FILE_LIST_FORMAT)) {
// ドロップを許可
NativeDragManager.acceptDragDrop(this);
}
}
private function onDragDrop(event:NativeDragEvent):void {
if(loadEnd) {
for(var i:uint = 0; i <= 15; i++) {
myImage.removeChild(myImage.getChildByName(String(i)));
}
myImage.removeChild(myImage.getChildByName(String(16)));
}
var cb:Clipboard = event.clipboard;
file_ary = cb.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
trace("ファイル名:" + File(file_ary[0]).name);
LoaderRequest();
}
private function LoaderRequest():void {
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, LoadCompleted);
var request:URLRequest = new URLRequest(File(file_ary[0]).url);
loader.load(request);
}
private function LoadCompleted(event:Event):void {
// 画像はBitmapでロード
var bitmapImage:Bitmap = Bitmap(event.target.loader.content);
empty = 15;
pieceWidth = bitmapImage.width / 4;
pieceHeight = bitmapImage.height / 4;
// ピース並べ
for(var i:uint = 0; i < 15; i++) {
// ピースと同じ大きさのビットマップをつくる
var newPieceBitmap:Bitmap = new Bitmap(new BitmapData(pieceWidth, pieceHeight));
// ロードした画像から新しく作ったビットマップにコピー
newPieceBitmap.bitmapData.copyPixels(bitmapImage.bitmapData, new Rectangle((i % 4) * pieceWidth, Math.floor(i / 4) * pieceHeight, pieceWidth, pieceHeight), new Point(0, 0));
// ピースの描画
var newPiece:Sprite = new Sprite();
newPiece.addChild(newPieceBitmap);
base = new Image();
base.addChild(newPiece);
base.width = pieceWidth;
base.height = pieceHeight;
base.x = (i % 4) * (pieceWidth + 3) + 20;
base.y = Math.floor(i / 4) * (pieceHeight + 3) + 30;
base.name = String(i);
base.addEventListener(MouseEvent.CLICK, click);
myImage.addChild(base);
}
// 空ピースは白い長方形
var emptyPiece:Sprite = new Sprite();
emptyPiece.graphics.beginFill(0xffffff);
emptyPiece.graphics.drawRect(0, 0, pieceWidth, pieceHeight);
emptyPiece.graphics.endFill();
base = new Image();
base.addChild(emptyPiece);
base.width = pieceWidth;
base.height = pieceHeight;
base.x = 3 * (pieceWidth + 3) + 20;
base.y = 3 * (pieceHeight + 3) + 30;
base.name = String(15);
myImage.addChild(base);
var lastPieceBitmap:Bitmap = new Bitmap(new BitmapData(pieceWidth, pieceHeight));
lastPieceBitmap.bitmapData.copyPixels(bitmapImage.bitmapData, new Rectangle(3 * pieceWidth, 3 * pieceHeight, pieceWidth, pieceHeight), new Point(0, 0));
var lastPiece:Sprite = new Sprite();
lastPiece.addChild(lastPieceBitmap);
base = new Image();
base.addChild(lastPiece);
base.width = pieceWidth;
base.height = pieceHeight;
base.x = 4 * (pieceWidth + 3) + 20;
base.y = 3 * (pieceHeight + 3) + 30;
base.name = String(16);
myImage.addChild(base);
// 完成判定のためIndexをセット
for(var j:uint = 0; j < 17; j++) {
myImage.setChildIndex(myImage.getChildByName(String(j)), j);
}
// ピクチャの大きさに合わせてウィンドウの大きさを変更
nativeWindow.width = bitmapImage.width + pieceWidth + 60;
nativeWindow.height = bitmapImage.height + 105;
loadEnd = true;
}
// ランダムシャッフル
private function shuffle():void {
var count:uint = 8888;
var pieceNumber:uint;
random = true;
for(var i:uint = 0; i < count; i++)
{
pieceNumber = Math.floor(Math.random() * 15);
if(checkMove(pieceNumber))
{
pieceMove(pieceNumber, random);
}
}
}
// ピースクリック時の処理
private function click(event:MouseEvent):void {
//ピースの番号
var pieceNumber:int = event.currentTarget.name;
trace("click:" + pieceNumber);
if(checkMove(pieceNumber)) {
pieceMove(pieceNumber, false);
}
}
// ピースが動かせるかチェック
private function checkMove(pieceNumber:int):Boolean {
var result:Boolean = false;
if((pieceNumber == (empty - 4)) || (pieceNumber == (empty + 4)))
result = true;
else if(((pieceNumber % 4) != 0) && ((pieceNumber - 1) == empty))
result = true;
else if(((pieceNumber % 4) != 3) && ((pieceNumber + 1) == empty))
result = true;
return result;
}
// ピース移動
private function pieceMove(pieceNumber:int, random:Boolean):void {
var oldEmpty:int = empty;
empty = pieceNumber;
if(!random) {
moveEffect(pieceNumber, oldEmpty);
} else {
myImage.getChildByName(String(oldEmpty)).x = (pieceWidth + 3) * (pieceNumber % 4) + 20;
myImage.getChildByName(String(oldEmpty)).y = (pieceHeight + 3) * Math.floor(pieceNumber / 4) + 30;
myImage.getChildByName(String(pieceNumber)).x = (pieceWidth + 3) * (oldEmpty % 4) + 20;
myImage.getChildByName(String(pieceNumber)).y = (pieceHeight + 3) * Math.floor(oldEmpty / 4) + 30;
}
myImage.getChildByName(String(oldEmpty)).name = String(pieceNumber);
myImage.getChildByName(String(pieceNumber)).name = String(oldEmpty);
indexExchange(pieceNumber);
}
private function moveEffect(fromNumber:int, toNumber:int):void {
myMove.target = myImage.getChildAt(myImage.getChildIndex(myImage.getChildByName(String(fromNumber))));
if(fromNumber == 16) {
myMove.duration = 800;
myMove.xFrom = (pieceWidth + 3) * 4 + 20;
myMove.yFrom = (pieceHeight + 3) * 3 + 30;
} else {
myMove.xFrom = (pieceWidth + 3) * (fromNumber % 4) + 20;
myMove.yFrom = (pieceHeight + 3) * Math.floor(fromNumber / 4) + 30;
}
myMove.xTo = (pieceWidth + 3) * (toNumber % 4) + 20;
myMove.yTo = (pieceHeight + 3) * Math.floor(toNumber / 4) + 30;
myMove.play();
if(fromNumber != 16) {
myMove.target = myImage.getChildAt(myImage.getChildIndex(myImage.getChildByName(String(toNumber))));
myMove.xFrom = (pieceWidth + 3) * (toNumber % 4) + 20;
myMove.yFrom = (pieceHeight + 3) * Math.floor(toNumber / 4) + 30;
myMove.xTo = (pieceWidth + 3) * (fromNumber % 4) + 20;
myMove.yTo = (pieceHeight + 3) * Math.floor(fromNumber / 4) + 30;
myMove.play();
}
}
// Index交換用関数
private function indexExchange(index:int):void {
var indexTemp:int;
indexTemp = myImage.getChildIndex(myImage.getChildByName(String(index)));
myImage.setChildIndex(myImage.getChildAt(myImage.getChildIndex(myImage.getChildByName(String(index)))), myImage.getChildIndex(myImage.getChildByName(String(empty))));
myImage.setChildIndex(myImage.getChildAt(myImage.getChildIndex(myImage.getChildByName(String(empty)))), indexTemp);
// ピース合わせ確認
checkComplete();
}
// 完成判定関数
private function checkComplete():void {
var complete:Boolean = true;
for(var i:uint = 0; i < 15; i++) {
if(!(i == myImage.getChildIndex(myImage.getChildByName(String(i))))) {
// 不一致があった場合
complete = false;
}
}
// 全て一致した場合
if(complete) {
trace("Complete!");
var alert:Alert = Alert.show("complete");
alert.isPopUp = false;
alert.cacheAsBitmap = true;
moveEffect(16, 15);
}
}
]]>
</mx:Script>
</mx:WindowedApplication>
↓前回のに上書きしてしまった。------------------------------------------
2009/9/20 追記
Flex版作りました。
こちら
2009年4月29日水曜日
[Flex][AIR]15パズルVer.0.1

Flex Builder3でFlexとかActionScriptの勉強をしているわけですが、
15パズルを作りたくなったので作ってみました。
最低限の機能はできたのでVer.0.1としてあげます。
制作にあたっては、こちらの記事とこちらの本を参考にさせてもらいました。
ドラッグ&ドロップで自動的に画像を16分割します。
画像ファイルそのままの大きさで16分割するので大きい画像はパズルに向かないかも。
今後そういうとこを直して機能を追加していこうと思ってます。
ソースコードは↓
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" nativeDragEnter="onDragEnter(event);" nativeDragDrop="onDragDrop(event);" width="500" height="400" viewSourceURL="srcview/index.html">
<mx:MenuBar labelField="@label" itemClick="useFile(event)" width="100%" id="myMenu">
<mx:dataProvider>
<mx:XMLList>
<Menu label="操作">
<MenuItem label="スタート" />
</Menu>
</mx:XMLList>
</mx:dataProvider>
</mx:MenuBar>
<mx:Image id="myImage" width="100%" height="100%" />
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.MenuEvent;
// 画像のロード判定用
private var loadEnd:Boolean = false;
private var file_ary:Array;
// 1ピースの幅
private var pieceWidth:Number;
// 1ピースの高さ
private var pieceHeight:Number;
// 空ピースの番号
private var empty:int;
// 完成確認用配列
private var order:Array = new Array();
// メニュー用の関数
private function useFile(e:MenuEvent):void {
if(e.label == "スタート") {
shuffle();
}
}
// ドラッグ&ドロップでロード
private function onDragEnter(event:NativeDragEvent):void {
trace("entered");
var cb:Clipboard = event.clipboard;
if(cb.hasFormat(ClipboardFormats.FILE_LIST_FORMAT)) {
// ドロップを許可
NativeDragManager.acceptDragDrop(this);
}
}
private function onDragDrop(event:NativeDragEvent):void {
if(loadEnd) {
for(var i:uint = 0; i <= 15; i++) {
myImage.removeChild(myImage.getChildByName(String(i)));
}
}
var cb:Clipboard = event.clipboard;
file_ary = cb.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
trace("ファイル名:" + File(file_ary[0]).name);
LoaderRequest();
}
private function LoaderRequest():void {
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, LoadCompleted);
var request:URLRequest = new URLRequest(File(file_ary[0]).url);
loader.load(request);
}
private function LoadCompleted(event:Event):void {
// 画像はBitmapでロード
var bitmapImage:Bitmap = Bitmap(event.target.loader.content);
empty = 15;
pieceWidth = bitmapImage.width / 4;
pieceHeight = bitmapImage.height / 4;
//ピース並べ
for(var i:uint = 0; i < 15; i++) {
// ピースと同じ大きさのビットマップをつくる
var newPieceBitmap:Bitmap = new Bitmap(new BitmapData(pieceWidth, pieceHeight));
// ロードした画像から新しく作ったビットマップにコピー
newPieceBitmap.bitmapData.copyPixels(bitmapImage.bitmapData, new Rectangle((i % 4) * pieceWidth, Math.floor(i / 4) * pieceHeight, pieceWidth, pieceHeight), new Point(0, 0));
// ピースの描画
var newPiece:Sprite = new Sprite();
newPiece.addChild(newPieceBitmap);
newPiece.width = pieceWidth;
newPiece.height = pieceHeight;
newPiece.x = (i % 4) * (pieceWidth + 5) + 20;
newPiece.y = Math.floor(i / 4) * (pieceHeight + 5) + 30;
newPiece.name = String(i);
newPiece.addEventListener(MouseEvent.CLICK, click);
myImage.addChild(newPiece);
}
// 空ピースは白い長方形
var emptyPieace:Sprite = new Sprite();
emptyPieace.graphics.beginFill(0xffffff);
emptyPieace.x = 3 * (pieceWidth + 5) + 20;
emptyPieace.y = Math.floor(3) * (pieceHeight + 5) + 30;
emptyPieace.graphics.drawRect(0, 0, pieceWidth, pieceHeight);
emptyPieace.graphics.endFill();
emptyPieace.name = String(15);
myImage.addChild(emptyPieace);
// 完成判定のためIndexをセット
for(var j:uint = 0; j <= 15; j++) {
myImage.setChildIndex(myImage.getChildByName(String(j)), j);
}
// ピクチャの大きさに合わせてウィンドウの大きさを変更
nativeWindow.width = bitmapImage.width + 60;
nativeWindow.height = bitmapImage.height + 105;
loadEnd = true;
}
// ランダムシャッフル
private function shuffle():void {
var count:uint = 10000;
var imgNum:uint;
for(var i:uint = 0; i < count; i++) {
imgNum = Math.floor(Math.random() * 15);
if(checkMove(imgNum)) {
pieceMove(imgNum);
}
}
}
// ピースクリック時の処理
private function click(event:MouseEvent):void {
//ピースの番号
var pieaceNumber:int = event.currentTarget.name;
trace("click:" + pieaceNumber);
if(checkMove(pieaceNumber)) {
pieceMove(pieaceNumber);
}
}
// ピースが動かせるかチェック
private function checkMove(pieaceNumber:int):Boolean {
var result:Boolean = false;
if((pieaceNumber == (empty - 4)) || (pieaceNumber == (empty + 4)))
result = true;
else if(((pieaceNumber % 4) != 0) && ((pieaceNumber - 1) == empty))
result = true;
else if(((pieaceNumber % 4) != 3) && ((pieaceNumber + 1) == empty))
result = true;
return result;
}
// ピース移動
private function pieceMove(pieaceNumber:int):void {
var oldEmpty:int = empty;
empty = pieaceNumber;
myImage.getChildByName(String(oldEmpty)).x = (pieceWidth + 5) * (pieaceNumber % 4) + 20;
myImage.getChildByName(String(oldEmpty)).y = (pieceHeight + 5) * Math.floor(pieaceNumber / 4) + 30;
myImage.getChildByName(String(oldEmpty)).name = String(pieaceNumber);
myImage.getChildByName(String(pieaceNumber)).x = (pieceWidth + 5) * (oldEmpty % 4) + 20;
myImage.getChildByName(String(pieaceNumber)).y = (pieceHeight + 5) * Math.floor(oldEmpty / 4) + 30;
myImage.getChildByName(String(pieaceNumber)).name = String(oldEmpty);
indexExchange(pieaceNumber);
}
// Index交換用関数
private function indexExchange(index:int):void {
var indexTemp:int;
indexTemp = myImage.getChildIndex(myImage.getChildByName(String(index)));
myImage.setChildIndex(myImage.getChildAt(myImage.getChildIndex(myImage.getChildByName(String(index)))), myImage.getChildIndex(myImage.getChildByName(String(empty))));
myImage.setChildIndex(myImage.getChildAt(myImage.getChildIndex(myImage.getChildByName(String(empty)))), indexTemp);
// ピース合わせ確認
checkComplete(); }
// 完成判定関数
private function checkComplete():void {
var complete:Boolean = true;
for(var i:uint = 0; i < 15; i++) {
if(!(i == myImage.getChildIndex(myImage.getChildByName(String(i))))) {
// 不一致があった場合
complete = false;
}
}
// 全て一致した場合
if(complete) {
trace("Complete!");
var alert:Alert = Alert.show("complete");
alert.isPopUp = false;
alert.cacheAsBitmap = true;
}
}
]]>
</mx:Script>
</mx:WindowedApplication>
---------------------------------
2009/9/4 追記
AIRファイルのダウンロードは、Ver.0.1を間違えて消してしまったためVer.0.15のみです。
上のリンクからたどるか、こちらからDLできます。
登録:
投稿 (Atom)

