トビラ開〜け〜て〜を作る

「アナと雪の女王」の勢いはすごいですね。
子供の影響で、DVDやらCDやら本までも、手元にある状態でございます。
アナとハンスが歌う「とびら開けて」を子供が聴いている脇で作っていたのが、「縦列を特定する」でした。
その時に、浮かんだアイデアなのですが、どうせ左右に分けるなら、扉が開くようにならないかなぁ〜と思っていました。
エフェクトを段階的にすることも、軸変換で回転させることも、拡大もやりましたので、イメージしていたものを作ってみようと思います。

左右それぞれ逆回転させる

扉の動きは、画面の左右端が回転中心になります。、
いきなり扉の動きを作ろうとしても難しいので、ここでは、左右に分けたアイコンを扉に見立てて、逆回転するところまで作ってみます。

ここでは、以下のサンプルを流用します。
・動きを止める(effect1_1)
・縦列を特定する(effect11_3_3)
・すべてのアイコンに(effect10_4)
・ページ拡大のいろいろ(effect5_1_1)
・ページ回転のいろいろ(effect4_2)
まぁ、こんな感じでしょうか。
流用といっても、考え方も含みますので、ここでも1つ1つ説明しようと思います。

とりあえず、エフェクト的にページ移動を無効にしなければなりませんので、以下の1行を入れておきます。

page:translate( offset )

2ページ目は、とりあえず消えていてもらうので、以下を入れておきます。

if( percent < 0 ) then page:scale(0)

アイコンを左右に分けるために、変数を用意しておきます。
center:左右振り分け用の中央値(0スタート)
i_num:アイコンの位置(0スタート)
dir:左右どちらか(左:-1 右:1)

では、実際にプログラムを記述してみましょう。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir

 page:translate( offset )

 if( percent < 0 ) then
  page:scale(0)
 else
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then dir = -1
   else dir = 1
   end
   icon:rotate(-dir*angle, 0, 1, 0)
  end
 end
end

effect33_1

変数centerとi_numは0スタートですので、-1が入っていることを確認してください。
サンプルの寄せ集めであることが一目瞭然ですね・・・

実際に動かしてみると、こんな感じです。

左右逆回転

ここまでは、問題ないですね。

回転軸をアイコン端にする

左右に分けたアイコンを扉に見立てて、逆回転するところまで作りました。
扉に近付けるために、アイコンの回転軸を、アイコンの中心から端にしましょう。

ここでは、以下のサンプルを追加流用します。
・行列を実感する(effect30_1)
軸変換を導入するだけですね。
ただ、左右を同時に考えなければならないので、左右振り分け用のdirを上手く使い、左を考えれば右の出来てるような進め方にします。

ここでは、回転軸を端にするので、移動量はアイコン幅の半分です。
これとdirを組合わせて、軸変換を追加します。

では、実際にプログラムを記述してみましょう。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir

 page:translate( offset )

 if( percent < 0 ) then
  page:scale(0)
 else
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then dir = -1
   else dir = 1
   end
   icon:translate( dir * icon.width/2 )
   icon:rotate(-dir*angle, 0, 1, 0)
   icon:translate( -dir * icon.width/2 )
  end
 end
end

effect33_2

回転の前後に、移動を追加しました。
移動させた分だけ戻すのがポイントです。

実際に動かしてみると、こんな感じです。

左右逆回転(アイコン端軸)

アイコンだけ見ると、扉が開いているように見えてきますが、いかがでしょうか。

ここも、問題なさそうですね。

回転軸を画面端にする

アイコンだけは扉のようになりましたので、次は、回転軸を画面の端にします。
ここが、今回のメインだと思います。

縦列を特定するために、アイコン番号を0スタートの値を取るi_numで置き換えていますが、アイコンの位置によって画面端までの距離が違います。
イメージはこんな感じです。

とりあえず、左側の2つができれば、右側に流用すれば良いので、左の2つに集中しましょう。

そもそも、アイコンのX座標は、画面左端からアイコン左端までの距離ですので、アイコンのX座標をそのまま使います。
右側のアイコンに関しては、画面右端もアイコン右端も捉えにくいので、後で考えることにします。
とりあえず、左側のアイコンだけ扉っぽくしてみたサンプルは、以下の通りです。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir

 page:translate( offset )

 if( percent < 0 ) then
  page:scale(0)
 else
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then dir = -1
   else dir = 1
   end
   icon:translate( dir*(icon.x+icon.width/2) )
   icon:rotate(-dir*angle, 0, 1, 0)
   icon:translate( -dir*(icon.x+icon.width/2) )
  end
 end
