頑張らないために頑張る

ゆるく頑張ります

メッセージダイアログをPhaser.jsで作る

Posted at — Feb 7, 2022

ノベルゲームでよくあるアレ

会話が主体のノベルゲームでは、登場人物の会話を表示するためのダイアログが存在します。よく、画面の下半分くらいに表示される小窓っぽいアレですね。アレをPhaser.jsを使って実装してみようという小ネタです。

実際どうするの

ここでは、四角や円といったPhaser.jsがもともと持っているゲームオブジェクトを使って表示します

ダイアログをアセットとして用意するのではなく、ゲームオブジェクトで表示するメリットはゲームの挙動を軽くできることです。Phaser.jsが持つ機能を使って描画するだけなので、アセットをダウンロードするのに比べて段違いに早いです。コード量も、アセットを使ったときと大差ありません。

ダイアログをアセットとして準備すれば、ただの四角などではなく凝ったデザインのダイアログにできるため、ゲーム画面のUIデザインに凝りたい場合は画像のアセットとして準備したほうがよいです。ゲーム画面のUI素材は、探せばCC0のものがあったりします。

ただ、アセットを多用し凝ったUIは見た目に華やかですが、そう感じるのは最初だけでゲームを進めていくうちに慣れてしまいます。ところが、「ロードが遅い」といったマイナス面はいくぶんか慣れこそするものの、ストレスがたまらなくなるわけでもないので、なるべくUIに凝るよりUX向上を狙います。

べ、別にデザインセンスがないのでUIには凝らない、とかそういうわけじゃないんだからね!

サンプル

codepenで実装してみました。

See the Pen Untitled by ysko909 (@ysko909) on CodePen.

コード

type DialogProps = {
  width: number;
  height: number;
  align: string;
  fontSize: number;
  color: string;
  strokeColor: number;
}

class OriginalDialogClass extends Phaser.GameObjects.Container{
  
  constructor (scene: Phaser.Scene, x:number, y:number, text:string, dialogProps: DialogProps){
    super(scene, x, y);
    
    this.scene = scene;
    this.scene.add.existing(this);
    
    // 入力されたオブジェクトから各要素を取り出す
    const {
      width = 100,
      height = 100, 
      align = 'left', 
      fontSize = 15, 
      color = '#222233',
      strokeColor = 0x009285
    } = dialogProps;    
    
    this.setSize(width, height);
    
    // ダイアログの枠を作成
    let outsideDialogRect = scene.add.rectangle(0, 0, width, height);
    outsideDialogRect.setStrokeStyle(2, strokeColor).setOrigin(0.5, 0.5);
    let insideDialogRect = scene.add.rectangle(0, 0, width - 6, height - 6);
    insideDialogRect.setStrokeStyle(1.5, strokeColor).setOrigin(0.5, 0.5);
    let rightUpCircle = scene.add.circle(width / 2 - 2, height / 2 * -1 + 1, 6, strokeColor);
    rightUpCircle.setOrigin(0.5, 0.5);
    
    // テキストの出力設定
    const padding = 5;
    
    const dialogTextStyle = {
      align: align,
      fontSize: fontSize,
      color: color,
      wordWrap: { width: width - padding * 2, useAdvancedWrap: true }
    };
    
    const dialogText = scene.add.text(width / 2 * -1 + (padding * 2), height / 2 * -1 + padding, text, dialogTextStyle).setOrigin(0, 0).setPadding(0, 2, 0, 0);
    
    // コンテナに各オブジェクトを追加
    this.add([outsideDialogRect, insideDialogRect, rightUpCircle, dialogText])
    
  }

}

class LayerSample extends Phaser.Scene
{
  constructor()
  {
    super('layerSample');
  }
  
  preload()
  {

  }
  
  create(){
    
    const {width, height} = this.game.canvas;
    const dialogMargin = 20;
    
    this.dialog = new OriginalDialogClass(this, width / 2, height / 6 * 5, 'あいうえおカキクケコさしすせそタチツテトなにぬねのハヒフヘホまみむめもやいゆえよラリルレロわをんほげふがぴよfoobarbaz', {
        width: width - dialogMargin,
        height: height / 3 - dialogMargin,
      });
  
  }
}

const config = {
  type: Phaser.AUTO,
  width: 400,
  height: 300,
  backgroundColor: '#dfdfdf',
}

let layerSample = new LayerSample();

let game = new Phaser.Game(config);

game.scene.add('layerSample', layerSample);

game.scene.start('layerSample');

ちょっとだけ解説

// ダイアログの枠を作成
let outsideDialogRect = scene.add.rectangle(0, 0, width, height);
outsideDialogRect.setStrokeStyle(2, strokeColor).setOrigin(0.5, 0.5);
let insideDialogRect = scene.add.rectangle(0, 0, width - 6, height - 6);
insideDialogRect.setStrokeStyle(1.5, strokeColor).setOrigin(0.5, 0.5);
let rightUpCircle = scene.add.circle(width / 2 - 2, height / 2 * -1 + 1, 6, strokeColor);
rightUpCircle.setOrigin(0.5, 0.5);

上記の部分で、ゲームオブジェクトの長方形と円を使ってダイアログを生成し表示しています。ただの四角と円なので見た目としては地味ですが。

なお、上記ではダイアログの外枠のみ表示しています。ダイアログの背景は設定していないので、人物や背景の画像などとかぶった際にテキストが見にくくなる、という可能性があります。その場合は、上記の部分にダイアログの背景として長方形を生成するコードを追加する必要があります。背景色やアルファ値などは実際の画面の都合で調節する必要があるかと思いますが、軽く透過させることでテキストの読みやすさと人物や背景の表示を両立させることができると思います。

const dialogTextStyle = {
    align: align,
    fontSize: fontSize,
    color: color,
    wordWrap: { width: width - padding * 2, useAdvancedWrap: true }
};

このオブジェクトでは、ダイアログ中に表示するテキストのスタイルを定義しています。ここでuseAdvancedWrapプロパティはtrueに設定することで、設定された幅でテキストを折り返すことが可能です。テキストが英語の場合でも日本語の場合でも問題なく改行してくれます。

まとめ

今回はゲームオブジェクトを使って、ダイアログを表示する小ネタでした。アセットとして用意するのももちろんアリですが、とくにプロトタイプを作成するときなどはアセットを作るよりUIはとりあえずの実装でいいのではないかと思うのです。凝ったことをしているわけでもないので、まずは動かしてみたいというときはPhaser.jsが持つゲームオブジェクトを使ってみるのも手だと思います。

参考

  1. TypeScriptを使ってノベルゲームを作ろう
comments powered by Disqus