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 15

【Day15】絵の具を使うときに必要なあれ。【QAC24】

Last updated at Posted at 2024-12-14

ちなみに答えはパレットです。

絵を描く時、アナログでもデジタルでも、カラーパレットというものがあると思います。
また、タイルエディターでも、タイルのパレット...タイルを選択して塗っていくようなものがあるような...多分。

パレットウィンドウを召喚してみよう。

Tauri では、現在のメインウィンドウの他にもウィンドウを召喚することが出来ます。

WebViewWindowというものを使って、新しいウィンドウを開いてみましょう。

/src/maps/editorMap.main.ts
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";

export class EditorMapMain extends GameMap {
  ...

  constructor() {
    super();

    const window = new WebViewWindow("newGoogleWindow", {
      alwaysOnTop: true,
      closable: false,
      title: "Tiles",
      url: "https://www.google.com",
    });
  }
}

alwaysOnTopは常に最前面に表示するかどうか、closableは閉じるボタンを表示するかどうか、titleはウィンドウのタイトル、urlは開くページの URL です。
まぁ、英語だけを読めば、Google のページを最前面で開くウィンドウを作成するということですね。

じゃあ実際に実行してみましょう。

Oops!

多分、なんも起きないと思います。
何なら、Inspector を開いてみても、何も表示されていないかもしれません。

実際には以下のようなエラーが発生しています。

"webview.create_webview_window not allowed. Permissions associated with this command: webview:allow-create-webview-window"

webview.create_webview_window が許可されていません。このコマンドに関連付けられた権限: webview:allow-create-webview-window

新しいウィンドウ 1 つを開くのだけでも権限が要求されます。
セキュリティ上の問題ですわね。

権限を追加してみよう~

/src-tauri/capabilities/default.jsonというファイルがあると思うのですが、この中のpermissionsという所で使用する権限を列挙していきます。

permissionsにさっき言われたwebview:allow-create-webview-windowを追加してみましょう。

...実際には webview:allow-create-webview-windowではなく、core:webview:allow-create-webview-windowですけど。

これは、Tauri がデフォルトで提供している機能に対する権限の親はcoreという名前空間で管理されているからです。
デフォルトで提供されていない機能として、ファイルを操作したりするfsといった機能がありますが、これはcoreではなく、fsという名前空間で管理されています。

これだけで新しく Google なウィンドウが出てきたと思います。
最前面に出てくるので邪魔ですね。

ってなんなら消せないじゃん。おい。

pnpm tauri devを実行したターミナルでCtrl + Cを押して強制終了しましょう。

ウィンドウを消す方法

先ほどのコードでclosable: falseとしていたので、閉じるボタンが表示されていませんでした。
しかし、プログラム内からウィンドウを閉じることは可能です。

それは、window.destroy()またはwindow.close()を実行することで可能です。

これは...そうですね、メインのウィンドウが閉じられたときに、ついでに閉じるようにするのが賢明でしょうか。

そしたら、現在のウィンドウが閉じられたときになんかするようなイベントハンドラが必要ですね。

ウィンドウに対してlistenを使うことでイベントを登録できます。
イベントについては自作することもできますし、デフォルトで用意されているもの (今回使用する「ウィンドウが閉じられるときに」とか)もあります。
詳しくはドキュメントを見ることをお勧めします。

/src/maps/editorMap.main.ts
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
import { getCurrentWindow } from "@tauri-apps/api/window";

export class EditorMapMain extends GameMap {
  ...

  constructor() {
    super();

    const window = new WebViewWindow("newGoogleWindow", {
      alwaysOnTop: true,
      closable: false,
      title: "Tiles",
      url: "https://www.google.com",
    });

    getCurrentWindow().listen("tauri://close-requested", () => {
      window.destroy();
      getCurrentWindow().destroy();
    });
  }
}

こんな感じでしょうか。
これで、メインウィンドウが閉じられたときに、ウィンドウも閉じられるようになりそうです。

が、ここでも権限の問題が発生します。

core:window:allow-destroypermissionsに追加しておきましょう。

これで動作すると思います。

一般的にはdestroyではなくcloseを用いるのですが、なんかうまく閉じないので...強制的に閉じることが出来るdestroyを使っています。

タイルパレットを作る

index.htmlをコピーしてtiles.htmlmain.tsをコピーしてeditMap.tiles.tsを作成しましょう。

index.htmlの中で<script src="/src/main.ts"></script>となっている部分を<script src="/src/editMap.tiles.ts"></script>に変更しましょう。