end

effect33_3

実際に動かしてみると、こんな感じです。

左右逆回転(画面左端軸)

問題発生ですね。
右も左も、思わぬ動きになってしまいました。
よく見てみると、右にあるアイコンほど遠くへ行ってしまいます。
こんなに移動するはずではないのですが・・・

icon.xを導入した途端に、動きが激しくなりましたよね。
よく考えると、「エフェクトを作る」の棚を作り始める前にサンプルを作っているときに、同様のことがありました。
それは、「常に動いている」ということを知った瞬間でした。
icon.xで移動したあと、回転して戻すまでの僅かな時間に、icon.xが変化している訳です。
対策として、動的なicon.xを直接使うのではなく、一度変数に格納してから使うようにします。

以下が、そのサンプルです。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir, itx

 page:translate( offset )

 if( percent < 0 ) then
  page:scale(0)
 else
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then dir = -1
   else dir = 1
   end
   itx = icon.x+icon.width/2
   icon:translate( dir*itx )
   icon:rotate(-dir*angle, 0, 1, 0)
   icon:translate( -dir*itx )
  end
 end
end

effect33_3_2

実際に動かしてみると、こんな感じです。

左右逆回転2(画面左端軸)

左側は、できてるみたいですね。
右側も、dirのおかげでそれっぽく動いてくれていますね。

今度は、みぎがわを仕上げましょう。

右側を仕上げるにあたって、左側を意識しない訳にはいきません。
きちんと左側の動きを保ちながら、右側を修正しなければならないのです。
そのために、また変数をいくつか導入します。

考え方は、「左側を反転する」です。
左側ができているので、そのまま反転できれば簡単ですので、この考えで進めます。
そのために、アイコン位置も左側のようにすれば良いので、i_posという変数を導入します。
イメージは以下の通りです。

右側の0と1はそのままで良いのですが、右側の2と3を1と0にしたいのです。
簡単な演算で変換できないかと思いましたが、dirの判定でif文を使っているので、ついでにi_posの処理も入れてしまいましょう。
扱う数値は0スタートですので、アイコン横最大数−1から0スタートのアイコン番号を引けば、変換完了です。
if文に2行以上の処理を記述するには、その処理全体を、「do」と「end」で囲います。
これも、改行やインデントで見やすくしておくと、どこからどこまでが処理範囲なのかわかりやすくなります。

アイコン番号が左右対称になりましたが、大きな問題が残っています。
右側のアイコンの回転軸を画面右端にしたいのですが、取得できるのが、アイコンの左側座標や画面幅ですので、左側と同じような計算ができないのです。
これもif文で処理を分ければいいのですが、表示に関する部分は最後にまとまっているので、できるだけitxの計算までに、右側に関する計算を完了しておきたいのです。

右側のアイコンに関してですが、アイコンの左端にアイコンの幅を足せば、アイコンの右側になります。
その値と画面幅の差が、右側のアイコンが画面右端を回転軸とするための移動量になります。

赤い矢印が計算する部分で、求める移動量は黄色い矢印です。
この方法でしたら、最右アイコンも同様に計算できます。

しかし、もっと楽な方法を考えました。
そもそも、iPhoneの表示は左右対称と考えれば、とても簡単でした。
最左アイコンの左側から画面左端までの距離と、最右アイコンの右側から画面右端までの距離は、同じと考えられるのです。
ここで、少しアイコンが5つだったら?と思い始めましたので、後からの対応を楽にするために、端から2つ目、3つ目という考え方ではなく、アイコン間の距離だけズラすという考え方で進めます。
そこで導入するのが、between_xです。
計算のしやすさを優先して、アイコン同士のスペースではなく、それぞれのX座標の差とします。

左側にある赤と黄色の矢印が示す距離を、右側でもそのまま使います。
for文で、アイコンを1つ1つ探索している間で、最左アイコンの左側座標を求めてもゴチャゴチャするだけなので、変数宣言の時に求めておくことにします。

最左アイコンのX座標は、1つ目のアイコンから取得できるので、その変数をicon_xとし、page[1].xで取得しておきます。
アイコンの幅も1つ目のアイコンから取得しますので、その変数をicon_wとし、page[1].widthで取得しておきます。
そして、アイコン間のX座標の差ですが、1つ目のアイコンと2つ目のアイコンから取得することにしますので、その変数をbetween_xとし、page[2].x-page[1].xで求めることにします。

