Safari 6の字幕機能を使ってゲームのオープニングムービーを

■Safari 6の新機能

 今回はSafari 6に搭載された新機能の一つであるHTML5のビデオトラック(字幕機能)を利用してゲームのオープニングを作ってみます。
ゲームはこれまで通り、enchant.jsライブラリを使って作ります。なお、enchant.jsライブラリに関しては以下の公式サイトを参照するか、以前の連載記事を参照してください。本連載では最新版ではなくv0.4.3を使用しています。

●enchant.js
http://enchantjs.com/ja/

いつの頃からかゲームには豪華なオープニング映像がつくようになりました。ミニゲームでもゲームのオープニング映像を作成するのは多少意味(効果)があります。例えば、せっかく作ったゲームが思ったほど面白くない場合です。また、新たにゲームを作るのも大変です。このような場合、豪華なオープニング映像を作って、つまらないゲームを面白く見せるようにするわけです。もちろん、オープニング映像の効果は他にもあります。

ゲームのオープニング映像はゲームを作ってから作成する方が楽です。今回は都合上、先にオープニング映像とビデオトラックについて説明し、ゲームの解説は後回しにします。なお、HTML5 Videoと映像上にテロップを表示する記事に関しては第29回、第34回の連載を参照してください。HTML5のvideo要素などに関して復習しておくとよいでしょう。

●第29回 HTML5 videoで作る“動くストリートビュー”
http://ascii.jp/elem/000/000/507/507722/

●第34回 HTML5 videoでニコニコ動画風プレーヤーを作ろう
http://ascii.jp/elem/000/000/521/521366/

 


■字幕はビデオトラックで

 HTML5 Videoでは字幕(テロップ)を表示する機能があります。これは以前からHTML5 Videoの仕様にありましたが、実際に製品としてのブラウザで対応したのはSafari 6が初めてです。Google Chromeでは設定を変更することでビデオトラックを処理する機能を有効にすることができます(アドレス欄にabout:flagsと入力しすると設定できます)。これまではビデオトラックがないため字幕を表示するにはvideo要素の上にdiv要素などを使ってスタイルシートで合成する必要がありました。ちなみにHTML5 VideoのビデオトラックはCSSほどの装飾機能を備えていないため、CSSを使って字幕を表示した方がよい場合もあります。HTML5のビデオトラックは会話など相手の話を表示するような場合にはよいでしょう。

 それでは早速字幕を表示してみましょう。まず。字幕を表示するには以下のようにvideo要素の中にtrack要素を記述します。track要素のsrc属性には字幕を表示するためのWebVTTというファイルのURLを指定します。

-----------------------
<video>
<source src="asc.mp4">
<source src="asc.ogv">
<track kind="subtitles" src="game.vtt" srclang="ja" default>
</video>
-----------------------

 track要素でのkind属性で字幕の種類を指定します。字幕の種類は以下のものが指定できます。Safari 6では今のところ、どれを指定しても同じように表示されるようです。

【表】kind属性
subtitlesサブタイトル
captionsキャプション
descriptions詳細/説明
chaptersチャプター
metadataメタデータ


【図】fig1-1.png
subtitlesを指定した場合



【図】fig1-2.png
captionsを指定した場合



【図】fig1-3.png
descriptionsを指定した場合



【図】fig1-4.png
chaptersを指定した場合

 

 track要素は複数内包することができ、言語別に用意することもできます。複数の言語の字幕の場合、srclang属性に表示する字幕の言語を指定します。track要素が複数ある場合、どれがデフォルトなのかを指定する必要があります。Safari 6ではdefault属性を指定しないと字幕が表示されません。


■字幕を表示する

 実際に映像上に表示する字幕はWebVTTというファイルに記述します。WebVTTの仕様は以下のページにあります。

-----------------------
●WebVTT
http://dev.w3.org/html5/webvtt/
-----------------------

 また日本語での解説は以下のページが参考になります。

-----------------------
●WebVTT ファイルフォーマット
http://d.hatena.ne.jp/sparkgene/20111216/1323988362
-----------------------

 まず最初にゲームのオープニング映像を用意します。今回作成するゲームは謎の粒子が宇宙からやってきて惑星上の携帯電話を破壊するというものです。
 実際に作成したゲームのオープニングが以下の映像になります。今回は頭の上に字幕を表示するため映像には字幕は入れていません。ただし、最初は映像編集ソフトで字幕を入れた方が感覚としてわかりやすいでしょう。


