AOYAMA Koji's PROGRAMMING BLOG

SVGで途中から表示させるために

2020/06/14
プログラミングブログ / グラフィックファイル / SVG / (本記事)

 日本時間で先週金曜日早朝に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)を表示


 それでは具体的に個々のパーツに入っていきます。 まずは基本的な、位置固定の「(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ファイルのソースを直接ご覧ください。

矢印を表示して動かしてみる


 次は矢印と「●(movingX,movingY)」を表示して動かします。 それらをひと塊として、原点よりちょい上からスタート。 まず下に動いて、原点に来たら一旦休憩、そして右上、左上、に動きます。
<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>

 textcircleは前述の(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で線を引く


 lineは(x1,y1)と(x2,y2)を結ぶ線を引きます。 色や太さも指定できます。

animateで指定した値を変更する


 line内ではanimateMotionは使え無さそうですが、 animateエレメントを使うことで、指定したアトリビュートの値を変更できます。 それを用いて、ここではx2とy2を変更して、矢印のpathに合わせて動かしてみました。
 しかし…画像を見ていただくとわかるのですが、うまくくっついてくれないのですよね。。。 どうやらanimateは、例えば目指すべき場所が4箇所あり、全体が8秒なら、それぞれの区画時間を等分して2秒ずつ動くのに対して、 animateMotionは、path全体をひとまとめにして等速で動くようなのです。 そのためズレができてしまいました。 最初最後は合っているのですけれど(汗

animateMotionにkeyPointsとkeyTimesを導入


 どうやらanimateMotionの中でkeyPointsとkeyTimesを用いると、 指定したタイミングで、path全体のどの位置にいるかを指定することができるようです。 要はanimateMotionの動きの速度を調整できるということになります。 調整できるなら、 全体の時間を4等分し各区画を2秒ずつとして、 そのタイミングでpathのどの位置にいるかを指定すれば今回の問題は解決しそうです。 パス全体および各区画の長さは計算で求められるので、その結果を指定すれば良いでしょう。
 そこで矢印のグループのanimateMotionエレメントの中に以下の要素を追加しました。
keyPoints="0;
           0.06677;
           0.066771;
           0.266;
           1"
keyTimes="0;
          0.25;
          0.50;
          0.75;
          1" 
calcMode="linear"

 その成果がこの画像です。 バッチリくっついているように見えますよね! 内部的にはそれぞれ別々に動いていますが、計算で合わせているので完璧に見えます。

タッチは円を広げて表現


 矢印が(startX,startY)●に来たタイミングは、タッチをイメージしたものでした。 そのためタッチ時の表現として、波紋のようなを表示してみることにしました。 最初は半径がなく、 矢印が●に来たタイミングでパーンと大きくげる円を描きます。
<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を指定することで、塗りつぶさない円が描けます。

文字を途中から出現させるのに苦労しました


 これで完成!としてもまあ良いのですが、起点座標を表す(startX,startY)は矢印が到達してから、 移動座標の(movingX,movingY)は、さらにそこから移動してから表示した方が解説と合うので、なんとか実現したいと思いました。
 ただ、、、途中から表示する方法を色々調べたのですが、どうも無さそうなのですよね。 そうすると、今できることを組み合わせて工夫する必要があります。
(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
著者プロフィール
青山公士(あおやま こうじ)
中学2年生からゲームプログラミングに明け暮れる。ゲーム開発者としての代表作に「スーパー桃太郎電鉄II」(ハドソン)メインプログラマー、[PR]『ドラゴンクエストX オンライン』(スクウェア・エニックス)テクニカルディレクター/プロデューサーなどがある。[PR]「ドラゴンクエストXを支える技術」(技術評論社)著者。本ブログは今までの経験を活かしプログラミングが楽しいと感じる人が少しでも増えるようなものにしたい。 @kojibm
株式会社ロジック推し
推し情報を論理的にわかりやすく紹介することで「世の中をちょっと楽しく」をミッションに活動中。 HP X Instagram
privacy policy
ピックアップ
Loading...
最新記事
Loading...
関連記事
Loading...