いろいろ置き換えたサンプルが、以下になります。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir, i_pos, itx
 local icon_x = page[1].x
 local icon_w = page[1].width
 local between_x = page[2].x - page[1].x

 page:translate( offset )

 if( percent < 0 ) then
  page:scale(0)
 else
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then
    do
     dir = -1
     i_pos = i_num
    end
   else     do
     dir = 1
     i_pos = page.max_columns-1 - i_num
    end
   end
   itx = (icon_x+icon_w/2+between_x*i_pos)*dir
   icon:translate( itx )
   icon:rotate(-dir*angle, 0, 1, 0)
   icon:translate( -itx )
  end
 end
end

effect33_3_3

実際に動かしてみると、こんな感じです。

左右逆回転3(画面両端軸)

良い感じですね。
我ながら感動しております。

調子に乗って、何度もページ送りしていると、エフェクトが無効になってしまいました。
私のiPhone6Plusでは、最後のページには1つのアイコンしかありません。


このページに移動しようとした時に、無効になってしまうのです。

ここで、エラーログを確認してみました。

アイコンを1つ1つ特定するときに見た「nil」です。
アイコンが1つしかないのに、2つ目のアイコン情報を取得しようとしたために、エラーになってしまったんですね。

ページが存在するということは、少なくともアイコンが1つ存在するので、1つ目のアイコン情報だけで解決しなければなりません。
そこで、厳密には違うのですが、アイコン間のスペースと画面左端から1つ目のアイコンまでのスペースが同じであるということにします。

このように考えることで、アイコンが1つしかないページでも、問題なく移動することができるようになり、書き換えも容易かと思います。
between_xが、icon_xとicon_wで表現できるわけです。

置き換えたサンプルが、以下になります。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir, i_pos, itx
 local icon_x = page[1].x
 local icon_w = page[1].width
 local between_x = icon_x + icon_w

 page:translate( offset )

 if( percent < 0 ) then
  page:scale(0)
 else
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then
    do
     dir = -1
     i_pos = i_num
    end
   else     
    do
     dir = 1
     i_pos = page.max_columns-1 - i_num
    end
   end
   itx = (icon_x+icon_w/2+between_x*i_pos)*dir
   icon:translate( itx )
   icon:rotate(-dir*angle, 0, 1, 0)
   icon:translate( -itx )
  end
 end
end

effect33_3_4

between_xの書き換えだけで済みました。
これで、どのページに移動しても、エフェクトが無効になることはなくなりました。

ここまでのエフェクトは、こんな感じです。

左右逆回転4(画面両端軸)

これで扉は完成なのですが、気になる点があります。
90°の回転なのですが、真横で終わっていません。
さらに、隣の列で、その角度も違います。
どちらかと言うと、隣り合うアイコンが、同一平面上に無いように見えます。

現時点では、視点の問題としか言えません。
しかも、perspectiveが弄れず、視点の移動ができませんので、「気になるけどスルー」しかありませんね・・・

イメージした動きにはなりましたので、完成度90%ですが、これで扉の完成とします。

扉を途中で止める

目的のエフェクトは、扉を開けて入っていくという動きですので、「扉を開ける」動作と、「入っていく」動作の2段階になります。
「扉を開ける」動作は完成しましたので、あとは「入っていく」動作を付け加えれば良いだけです。

扉を完全に開けると、趣が損なわれますので、ある程度開けた状態で入って行くようにしましょう。
その「ある程度」を、どの程度にすれば良いか決まりませんので、変数を導入して進めます。
その変数をdr(door rate)とし、6割ほど開けることを想定し、dr = 0.6としておきます。
次に、扉を開けるエフェクトを全体のどの程度で完了させるかを考えたのですが、これも決まらないので、er(effect rate)という変数を導入し、これを0.4としておきます。
ここで目指すエフェクトは、全体の4割りでドアを6割開けるというものです。

ここでは、以下のサンプルを流用します。
・エフェクトを段階的にする(effect26_1_4)

では、段階的にしていきましょう。
段階的にするといっても、切り替わりで動きが繋がっていなければなりません。
if文の処理にfor文を入れると、elseにも同じようにfor文を入れなければならないので、スマートではありません。
逆に、for文の中にif文を入れるのも、軸変換や回転を2度記述しなければなりません。
したがって、ここにも変数を導入します。
その変数をep(effect percent)とし、percentがer未満であればドアが開いている最中ということで、epにはpercentを入れます。
percentがer以上であれば、epにはerを入れます。
このepでfor文の回転部分を書き換えれば、スマートかと思います。

