173
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

クソアプリ2Advent Calendar 2018

Day 13

Marqueeタグに詳しくないと攻略できないブロック崩しゲーム

Last updated at Posted at 2018-12-13

はじめまして, 都内でCTO(Chotto Technology Ojisan)やCFO(Chotto Frontend Ojisan)をしている @sadnessOjisan です. この投稿はクソアプリカレンダー2の13日目の投稿です.

先人たちはすごいクソの山 1 を作っていたので, ぼくも負けじとクソを作ろうと思い, 気合を入れました. そこで玉もバーもmarqueeタグで実装されたブロック崩し Marquee Breakout を開発しました. (残念ながらPCのみでの対応です. 小さい画面だとプレイできないようになっています. )
ソースコードは記事の末尾にあります. 初めての投稿なのですが, よろしくお願いいたします.

ゲームはこちら: https://inspiring-galileo-db9414.netlify.com/

はじめに

みなさん, marqueeタグってご存知でしょうか??こんなやつです.

sample.gif

marqueeタグって昔のHPにあった, 要素をスライドさせられるタグのことです.

<marquee>あなたは100人目のお客様です</marquee>

実はこのmarqueeタグは, 入れ子にすることができます.

<marquee direction='up' height='400px'>
  <marquee>入れ子だよ〜〜〜〜</marquee>
</marquee>

入れ子にすると何が起きるでしょうか. なんと, 片方のmarqueeの移動方向を上向きにしていると, 斜めに動くようになります .

ireko.gif

そして, 入れ子にするとなんとブロック崩しを作れます(困惑).
バーは普通のmarqueeで作れます.

<marquee behavior='alternate'>ーーーーーーーーーーーーーーー</marquee>

behavior='alternate'は跳ね返りを指定するためのプロパティです.

玉は斜めに動かす + 跳ね返り指定で作れます. (入れ子 + alternate指定)

<marquee direction='up' height='400px' behavior='alternate'>
  <marquee behavior='alternate'>●</marquee>
</marquee>

今日はmarqueeタグ実装したブロック崩しゲームをクソアプリとして持ってきました!それが, marqueeタグの挙動に詳しくないとなかなか高得点を取れないゲーム, Marquee Breakoutです.

gamesample.gif

この記事で学べること

  • Marquee Breakoutの遊び方
  • marqueeタグが魅せる未来
  • marqueeタグでブロック崩しを作る方法
  • ログインとスコア保存機能をもったサービスを, 無料で開発・ホスティングできる方法

Marquee Breakout

遊び方

  1. 全国ランキングに挑戦したい人はサインアップする.
  2. マーキーのパラメータを入力してスタートする.
  3. 球が下に落ちないようにパラメータを調節する.

ぜひとも, marqueeに変わって台頭してきた今時の子たちに球をぶつけてください. 2
スクリーンショット 2018-12-13 1.23.19.png

どれを壊しても1点です.

marqueeタグが魅せる未来

Marqueeタグは今のHTML5の仕様では廃止されている

マーキー要素は様々な理由からHTML5から廃止されたタグです. 明確な理由は私が探す限りでは見つけられなかったのですが, アクセシビリティが悪い, セマンティックなタグではないといった批判がされていました. 3

Marqueeタグの使い道

そんなmarqueeタグですがこの子は多種多様なことができます.
その面白い挙動は marqueeのいろいろな移動にまとめられていたので, 再現してみました.

文字を流す

これは先ほどのものです.
sample.gif

<marquee>あなたは100人目のお客様です</marquee>

折り返す

alt.gif

<marquee behavior='alternate' scrollAmount='50'>折り返すよー</marquee>

上にも流せる

ue.gif

<marquee direction='up' scrollAmount='50' height='500px'>上上上上上</marquee>

波を作る

marqueeのいろいろな移動 さんから拝借しています.

nami.gif

<marquee bgcolor="#f0f0ff" width="500">
  <marquee bgcolor="#f0f0ff" height="50" width="40" direction="up" behavior="alternate">
    波~
  </marquee>
</marquee>

斜めに流す

これも先ほどのものです.

ireko.gif

マーキーの入れ子でブロック崩しを作れる

marqueeのいろいろな移動 さんから拝借しています.

break.gif

<marquee bgcolor="#f0f0ff" width="300" behavior="alternate" scrollamount="11">
  <marquee bgcolor="#f0f0ff" width="16" height="270" direction="up" behavior="alternate" scrollamount="4">●</marquee>
</marquee>
<BR>
<marquee bgcolor="#f0f0ff" width="300" behavior="alternate" scrollamount="12">
</marquee>

ブロック崩しを作るにあたっての課題

このように入れ子にして多様な動きを出せました. しかしブロック崩しの動きであって, ブロック崩しではありません. ブロック崩しをするためにはブロックとの衝突判定, バーとの衝突判定や跳ね返りが必要です. またゲームのスコアやゲームオーバーしているかの状態の管理も必要になってきます. それらはどのようにして実装すればよいのでしょうか. それらを次の章にまとめてみました.

技術的なおはなし

フロントエンド側

