feature image

2023年12月7日 | ブログ記事

CSSでドット絵アニメーションを描こう!

これはアドベントカレンダー7日目の記事です。

23MのNapoliNです。グラフィック班に所属してます。

さて、今回の記事は「CSSでドット絵アニメーションを描く」です。

他の部員のかっこいいポートフォリオサイトに憧れて、自分もかっこいいのを作りたい!と思ったので頑張って書きました。

完成したドット絵アニメーションは以下で確認できます。

Napoolin’s Lab

書いたコードはこの辺りに転がってます。整理してなくてごめなさい。

napolin.github.io/src/component/WalkingTech.scss at master · NapoliN/napolin.github.io
my Portfolio. Contribute to NapoliN/napolin.github.io development by creating an account on GitHub.

動き

今回作成するアニメーションは以下のようなものです。

  1. タイルが回転してテックちゃんが登場する(RotateTech)
  2. テックちゃんが歩いて、歩いた跡にロゴが出てくる(WalkingTech)
  3. ロゴが出きったらテックちゃんが立ち止まる(IdleTech)

それぞれ括弧内で名前をつけました(githubに挙げてるファイル名と対応してます)。3つのアニメーションを組み合わせることで良い感じにします。

描き方 RotateTech

まず登場シーンから行きます。こんな感じでじわじわと表示される!みたいなアニメーションを描きます。

書いたコードは以下の通り。可読性、保守性を少しでも上げるためにSCSSを使って書いてます。

.rotate-tech-position {
    position:absolute;
    top: calc(50vh - (var(--scale))* 48);
    left: calc(50vw - (var(--scale) * 32) - (var(--scale) * 64));
}

.rotate-tech{
    width: var(--scale);
    height: var(--scale);
    position: relative;
}
$bmps : 32 18 #1f4ea2,33 18 #1f4ea2,34 18 #1f4ea2,31 19 #1f4ea2,32 19 #639bff,33 19 #3a6cc7,34 19 #1f4ea2,28 20 #1f4ea2,29 20 #1f4ea2,30 20 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #1f4ea2,34 20 #1f4ea2,35 20 #1f4ea2,27 21 #1f4ea2,28 21 #639bff,29 21 #639bff,....(省略);

@keyframes delay-visible {
    0% {
        visibility: hidden;
    }
    100% {
        visibility: visible;
    }}

@keyframes rotate-one {
    0% {
        transform: rotateY(270deg);
    }
    100% {
        transform: rotateY(360deg);
}}
@keyframes hide {    
    0% {
        visibility: visible;
    } 
    100% {
        visibility: hidden;
    }}
    