そしたら新しいマップとしてeditorMap.tiles.tsを作成しましょう。
(editorMap.main.tsをコピーすると楽)

このウィンドウでは、タイルマップ画像を読み込むので、今回は超省略して、promptで画像のパスを入力してもらうことにしましょう。

/src/maps/editorMap.tiles.ts
export class EditorMapTiles extends GameMap {
  ...

  private imagePath: string = "";
  private image: HTMLImageElement | null = null;

  private async drawImage() {
    if (this.image) {
      getContext().drawImage(this.image, 0, 0);
    } else {
      image = new Image();
      image.src = imagePath;
      await image.decode();
      getContext().drawImage(image, 0, 0);
    }
  }

  constructor() {
    super();
    imagePath = prompt("画像のパスを入力してください。");
    drawImage();
  }
}

そして、先ほどまでウィンドウがどうのこうのっていじってたeditorMap.main.tsを修正して、editorMap.tiles.tsを開くようにしましょう。

/src/maps/editorMap.main.ts
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
import { getCurrentWindow } from "@tauri-apps/api/window";

export class EditorMapMain extends GameMap {
  ...

  constructor() {
    super();

    const window = new WebViewWindow("editorMapTiles", {
      alwaysOnTop: true,
      closable: false,
      title: "Tiles",
      url: "./tiles.html",
    });

    getCurrentWindow().listen("tauri://close-requested", () => {
      window.close();
    });
  }
}

タイルパレットの方で、タイルマップ画像のタイルを選択したら、メインウィンドウの方でそのタイルを描画出来るようにしましょう。

window のイベント

windowには、独自のイベントを作ることができ、これを用いることでウィンドウ間でデータのやり取りとかが行えます。

/src/maps/editorMap.main.ts
export class EditorMapMain extends GameMap {
  ...

  private paintTile: RealTile | null = null;

  constructor() {
    super();

    const window = new WebViewWindow("editorMapTiles", {
      alwaysOnTop: true,
      closable: false,
      title: "Tiles",
      url: "./tiles.html",
    });

    getCurrentWindow().listen("tauri://close-requested", () => {
      window.close();
    });

    getCurrentWindow().listen("tile", async (e) => {
      const tile = e.payload as FakeTile;
      console.log(`Received: ${JSON.stringify(tile)}`);
      const realtile = await fakeTileToRealTile(tile);
      this.paintTile = realtile;
    })
  }
}

さっきも使ったlistenで、tileというイベントを登録しています。
公式には存在しないtileというイベントですが、なんでも自作できてしまいます.
e.payloadには、イベントのデータ...送信されてきたデータが入ります。
型はただの Object だと思うので、アサーションでなんとかしてあげましょう。
(安全にやるなら、TypeGuard な関数を作るといいだろう。)

タイルをクリックしたら...?

もうそろそろイベントハンドラはお手の物かと思われます。
clickイベントに対してマウス座標の取得と、その座標にあるタイルを取得する処理を書いてみましょう。

/src/maps/editorMap.tiles.ts
import { getCurrentWindow } from "@tauri-apps/api/window";

export class EditorMapTiles extends GameMap {
  ...

  protected async click(e: MouseEvent) {
    e.preventDefault();
    const { x, y, isInside } = getMousePosition(e);
    if (!isInside) return;
    const canvasPos = getCanvasPosition(x, y);
    const tile: FakeTile = {
      path: imagePath,
      x: canvasPos.x,
      y: canvasPos.y,
      width: 1,
      height: 1,
    };
    getCurrentWindow().emit("tile", tile);
  }

  ...
}

こんな感じかな?

emitを使うことでイベントを発火させることが出来ます。
今回は、CurrentWindow に対してemitしていますが、ここら辺は正直私もまだ理解しきれていなくて...
(イベント定義元ウィンドウに対してemitしなくてもイベントは受け取れるような...?)

ここにイベントに関するドキュメントがあるので、これを読んで理解を深めるといいかもしれません。

あとは簡単ですね。
editorMap.main.tsで、クリックしたらその座標を取得してthis.tilesにうまい事はめ込んで...ですか。

なお、このままではファイル出力などは出来ませんが...JSON.stringifyとかconsole.logを使えば外部へ出すことはできます。
ここら辺は、好みでいろんな実装をしてみてください。

おわりに

ちょっとだけモチベが落ちたんだけど、書かなきゃな~で書いてたから他の記事に比べても突出して乱雑だろうな!

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?