Phaser3 : Web Speech Apiを用いた読み上げ神経衰弱
今回はWeb Speech APIを用いた合成音声の読み上げをしてみます。
合成音声の読み上げにはSpeechSynthesisとSpeechSynthesisUtteranceの設定が最低限必要になります。
Web Speech APIはPhaserの機能ではないので、ブラウザから取得する必要があります。
SpeechSynthesisUtterrance.langで読み上げるテキストのロケールを選択することができます。
以上でWebSpeechSynthesisの設定ができました。
次はクリックされたときに音声を呼び出すタイルオブジェクトSpeechTileを作成します。
SpeechTileクラスに初期設定用の関数Initを作成します。
クリック時にはMyScene1のSpeechと判定用のJudge関数を呼び出しています。 MyScene1に戻り、判定用の配列と関数を作成します。
以上で簡単な読み上げ式の神経衰弱ができました。
Web Speech APIはPhaser3独自の機能というわけではないのですが、Phaser3と組み合わせて使うことができるので、試しにこちらを使って読み上げ式の神経衰弱を作ってみようと思います。
SpeechSynthesisを作る
SpeechSynsethisで読み上げ用を行うオブジェクトを作成し、SpeechSynthesisUtteranceは読み上げの設定をするというようなイメージです。
//テキスト読み上げ用のSpeechSynthesisを作成
protected Voice: SpeechSynthesis;
protected VoiceText: SpeechSynthesisUtterance;
create(){
~~
//PhaserではなくブラウザからSpeechSynthesisを取得
this.Voice = (<any>window).speechSynthesis;
this.VoiceText = new (<any>window).SpeechSynthesisUtterance("");
~~
}
"ja-JP"を指定すれば日本語で読み上げることができます。//読み上げる声のロケールを選択
this.VoiceText.lang = "ja-JP";
あとはSpeechSynthesisUtterance.textで読み上げるテキストを設定し、SpeechSynthesis.speakで設定したテキストを読み上げることができます。//音声の再生
public Speech(str: string) {
this.VoiceText.text = str;
this.Voice.speak(this.VoiceText);
}
graphicsに当たり判定をつける
SpeechTileはcontainerを拡張して作ることにします。export class SpeechTile extends Phaser.GameObjects.Container {
public graphics: Phaser.GameObjects.Graphics;
public myNum: number;
constructor(scene: Phaser.Scene, x?: number, y?: number, children?:Phaser.GameObjects.GameObject[]) {
super(scene, x, y, children);
}
}
Initではgraphicsの描画に用いたrectを流用してgraphics.setInteractive(rect, Phaser.Geom.Rectangle.Contains);でgraphicsにクリック判定を設定しています。
昨日textにsetintractiveを設定したときは引数が不要でしたが、graphicsにはデフォルトではクリック判定が用意されていないのでrectを渡す必要があります。
this.graphics.setInteractiveは第一引数を設定した場合、第二引数のcallbackを設定しないとエラーが起きてしまうのでPhaser.Geom.Rectangle.Containsを設定しています。export class SpeechTile extends Phaser.GameObjects.Container {
~~~
public Init(mynumber: number, color: number) {
//当たり判定と表示に使用する矩形を作成する
let rect = new Phaser.Geom.Rectangle(0, 0, 32, 32);
//グラフィックス作成
this.graphics = this.scene.add.graphics();
this.graphics.fillStyle(color);
this.graphics.fillRectShape(rect);
//コンテナにグラフィックスを追加
this.add(this.graphics);
//rectを範囲としたクリック判定
this.graphics.setInteractive(rect, Phaser.Geom.Rectangle.Contains);
this.myNum = mynumber;
this.graphics.on('pointerdown', function () {
let scene = this.scene as MyScene1;
//mynumberの読み上げ
scene.Speech(mynumber.toString());
scene.judgeArray.push(this);
scene.Judge();
}, this);
}
~~
}
this.graphics.on('pointerdown', function () {
let scene = this.scene as MyScene1;
//mynumberの読み上げ
scene.Speech(mynumber.toString());
scene.judgeArray.push(this);
scene.Judge();
}, this);
タイルをクリックすることでjudgeArrayにクリックされたタイルが格納され、配列の長さに応じてjudge関数が実行されます。export class MyScene1 extends Phaser.Scene {
//グラフィックオブジェクトを用意
public graphics: Phaser.GameObjects.Graphics;
//テキスト読み上げ用のSpeechSynthesisを作成
protected Voice: SpeechSynthesis;
protected VoiceText: SpeechSynthesisUtterance;
//左用のタイルと右用のタイルを作成
public lefts: SpeechTile[] = [];
public rights: SpeechTile[] = [];
public judgeArray: SpeechTile[] = [];
constructor() {
super({ key: 'MyScene1', active: false });
}
preload() {
}
create() {
//タイルのために0から9までの数値をシャッフルした配列を作る
let l: number[] = [];
let r: number[] = [];
for (let i = 0; i <= 9; i++) {
l.push(i);
r.push(i);
}
l = this.Shuffle(l);
r = this.Shuffle(r);
//背景を白に
this.cameras.main.setBackgroundColor("#ffffff");
//PhaserではなくブラウザからSpeechSynthesisを取得
this.Voice = (<any>window).speechSynthesis;
this.VoiceText = new (<any>window).SpeechSynthesisUtterance("");
//読み上げる声のロケールを選択
this.VoiceText.lang = "ja-JP";
//SpeechTileを並べる
for (let i = 0; i < 9; i++) {
let left = new SpeechTile(this, this.sys.canvas.width/2 -60, 50 * i + 50);
left.Init(l[i], 0x2a2a2a);
this.add.existing(left);
this.lefts.push(left);
let right = new SpeechTile(this, this.sys.canvas.width / 2 + 60, 50 * i + 50);
right.Init(r[i], 0x8a8a8a);
this.add.existing(right);
this.rights.push(right);
right.graphics.input.enabled = false;
}
}
//配列のシャッフル
public Shuffle(array: number[]) {
let ary = array.slice();
for (let i = ary.length - 1; 0 < i; i--) {
let r = Math.floor(Math.random() * (i + 1));
// ary[i] <-> ary[r]
[ary[i], ary[r]] = [ary[r], ary[i]];
}
return ary;
}
update() {
}
//音声の再生
public Speech(str: string) {
this.VoiceText.text = str;
this.Voice.speak(this.VoiceText);
}
//神経衰弱の判定
public Judge() {
switch(this.judgeArray.length){
case 1:
for (let l of this.lefts) {
l.graphics.input.enabled = false;
if (l !== this.judgeArray[0]) {
l.Tween(0.3);
}
}
for (let r of this.rights) {
r.graphics.input.enabled = true;
r.Tween(1);
}
break;
case 2:
for (let l of this.lefts) {
l.graphics.input.enabled = true;
l.Tween(1);
}
for (let r of this.rights) {
r.graphics.input.enabled = false;
if (r !== this.judgeArray[1]) {
r.Tween(0.3);
}
}
if (this.judgeArray[0].myNum === this.judgeArray[1].myNum) {
this.lefts.splice(this.lefts.indexOf(this.judgeArray[0]),1)
this.rights.splice(this.rights.indexOf(this.judgeArray[1]), 1)
this.judgeArray[0].Clear();
this.judgeArray[1].Clear();
}
this.judgeArray = [];
break;
}
}
}
プレイしてみた動画がこちらです。
今回のソース全文はこちらです。
/// <reference path="../app.ts" /> namespace MyGame { export class SpeechTile extends Phaser.GameObjects.Container { public graphics: Phaser.GameObjects.Graphics; public myNum: number; constructor(scene: Phaser.Scene, x?: number, y?: number, children?:Phaser.GameObjects.GameObject[]) { super(scene, x, y, children); } public Init(mynumber: number, color: number) { //当たり判定と表示に使用する矩形を作成する let rect = new Phaser.Geom.Rectangle(0, 0, 32, 32); //グラフィックス作成 this.graphics = this.scene.add.graphics(); this.graphics.fillStyle(color); this.graphics.fillRectShape(rect); //コンテナにグラフィックスを追加 this.add(this.graphics); //rectを範囲としたクリック判定 this.graphics.setInteractive(rect, Phaser.Geom.Rectangle.Contains); this.myNum = mynumber; this.graphics.on('pointerdown', function () { let scene = this.scene as MyScene1; //mynumberの読み上げ scene.Speech(mynumber.toString()); scene.judgeArray.push(this); scene.Judge(); }, this); } //各種演出 public Tween(n: number) { this.scene.add.tween({ targets: this, alpha: n, duration:100 }) } public Clear() { this.scene.add.tween({ targets: this, scaleX: 0, duration: 100, onComplete: () => { this.destroy(); } }) } } export class MyScene1 extends Phaser.Scene { //グラフィックオブジェクトを用意 public graphics: Phaser.GameObjects.Graphics; //テキスト読み上げ用のSpeechSynthesisを作成 protected Voice: SpeechSynthesis; protected VoiceText: SpeechSynthesisUtterance; //左用のタイルと右用のタイルを作成 public lefts: SpeechTile[] = []; public rights: SpeechTile[] = []; public judgeArray: SpeechTile[] = []; constructor() { super({ key: 'MyScene1', active: false }); } preload() { } create() { //タイルのために0から9までの数値をシャッフルした配列を作る let l: number[] = []; let r: number[] = []; for (let i = 0; i <= 9; i++) { l.push(i); r.push(i); } l = this.Shuffle(l); r = this.Shuffle(r); //背景を白に this.cameras.main.setBackgroundColor("#ffffff"); //PhaserではなくブラウザからSpeechSynthesisを取得 this.Voice = (<any>window).speechSynthesis; this.VoiceText = new (<any>window).SpeechSynthesisUtterance(""); //読み上げる声のロケールを選択 this.VoiceText.lang = "ja-JP"; //SpeechTileを並べる for (let i = 0; i < 9; i++) { let left = new SpeechTile(this, this.sys.canvas.width/2 -60, 50 * i + 50); left.Init(l[i], 0x2a2a2a); this.add.existing(left); this.lefts.push(left); let right = new SpeechTile(this, this.sys.canvas.width / 2 + 60, 50 * i + 50); right.Init(r[i], 0x8a8a8a); this.add.existing(right); this.rights.push(right); right.graphics.input.enabled = false; } } //配列のシャッフル public Shuffle(array: number[]) { let ary = array.slice(); for (let i = ary.length - 1; 0 < i; i--) { let r = Math.floor(Math.random() * (i + 1)); // ary[i] <-> ary[r] [ary[i], ary[r]] = [ary[r], ary[i]]; } return ary; } update() { } //音声の再生 public Speech(str: string) { this.VoiceText.text = str; this.Voice.speak(this.VoiceText); } //神経衰弱の判定 public Judge() { switch(this.judgeArray.length){ case 1: for (let l of this.lefts) { l.graphics.input.enabled = false; if (l !== this.judgeArray[0]) { l.Tween(0.3); } } for (let r of this.rights) { r.graphics.input.enabled = true; r.Tween(1); } break; case 2: for (let l of this.lefts) { l.graphics.input.enabled = true; l.Tween(1); } for (let r of this.rights) { r.graphics.input.enabled = false; if (r !== this.judgeArray[1]) { r.Tween(0.3); } } if (this.judgeArray[0].myNum === this.judgeArray[1].myNum) { this.lefts.splice(this.lefts.indexOf(this.judgeArray[0]),1) this.rights.splice(this.rights.indexOf(this.judgeArray[1]), 1) this.judgeArray[0].Clear(); this.judgeArray[1].Clear(); } this.judgeArray = []; break; } } } }