$i: 0;
@each $x, $y, $color in $bmps {  
    .rotate-tech-#{$i} {    
        top: calc($y * var(--scale));
        left: calc($x * var(--scale));
        background-color: $color;
        animation:
            delay-visible calc(#{$i}* 0.005s) step-end 0s 1,
            rotate-one 0.5s ease-in calc(#{$i}* 0.005s) 1,
            hide 0s step-end var(--logo-gen-start) 1 forwards;
    }    
    $i : $i + 1}

var変数は上位コンポーネントで定義されているグローバルな変数です。

RotateTechは3つのアニメーションの組み合わせで表現されています。

ピクセルごとに違うアニメーションをしなければならないので、四角い<div>要素をいっぱい並べて実装します。
html側のコードは大体こんな感じです。(React使ってるので実際にはfor文で生成したりしてます。数はハードコーディング)

<div className="rotate-tech-position">
    <div className="rotate-tech">
        <div className="rotate-tech-0 tech-gen"></div>
        <div className="rotate-tech-1 tech-gen"></div>
        <div className="rotate-tech-2 tech-gen"></div>
        ...
        <div className="rotate-tech-655 tech-gen"></div>
    </div>
</div>

rotate-tech-{i}i番目のピクセルを表します。

ピクセルの配置情報は$bmpsに並べてます(かなり長いので省略)。描いたドット絵をopenCVで読み込んで座標とカラーコードを順番に出力させれば作れます。
「x座標 y座標 colorcode」の列で構成されており、scssのfor構文を使ってそれぞれの配置・色を弄っていきます。

$i: 0;
@each $x, $y, $color in $bmps {  
    .rotate-tech-#{$i} {    
        top: calc($y * var(--scale));
        left: calc($x * var(--scale));
        background-color: $color;
        animation:
            delay-visible calc(#{$i}* 0.005s) step-end 0s 1,
            rotate-one 0.5s ease-in calc(#{$i}* 0.005s) 1,
            hide 0s step-end var(--logo-gen-start) 1 forwards;
    }    
    $i : $i + 1
}

scssにはeach構文があり、C++でのfor eachみたいなことができます。indexが必要なので、変数$iを外側で宣言しました。
topleftでピクセルの位置を調整して、background-colorで色を指定します。
一枚ずつめくれるようになってほしいので、各アニメーションに$iに応じたdelayをかけます。

回転アニメーションでは、少しずつ見えるようになってほしいので、270deg~360degで回転させます。

描き方 WalkingTech

以下の歩行アニメーションをCSSで描きます。

このアニメーションは次の4枚の画像から構成されています。

結論から言うと4つのdiv要素を動かしながら順番に表示させてるだけです。

最終的なscssは以下の通り。

$tech1_bmps : 32 18 #1f4ea2,33 18 #1f4ea2,34 18 #1f4ea2,31 19 #1f4ea2,32 19 #639bff,33 19 #3a6cc7,34 19 #1f4ea2,28 20 #1f4ea2,29 20 #1f4ea2,30 20 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #1f4ea2,34 20 #1f4ea2,35 20 #1f4ea2,27 21 #1f4ea2,28 21 #639bff,29 21 #639bff,30 21 #1f4ea2,...;

$tech2_bmps : 32 19 #1f4ea2,33 19 #1f4ea2,34 19 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #3a6cc7,34 20 #1f4ea2,28 21 #1f4ea2,29 21 #1f4ea2,30 21 #1f4ea2,31 21 #1f4ea2,32 21 #639bff,33 21 #1f4ea2,34 21 #1f4ea2,35 21 #1f4ea2,27 22 #1f4ea2,28 22 #639bff,29 22 #639bff,30 22 #1f4ea2,...;

$tech3_bmps : 32 18 #1f4ea2,33 18 #1f4ea2,34 18 #1f4ea2,31 19 #1f4ea2,32 19 #639bff,33 19 #3a6cc7,34 19 #1f4ea2,28 20 #1f4ea2,29 20 #1f4ea2,30 20 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #1f4ea2,34 20 #1f4ea2,35 20 #1f4ea2,27 21 #1f4ea2,28 21 #639bff,29 21 #639bff,30 21 #1f4ea2,31 21 #1f4ea2,32 21 #639bff,33 21 #639bff,34 21 #639bff,35 21 #1f4ea2,...;

$tech4_bmps : 32 19 #1f4ea2,33 19 #1f4ea2,34 19 #1f4ea2,31 20 #1f4ea2,32 20 #639bff,33 20 #3a6cc7,34 20 #1f4ea2,28 21 #1f4ea2,29 21 #1f4ea2,30 21 #1f4ea2,31 21 #1f4ea2,32 21 #639bff,33 21 #1f4ea2,34 21 #1f4ea2,35 21 #1f4ea2,27 22 #1f4ea2,28 22 #639bff,29 22 #639bff,30 22 #1f4ea2,...;
$tech_bmps : $tech1_bmps, $tech2_bmps, $tech3-bmps, $tech4_bmps;

.walking-tech-position {
    position: absolute;
    top: calc(50vh - (var(--scale))* 48);
    left: calc(50vw - (var(--scale) * 32) - (var(--scale) * 64));
}

.walking-tech {
    position: relative;
  }

.walking-tech > * {
    visibility: hidden;
} 

@function scale_bmps($s,$bmp){
    $list : ();
    @each $x, $y, $bmp in $bmp {
        $list : append($list, calc($x * $s) calc($y * $s) $bmp, 'comma')
    }
    @return $list;
}

$move-step : 70;

@for $i from 1 through 4 {
    @keyframes moveAnimation#{$i} {
      $n : calc(100% / $move-step);
      $j : 0;
      @while $n * $j <= 100% {
        #{$n * ($j + $i - 1)} {
          visibility: visible;
          transform: translateX(calc(var(--scale) * ($i + $j) * 2));
        }
        #{$n * ($j + $i)} {
          visibility: hidden;
        }
        $j : $j + 4;
      }
      100% {
        visibility: hidden;
      }
    }

    .walking-tech-#{$i} {
        box-shadow: scale_bmps(var(--scale), nth($tech_bmps, $i));
        animation : moveAnimation#{$i} calc(var(--logo-gen-length) + 1s) step-end var(--logo-gen-start) 1 forwards;
    }
}

