0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Tauriでエンジンからゲームを作ってみるAdvent Calendar 2024

Day 4

【Day4】画面描画の方法、Canvas APIについて【QAC24】

Last updated at Posted at 2024-12-03

Canvas を使って画面描画をしてみよう!

今回は、Canvas API を使って画面描画をしてみます。

まずはindex.htmlをいい感じにまっさらにして、Canvas 要素を追加しましょう。

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="/src/styles.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Tauri App</title>
    <script type="module" src="/src/main.ts" defer></script>
  </head>

  <body>
    <canvas id="test-canvas" width="300" height="300"></canvas>
  </body>
</html>

次に、main.tsをもっとまっさらにして、Canvas の DOM を取得してみましょう。

main.ts
window.addEventListener("DOMContentLoaded", () => {
  const canvas = document.getElementById("test-canvas");
});

前回の記事で吹っ飛ばした気がしますが、window.addEventListener("DOMContentLoaded", () => { ... });は、DOM の読み込みが完了した時に実行される関数です。
んでたまに何度も出てきてる DOM っていうのが何なのかといいますと、HTML の要素 (<div>...</div>とか<p>...</p>とか) のことを、TypeScript では DOM と呼びます。
すなわち、「DOM の読み込み完了」とは、HTML の要素が全て読み込まれ(画面に描画され)た時を指します。

更に、document.getElementById("test-canvas")は、HTML におけるidtest-canvasである要素を取得する関数です。
さっきのindex.htmlでは、<canvas id="test-canvas" width="300" height="300"></canvas>という要素がありますので、これを取得しています。

という訳で、まずは Canvas の説明です。

Canvas とは

Canvas というものは HTML5 で導入された、画面描画を行うためのやつです。
画面描画というと、画像や図形、文字なんかを描画することが出来ます。
つまりこれ、これまで CSS でなんかめっちゃ頑張って図形作成とかやってたような奴が、TypeScript から簡単に操作できるって話なんですよ。

まずはものは試しです。

Canvas に何かを描画するにはコンテキストというものを取得する必要があります。

main.ts
window.addEventListener("DOMContentLoaded", () => {
  const canvas = document.getElementById("test-canvas");
  const ctx = canvas.getContext("2d");
});

はい、まず Canvas 要素を取得して、それにgetContextというのを呼び出して 2D の Context を取得して...

スクリーンショット 2024-11-25 002847.png

あれ?
なんかエラー...?

あーあ、あなた TypeScript に怒られてますね。

TypeScript で DOM をいじいじするときのお話

上記コードは JavaScript では、エラーもなく動作します。
が、TypeScript はそうはいきません。

まず、const canvas = document.getElementById("test-canvas");から見ていきましょう。

先ほどの画像を見ても分かるように、: HTMLElement | nullと書いてあります。
これは、canvasHTMLElement型かnull型のどちらかであることを示しています。

なぜならば、test-canvasという id を持つ要素が存在しないかもしれないからです。
これではいけません。まさに Null Pointer Exception です。ガッ...
nullを消費しましょう。

main.ts
window.addEventListener("DOMContentLoaded", () => {
  const canvas = document.getElementById("test-canvas");
  if (!canvas) return;
  const ctx = canvas.getContext("2d");
});

if 文を使いましょう。
TypeScript での if 文は、if (条件) { ... }と書くことが出来ます。
(条件が真の時の処理が 1 行ですむ場合は、波かっこを省略することが出来ます)
!canvasというのですが、まず!は否定を表す演算子 (NOT) です。
!truefalse!falsetrueです。

そして、以下の記事にもあるように、TypeScript の条件式の中で、nullfalseとして扱われます。
(というより、nullじゃないような 1 とか存在する値はtrueとして扱われます)

つまり、!canvascanvasnullである場合にtrueを返します。

これで、canvasnullである場合は、returnで関数を終わらせ、nullじゃない場合は次の処理に進むようになりました。

よし! これでエラーは出なくな...あれ?

getContextがエラーを吐いていますね。
これは、canvasにはgetContextメソッドが存在しないからです。

いや待て、さっきお前「JavaScript なら正常に動く」言ってたやんかと。
ってことはgetContextだかなんだかもあるんやろ?と。

原因は、変数canvasHTMLElementだからです。