衝突判定を行うためにJavaScriptを利用してmarqueeタグの要素を監視・制御しました.

Marqueeの挙動

プロパティを設定することで動き方を制御できます. 方向は direction, 跳ね返りの挙動は behavior, スピードはscrollamountで指定できます. どのようなプロパティを利用できるかは: マーキー要素 (廃止)を参照すると良いでしょう. しかしアニメーション中にプロパティを書き換えても, アニメーション終了後にしか反映されないため注意が必要です.

ブロックやバーにぶつかると跳ね返るというのは, behavior='alternate'とすれば実現できます.

プロパティ名 制御できる箇所
behavior スクロール方法
bgcolor 背景色
direction スクロール方向
height スクロールの高さ
hspace 高さのpadding
loop 繰り返し回数
scrollamount 1インターバルにおける要素の移動ピクセル数
scrolldelay 1インターバルとして扱う時間
truespeed scrolldelayに足切りを設けるかのフラグ
vspace 垂直方向のpadding
width スクロールの幅

marqueeのプロパティの数値調整

背に腹は代えられません. React を使いました. 反則かもしれませんが, 便利なものはどんどん使って行きましょう. marqueeJSXの中でも動きます. marqueeのプロパティにpropsを渡してプログラマブルなmarqueeタグを作ることができます. 4

<GameCanvas>
  <Score>your score is {score}. </Score>
  <Blocks />
  <marquee
    behavior={ballYBehavior}
    scrollamount={ballYSpeed}
    height={GAME_SCREEN_HEIGHT}
    style={{ position: "absolute", top: `${bounceBorder}px` }}
    direction={vBallDirection}
  >
    <marquee
      behavior={ballXBehavior}
      scrollamount={ballXSpeed}
      direction={hBallDirection}
      width={`${width}%`}
    >
      <Ball ref={this.ball}>●</Ball>
    </marquee>
  </marquee>
  <marquee
    behavior={barBehavior}
    scrollamount={barSpeed}
    style={{ position: "absolute", top: GAME_SCREEN_HEIGHT }}
  >
    <span ref={this.text}>--------------------</span>
  </marquee>
</GameCanvas>

ちなみにReactMarqueeタグ (ReMarquee) を作ったこともあります.
FYI. Marqueeタグでブロック崩しを作りたい

marquee内の要素の位置を取得する

refを取ってDOMの位置を取得します. marquee移動中の位置はsetTimeoutで毎sec取得し, stateを更新していきましょう. そのstateは球, バー, ブロックにpropsとして渡して生きましょう. 「なんかやばいことしていない?」だって?クソアプリなので実装はクソで良いんですよ. 5

private text = React.createRef<HTMLParagraphElement>();
private ball = React.createRef<HTMLParagraphElement>();
setInterval(() => {
  if (this.text.current && this.ball.current) {
    const barPosition = this.text.current.getBoundingClientRect();
    const barLeftPosition = barPosition.left;
    const barTopPosition = barPosition.top;
    const barBottomPosition = barPosition.bottom;
    const ballPosition = this.ball.current.getBoundingClientRect();
    const ballLeftPosition = ballPosition.left;
    const ballWidth = ballPosition.width;
    const ballRightPosition = ballLeftPosition + ballWidth;
    const ballTopPosition = ballPosition.top;
    const ballBottomPosition = ballPosition.bottom;
    const barWidth = this.text.current.getBoundingClientRect().width;
    const barRightPosition = barLeftPosition + barWidth;
    this.setState({
      barPositon: {
        left: barLeftPosition,
        right: barRightPosition,
        top: barTopPosition,
        bottom: barBottomPosition
      },
      ballPosition: {
        top: ballTopPosition,
        left: ballLeftPosition,
        right: ballRightPosition,
        bottom: ballBottomPosition
      }
    });
  }
}, 1);

marqueeタグ要素とブロックの衝突判定を行う

ここが僕の一番の工夫ポイントです. 実はblock一つ一つをコンポーネントとして作り, その中で, blockの位置のstateprops経由で知られせるバーと球の一から, ブロックにぶつかったkどうかの衝突判定を行なっていました.

玉の位置はms間隔でpropsから流し込まれます. あとはpropsが変更されるたびに static getDerivedStateFromPropsでstateを衝突判定とstateの更新を行うと良いです. 6

static getDerivedStateFromProps(nextProps: Props, prevState: State) {
    const blockState = prevState;
    const { onCollide } = nextProps;
    const { left, right, top, bottom } = nextProps.ballPosition;
    if (left && right && top && !blockState.isCollapsed) {
      if (
        top <= blockState.bottom &&
        right >= blockState.left &&
        left <= blockState.right &&
        bottom >= blockState.top
      ) {
        onCollide(bottom);
        return { isCollapsed: true };
      }
    }
  }

最後にお型付け

とりあえずanyって書いておきましょう. クソアプリカレンダーなので大丈夫です. ここには上司もレビュアーもいません. 7

スクリーンショット 2018-12-09 23.38.59.png

ちなみにstyled-componentsで使うpropsの型付けの方法が分からなくてて苦労しました.