例のごとくbmp情報は省略しました。
歩行アニメーションは次のように構成できます。

  1. 全部消す
  2. 1を0pxずらして表示
  3. 2を1pxずらして表示、1を消す
  4. 3を3pxずらして表示、2を消す
  5. 4を4pxずらして表示、3を消す
  6. 1を5pxずらして表示、4を消す
  7. ...繰り返し

頑張ってハードコードして書けばいいのですが、それでは芸がないので歩行step数をパラメータにできるようにします。

for構文を使って4枚をそれぞれ描画+アニメーションさせます。
@keyframe構文ではパーセントを使ってアニメーションを記述します。動きをmovestep数で分割したいので、1フレーム辺りに進むパーセンテージは$n : calc(100% / $move-step);で表記できます。

使うべきパーセンテージが分かったので、各フレームでの状態を記述していきます。

 @while $n * $j <= 100% {
    #{$n * ($j + $i - 1)} {
      visibility: visible;
      transform: translateX(calc(var(--scale) * ($i + $j) * 2));
    }
    #{$n * ($j + $i)} {
      visibility: hidden;
    }
    $j : $j + 4;
  }

4フレームごとに動かして表示、次のフレームで消すという処理です。$iがイメージ番号なことに留意すると、常に別のイメージが1つだけ表示されている状態になることが分かります。

さて、これで歩行アニメーションは完成です。

最後に、イメージをbox-shadowを用いて描画します。ここはRotateTech同様div要素をいっぱい並べても構わないのですが、美しさを重視してbox-shadowを使います。
box-shadowを用いたドット絵描画は様々な参考サイトがあるので、そちらをご覧ください。
私は以下のページを参考にしました。

https://kuroeveryday.blogspot.com/2018/10/draw-and-animate-pixel-art-using-only-css.html

まとめ

CSSでドット絵アニメーションを作る方法を紹介しました。ドット絵はやっぱりかわいいですね。ちなみに、レスポンシブ対応するために、グリッドの大きさなど様々な変数でパラメータ化できるようにしました。

明日は@ikura-hamuさんと@ramdosさんの記事です。:tanosimi-:

NapoliN icon
この記事を書いた人
NapoliN

情報理工学院情報工学系。アイコンは一応自作です。 ぷよぐやみんぐできないのに情報系きちゃったよぉふぇぇな美少女だよ。

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2023年12月11日
DIGI-CON HACKATHON 2023『Mikage』
toshi00 icon toshi00
2023年3月20日
traPグラフィック班の活動紹介(Ver.2023)
NABE icon NABE
2022年4月7日
traPグラフィック班の活動紹介
annin icon annin
2021年3月19日
traPグラフィック班の活動紹介
NABE icon NABE
2023年12月13日
HgameOPについて語る
noc7t icon noc7t
2022年9月16日
5日でゲームを作った #tararira
Komichi icon Komichi
記事一覧 タグ一覧 Google アナリティクスについて