CSSでベルトスクロールゲームを作った。

この記事は [CSS Advent Calendar 2019](https://qiita.com/advent-calendar/2019/css) 16日目の記事です。



# 概要
CSSで昔懐かしのベルトスクロールゲームを作りました。
PC、画面サイズ大きめで遊んでください><
https://codepen.io/Rin_T_T/pen/MWYjJKv?editors=1100  
本記事はあくまで作った物の簡単な説明のため、コード,制作過程を一部省略しています。

# 設計書
必須機能をまとめます。

衝突判定：　hoverで判定
ゲーム画面のスクロール: transitionで実装
スタート: hoverで判定
スコア用のアイテムを設置: checkboxで実装
キャラクターの移動：　カーソルを画像に置き換える。

早速作っていきます。


### 最初にスクロールエリアと衝突オブジェクトを作成します。  

```pug:index.pug  

    .gameArea_inner
      .playArea.-test2
        - for(var i = 1; i <= 50; i++)
          .item.-object(class='-o' + i)
```  


衝突判定を実装します。
.item.-objectをhoverすると兄弟要素のFailが最前面に表示するようにします。
※faile要素を最前面に維持するため、.faile要素自体にもhover状態を指定しておきます。
ひとまず判定が通ることが確認できれば良いでしょう。  

```pug:index.pug  

    .gameArea_inner
      .playArea.-test2
        - for(var i = 1; i <= 50; i++)
          .item.-object(class='-o' + i)
        + .faile
          　.faile_inner
            　.faile_contents   
```  



```scss:style.scss
.faile {
    position: absolute;
    width: 100%;
    height: 100%;
    z-index: -100;//初期状態では非表示
    display: none;//初期状態では非表示
    &:hover{
      opacity: 1;
      z-index: 100;
      display: block;
    } 
    &_inner{
      display: flex;
    }
    &_contents{
      text-align: center;
      background-image: url('https://portfolio.littledemon.pw/cssadvent/failed.png');
      background-size: 386px 160px;
      width: 100%;
      height: 100vh;
    & > *{
      margin-top: 15px;
    }
  }
}


.item{
  width: 30px;
  height: 30px;
  background-color: #9c290d;
  position: absolute;
  z-index: 100;
  opacity: 1;
 &.-object{//衝突オブジェクトクラス
    position: absolute;
　  &:hover{
      & ~ .faile{
        z-index: 100;
        opacity: 1;
        display: block;
      }
    }
  }
}
```

### ゲーム画面のスクロールを実装します。  
.gameArea_innerのサイズをプレイエリアとし最大値を560vwに設定します。
animationを指定し、スクロールスピードを設定します。  
ついでにスタート/リスタート用のanimationも作っておきます。  

```scss:style.scss


.gameArea{
 
  &_inner{
    width: 560vw;
    display: flex;
    position: relative;
    z-index: 0;
    left: 0;
    animation: areaScroll 70s linear forwards;
  }
}

@keyframes areaScroll {
  0% {
    position: relative;
    left: 0vw;
  }
  100% {
    position: relative;
    left: -560vw;
  }
}


@keyframes areaScrollBack {
  0% {
    position: relative;
    
  }
  100% {
    position: relative;
    left: 0vw;
  }
}

```

### スクロールを制御できるスタート/リスタートを作成します。


CSSは常に親から子へもしくは兄弟要素のみ指定できます。
スタートはスクロールしている要素と同じレイヤーに配置します。
hoverで先ほど作成したareaScrollBackを発火できるよう設定します。

```pug:index.pug

+ .wrap
+  .gameArea
+   .startArea
+     .startChecker
+     .startLine 
+       .startLine_text START 
    .gameArea_inner
      .playArea
        .item.-object.-ribbon.-top
        .item.-object.-ribbon.-bottom
        - for(var i = 1; i <= 50; i++)
          .item.-object(class='-o' + i)

```



```scss:style.scss

.startArea{
  display: flex;
  z-index: 10;
  &:hover + .gameArea_inner{//　兄弟要素を指定し、animationを発火する。
    animation: areaScrollBack 150s ease forwards;
  }
}
.startChecker{
  width: 160px;
  background: linear-gradient(45deg, black 25%, transparent 25%, transparent 75%, black 75%),
              linear-gradient(45deg, black 25%, transparent 25%, transparent 75%, black 75%);
  background-color: white;
  background-size: 40px 40px;
  background-position: 0 0, 20px 20px;
}

.startLine{
  background-color: white;
  height: 100%;
  padding: 0 15px;
    &_text{
    writing-mode: vertical-rl;
    height: 100%;
    text-align: center;
  }
}


.gameArea{
  width: 100%;
  height: 100vh;
  background-color: white;
  overflow: hidden;
  display: flex;
  &_inner{
    ~~~
  }
}

```

### スコア用のアイテムを作成します。

```pug:index.pug
 .wrap
  .gameArea
   .startArea
     .startChecker
     .startLine 
       .startLine_text START 
    .gameArea_inner
      .playArea
        .item.-object.-ribbon.-top
        .item.-object.-ribbon.-bottom
        - for(var i = 1; i <= 50; i++)
          .item.-object(class='-o' + i)
+　　　　- for(var i = 1; i <= 24; i++)
+         label.present
+           input(type="checkbox")
+           .present_item(class='-p' + i)

```

```scss:style.scss
.present{
  &_item{
    position: absolute;
    width: 40px;
    height: 40px;
    display: block;
    background-size: 30px 30px;
    transition: left 7s;
    &.-p1{//ゲームエリア上の位置(checkboxがfalseの時)
        top: 45vh;
        left: 20.5vw;
     }
  }
  & input{
    display: none;
    &:checked {
      & + .present_item {
            pointer-events: none;
            transition: left 4s;
            &.-p1{//アイテムを取得した後の位置(checkboxがtrueの時)
             top:60vh;
             left: 585vw;
        }
      }
    }
  }
}
```

scssが少し複雑ですね、メインのデザインは.present_itemに担当してもらいます。
input要素は非表示にしておき、checkBoxがtureの時、後述するゴールエリアに遷移するように設定します。  

### キャラクターの移動

Cursolを画像に変更します。
利用できるサイズは最大128*128のようですが、<br>ブラウザの互換性から考えると32*32が推奨のようです。
※今回は少し小さかったため、64pxにしました。

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Basic_User_Interface/Using_URL_values_for_the_cursor_property


以上で完成です。

反省点。

遊んでいただけると気づくかもしれませんが、hover要素はカーソルの移動時に現在位置を再取得します。強制スクロールというゲーム上、衝突オブジェクトの手前で停止するとhoverの判定されず貫通します。
とりあえずゴールを見たいという人はカーソル放置でゴールまでたどり着きます(笑)

スコア用のカーソル衝突オブジェクトはもっとCSS芸的なもので、頑張りたかった...時間が足りませんでした...

chrome　PCのみの対応です。