サーバー&インフラ側

@kt3kさんのNetlify+Node.js+MongoDBでJAMstackな草を生やすサービスをOSSで公開しました!という記事を参考にしました. この記事では, スケーラブルで高機能なWebアプリケーションを0円で開発・公開するための方法を紹介しており, 当記事でもその構成を参考にしました. また記事内には,

Web 2.0 になってフロントエンド JavaScript が複雑化する中で、これが正解と言える Web プログラミングのスタイルがなかなか分かりづらい時期が続いていたように思います。また、上から下までの動的な Web サイトを作るのに必要な知識が相当に増えてしまったように感じました。そのせいか、(統計がある訳ではありませんが)自分の観測範囲では、誰かが趣味で作った変な Web サービスを見かける機会が減ってしまったように感じました。
...
この記事を読んで、変な思いつきの/独創的な Web サービスを作る人が出てくれたら良いなと思います。

とあり, 変な思いつきなWebサービス作りのために参考にさせていただきました. ソースコードも公開されているため, 興味がある方は一読されることをオススメします.

NodeJS

NodeJSは, サーバーサイドで動くJSです. expressというライブラリを利用することで, ルーティングの設定やリクエストのparseを簡単に行え, 手間がかかる設定も少なくWebアプリを動かせます.

Auth0

Auth0は, 認証機能を提供するサービス(IDaaS)です. このサービスを使うことで, ログインやユーザーを管理する機能を自前で作ることなく, それらの機能を利用できます. 実装もとても簡単で, フロントエンド側のJavaScriptにはauth-0.jsというライブラリを入れてそのメソッドを呼ぶだけでよく, サーバー側もSDKとを入れて, 認証したいルーティングに少し設定を書くだけで認証ができます.

これにより, ログインユーザーでしかAPIにアクセスできなくしたり, ログインしたユーザーの特定などができます. Marquee Breakoutではスコアを登録するときの機能をログインユーザーしか行えないようにしています.

mongodb + mongoose + mlab

mongodb は, 「JSON をそのまま保存できて, そのまま取り出せるかのように扱うことができてしまうDB」です. スキーマ的なものは, アプリケーションのコードにそのまま書けて, どんどん破壊的な変更を加えても動きはするので, スピード重視開発だとすごい楽です. 操作やデータの構造も直感的なのでそれもスピーディな開発を行う上で非常に助かりました.

そして, mlabはそのDBをクラウド上にホスティングしてくれるサービスで500MBまでならタダで使えます. 同様のサービスにmongodbが提供しているMongoDB Atlasというものがあります. 本当はこちらを採用したかったのですが, プランの設定, NodeJSのバージョン, mongodbのバージョン, mongoose (mongodbのODM)のバージョンが合わないと使えなさそうだったので, 少し格闘した末に諦めてmlabを採用しました.

Now

これらの 機能を呼び出せるサーバーは Now というサービスにデプロイしています. Nowも無料で利用できます. package.jsonDockerfileを書いて

$ now 

ってすれば, もうそれだけでホスティングできます. 無料プランでは発行されるURLはランダムなのですが, 固定化できたり, デプロイ前に発行されるURLも知れるので, クライアント側にあらかじめ向き先を登録することができます.

Netlify

クライアントサイドのコードは Netlifyにデプロイしています. 僕はZeit教なのでこちらもNowにあげたかったのですが, クソアプリカレンダーは失敗しても良さそうな雰囲気を感じ, 新しいことに挑戦してもバチが当たらないと思い, Netlifyを使ってみました. これはGithubと連携し, あるブランチにコードがpushされたらそのコードを対応する向き先にデプロイしてくれるサービスです. CDっぽいことをしてくれるだけでなく, デプロイ先も提供してくれるのに無料で利用できます. 学習コストがかからなかったのでとても助かりました.

終わりに

後半は真面目な話になってしまいましたが, このクソアプリはいかがでしたでしょうか. 是非, 全国TOP目指して頑張ってください!!!

ブロック崩しコード: https://github.com/sadnessOjisan/MarqueeBreakout

(P.S.) Qiitaの投稿楽しいですね!!いつも見てばっかりだったのですが, これからもがんばって投稿していきたいです!

  1. 「通知止まらんwww」を体験できるアプリを作った / 社会に一石を投じるクソアプリ開発

  2. なにぃ!?「そこはbrowselifyじゃなくてWebpackだろ」だと!? 画像を差し替えるPRをください(涙)

  3. 他にも, HTML5ではcenterタグやblinkタグといったものも削除されています.

  4. (注)styled-componentsからはmarqueeタグをスタイリングできないため,スタイルは直接設定しましょう.

  5. 職場で私は非常に真面目です. 今日だけです.

  6. static getDerivedStateFromPropsReact v16.3で導入されたAPIです. Reactv17から, propsが更新されたときに発火される組み込み関数の componentWillReceivePropscomponentWillUpdateの利用は非推奨になります.

  7. 普段のお仕事ではきちんと型を書いています. こんなことをしているのは今日だけですよ. 今日だけ.

173
51
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
173
51

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?