たまにやると楽しノベルゲーム。
なんとなく自分で作ってみたいなと思っても、作成するソフトが有料だとやっぱりやめようかなと思ってしまいます。
今回はJavaScriptでノベルゲームの型を作成してみました。
- 無料でノベルゲームを作りたい。
- JavaScriptでノベルゲームを作りたい。
- WordPressでノベルゲームを公開したい。
こんな方に読んでもらいたいです。
作成したノベルゲーム
※この記事ではプログラミングがメインなのでストーリーは途中で終わります。
ユーザーインタフェースもへったくれも無い仕様ですが、クリックして会話が進むようになっています。
ストーリーや素材は既に準備できていて後は動かす土台が欲しい方や、これからJavaScriptでノベルゲームを作ろうと考えている方にはちょうど良いと思います。
HTMLソースとJavaScriptのプログラム(コピペ用)
WordPressを使っている方の場合は
この下のHTMLソースをコピーして記事本文に貼り付けてください。
<canvas id="axisCanvas" width="600" height="450"></canvas> <!-- BGMファイル --> <audio id="bgm1" preload="" loop="loop"> <source src="op.ogg" type="audio/ogg" /> <source src="https://mizusibuki.com/xxxxxxxxxxxxxxxxxx" type="audio/mp3" /> </audio> <!-- BGM再生ボタン --> <button id="btn-play" type="button"><i class="fas fa-play"></i></button> <!-- SEファイル --> <audio id="se1" preload="" loop="loop"> <source src="op.ogg" type="audio/ogg" /> <source src="https://mizusibuki.com/xxxxxxxxxxxxxxxxxx" type="audio/mp3" /> </audio>
次にこの下のプログラムをコピーして記事投稿のカスタムJavaScriptへ貼り付けてください。
//要素 var canvas; // canvas要素(HTMLCanvasElement) var ctx; // 2Dコンテキスト(CanvasRenderingContext2D) var canvasW = 1049; // canvas要素の横幅(px) var canvasH = 600; // canvas要素の縦幅(px) var mouseX; // 最後にクリックされた位置のx座標 var mouseY; // 最後にクリックされた位置のy座標 const back_img = new Image(); const left_img = new Image(); const right_img = new Image(); const msg_img = new Image(); const split = "_,_"; var item =""; const chapter_fontsize = 40; //チャプター表示の文字サイズ let write_count = 0; var chapter_write_end = true; //チャプターの表示が完了したか確認用フラグ var text_write_end = true; //メッセージの表示が完了したか確認用フラグ var text_write_delay = 35; //メッセージウインドウの表示スピード(0が速い) var text_write_fast = false; var text_write_click = false; //テキスト表示中にクリックされたら即座に全て表示させるフラグ var write_text; //ページにある本文を読み込む var music = new Audio(); var se = new Audio(); var music = document.getElementById("audio"); //イメージ const root_ = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx/"; const msgwin = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx.png"; const suika = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx.jpg"; const chapter_background = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx.png"; //キャラ const right_b_rina_angry1 = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx.png"; const right_b_rina_shadow = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx.png"; //bgm const bgm1 = document.querySelector("#bgm1"); // <audio> const btn = document.querySelector("#btn-play"); // <button> const bgm_heionn = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx.mp3"; const bgm_kowai = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx.mp3"; //se const se1 = document.querySelector("#se1"); // <audio> const se_hanabis = "https://mizusibuki.com/xxxxxxxxxxxxxxxxxx.mp3"; let items = [ { text: "", back: "", left: "", right: "", msgw: "", bgm: "", se: "" ,chapter: "〜花火の前日〜" }, { text: "俺の名前は吉野 陸(よしの りく)北海道へ一人旅に来ている。", back: suika, left: "", right: "", msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "なのに。", back: suika, left: "", right: "", msgw: msgwin, bgm: bgm_kowai, se: "" ,chapter: "" }, { text: "それなのに今は人の畑でスイカの盗み食いをしている。", back: suika, left: "", right: "", msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "道具は何もないのでスイカを地面に叩きつけて割れたスイカにかぶりついた。", back: suika, left: "", right: "", msgw: msgwin, bgm: "", se: "" ,chapter: "" }, { text: "食べる事に夢中で種ごと飲み込んでいた。", back: suika, left: "", right: "", msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "???:ちょっとアンタなにやってんのよ!", back: suika, left: "", right: right_b_rina_shadow, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "背中に投げかけられる声。", back: suika, left: "", right: right_b_rina_shadow, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "本当なら驚いて振り向くところだが、そうではなかった。", back: suika, left: "", right: right_b_rina_shadow, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "どちらかと言うと寝ている時に誰かに何度も起きろと言われているような・・", back: suika, left: "", right: right_b_rina_shadow, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "そう、無視したい感覚だった。", back: suika, left: "", right: right_b_rina_shadow, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "???:オイ!こらー!", back: suika, left: "", right: right_b_rina_shadow, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "???:なに勝手に畑入ってスイカ食ってんのよ!", back: suika, left: "", right: right_b_rina_shadow, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "何回目の呼びかけだろうか、ようやく正気に戻り振り向くべき状況であると認識してゆっくり振り向く。", back: suika, left: "", right: right_b_rina_shadow, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "陸:(女の子だ。高校生?)", back: suika, left: "", right: right_b_rina_angry1, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "女の子:何やってんのかって聞いてんの!ドロボー?", back: suika, left: "", right: right_b_rina_angry1, msgw: msgwin, bgm: bgm_heionn, se: "" ,chapter: "" }, { text: "", back: "", left: "", right: "", msgw: "", bgm: bgm_heionn, se: "" ,chapter: "終わりです。クリックでスタートに戻ります。" }, ]; var items_number = 0; canvas = document.getElementById('axisCanvas'); // canvas要素を取得し、サイズ設定 canvas.width = canvasW; canvas.height = canvasH; // 描画のために2Dコンテキスト取得 ctx = canvas.getContext('2d'); //BGM再生ボタン操作 function bgm_switch(){ // pausedがtrue=>停止, false=>再生中 if( ! bgm1.paused ){ btn.innerHTML = '<i class="fas fa-play"></i>'; // 「再生ボタン」に切り替え bgm1.pause(); } else{ btn.innerHTML = '<i class="fas fa-pause"></i>'; // 「一時停止ボタン」に切り替え bgm1.play(); } } btn.addEventListener("click", ()=>{ bgm_start(); }); /** * [event] 再生終了時に実行 */ bgm1.addEventListener("ended", ()=>{ bgm1.currentTime = 0; // 再生位置を先頭に移動(こいつはなくても大丈夫です) btn.innerHTML = '<i class="fas fa-play"></i>'; // 「再生ボタン」に変更 }); //SE再生ボタン操作 function se_switch(){ // pausedがtrue=>停止, false=>再生中 if( ! se1.paused ){ se1.pause(); } else{ se1.play(); } } //次 function chapter_wiew(chapter){ //黒背景で文字を黒から白に変更 var count = 0;//アニメーションカウンター var timer = setInterval(function(){ text_write_end = false; ctx.font = "bold " + chapter_fontsize + "px 'MS ゴシック'"; ctx.fillStyle= "rgb( " + (255/20) * count + ", " + (255/20) * count + ", " + (255/20) * count + " )" back_img.src = chapter_background; ctx.drawImage(back_img, 0, 0, 1049, 600); //背景描写 ctx.fillText(chapter, canvasW / 2 - chapter.length * chapter_fontsize / 2 , canvasH / 2 - 40, canvasW); //文章 count++; if(count>20){ text_write_end = true; clearInterval(timer); } },100); }; //文字を表示する際の1文字ずつ判定 // setIntervalの基本 max_row = 28; var timer1 = null; var cnt = 0; function event() { chapter_write_end = false; write_text.length; row = 0; if (cnt > max_row * 1)row = 1; if (cnt > max_row * 2)row = 2; if (cnt > max_row * 3)row = 3; column = 0; if (cnt > max_row * 1)column = -1 * max_row - 1; if (cnt > max_row * 2)column = -2 * max_row - 1; if (cnt > max_row * 3)column = -3 * max_row - 1; ctx.fillText(write_text.charAt(cnt), 70 + cnt * 30 + column * 30, 480+ row * 30, 900); //文章 cnt++; //文字描写中にクリックされた場合は一気に描写する if (text_write_fast == true){ row = 0; column = 0; cnt = 0; clearInterval(timer1); text_write_fast = false; timer1 = setInterval(event, 1); } if (cnt >= write_text.length && timer1 != null) { // 文字数以上になったら、タイマーを停止する chapter_write_end = true; text_write_click = false; row = 0; column = 0; cnt = 0; clearInterval(timer1); } } function next_item(){ const chapter = (String(items[items_number].chapter)); //チャプター文字取得 write_text = (String(items[items_number].text)); //文章を //BGMがセットされていない場合はBGMは変えない。//BGMが現在と違う場合のみBGMを再設定する。 //if ( (String(items[items_number].bgm)) != "" && bgm1.src != (String(items[items_number].bgm)) ){ if ( bgm1.src != (String(items[items_number].bgm)) ){ bgm1.src = (String(items[items_number].bgm)); //BGM bgm1.play(); } if ( "" == (String(items[items_number].bgm)) ){ bgm1.src = ""; //BGM bgm1.pause(); } se1.pause(); if ( (String(items[items_number].bgm)) != "" ){ se1.src = (String(items[items_number].se)); //SE se1.play(); } // 一度描画をクリア ctx.clearRect(0, 0, canvasW, canvasH); ctx.fillStyle = 'white'; ctx.font = "30px 'MS ゴシック'"; ctx.textAlign = "left"; ctx.textBaseline = "top"; left_img.src = ""; left_img.src = String(items[items_number].left); write_flag = "NG"; right_img.src = ""; right_img.src = String(items[items_number].right); msg_img.src = ""; msg_img.src = String(items[items_number].msgw); back_img.src = ""; // 一度か空にしないと同じ画像がonloadで読み込まれないので回避策として入れる back_img.src = String(items[items_number].back); // 画像のURLを指定 back_img.onload = () => { ctx.drawImage(back_img, 0, 0, 1049, 600); //背景描写 ctx.drawImage(left_img, 50, 50); //left ctx.drawImage(right_img, 600, 50); // ctx.drawImage(msg_img, 0, 450, 1049, 150); //メッセージウィンドウ timer1 = setInterval(event, text_write_delay); }; //読み込みページにチャプター文字がある場合はチャプターモードで表示する if ( chapter != "" ){ chapter_wiew(chapter); } }; function keydownfunc( event ) { //押されたボタンに割り当てられた数値(すうち)を、key_codeに代入 var key_code = event.keyCode; if( key_code === 37 ) ; //「左ボタン」が押されたとき if( key_code === 38 ) ; //「上ボタン」が押されたとき if( key_code === 39 ) next(); //「右ボタン」が押されたとき if( key_code === 40 ) next(); //「下ボタン」が押されたとき } function next(){ //クリックされた場合に前の読み込みが完了していれば次のページへ。 //そうでなければ読み込みスピードをMAXにする。 if ( chapter_write_end == true && text_write_end == true ){ next_item(); items_number++; }else{ if ( text_write_click == false ){ text_write_fast = true; text_write_click = true; } } if(items.length <= items_number) items_number = 0; //終わったら最初に戻る } window.onload = function() { next_item(); items_number++; canvas.onclick = function(e) { var rect = e.target.getBoundingClientRect(); mouseX = e.clientX - Math.floor(rect.left) - 2; mouseY = e.clientY - Math.floor(rect.top) - 2; console.log(mouseX,mouseY); next(); } }; addEventListener( "keydown", keydownfunc );
コードは汚いかも知れませんが、動けばいいのです。
BGM、画像のURLがあると思いますが、そこはメディアを追加ボタンからご自身のブログにBGMと画像をアップロードしてURLを修正してください。
プログラム上は「xxxxxxxxxxxxxxxxxx」に書き換えています。
あとはテンプレートに従って文字や画像、BGMを指定すればノベルゲームの完成です。
テンプレートの開始を示します。
黒背景にタイトルを表示させる
赤い部分に文字を入れるとタイトルのように表示されます。
会話画面
text ・・会話本文を入力します。
back ・・背景画像で指定した変数を書きます
left ・・キャラ画像で指定した変数を書きます。左側に表示されます。
right ・・キャラ画像で指定した変数を書きます。右側に表示されます。
msggw ・・メッセージの囲み画像で指定した変数を書きます。メッセージ部分の背景になります。
bgm ・・bgmで指定した変数を書きます。※続けて流す場合は最初に1回書くだけでいいです。
se ・・seで指定した変数を書きます。※SE機能は調整中です。
chapter ・・タイトルの指定なので、会話画面では空欄にしておきます。
基本はこの会話画面の行をコピーして会話を繋げてストーリーを作成してください。
テンプレートの終了を示します。
BGMや画像は別途必要ですが、このようにテンプレートの開始から終了の間だけ行を追加していけばストーリーを組んでいくことができます。
JavaScriptはどうやって覚える?
プログラムのコードが読めないと、参考にしているコードの理解や自分でプログラムを書くときに時間がかかってしまいます。そんな時は基礎から学ぶと応用もできるようになり自分の作りたいプログラムがスムーズに作れるようになります。
無料でやるならネット上の学習サイトを参考にするといいです。
こちらはJavaScriptの基礎から説明している無料のWEBサイトになります。
>>基礎から学べるWEBサイトを見てみる
過去にネットでの学習でつまずいた方には専門書をオススメします。
本なら基礎から学べる事はもちろん、読者視点で分かりやすい解説になっています。
一人で集中してコツコツ進めたい方は書籍を試してください。
本を読んでもわからない所が多すぎたり、誰かに質問したい場合はオンラインスクールを見てみましょう。
カウンセリングから目的に応じた学習プランと教材を提供してくれるので、プログラミングの敷居がとても低くなります。
誰かに相談できるのは心強いです。
>>オンラインスクールを無料体験で始める
まとめ
今回はJavaScriptでノベルゲームのプログラムを作成しました。
作りは単純ですが自作プログラムなので無料で使うことができます。
実際に使用する時はBGMや画像の準備が必要になりますが、このブログのようにWordPress上で動かす事ができます。
まだまだ改良の余地があるプログラムなので、手を加えて良い物にしてみてください。