置き換えたサンプルが、以下になります。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir, i_pos, itx
 local icon_x = page[1].x
 local icon_w = page[1].width
 local between_x = icon_x + icon_w
 local dr = 0.6
 local er = 0.4
 local ep

 page:translate( offset )

 if( percent < 0 ) then
  page:scale(0)
 else
  if( percent < er ) then ep = percent
  else ep = er end
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then
    do
     dir = -1
     i_pos = i_num
    end
   else
    do
     dir = 1
     i_pos = page.max_columns-1 - i_num
    end
   end
   itx = (icon_x+icon_w/2+between_x*i_pos)*dir
   icon:translate( itx )
   icon:rotate(-dir * math.pi/2 * dr*ep/er, 0, 1, 0)
   icon:translate( -itx )
  end
 end
end

effect33_4

回転の部分がポイントです。
90°にdrを掛ければ、ドアの開き具合が変わりますが、erまでに開き切った状態にするためep/erを掛けています。

こんな感じです。

扉を途中で止めるエフェクト

ドアの開き具合が6割で、エフェクト全体の4割で止まっていることを確認してください。

扉を止めたまま拡大する

第1段階で、扉を途中で止めましたので、第2段階に入ります。
第2段階は、扉を止めたまま、全体を拡大します。

今回は簡単です。
と言うのも、ページ全体に対して拡大するため、アイコン1つ1つを、位置を考えながら拡大する必要がないからです。
ですので、扉が止まっている状態に、ページ拡大エフェクトを追加するだけで完了します。

では、拡大方法ですが、今回もページ拡大エフェクトを1行で済ませたいので、変数を導入します。
その変数をsp(scale percent)とし、if文で拡大率を切り替えるための変数とします。
percentがer未満であれば、spを1にすることで、拡大も縮小もしない状態で扉が開きます。
percentがer以上であれば、扉が止まりますので、拡大率をspに入れることで、大きさを変えます。

ここで、拡大率の算出がポイントになります。
er以上のpercentが動く範囲で、取る値が0〜1になるようにしなければなりません。
具体的には、(percent-er)/(1-er)で求めることができますので、これをspに入れます。

拡大エフェクトを追加したサンプルが、以下になります。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir, i_pos, itx
 local icon_x = page[1].x
 local icon_w = page[1].width
 local between_x = icon_x + icon_w
 local dr = 0.6
 local er = 0.4
 local ep, sp

 page:translate( offset )

 if( percent < 0 ) then
  page:scale(0)
 else
  if( percent < er ) then
   do
    ep = percent
    sp = 1
   end
  else
   do
    ep = er
    sp = 1+((percent-er)*(1-er))^2 *20
   end
  end
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then
    do
     dir = -1
     i_pos = i_num
    end
   else
    do
     dir = 1
     i_pos = page.max_columns-1 - i_num
    end
   end
   itx = (icon_x+icon_w/2+between_x*i_pos)*dir
   icon:translate( itx )
   icon:rotate(-dir * math.pi/2 * dr*ep/er, 0, 1, 0)
   icon:translate( -itx )
  end
  page:scale(sp)
 end
end

effect33_5

実際の拡大率は、「ページ拡大のいろいろ」の拡大率を流用しました。
この拡大率が結構お気に入りなのです。

扉が開いたまま拡大されるエフェクトは、こんな感じです。

開いた扉を拡大するエフェクト

これで、1ページ目が完成ですね。

2ページ目を登場させる

1ページ目が完成しましたので、消しておいた2ページ目を仕上げましょう。
「扉を開けて中に入る」という動きですので、2ページ目は、遠くからこちらに近付いてくる動きがベストですね。
ということは、2ページ目も、「ページ拡大のいろいろ」流用になります。
しかし、2ページ目の拡大は、1ページ目の拡大とタイミングを合わせなければならないので、2ページ目も段階的な処理になります。

考え方は同じですが、percentの値が負数ですので、その点に注意して、条件式を書かなければなりません。
さらに拡大率もまた、負数に注意して書かなければなりません。

負数を意識して2ページ目の処理を追加したサンプルが、以下になります。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir, i_pos, itx
 local icon_x = page[1].x
 local icon_w = page[1].width
 local between_x = icon_x + icon_w
 local dr = 0.6
 local er = 0.4
 local ep, sp

 page:translate( offset )

 if( percent < -1+er )then
  page:scale(0)
 elseif( percent < 0 ) then
  page:scale(1+percent/(1-er))
 else
  if( percent < er ) then
   do
    ep = percent
    sp = 1
   end
  else
   do
    ep = er
    sp = 1+((percent-er)*(1-er))^2 *20
   end
  end
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then
    do
     dir = -1
     i_pos = i_num
    end
   else
    do
     dir = 1
     i_pos = page.max_columns-1 - i_num
    end
   end
   itx = (icon_x+icon_w/2+between_x*i_pos)*dir
   icon:translate( itx )
   icon:rotate(-dir * math.pi/2 * dr*ep/er, 0, 1, 0)
   icon:translate( -itx )
  end
  page:scale(sp)
 end
