実例!ドイル製作「雪祭り"2000"」作り方
どもー、こんにちは。変なモノばかり作ってるドイルです。
今回は「Dancing Onigiri Xmas Festival」に提出した「雪祭り "2000"」を例に取り解説をしていきたいと思います。

とりあえずソースを置いといてみますね。 つ[ソース]
FlashMX以降なら開けるはずです。これを見ながら進めていきませう。
半年前に作ったものなので今見ると「あーこここうした方がいいなぁ」と思うところは自分でもあるのですが・・・。

どうみても見辛いのですがお付き合いいただければ幸い。
舞い落ちる雪
この曲を聴いたときから雪が降ってくるというエフェクトは考えていました。
とにかく絵がないと話になりませんよね。雪だけだと寂しかったので雪と結晶の2種類を降らせることにしました。
水色のは雪の結晶です。誰がなんと言おうと。
雪のグラフィックは白い円を描いた後に[ソフトエッジ]をかけることで雪っぽく見せました。

実のところこの作業以外は全てASにて行っています。
自分はあまりトゥイーンを使うのが得意ではないため結構ASで動きまで制御してることが多いのです。

ステージ左下に[MAKE矢印][コントロール][コントロール]と3つのMCがありますよね。
最初の2つは元からあるもので今回雪の制御のために追加したのが3つ目のMCです。
インスタンス名は「ctrlSpell」・・・Lunaticの名残です('A`)
では、とりあえずその内容を見てみませう。

onClipEvent (load) {
 snowcnt = 501;
 function makesnow(val) {
  var snowtype;
  if (val) {
   snowtype = Math.floor(Math.random() * 4);
  } else {
   snowtype = 1;
  }
  var newname = "snow" + snowcnt;
  _root.snow.duplicateMovieClip(newname, snowcnt);
  _root[newname].vy = (Math.random() + 1) / 2;
  _root[newname].x = Math.random() * 300 + 25;
  _root[newname]._y = 10;
  _root[newname]._rotation = (Math.random() * 360 / 180 * 3.14);
  if (snowtype) {
   _root[newname]._x = _root[newname].x + Math.sin(_root[newname]._rotation) * 5;
   _root[newname].onEnterFrame = function() {
    this._y += this.vy;
    this._rotation += 0.1;
    this._x = this.x + Math.sin(this._rotation) * 5;
    if (this._y > 360 || !_global.playing) {
     this.removeMovieClip();
    }
   };
  } else {
   _root[newname].vx = (Math.random() * 2 - 1) / 4;
   _root[newname]._x = _root[newname].x;
   _root[newname].gotoAndStop(2);
   _root[newname].onEnterFrame = function() {
    this._x += this.vx;
    this._y += this.vy;
    this._rotation += 0.5;
    if (this._y > 360 || this._x < -10 || this._x > 360 || !_global.playing) {
     this.removeMovieClip();
    }
   };
  }
  snowcnt++;
  if (snowcnt > 550) {
   snowcnt = 501;
  }
 }
}
onClipEvent (enterFrame) {
 if (_root._currentframe > 4700 && _root._currentframe % 30 == 0) {
  makesnow(1);
 } else if (_root._currentframe > 2500 && _root._currentframe % 60 == 0) {
  makesnow(1);
 } else if (_root._currentframe > 1300 && _root._currentframe % 100 == 0) {
  makesnow(0);
 }
}

はい、これで雪が降ってきます。
・・・で終わらせるわけにはいかないので順に解説をしていきます。

まずはこの部分「どのタイミングでどのように雪を出すか」を制御している部分です。

  1. onClipEvent (enterFrame) {
  2.  if (_root._currentframe > 4700 && _root._currentframe % 30 == 0) {
  3.   makesnow(1);
  4.  } else if (_root._currentframe > 2500 && _root._currentframe % 60 == 0) {
  5.   makesnow(1);
  6.   else if (_root._currentframe > 1300 && _root._currentframe % 100 == 0) {
  7.   makesnow(0);
  8.  }
  9. }

1行目
クリップイベントのenterFrameは毎フレームごとに行われる処理でしたね。
2行目以降
_rootは最上階層を指し、_currentframeは現在のフレーム数を表すプロパティ、
%演算子は除算した余りを求めます。
なので一番最初のif文では「現在のフレーム数が4700より大きく
かつ 現在のフレーム数を30で割った余りが0ならば」
という意味になります。
つまり「4700フレーム以降、30フレーム毎(1秒に2回)に呼び出される」ということになります。
他のif文も同じような内容です。
纏めると
1300フレーム以降 → 100フレーム毎にmakesnow(0)
2500フレーム以降 → 60フレーム毎にmakesnow(1)
4700フレーム以降 → 30フレーム毎にmakesnow(1)
という処理にしたかったはずなのですが
よく考えるとこの構成だと仮に5200フレームの場合
一番上のif文は5200を30で割った余りが0ではないので処理されないのですが
3つ目のif文が行われてしまいますね。
このプログラムは雪を降らせるだけなので大した問題にはならないのですが・・・
ちゃんと考えて構成しようぜ!ってことですorz

さて、説明を省いてここまで来ていましたが
「makesnow()」というのは関数(function)です
関数というのは「これをこうしてあれをああする」という処理を1つに纏めたものです。
ここでは雪の生成に関する内容が含まれているわけです。
その内容は後で説明します。
また、()の中の値は「引数」といい関数に数値や文字列を渡すためのものです。
ここでは0と1となっていますが
0では雪のみ、1では結晶も降らせるという条件分岐のために設定しています。



補足@
先ほどの想定外のところでmakesnow関数が実行されてしまうのは
if (_root._currentframe > 4700){
 if(_root._currentframe % 30 == 0) {
  makesnow(1);
 }
}else if (_root._currentframe > 2500){
(以下略)

というようにまず規定フレームに達しているかの判定を先に行って
その後に更に条件分岐を作れば解決されます。

また、このようにif(else)文が連続する場合は
「switch case」を使う手もあります。

p=1;
switch(p){
 case 1:
  trace("aa");
  break;
 case 2:
  trace("bb");
  break;
}

pの値によって処理を分け、caseの値と同じ部分が実行されます。
breakを書かなければその下に書いてある処理も条件に関わらず実行されてしまうので注意しましょう。
この場合は「aa」と出力されます(traceは()内の内容を出力するモノ)
case部分に条件式を持ってくる場合は
switch(true){
 case (条件式):
という風に書きます。

詳しく知りたい場合は自分で調べていただきたい・・・('A`)

補足A
何度も_root._currentframeという長ったらしいのが出てきて嫌になるので
frame = _root._currentframe
という感じに別の変数に代入してから処理をするということもできます。
更に_root._currentframeと何度も書いてあるとそれが出てくるたびに
_rootのフレーム数を取得しに行くという処理が行われるため
別変数に代入するということは処理速度の向上にも繋がります。

補足B
Aの続きになるのですがローカル変数というものがあります。
関数内でvarステートメントを用いて変数を宣言するとローカル変数となります。
これはその関数内でしか有効でない変数で関数を抜けると自動で削除されます。
同オブジェクト内にもし同名の変数が存在した場合にそちらとは別の物として扱えたり、メモリの節約にもなります。

@ABを纏めると先ほどの部分はこのような感じになります。

onClipEvent (enterFrame) {
var frame = _root.currentframe;
 switch(true){
 case (frame>4700):
  if(frame%30){
  makesnow(1);
  }
  break;
 case (frame>2500):
  if(frame%60){
  makesnow(1);
  }
  break;
 case (frame>1300):
  if(frame%100){
  makesnow(0);
  }
  break;
}

すっきりしたようなしてないような・・・
@は好みによりますが、ABは便利です。

続いて「onClipEvent (load)」の方の処理を見ていきましょう。

  1. onClipEvent (load) {
  2.  snowcnt = 501;
  3.  function makesnow(val) {
  4.   var snowtype;
  5.   if (val) {
  6.    snowtype = Math.floor(Math.random() * 4);
  7.   } else {
  8.    snowtype = 1;
  9.   }

とりあえずここまでです。
1行目
クリップイベントのloadは読み込まれたときに1度だけ実行されるものです。
通常、変数の初期化や関数の定義に用います。
2行目
これは後で使うのでここでは省略
3行目
ここからが関数定義になります。
function 関数名(p1,p2・・・){
処理内容
}
という形で宣言します。
()内には送られてきた引数を受け取り、関数内での処理するための変数名を書きます
(この説明は少し違う気がするのですがそのようなものと思っていいかと)
ここでは引数として送られてきた値(0か1でしたね)がvalという変数に代入されたことになります。
4行目
補足Bにて説明しましたがローカル変数の定義を行っています
5行目
valの値で条件分岐を行っています。
条件式が「true」であれば処理が行われるのですが「1以上の値」もtrueになります。
valは0か1なので1であればtrueなので6行目が、0であればfalseなので8行目が実行されます。
6行目
Math.floor(p) はpの値を小数第一位で切り捨てする数学関数で、
Math.random() は0以上1未満の乱数を生成する数学関数です。
Math.floor(Math.random() * n) という形で0から(n-1)までの整数をランダムに生成することになります。
これは結構色んな場面で使います。
なのでここでは0,1,2,3のどれかがsnowtypeという変数に代入されることになります。
7行目以降は省略

それでは次の部分へ

  1.   var newname = "snow" + snowcnt;
  2.   _root.snow.duplicateMovieClip(newname, snowcnt);
  3.   _root[newname].vy = (Math.random() + 1) / 2;
  4.   _root[newname].x = Math.random() * 300 + 25;
  5.   _root[newname]._y = 10;
  6.   _root[newname]._rotation = (Math.random() * 360 / 180 * 3.14);

1行目
newnameというローカル変数にこの時点ではsnowcntは501なので"snow501"という文字列を代入

2行目
「duplicateMovieClip」はMCを複製するメソッドになります。
「複製元のインスタンス名.duplicateMovieClip(複製先のインスタンス名,深度)」という形で使っています。
ステージ上に配置されている雪のMC(_root.snow)を深度snowcnt(501)にnewname("snow501")というインスタンス名で複製するということです。


※深度について

深度とはインスタンスの重なりの順序を表したものです。
この値が大きいと手前に、小さいと奥に表示されます。
1つの深度には1つのインスタンスしか置けず、同じ深度に別のものを置こうとすると元のものは消えてしまいます。
ここで深度を501にしている理由は、このソースでは矢印に1〜500が使われているからです。
なお、深度は-16384〜1048575まで指定でき、swapDepths()で変更できます。
通常配置したときは-16325辺りに自動で振り分けられるそうです。
また、MCを消去するremoveMovieClip()では深度が0以上のものしか削除できません


3行目
パスに変数名が入っている場合このように[]を使うことができます。
[]の前にドットが必要ないことに注意してください。
この場合_root.snow501.vyを表しています。
なのでこの行は_rootにあるsnow501というMCのvyという変数に0.5以上1未満の乱数を代入しています。
なお、vyはy方向の移動量を保存する変数です。これによって降ってくる速さが変わります。

4行目
同じようにそのMCのxという変数に25以上325未満の乱数を代入
この変数は雪の基準x座標を保存するための変数です。実際のx座標はこれに補正がかかります。

5行目
_yプロパティはMCのy座標を表します。
なのでそのMCのy座標を10に設定ということです。

6行目
_rotationプロパティはMCの傾きを表します。
ここでの傾きというのは度数法(°)ではなく孤度法(ラジアン)になります。
プログラミングではラジアンが使われることが多いのです。
度数法→孤度法の変換は「角度/180*π」
逆は「ラジアン*180/π」で出来ます。
なのでここではMCの傾きを0〜360°の範囲でランダムに決めています。
雪のMCは円形だったので傾きを決めても見た目は変わらないのですが、
この傾きは別のところで使うことになります。

それでは次は雪の動作を設定する部分です。

  1.   if (snowtype) {
  2.    _root[newname]._x = _root[newname].x + Math.sin(_root[newname]._rotation) * 5;
  3.    _root[newname].onEnterFrame = function() {
  4.     this._y += this.vy;
  5.     this._rotation += 0.1;
  6.     this._x = this.x + Math.sin(this._rotation) * 5;
  7.     if (this._y > 360 || !_global.playing) {
  8.      this.removeMovieClip();
  9.     }
  10.    };
  11.   } else {
  12.    _root[newname].vx = (Math.random() * 2 - 1) / 4;
  13.    _root[newname]._x = _root[newname].x;
  14.    _root[newname].gotoAndStop(2);
  15.    _root[newname].onEnterFrame = function() {
  16.     this._x += this.vx;
  17.     this._y += this.vy;
  18.     this._rotation += 0.5;
  19.     if (this._y > 360 || this._x < -10 || this._x > 360 || !_global.playing) {
  20.      this.removeMovieClip();
  21.     }
  22.    };
  23.   }

1行目
snowtypeにはvalが0の時(雪のみ)は必ず1が入り
valが1の時(結晶も生成)はランダムで0〜3でした。
1,2,3の場合はtrueになり0の場合はfalseになります。
つまり雪のみを生成する場合はここは確実にtrueになり、
結晶も生成する場合は4分の1の確率でfalseになるということです。
ここから10行目までは雪の動きを設定する部分になります。

2行目
ここで初期x座標を設定します。
_xは実際に表示されるx座標で、xは基準x座標でこれに傾きによって補正を加えたものを代入しているわけです。
詳しくは6行目にて。

3行目
ここから10行目まで雪の動きを制御するASになります。
クリップイベントで enterFrame というものが出てきましたね。
あれは最初からMCに記述しておくものでしたがここでは外部からMCに対して後から設定しています。
このようにすると条件分岐で違うことを設定したり、設定したものを消したり、上書きしたりすることができます。

また、ここではthisの意味合いが少し違ってきます。
本来ならばthisは自分自身を指しますからここでは雪の制御のためのMC(ctrlSpell)を指すはずなのですが
この場合onEnterFrameに「this」がそのまま付加されて対象のMC自身を指すことになります。
逆にthisがない場合、対象のMCではなく、このMC(ctrlSpell)を指してしまいますのでthisは必須です。
うん・・・確かそんな感じ。

4行目
_yはy座標を表すプロパティで、vyはy方向の移動量の変数でした。
A += BでAにBを足したものがAに代入されます。A = A + Bと同じです。
なのでここでは毎フレームthis._yにthis.vyを加算しています。

5行目
_rotationは傾きを表すプロパティなので
毎フレーム_rotationに0.1(ラジアン)加算しています。
この0.1という値は何度かテストしてこれくらいがいいかなぁという感じに決めたものです。

6行目
ここでx座標を設定しています。
2行目と同じ内容です。
基準x座標に足されている「Math.sin(this._rotation) * 5」ですが
Math.sin()はサインを求める数学関数です。
サインカーブというものをご存知でしょうか?
三角比の勉強をしていれば知っていると思いますが y=sinθのグラフを描くと波状のグラフになります。
参考:三角関数(Wikipediaより。少し下げるとグラフがあります)
これを利用して雪を左右にゆらゆらと動かしているのです。

7行目
「this._y > 360」とはy座標が360より大きかったら、で
これは下の方にいって画面から消えてしまったら、ということです。
_global.playingというのは僕が付け足したフラグで
ゲーム中はtrue、クリアしたりゲームオーバーになったりするとfalseになるようにしています
よく見ると「!」というのが付いてますよね?
これは否定を表します。「_global.playingがtrueでなかったら」になります。
また、間にある「||」というのは「または」を表します。
纏めるとこのif文は「このMCのy座標が360より大きい または _global.playingがtrueでない ならば」となります

8行目
7行目の条件を満たしたらこのMCを消去します

11行目
ここからは結晶の動きを設定する部分になります。
結晶の動きは少し横に移動しながら真っ直ぐ降ってくるという動きです。

12,13行目
vxはx方向の移動量です。
「(Math.random() * 2 - 1) / 4」はどんな値になるでしょうか?
正解は -0.25以上0.25未満 の乱数となります。
今回は基準x座標を保存しておく必要はないのでそのままx座標に代入します。

14行目
結晶のグラフィックは雪のMCの2フレーム目に描いてあるので2フレーム目に移動します。

15行目
ここから22行目まで、毎フレーム実行されるonEnterFrameイベントを設定します。

16-18行目
ここはもうどういうことかわかりますよね

19行目
7行目に出てきたものにx座標の条件が加わっています
斜めに移動するので横方向の画面外に出たときも無駄なので消すようにしています。

  1.   snowcnt++;
  2.   if (snowcnt > 550) {
  3.    snowcnt = 501;
  4.   }
  5.  }
  6. }

さぁついに最後にやってきました。

1行目
雪を生成し終えたら雪の番号を1増やしています
これで次に生成されるときは502、503・・・という風に作られます。

2-4行目
ずっとsnowcntを増やしていくと無駄が出たり何か問題が起きてしまうかもしれないので
ある程度までいったら初期値に戻します。
50番目が生成される頃には1番目はとっくに消えてしまっているはずなのでこのくらいに設定しました。

5行目
function makesnow(val){
で始まったところの終わりの括弧です。

6行目
クリップイベントの終わりの括弧です。
Merry Xmas!!
この「雪祭り "2000"」にはもう1つエフェクトがあり
クリスマスフェスティバルということで最後に「Merry Xmas!!」と表示しています。



【メイン】シーン、MERRY XMASレイヤーの7777フレーム(偶然です。偶然。)以降に配置されているMCがそれで
内容は見てもらうとわかるのですが1文字表示するMCを時間をずらして設置しています。
この1文字表示するMCは、自分でもこういう方法は変なんだろうなぁとか思ってるんですが
中に表示する文字がダイナミックテキストになっていて後から表示する文字を設定しています。
詳しくは今から書きますゆえ。

構成は
_root > MC(MERRY XMAS) > MC(シンボル253)×文字数

この1文字表示するMC、シンボル253(デフォな名前でスイマセン)は2つのレイヤーで構成されていて
1つ目は文字を徐々に表示していくモーショントゥイーンのレイヤー
2つ目は波紋のように円を表示するモーショントゥイーンのレイヤーとなっています。
1つ目のレイヤーでモーションさせているトゥイーンは静止テキストではなくダイナミックテキストになっています。
ダイナミックテキストとは指定した変数の内容を表示するもので変数の内容が変われば表示される文章も変わります。
ここでは表示する変数にcharと指定しています。
また、最後に文字が表示されたところで止めなければいけないので最後のフレームに「stop();」が記述されています。
2つ目のレイヤーはふつーにモーションさせているだけなので特筆することはありません。
円が広がり、途中からは薄くしながら(αを下げながら)広がっていく、というモーションです。

続いてその1つ上のMC、MERRY XMASを見ていきましょう。
先ほどの文字を表示するMCが時間差で設置されています。
そのうち1つ目に記述されているASを見てみると
onClipEvent (load) {
  this.char = "M";
}
となっています。 読み込まれた時点でこのMC内のcharという変数に"M"を代入するということです。
先ほどのダイナミックテキストで変数charを表示するようにしていたので
これでMという文字が表示されるモーションになります。
同じように各MCに対して
e,r,r,y,X,m,a,s,!!を表示するように設定しています。

以上、Merry Xmas表示部分の解説でした。
最後に
これはあくまでも「」です。
自分の場合これはこうした方が楽できるかなぁと思って作ったものなので
人によって勿論違う作り方をします。

「やりたいことを如何にして実現させるか」
それを考えるのがプログラミングの面白さでもあります。

わからないことがあればヘルプを見るなり、検索するなり、人に聞くなりすればいいのです。
検索のコツさえわかればインターネットは知識の宝庫です。
是非ともこれを機にプログラミング、とまでは行かなくとも
多少の改造が出来る程度にFLASHの使い方を身につけて頂きたい。


かなりザクザク書いた感があるのでわからないことがあれば気軽に質問してくださいませ。