【図】fig2.png
オープニング映像
【URL】http://www.youtube.com/watch?v=eC7tkqOwK28

 さて映像作成したら実際に表示する字幕を作成していきます。字幕はテキストエディタを使って入力していきます。WebVTTのフォーマットは決まっており以下のようになっています。

-------------------------------------------------
WEBVTT
【改行】
ID名
開始時間 --> 終了時間
表示する文字
【改行】
  :
ID名
開始時間 --> 終了時間
表示する文字
【改行】
  :
-------------------------------------------------

1行目にはWEBVTTの文字を入れます。次に空行(改行)を入れます。WebVTTでは空行が区切りとなっています。
その次にID名を指定しますが、このID名は必須ではなく省略することができます。ID名の後に字幕を表示する時間範囲を指定します。時間は「HH:MM:SS.msec」形式でミリ秒単位まで指定できます。開始時間と終了時間の間には-->を入れます。
時間を指定したらその下に実際に表示する字幕を入れます。字幕は空行までを1セットとして表示します。表示する字幕が複数ある場合は改行コードを区切りとして時間と字幕をセットで指定していきます。なお、表示する字幕には以下のタグを指定することができます。Safari 6では全てに対応しているわけではありませんが、文字を太くしたり斜体にすることはできます。他にも字幕の位置指定などもできますが、Safari 6では一部しか対応してはいません。

【表】WebVTTで指定できるタグ

b太字
i斜体
u下線
v話している人の名前
ruby,rtルビ/ふりがな。<ruby>ASCII<rt>アスキー</rt></ruby>のように指定する。
cCSSを指定



【図】4-1.png
bタグを指定した場合。サンプルの文字が太字になっている。



【図】4-2.png
iタグを指定した場合。サンプルの文字が斜体になっている。



【図】4-3.png
uタグを指定した場合。サンプルの文字に下線が表示されている。



【図】4-4.png
rubyタグを指定した場合。文字にルビ(ふりがな)がふられている。



【図】4-5.png
cタグを指定した場合。サンプルの文字を赤くするようにCSS指定したが反映されていない。

【表】表示位置などの設定

verticalrlかlr。(縦書き/筆記方向)
line-か行和を示す数値(または割合)
position0〜100%までの表示位置
size0〜100%までの表示サイズ
align行揃え位置。start, middle, endの後に位置を指定

-------------------------------------------------
実際の表示設定は以下のように指定する。
00:00:03.000 --> 00:00:26.000 align:start size:25%
-------------------------------------------------

 これで表示する字幕のWebVTTファイルができました。早速Safari 6で動作を確認します。コントローラーが表示されている場合とそうでない場合では字幕の表示位置が異なります。これはSafariが自動的に処理します。期待する時間に字幕が表示されているかどうかはコントローラーを操作することで確認することができます。

-------------------------------------------------
【HTML】
-------------------------------------------------
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name = "viewport" content = "width=device-width, initial-scale=1,user-scalable=no">
<title>Pozon Opening Movie</title>
</head>
<body>
<h1>Pozon Opening Movie</h1>
<video controls autoplay width="320" height="240">
<source src="movie/opening.mp4">
<source src="movie/opening.ogv">
<track kind="subtitles" src="game.vtt" srclang="ja" default>
</video>
</body>
</html>
-------------------------------------------------
【WebVTT】game.vtt
-------------------------------------------------
WEBVTT

00:00:03.000 --> 00:00:26.000
星歴2012年
惑星ガラパゴス
-------------------------------------------------



【図】fig3-1.png


【図】fig3-2.png
再生するかコントローラーを使って字幕の確認を行う。3秒目から26秒まで(26.001になると表示されない)字幕が表示される。

字幕の確認ができたらゲームのオープニング映像を完成させます。今回はWebVTTファイルを以下のようにしました。これで映像の上に時間に応じて字幕が表示されます。
また、映像の再生が終了したらゲームのページ(game.html)に移動するようにします。これはvideo要素のonendedにゲームのページURLに移動するようにJavaScriptを記述しておきます。

-------------------------------------------------
<video controls autoplay width="320" height="240" onended="location.href='game.html'">
-------------------------------------------------

-------------------------------------------------
WEBVTT

00:00:03.000 --> 00:00:06.000
星歴2012年
惑星ガラパゴス

00:00:08.000 --> 00:00:11.000
惑星ガラパゴスは謎の粒子ポゾンにより危機に瀕していた

