SVGで途中から表示させるために
2020/06/14
日本時間で先週金曜日早朝にPlayStation®5の本体画像の発表がありました。
どうやらディスクレス版もあるようで、時代ですねー。 私もゲームは基本的にダウンロードで購入しているからディスクは不要かなと思いつつ、 BDやDVDも動画配信に切り替えるかなあ、、、と迷い中。 映画は配信サービスだとステレオなのが物足りないのですよね。
、、、という認識でしたが調べてみたらなんと、対応機器、 例えばPS4なら5.1chサラウンドで見られる動画配信サービスが多いのですね。 PCとiOSで見ていて、それらは非対応のことが多くて知らなかったです。 早速PS4でamazonプライム・ビデオを見てみると、 PCやiPhoneでは表示されていなかった5.1のマークが…。 もちろん対応コンテンツのみですが、映画は結構対応されているっぽい。 これはイイね! 遅ればせながら映画配信もうちのサラウンドシステムが火を吹くぜ!
というわけで今回は、いや、このフリとはほとんど関係ありませんが、 昔のゲーム機ではよくやっていたような工夫を最近このブログでしたので、突っ込んで書きたいと思います。
SVGには途中から登場させる術が無い?
この画像は前記事で使用したSVGです。 これを作るのにSVGの機能を用いているのですが、 タイムラインの途中から要素を加えるとことが不可能なようでしたので、 途中から文字を表示するのにちょっと苦労しました。
その他にも、パスを合わせるのも苦労したので、少しずつ解説していきたいと思います。
全体をgで囲って調整しやすいように
個々の解説に入る前に、このSVGファイルの構成について少し解説します。 後で色々と調整しやすいように以下のような形にしています。
<g transform="translate(256,160)"> (中身) </g>
塊ごとにgエレメントで囲い、 transform="translate(横,縦)"を使用して位置調整ができるようにしています。
具体的には、ここで解説するSVGファイルは(startX,startY)の右側に表示されている●を原点(0,0)としています。 手書きだとこうした工夫で結構楽になったりします。
固定位置に(startX,startY)を表示
<g (略)> <circle cx="0" cy="0" r="4" fill="black" /> <text x="-120" y="8" > (startX,startY) </text> </g>
circleエレメントで●を、 textエレメントで(startX,startY)を指定位置に書いています。 円の位置や半径、色、テキストの位置を指定していますが、おそらく理解できるかなあと。
「(略)」になっているところにも色々と情報が入っていますが、 解説には直接関係ないので省略します。 実際にどうしているかは、各SVGファイルのソースを直接ご覧ください。
矢印を表示して動かしてみる
<g (略)> <polygon points=" 0, -4 48,-32 24,-32 24,-48 -24,-48 -24,-32 -48,-32" fill="#e9a176" /> <circle cx="0" cy="0" r="4" fill="black" /> <text x="4" y="10"> (movingX,movingY) </text> <animateMotion dur="8s" repeatCount="indefinite" path="M0,-24 L0,0 L64,-32 L-192,-96" /> </g>
textとcircleは前述の(startX,startY)と同様ですね。 以下ではpolygonとanimateMotionを解説します。
polygonで矢印を表現
polygonエレメントを用いて矢印を表現します。 多角形の各頂点の座標を、ひとつひとつ指定することで作成します。 これは(0,-4)(48,-32)(24,-32)(24,-48)(-24,-48)(-24,-32)(-48,-32)の…7角形ですね。
ちなみに今の3Dゲームだとpolygon(ポリゴン)といえば実質的に三角形を指すことが多いのですが、 本来polygonは多角形の意味なので、こっちの方が正しいですね。
animateMotionでg全体を動かす
animateMotionエレメントを使用して矢印とそれに関連する絵を一気に動かします。 durで全体の時間を指定します。8sは8秒。 repeatCountはindefiniteで無限に。 いやindefiniteは不定、つまりいつ終わるかわからない、という意味なので本当は実装依存ということなのでしょうか。 実際には無限に動いていると思います。
そして次のpathは、正直なんだかよくわからないと思いますが、この指定に沿って動くので重要です。 Mが開始位置、Lは直線で結ぶ位置を示していますので、ここの指定はつまり、 (0,-24)→(0,0)→(64,-32)→(-192,-96)の順に直線的に動けよ、ってことです。
2点間の差分を表す赤い線
今回のSVGでは(startX,startY)と(movingX,movingY)の差が重要だったので、
それを赤い線で表現してみました。
線の片方の端は原点、もう片方は、「●(movingX,movingY)」の●につけます。
「●(movingX,movingY)」につく方は、矢印グループと同じ動きをさせる必要があります。
まあ同じ動きなら簡単だろう…と考えていたのですが、苦労しています。
まったく同じ手法は使えないようでした。
<line x1="0" y1="0" x2="0" y2="0" stroke="red" stroke-width="3">
<animate attributeName="x2"
values="0;
0;
0;
64;
-192"
dur="8s" repeatCount="indefinite" />
<animate attributeName="y2"
values="0;
0;
0;
-32;
-96"
dur="8s" repeatCount="indefinite" />
</line>
<line x1="0" y1="0" x2="0" y2="0" stroke="red" stroke-width="3"> <animate attributeName="x2" values="0; 0; 0; 64; -192" dur="8s" repeatCount="indefinite" /> <animate attributeName="y2" values="0; 0; 0; -32; -96" dur="8s" repeatCount="indefinite" /> </line>
lineで線を引く
lineは(x1,y1)と(x2,y2)を結ぶ線を引きます。 色や太さも指定できます。
animateで指定した値を変更する
line内ではanimateMotionは使え無さそうですが、 animateエレメントを使うことで、指定したアトリビュートの値を変更できます。 それを用いて、ここではx2とy2を変更して、矢印のpathに合わせて動かしてみました。
しかし…画像を見ていただくとわかるのですが、うまくくっついてくれないのですよね。。。 どうやらanimateは、例えば目指すべき場所が4箇所あり、全体が8秒なら、それぞれの区画で時間を等分して2秒ずつ動くのに対して、 animateMotionは、path全体をひとまとめにして等速で動くようなのです。 そのためズレができてしまいました。 最初と最後は合っているのですけれど(汗
animateMotionにkeyPointsとkeyTimesを導入
そこで矢印のグループのanimateMotionエレメントの中に以下の要素を追加しました。
keyPoints="0; 0.06677; 0.066771; 0.266; 1" keyTimes="0; 0.25; 0.50; 0.75; 1" calcMode="linear"
その成果がこの画像です。 バッチリくっついているように見えますよね! 内部的にはそれぞれ別々に動いていますが、計算で合わせているので完璧に見えます。
タッチは円を広げて表現
<circle cx="0" cy="0" fill="none" stroke="lightblue" stroke-width="2"> <animate attributeName="r" values="0; 0; 928; 4096; 4096" dur="8s" repeatCount="indefinite" /> </circle>
circleエレメント内でanimateで半径rを設定するようにして、あるタイミングでグッと大きくしています。 なおfillにはnoneを指定することで、塗りつぶさない円が描けます。
文字を途中から出現させるのに苦労しました
ただ、、、途中から表示する方法を色々調べたのですが、どうも無さそうなのですよね。 そうすると、今できることを組み合わせて工夫する必要があります。
(a)最初は白くしておき、ある瞬間から黒にする
(b)最初は画面外に置いておいて瞬間移動で登場させる
(c)白いマスクを使う
などの方法が考えられるのですが、 (a)はできそうなのですが色を瞬時に変更するにはアニメーションの変化タイミング、つまり今まで作ってきた区画を増やす必要があってやや面倒、 (b)は瞬間移動が不可能っぽかったので(a)と同様に区画を増やして移動させる必要がありそうだったので、 区画を増やさなくて良い(c)白いマスクを使うことにしました。
ああ、コロナ禍で政府から配られたヤツ…、ではなくて、 絵を隠すためのものの意味ですね。 白い四角を上に重ねることで、その下が見えないようにします。 この画像はわかりやすいように色を変えて半透明にしていますが、本番では不透明のwhiteを指定しています。 マスクを解除するのは、高速に下に動かす形で実装しています。
もう少し具体的には、長方形を表示するrectエレメントのyすなわち上の位置をanimateで変更します。 (startX,startY)●を隠すマスクのSVGソースは以下の通り。
<rect x="-256" y="-29" width="260" height="288" fill="white"> <animate attributeName="y" values="-29; -5; 1024; 1024; 1024" dur="8s" repeatCount="indefinite" /> </rect>
●(movingX,movingY)の方は以下です。
<rect x="4" y="-29" width="256" height="288" fill="white"> <animate attributeName="y" values="-29; -5; -5; 1024; 1024" dur="8s" repeatCount="indefinite" /> </rect>
この手法の場合、フレームレートの高い環境だと文字が上から徐々に現れるのですが、 高速なので気にならないと思います。
これで一件落着…じゃなかった!円が隠れている!
つまり円はマスクより優先度を上げなければなりません。 SVGは下に書いたほうが表示優先度が高くなりますので、ソース内の記述位置をずらすだけです。 やってみたのがこの画像です。
右下は良くなったのですが、 矢印よりも手前に表示されてしまい、タッチの波紋としては変と言える状態になってしまいました。
文字の方は、上に円が重なってもほとんど気にならなかったので、 矢印だけ優先度を上げることにしました。 具体的には、元のgから矢印のpolygonエレメントを分離して、SVGソース内で円より下に書くようにします。 そして矢印を動かすために、新規gで囲い、animateMotionをコピーする手法を取りました。
それで完成したのが次の画像です。どうでしょう。ようやくうまくいきましたね。おつかれさまでした!
まとめ
昔のゲーム機はできることが少なかったので、本記事のような、できる範囲でなんとかするという工夫を色々としていたのですよね。 それらもどこかで解説できると良いなと思っています。 もう古い技術で今は役に立たないものは紹介しても怒られることは無いとは思うのですが、危険かなあ。。。
補足
・それぞれできないことを前提に解説をしていますが、SVGの仕様をすべて把握しているわけではないので、実はできたらスミマセン。・同一pathを複数箇所で使うことはできそうですが、今回はコピーで対応しました。
・コピーは保守性が悪くなるのでできるだけ避けるべきですが、今回の用途での費用対効果は十分でしょう。趣味でそれを考えるのも避けたいですが(汗
カテゴリー:SVG
Copyright (C) Logic Lovers Inc.