HTMLElement にはgetContextメソッドが存在しません。
なので、canvas<canvas></canvas>であることを TypeScript に伝える必要があります。
<canvas></canvas>...すなわちHTMLCanvasElementにはgetContextメソッドが存在します。

そして、その TypeScript に伝える方法というのが、型アサーションです。

main.ts
window.addEventListener("DOMContentLoaded", () => {
  const canvas = document.getElementById("test-canvas") as HTMLCanvasElement | null;
  if (!canvas) return;
  const ctx = canvas.getContext("2d");
});

document.getElementById("test-canvas")の後ろにas HTMLCanvasElement | nullと書いています。
これで、TypeScript はcanvasHTMLCanvasElementnullのどちらかであることを理解しました。

この型アサーションというのはとても強力なもので、型を強制的に変換することが出来ます。
今回、HTMLElementからHTMLCanvasElementへの変換のため、互いの型が近いため正常に動きます。
しかし、as HTMLCanvasElement | nullというのをas number | nullとでも書いてみましょうか。
現在の私のエディタではエラーが発生しません。
しかし、人間の目で見る限り、document.getElementById("test-canvas")numberを返すことはないでしょう。

...というように、型アサーションは便利ながらとても強力なものなので、使いすぎには注意しましょう。

ちなみに、as HTMLCanvasElement | nullas numberとしてみると、私のエディタでもエラーを吐いてくれました。

型 'HTMLElement | null' から型 'number' への変換は、互いに十分に重複できないため間違っている可能性があります。意図的にそうする場合は、まず式を 'unknown' に変換してください。
型 'HTMLElement' は型 'number' と比較できません。ts(2352)

最後に、ctxにも同様にnull消費を行いましょう。

main.ts
window.addEventListener("DOMContentLoaded", () => {
  const canvas = document.getElementById("test-canvas") as HTMLCanvasElement | null;
  if (!canvas) return;
  const ctx = canvas.getContext("2d");
  if (!ctx) return;
});

描画だ!

さて、ここまでで Canvas の Context を取得することが出来ました。
描画するにはその Context を使います。

試しに、 Canvas に四角形を描画してみましょう。

main.ts
window.addEventListener("DOMContentLoaded", () => {
  const canvas = document.getElementById("test-canvas") as HTMLCanvasElement | null;
  if (!canvas) return;
  const ctx = canvas.getContext("2d");
  if (!ctx) return;

  ctx.fillStyle = "red";
  ctx.fillRect(10, 10, 100, 100);
});

スクリーンショット 2024-11-25 010441.png

おっ!?
赤い四角形が描画されました!

ctx.fillStyle = "red";で色 (特に fillするための色) を指定し、ctx.fillRect(10, 10, 100, 100);で四角形 (Rectangular) を左から 10px、上から 10px のところに 100x100 で描画しています。

はは...rectangular...四角形...AutoCAD...思い出しちゃったネ...

私は AutoCAD で「四角形」がRectangであると学んだよ!

更に画像も描画しちゃいましょう!

ベーコンの画像でも描画してみますか。

main.ts
  const img = new Image();
  img.src = "https://baconmockup.com/200/200/";
  img.onload = () => {
    ctx.drawImage(img, 50, 50);
  };

(先ほどのctx.fillRect(...);の下)

スクリーンショット 2024-11-25 011152.png

飯テロですか!?!?!?

と、このように画像も出すことが出来ちゃいます。

const img = new Image();で画像を作成しています。
これは、HTML の<img>要素と同じものです。
img.src = "https://baconmockup.com/200/200/";で画像の URL を指定しています。
そして、img.onload = () => { ctx.drawImage(img, 50, 50); };で画像の読み込みが完了した時にコンテキストに画像を、上から 50px、左から 50px の位置に描画しています。
画像の読み込みが終わる前にctx.drawImage(img, 50, 50);を実行してしまうと、正常に描画されません。

似たような方法でimg.decode()という方法もありますが、今回は省略します。
数日の間だけ頭に入れておいてください。

まとめ

はい、もうこれであなたは何でも描画できるようになりましたね。

え?なに?ベーコンが邪魔だから動かしてほしい?
分かりましたよ...動かす...動かす...

...ということで、次回は Canvas でのアニメーションについてお話しようと思います。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?