00:00:15.000 --> 00:00:20.000
謎の粒子によって携帯は次々と破壊され残すは1台となってしまった

00:00:22.000 --> 00:00:26.000
あなたはIT戦士となって最後の携帯を守るのだ!
-------------------------------------------------


【図】fig5-1.png


【図】fig5-2.png


【図】fig5-3.png


【図】fig5-4.png
再生時間に応じて字幕が表示される

 

------------------------------------------------------------------------------------------------------------------------------------

■ゲームを作成

 それではゲームを作成しましょう。今回は手順を追って解説せず、最初に完成したゲームのコードを示します。これまでの連載で解説した部分は行わず、このゲーム独自の部分を解説します。
 今回作成するゲームは画面中を動き回る謎の原子ポゾンをクリック(またはタッチ)して全て消し去ることです。原子は3種類あり赤青緑色で表示されており、色によって速度が異なります。赤がもっとも遅く、青がもっとも速い速度で動きます。原子をクリックすると得点が加算されますが、同じ色の原子をクリックすると加算される得点が2倍、3倍、4倍...になっていきます。
 また、画面中央には携帯があり画面には耐久力が表示されます。携帯に原子があたると耐久力が1つずつ減っていきます。携帯の耐久力がなくなるとゲームオーバーとなります。また、ゲームは最終面である10面をクリアすると終了となります。
 ゲーム画面の背景は4枚あり1面ごとに切り替わっていきます。背景が切り替わる処理は前回の記事を参照してください。


【図】fig6-1.png


【図】fig6-2.png


【図】fig6-3.png


【図】fig6-4.png
このゲームでは4枚の背景がステージ数に応じて切り替わる。


【図】fig6-5.png
携帯の耐久力がなくなるとゲームオーバーになる。

 このゲームのコードは以下のようになっています。

【実際のゲームのURL (オープニング映像つき)】
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0004/04_pozon/

【実際のゲームのURL】
http://www.openspc2.org/reibun/enchant.js/v0.4.3/asc/0004/04_pozon/game.html

