JavaScriptで1文字ずつ表示するサンプルをドラクエ風に作ってみる
前回、「JavaScriptでキー入力を判別してドラクエ風の戦闘画面を作ってみる」ではonkeydownの解説だけではつまらないかと思い、思わずドラクエ風の戦闘画面を作ってしまいました。
しかし、記事の最後に書いたとおり、メッセージ表示部分がCSSによるやっつけ仕事で、1行表示にしか対応していなかったため、今回はJavaScriptを使って、ちゃんと1文字ずつメッセージを流れるように表示したいと思います。
JavaScriptで1文字ずつメッセージを表示するサンプル
動作サンプル
下の[表示]ボタンをクリックすると「メッセージが左から右へ1文字ずつ表示されます。」というメッセージが表示されます。
また、1度クリックするごとに改行も入るため、[表示]ボタンを押すたびにどんどん行数が増えていくはずです。
サンプルコード
<div id="message_window"></div>
<button onclick="message('メッセージが左から右へ1文字ずつ表示されます。');">表示</button>
<script>
var msg_buff = '';
function message(msg)
{
if (msg_buff == '') {
msg_buff += msg + "\n";
message_char();
} else {
msg_buff += msg + "\n";
}
}
function message_char()
{
if (msg_buff == '') {
//メッセージバッファに文字がなければ何もしない
return;
}
//メッセージバッファの先頭1文字を取得
var c = msg_buff.slice(0, 1)
if (c == "\n") {
c = '<br>';//改行の場合はタグへ変換
}
document.getElementById('message_window').innerHTML += c;
//メッセージバッファから先頭1文字を削除
msg_buff = msg_buff.slice(1);
//
setTimeout('message_char()', 30);
}
</script>
解説
グローバル変数に文字列を書いて、setTimeoutで1文字ずつ切り取ってdiv要素などへ書き出す、というサンプルでも良かったのですが、それだと汎用性がアレかなぁ、と思い、message関数に文字列を渡すだけで使えるような作りにしてみました。
ポイントとしてはmessage関数は引数として渡された文字列をグローバル変数であるmsg_buffに格納しているだけ、というところですね。
まぁ、だけ、と言っても最初の1回目、バッファが空だったときはmessage_char関数をキックしています。
そのmessage_char関数はバッファ(msg_buff)から先頭の1文字を切り出して、message_windowに書き込み、バッファの先頭1文字は削除する、という動作をしています。
そして最後に自分自身(message_char)を呼び出すことにより、バッファがなくなるまで先頭の文字を順番にmessgae_windowに書き込む動作となるわけです。
なんでこんな手間をかけているかというと、
message('1行目のメッセージです。');
message('2行目のメッセージです。');
message('3行目のメッセージです。');
みたいな書き方がしたいから、です。
あらかじめdivタグ等でHTML中に全部のメッセージを書き込んでおいて、それを1文字ずつ表示するだけ、というほうが楽なケースもあるとは思いますが、JavaScriptで処理分岐をして、このボタンを押されたらこっちのメッセージを表示する、みたいな処理をしたい場合はこうした作りのほうが良いでしょう。
つまり、こういうことです↓
またもやドラクエ風の戦闘画面にしてみる
動作サンプル
サンプルコード
ちと長いです。
<html>
<head><meta charset="UTF-8"><title>JavaScriptのKeyDownサンプル</title></head>
<body>
<script>
var menu_id = 0;
//キー入力による分岐処理
function game_keydown()
{
switch (event.keyCode) {
case 37: //カーソルキーの左
document.getElementById("game_control").value = "←";
break;
case 38: //カーソルキーの上
document.getElementById("game_control").value = "↑";
if (menu_id <= 1) {
activeMenu(4);
} else {
activeMenu(menu_id - 1);
}
break;
case 39: //カーソルキーの右
document.getElementById("game_control").value = "→";
break;
case 40: //カーソルキーの下
document.getElementById("game_control").value = "↓";
if (menu_id >= 4) {
activeMenu(1);
} else {
activeMenu(menu_id + 1);
}
break;
case 13: //エンターキー
doCommand(menu_id);
break;
default: //その他の場合はキーコードを表示
document.getElementById("game_control").value = "キーコード:" + event.keyCode;
break;
}
}
function activeMenu(id)
{
if (menu_id == id) {
//前回と同じメニューが選ばれた場合はコマンドを実行
doCommand(id);
} else {
if (menu_id != 0) {
//現在のメニューのカーソルを消す
document.getElementById('menu' + menu_id).className = 'menu';
}
//今回選ばれたメニューにカーソルを表示
document.getElementById('menu' + id).className = 'menu menu-active';
menu_id = id;
}
}
//コマンドの実行
function doCommand(command_id)
{
document.getElementById("game_control").value = "コマンド番号:" + command_id;
switch (command_id) {
case 1: //たたかう
message("ああああの こうげき!");
message("ミス! ダメージをあたえられない!");
break;
case 2: //ぼうぎょ
message("ああああは みをまもっている!");
message("顔文字マンは ようすをみている。");
break;
case 3: //どうぐ
message("しかし なにももっていなかった。");
break;
case 4: //にげる
message("ああああは にげだした!");
message("しかし まわりこまれてしまった!");
break;
default:
break;
}
}
var msg_buff = '';
function message(msg)
{
if (msg_buff == '') {
msg_buff += msg + "\n";
message_char();
} else {
msg_buff += msg + "\n";
}
}
function message_char()
{
if (msg_buff == '') {
//メッセージバッファに文字がなければ何もしない
return;
}
//メッセージバッファの先頭1文字を取得
var c = msg_buff.slice(0, 1)
if (c == "\n") {
c = '<br>';//改行の場合はタグへ変換
var obj = document.getElementById('message_window');
obj.scrollTop = obj.scrollHeight;
}
document.getElementById('message_window').innerHTML += c;
//メッセージバッファから先頭1文字を削除
msg_buff = msg_buff.slice(1);
//
setTimeout('message_char()', 30);
}
</script>
<div class="game_window">
<div class="game_menu">
<div id="menu1" onclick="activeMenu(1);" class="menu">たたかう</div>
<div id="menu2" onclick="activeMenu(2);" class="menu">ぼうぎょ</div>
<div id="menu3" onclick="activeMenu(3);" class="menu">どうぐ</div>
<div id="menu4" onclick="activeMenu(4);" class="menu">にげる</div>
</div>
<div class="game_enemy">
ヽ(=´▽`=)ノ
</div>
<div id="message_window"></div>
</div>
<input type="text" id="game_control" onkeydown="game_keydown();"></div>
<style>
/* ゲーム画面全体のデザイン */
.game_window {
width:100%;
height:300px;
background-color:black;
color:white;
}
/* メニュー */
.game_menu {
width:100px;
border:3px solid white;
border-radius:6px;
margin:1em;
padding:1em;
float:left;
}
.game_enemy {
width:300px;
text-align:center;
margin:1em;
padding:1em;
float:left;
}
#message_window {
width:300px;
border:3px solid white;
border-radius:6px;
margin:1em;
padding:1em;
float:left;
height:2.4em;
overflow:hidden;
}
/* メニュー用 */
.menu {
cursor:pointer;
}
.menu:before {
cursor:pointer;
content: ' ';
}
/* メニュー選択状態 */
.menu-active:before {
content: '▶';
}
</style>
<script>
activeMenu(1);
message('顔文字マン が あらわれた');
</script>
</body>
</html>
解説
1文字ずつ表示する部分については最初のサンプルで解説したから省きます。
こちらの悪ノリサンプルのポイントとしてはメッセージがスクロールするようになっている点ですね。
具体的にはmessage_char関数内のこの部分。 if (c == "\n") {
c = '<br>';//改行の場合はタグへ変換
var obj = document.getElementById('message_window');
obj.scrollTop = obj.scrollHeight;
}
改行が発生した際、message_windowのscrollTopにscrollHeightをセットしています。
こうすることで、常に一番下までスクロールさせています。
また、message_windowはdivタグなので、styleの定義で高さ指定とoverflow:hidden;(はみ出た部分は隠す)の指定をしているのがポイントでしょうか。
まとめ
JavaScriptで1文字ずつ表示するには、バッファを用意してsetTimeout関数で一文字ずつ切り取ろう。
あと、ドラクエ風の戦闘画面は書いていて楽しい。