end

effect33_6

動作は、こんな感じです。

扉を開けて中に入るエフェクト

開いた扉が拡大するタイミングと、2ページ目が登場するタイミングが合っていますね。

仕上げる

動きは完成しました。
扉が開くような動きも、扉を止めたままの拡大もできています。
ただ、気になるところがあります。

扉がもう少し開いていても良いかなぁ〜とも思うのですが、それ以上に気になるのは、2ページ目の登場スピードです。
拡大スタートのタイミングは良いのですが、その瞬発力がハンパないのです。
このスタートダッシュは、バランスを考えると逆効果ですので、工夫しなければいけませんね。
ここは、平方根を取ることで対応してみます。
ドアを開ける動作は、もう少し早くしたいので、全体の3割で完了させましょう。

書き換えたサンプルが、以下になります。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir, i_pos, itx
 local icon_x = page[1].x
 local icon_w = page[1].width
 local between_x = icon_x + icon_w
 local dr = 0.8
 local er = 0.3
 local ep, sp

 page:translate( offset )

 if( percent < -1+er )then
  page:scale(0)
 elseif( percent < 0 ) then
  page:scale(1-math.sqrt(percent/(er-1)))
 else
  if( percent < er ) then
   do
    ep = percent
    sp = 1
   end
  else
   do
    ep = er
    sp = 1+((percent-er)*(1-er))^2 *20
   end
  end
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then
    do
     dir = -1
     i_pos = i_num
    end
   else
    do
     dir = 1
     i_pos = page.max_columns-1 - i_num
    end
   end
   itx = (icon_x+icon_w/2+between_x*i_pos)*dir
   icon:translate( itx )
   icon:rotate(-dir * math.pi/2 * dr*ep/er, 0, 1, 0)
   icon:translate( -itx )
  end
  page:scale(sp)
 end
end

effect33_7

拡大率の部分ですが、もともと(-percent/(1-er))^0.5としていました。
0.5乗よりも、平方根を求める関数の方がスマートかと思い、関数で求めています。
さらに、改めて符号を見てみると、改善の余地がありましたので、サンプルのようにしました。

動作は、こんな感じです。

扉を開けて中に入るエフェクト

これで完成ですね。

忘れ物

しばらく使ってみましたが、奇数列をすっかり忘れていました。

ここでは、以下のサンプルを流用します。
・縦列を特定する(effect11_3_5)

奇数列に対応したサンプルが、以下になります。

return ( page, offset, screen_width, screen_height)
 local percent = offset/page.width
 local angle = percent*math.pi/2
 local dir, i_pos, itx
 local icon_x = page[1].x
 local icon_w = page[1].width
 local between_x = icon_x + icon_w
 local dr = 0.8
 local er = 0.3
 local ep, sp

 page:translate( offset )

 if( percent < -1+er )then
  page:scale(0)
 elseif( percent < 0 ) then
  page:scale(1-math.sqrt(percent/(er-1)))
 else
  if( percent < er ) then
   do
    ep = percent
    sp = 1
   end
  else
   do
    ep = er
    sp = 1+((percent-er)*(1-er))^2 *20
   end
  end
  for i, icon in subviews(page) do
   local center = (page.max_columns-1)/2
   local i_num = (i-1) % page.max_columns
   if ( i_num < center ) then
    do
     dir = -1
     i_pos = i_num
    end
   elseif( i_num == center ) then
    do
     dir = (i%2 == 0) and -1 or 1
     i_pos = i_num
    end
   else
    do
     dir = 1
     i_pos = page.max_columns-1 - i_num
    end
   end
   itx = (icon_x+icon_w/2+between_x*i_pos)*dir
   icon:translate( itx )
   icon:rotate(-dir * math.pi/2 * dr*ep/er, 0, 1, 0)
   icon:translate( -itx )
  end
  page:scale(sp)
 end
end

effect33_8

if文の中にif文を入れたくないので、ここでも論理演算子を使いました。

動作は、こんな感じです。

扉を開けて中に入るエフェクト(5列)

お腹いっぱいです。



当サイトの更新状況を、アラートで表示するかどうかの設定をします。


保存する

その機能で月額1500円もするのですか??
その機能なら年額1500円で手に入りますよ!

当サイトもこちらのレンタルサーバーを利用しています。

Copyright (C) 2007 Bokechans.net All Rights Reserved.