-------------------------------------------------
【HTML】
-------------------------------------------------
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name = "viewport" content = "width=device-width, initial-scale=1,user-scalable=no">
<title>Sample game</title>
<style>
body { margin: 0; }
</style>
<script src="js/enchant.min.js"></script>
<script src="js/main.js"></script>
</head>
<body></body>
</html>
-------------------------------------------------
【JavaScript】main.js
-------------------------------------------------
enchant(); // ライブラリの初期化
window.onload = function(){
var game = new Game(480, 360); // 480×360画面(Canvas)を作成
game.fps = 30; // フレームレートの設定。30fpsに設定
// 画像データをあらかじめ読み込ませる
game.preload("images/red.png", "images/green.png", "images/blue.png", "images/bg0.jpg",
"images/bg1.jpg", "images/bg2.jpg", "images/bg3.jpg", "images/garakei.png");
game.rootScene.backgroundColor = "black"; // ゲームの背景色を黒色に設定
game.score = 0; // スコアを入れる変数を用意する
game.stage = 1; // 開始ステージ
game.pozonCount = 0; // 粒子の総数を入れておく変数
game.prevColor = null; // 直前にタッチしていた原子の色
game.rate = 1; // ★直前に消した原子の色を格納する変数
game.oldAtomType = ""; // ★同じ色の原子を消すと倍率があがる。その倍率を入れる変数
// スコアを表示するラベルを作成
var scoreLabel = new Label("SCORE : 0");
scoreLabel.font = "16px Tahoma";
scoreLabel.color = "white";
scoreLabel.x = 10; // X座標
scoreLabel.y = 5; // Y座標
// ステージを表示するラベルを作成
var stageLabel = new Label("STAGE 1");
stageLabel.font = "14px Tahoma";
stageLabel.color = "red";
stageLabel.x = 400; // X座標
stageLabel.y = 330; // Y座標
// ガラケーの耐久力を表示するラベルを作成
var powerLabel = new Label("99");
powerLabel.font = "24px Tahoma";
powerLabel.color = "blue";
powerLabel.x = 228; // X座標
powerLabel.y = 220; // Y座標
// 原子を格納する配列
var atom = new Array();
// 背景のスプライト
var bg = new Sprite(480, 360);
// ガラケーのスプライト。中央に配置
var keitai = new Sprite(64, 160);
keitai.x = (480-64) / 2;
keitai.y = 360-160;
keitai.power = 99; // ガラケーの耐久力
// データの読み込みが完了したら処理
game.onload = function(){
// 背景の設定
bg.image = game.assets["images/bg1.jpg"];
game.rootScene.addChild(bg);
game.rootScene.addChild(scoreLabel);
game.rootScene.addChild(stageLabel);
keitai.image = game.assets["images/garakei.png"];
game.rootScene.addChild(keitai);
game.rootScene.addChild(powerLabel);
// 原子を描く
drawAtom();
}
// ゲーム処理開始
game.start();
// ------------ 原子を描く/移動させる -----------------
var spriteList = new Array();
function drawAtom(){
// ステージに出てくる原子の出現数と種類(RGB順)
var stageData = [[0,0,0],
[3, 0, 0], [3, 3 ,0], [3, 3, 3], [6, 3, 2], [2, 2, 8], // Stage 1〜5
[6, 6, 6], [9, 0, 9], [20, 0, 0], [10, 10, 10], [15, 2, 4] // Stage 6〜10
];
var atomType = ["red", "green", "blue"];
var data = stageData[game.stage];
var count = 0;
for(var j=0; j<3; j++){
for(var i=0; i<data[j]; i++){
atom[count] = new Sprite(32, 32);
atom[count].image = game.assets["images/"+atomType[j]+".png"];
atom[count].x = Math.random() * 440; // X座標
atom[count].y = Math.random() * 120; // Y座標
atom[count].dx = 1 + j*2; // X方向移動量
atom[count].dy = 1 + j*2; // Y方向移動量
atom[count].type = atomType[j];
atom[count].id = count;
atom[count].point = j+1; // 赤は1点、緑は2点、青は3点
// ENTER_FRAMEイベント発生時の動作
atom[count].addEventListener(Event.ENTER_FRAME, function(){
this.x = this.x + this.dx;
this.y = this.y + this.dy;
if (this.x < 0){ this.dx = -this.dx; }
if (this.y < 0){ this.dy = -this.dy; }
if (this.x > 448){ this.dx = -this.dx; }
if (this.y > 328){ this.dy = -this.dy; }
// ガラケーとの接触判定
if (this.intersect(keitai)){ // 接触したか?
this.dx = -this.dx; // 移動方向を反転させる
this.dy = -this.dy;
keitai.power = keitai.power - 1; // ガラケーの耐久力を減らす
powerLabel.text = keitai.power; // 耐久力を表示
if (keitai.power < 0){
game.stop();
alert("最後の携帯が破壊されました。ゲームオーバーです");
}
}
});
// タッチされた時の処理
atom[count].addEventListener(Event.TOUCH_START, function(){
game.rootScene.removeChild(this);
// 同じ色か調べる
if (this.type == game.oldAtomType){
game.rate = game.rate + 1; // 倍率を増やす
}else{
game.rate = 1; // 違い色をタッチしたので倍率を元に戻す
}
game.oldAtomType = this.type; // 直前にタップした時の色を保存
// 原子の数を減らす
game.pozonCount = game.pozonCount - 1;
if (game.pozonCount < 1){
game.stage = game.stage + 1;
// 全10ステージをクリアしたか?
if (game.stage > 10){
game.stop();
alert("ガラパゴス星は脅威から解放されました。ゲームクリアです。");
}
var n = game.stage & 3; // 背景は4種類なので3との論理積を取る(0〜3の数値におさまる)
bg.image = game.assets["images/bg"+n+".jpg"];
stageLabel.text = "STAGE "+game.stage;
drawAtom();
game.rate = 1; // ★同色の原子を消した時に乗算される倍率を1に戻す
game.oldAtomType = ""; // ★直前に消した原子の色
}
// スコアを加算して表示。倍率も考慮
game.score = game.score + this.point*game.rate;
scoreLabel.text = "SCORE : " + game.score;
});
// ルートシーンに追加
game.rootScene.addChild(atom[count]);
count = count + 1;
}
}
game.pozonCount = count; // 原子の総数
}
}
-------------------------------------------------

 

------------------------------------------------------------------------------------------------------------------------------------

■ステージによって登場する原子の数を変える

 このゲームは全10面ですので、面によって登場する原子の種類と数を変えています。どのステージも同じ原子の数では変化がなく面白くないためです。このステージによって出現する原子の数は以下のstageData配列に入れてあります。配列は二次元配列になっていて、配列内にさらに配列が入っている構造です。内側の配列には「赤緑青」の順番で出現する原子の数が指定してあります。例えば4面であれば[6, 3, 2]となっていますので、赤色が6個、緑色が3個、青色が2個となります。これをステージの数だけ用意します。なお、最初に[0, 0, 0]としてありますが、これはステージ0に出現する原子の数です。とは言ってもステージ0は存在しないので、この部分は使われません。このようになっているのはプログラムを簡単にするためです。このようにすると「var data = stageData[game.stage];」として配列にアクセスすることができるからです。

-------------------------------------------------
// ステージに出てくる原子の出現数と種類(RGB順)
var stageData = [[0,0,0],
[3, 0, 0], [3, 3 ,0], [3, 3, 3], [6, 3, 2], [2, 2, 8], // Stage 1〜5
[6, 6, 6], [9, 0, 9], [20, 0, 0], [10, 10, 10], [15, 2, 4] // Stage 6〜10
];
-------------------------------------------------

上記の配列の内容に従って原子を出現させます。これは今まで解説してきたenchant.jsのスプライト生成処理と全く変わりません。また、ブロック崩しの時はブロックを画面外に出すことで表示しないようにしていましたが、このゲームではタッチ(クリック)された場合、以下のようにしてスプライトを消しています。これによりシーンからスプライトノードが切り離され表示されなくなります。

-------------------------------------------------
atom[count].addEventListener(Event.TOUCH_START, function(){
game.rootScene.removeChild(this);
-------------------------------------------------

原子の動きはブロック崩しのボールと同じで画面の端までいくと跳ね返ります。また、携帯にあたったときも跳ね返ります。この処理はブロック崩しのボールの移動処理と同じです。

携帯に原子があたると携帯の耐久力が減ります。耐久力はkeitai.powerに入っており、1ずつ減らします。耐久力がマイナスになるとゲームオーバーのメッセージを表示します。
この処理は以下のようになっています。

-------------------------------------------------
// ガラケーとの接触判定
if (this.intersect(keitai)){ // 接触したか?
this.dx = -this.dx; // 移動方向を反転させる
this.dy = -this.dy;
keitai.power = keitai.power - 1; // ガラケーの耐久力を減らす
powerLabel.text = keitai.power; // 耐久力を表示
if (keitai.power < 0){
game.stop();
alert("最後の携帯が破壊されました。ゲームオーバーです");
}
}
-------------------------------------------------

 

------------------------------------------------------------------------------------------------------------------------------------

■倍率の処理

 このゲームでは同色の原子をクリックしていくと加算される得点の倍率があがります。倍率はgame.rateに入れてあり初期値は1になっています。つまり1倍です。直前にクリックした原子の色はgame.oldAtomTypeに入っていますので、今クリックした原子の色と同じかどうか調べ同じならgame.rateに1を加算します。違う色の場合はgame.rateに1を入れ倍率を元に戻します。その後、game.oldAtomTypeに今クリックした原子の色を入れます。この部分の処理は以下のようになっています。

-------------------------------------------------
// 同じ色か調べる
if (this.type == game.oldAtomType){
game.rate = game.rate + 1; // 倍率を増やす
}else{
game.rate = 1; // 違い色をタッチしたので倍率を元に戻す
}
game.oldAtomType = this.type; // 直前にタップした時の色を保存
-------------------------------------------------

 クリックされたら原子の数を減らします。すべて消したらステージクリアになります。最後の第10ステージをクリアした場合、クリアメッセージを表示してゲームエンドとします。

-------------------------------------------------
// 原子の数を減らす
game.pozonCount = game.pozonCount - 1;
if (game.pozonCount < 1){
game.stage = game.stage + 1;
// 全10ステージをクリアしたか?
if (game.stage > 10){
game.stop();
alert("ガラパゴス星は脅威から解放されました。ゲームクリアです。");
}
-------------------------------------------------

ゲームをクリアした際、簡単なメッセージしか表示していませんがエンディングムービーを流すのもよいでしょう。
なお、このゲームをiPhone/iPad, Androidでプレイした際、バグがあって簡単にクリアできてしまいます。パソコンの場合には発生しません。このヒントでどのようなバグかわかる人もいるでしょう。(他にもGoogle Chrome (Mac)の21.0.1180.89では画面の書き換えが正常に行われない不具合があります)

それでは、また次回。

目次に戻る