Contents
スポンサーリンク
概要
現在、TypeScriptの勉強をしています。
折角なので自分の好きなサウンドノベルを作る目標で勉強していこうかと思います。
今回は、単純な以下のようなマウスクリック時に文字を表示していく処理を考えます。
サンプル
スポンサーリンク
スポンサーリンク
TypeScriptとは
まずはTypeScriptに関して簡単に触れようと思います。TypeScriptとは、MicroSoftが提供しているプログラミング言語です。
詳細はWikiをなどを見ていただければわかると思いますが、
簡単に言ってしまうとJavaScriptに色々と機能を追加したもので、コンパイル時にJavaScriptを出力してくれる言語です。
出力される最終的なアウトプットがJavaScriptになるので、これまで通りブラウザ上で動くものになりつつ、コードを記述する際はJavaScriptのちゃらんぽらんな部分を補って、ある程度静的な型付言語のように扱えるようになります。
そのため記述ミスなどを防ぎ、生産性を上げてくれるため、非常に有用な言語だと思います。
また、MS製ということでVisual Studioとの相性も良いので、普段C#を使っている開発者にもなじみやすい言語でもあります。
Visual Studio自体も現在はVisual Studio Communityとして個人開発者は無料で使うことができるため、これからJavaScriptをやってみようと思っている人がTypeScriptから学んで見るのも良いかもしれません。
スポンサーリンク
やりたいこと
折角学ぶのであれば、自分が楽しめるものが良いということで、自分の好きなジャンルであるサウンドノベルの作成を目標にやっていこうかと思います。
複雑なものをいきなり実現は難しいと思いますので、まずは文章表示させるところから学んでいこうと思います。
TypeScriptプロジェクトの作成と内容確認
まずはVisual Studioを立ち上げて、新規プロジェクトでTypeScriptアプリケーションを選択し作成します。
cssとhtmlとtsファイルという非常にシンプルな構成のファイルが出力されています。
Web.configがありますがこれはASP.NETとの連携用でしょうか?
htmlを開くとtsファイルと同名のjsファイルを読み込む記述がされています。
どうやらtsファイルと同名のjsファイルがコンパイル時に出力されるような仕組みなようです。
実際にtsファイルを見て見ると、以下のようなGreeterクラスが記述されています。タイマーで定期的に時刻を取得して画面出力する処理が記述されているようです。
また、ページ起動時に上記クラスを作成する処理が記述されており、これによって実際に処理が行われるようになっています。
実際にコンパイル実行すると、以下のような表示されたページが表示されます。時刻部分は定期的に表示が更新されます。
まずはTypeScriptアプリの起動確認まで行いましたので、実際にコード記述などを行います。
HTMLの記述
TypeScriptを実際に書く前に、まずはサウンドノベルとして文章表示を行いたい領域を作成します。
HTML5で何かを描画というと、Canvasという要素がありますのでそれを使って見ようと思います。
まずはテキトーに幅700と高さ550として宣言します。
また、IEの一部のバージョンでCanvasのコンテキスト取得時にエラーが出るため最新のモードで動くようにメタデータを宣言しておきます。
また、読み込むスクリプトはapp.jsからnovel.jsとして、後ほど作成していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!DOCTYPE html> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <html lang="en"> <head> <meta charset="utf-8" /> <title>TypeScript HTML App</title> <link rel="stylesheet" href="app.css" type="text/css" /> <script src="novel.js"></script> </head> <body> <canvas id="canvas1" width="700" height="550"></canvas> </body> </html> |
Canvasでの文字の描画
ここで一度、Canvasでの文字の表示方法を考えてみます。
Canvasでの文字の描画1 改行
サウンドノベルとして文章を表示するための方法を考えます。
Canvasには文章を描画するためのインタフェースとして以下のようなfillTextメソッドが用意されてされています。
fillText(text, x, y [, maxWidth ] )
参考URL
ただし、上記のインタフェースを見てもらえばわかる通り、文字の位置指定はx,yによる座標指定になります。
よって、よくあるテキストボックスやラベルのように、ただ表示したい文字列だけを渡すだけではなく、
改行を考えて自分でy座標を位置して描画する必要があります。
そのためのインタフェースとして、measureTextメソッドが用意されています。
metrics = context . measureText(text)
現在のフォントにおける指定テキストの長さを持った TextMetrics オブジェクトを返します。
metrics . width
measureText() メソッドに引き渡したテキストの幅を前もって返します。
このメソッドを使って1文字ずつ、描画前に改行すべきかどうかを判定し、描画領域を超える場合には改行を行うようにします。
文章を表示するためにだけにここまでしないといけないのは正直面倒くさいですね・・・。
Canvasでの文字の描画2 アニメーション表示
一般的なサウンドノベルでは、文章を表示する際に一度に全ての文章の表示を行うのではなく、徐々に表示を行う必要があります。
下記はかまいたちの夜の場合です。
これを実現するためには1文字ずつ、タイマーを用いて文字を描画する必要があります。
JavaScriptでは一定時間ごとに処理を行うためのsetInterval関数が用意されているのでそれを利用します。
参考URL
Canvasでの文字の描画 まとめ
上で説明したように、Canvasを用いてサウンドノベル的な文章表示を行うためには以下の2つの機能を実装する必要があります。
1.改行を意識した文字表示
2.タイマーを用いた1文字ずつの表示
文章表示をするのにここまでしないといけないのは、多機能なUI部材に慣れていると少し億劫ですね。
もう少しCanvasの機能が拡張されると良いのですが・・・。
TypeScript作成
実際にTypeScriptファイルを作成していきます。
ソリューションエクスプローラから新しいTypeScriptファイルを追加します。
作成されたtsファイルに以下のようなNovelDisplayクラスを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
class NovelDisplay{ //表示文字列[ページ数][行数] private sentences: string[][]; //キャンバス private canvas: HTMLCanvasElement; private canvas2D: CanvasRenderingContext2D; //表示幅,高さ private maxWidth: number; private maxHeight: number; //1行の高さ private rowHeight: number; //表示インターバル private displayInterval: number; //文字表示用タイマー private timer: number; //表示中の行と列とページ private currentColumn: number; private currentRow: number; private currentPage: number; //1文字列表示後に自動改行するかどうか private isAutoNewLine: boolean; //文字列二重表示可能判定 public isEnableSendText: boolean; constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.canvas2D = canvas.getContext("2d"); //位置の初期化 this.currentColumn = 0; this.currentRow = 0; this.currentPage = 0; //表示領域の指定 this.maxHeight = 500; this.maxWidth = 500; //配列を初期化 this.sentences = new Array(); this.sentences[0] = new Array(); this.sentences[0][0] = ""; //表示間隔とタイマーの初期化 this.displayInterval = 20; this.timer = 0; //フォント指定と行の高さ指定 this.canvas2D.font = "20pt Gothic"; this.rowHeight = 30; this.isAutoNewLine = true; this.isEnableSendText = true; } //1文字列を表示。描画中の場合はfalseを返す public showOneString(str: string) { //表示中のクリックを受け付けないようにする this.isEnableSendText = false; //1文字ずつタイマー表示 var strCount = 0; this.timer = setInterval(() => { if (str.length > strCount) { this.showOnechar(str[strCount]); strCount++; } else { //タイマーをクリア clearInterval(this.timer); this.timer = 0; //自動改行の場合は改行を追加 if (this.isAutoNewLine) { this.changeRowOrPage(); } this.isEnableSendText = true; } }, this.displayInterval) } //一文字を表示 private showOnechar(oneChar: string) { //次の文字を表示した場合の幅を取得 var currentWidth = this.canvas2D.measureText( this.sentences[this.currentPage][this.currentRow] + oneChar).width; //最大幅を超えたら改行 if (currentWidth > this.maxWidth) { //改行か改ページか判定 this.changeRowOrPage(); } //文字を表示して格納 var x = this.canvas2D.measureText(this.sentences[this.currentPage][this.currentRow]).width; var y = this.rowHeight * (this.currentRow +1); this.canvas2D.fillText(oneChar, x, y); this.currentColumn++; this.sentences[this.currentPage][this.currentRow] += oneChar; } //行もしくはページを変更します private changeRowOrPage() { var nextHeight = (this.currentRow + 1) * (this.rowHeight) + this.rowHeight; //最大高さを超えたら改行 if (nextHeight > this.maxHeight) { this.changePage(); } else { this.changeRow(); } } //行を変更 private changeRow() { //列数を追加して行情報をクリア this.currentRow++; this.currentColumn = 0; this.sentences[this.currentPage][this.currentRow] = ""; } //ページを変更 private changePage() { //ページをインクリメントして表示位置をリセット this.currentPage++; this.currentRow = 0; this.currentColumn = 0; this.sentences[this.currentPage] = new Array(); this.sentences[this.currentPage][0] = ""; //表示をクリア this.canvas2D.clearRect(0, 0, this.maxWidth, this.maxHeight); } } |
簡単にですが主なポイントの説明を行います。
基本的な考え方
文字列の2次元配列を利用して、ページ、行単位での表示済みの文字を保持しておきます。(sentencesフィールド)
このフィールドと現在の行と列位置を使って文字の表示位置を決めていきます。
showOnecharメソッド
このメソッドは渡された1文字の文字列を描画します。1文字な理由は前述の通り、タイマー処理によって徐々に表示を行うためです。
まず描画前に、measureTextを用いて改行もしくは改ページ判定を行い、必要であれば改行、改ページを行います。
その後、実際に表示位置を計算して描画を行います。
changeRowOrPage, changeRow, changePageメソッド
改行もしくは改ページ処理を行います。
changeRowOrPageメソッドでどちらを行うかを判定します。
現在の文字列描画の高さを計算し、画面下部まで達したかどうかを基準とします。
改行の場合には、現在の行数をインクリメントして、行情報をクリアします。
改ページの場合にはキャンバスの描画をクリアして表示文字を全て消し、行、列どちらの情報もクリアします。
showOneStringメソッド
渡した文字列を表示します。上記のshowOnecharメソッドに1文字ずつ渡す文字を渡すタイマーを定義して表示するように処理します。
文字描画中に処理を呼び出されても進まないようにタイマーのチェックを行います。
描画が終了するとタイマーをリセットして描画を受け入れるようにしています。
呼び出し処理
とりあえず、ここまで作成した処理を呼び出してみましょう。
Canvasのクリックイベント時に処理を呼び出すようにして、3パターンの文章を延々と表示させます。
実際のサンプルはこちらになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
window.onload = () => { //表示文字列 var tList = [ "ようやく覚えたボーゲンでなんとか麓のレストハウスまでたどり着き、ぼくは一息ついていた。", "真理はそんなぼくの目の前で、雪をけたてて鮮やかに止まった。", "ゴーグルが粉雪まみれになって、何も見えない。", ]; //キャンバスを取得して表示クラスに渡す var canvas = <HTMLCanvasElement>document.getElementById('canvas1'); var novel = new NovelDisplay(canvas); var currentTxtIndex = 0; function write() { //テスト用にループ if (tList.length <= currentTxtIndex) { currentTxtIndex = 0; } //2重クリック防止 if (novel.isEnableSendText) { novel.showOneString(tList[currentTxtIndex]); currentTxtIndex++; } } //クリック時に呼び出し canvas.onclick = write; } |
まとめ
ということで、TypeScript(HTML5 + JavaScript)を用いてサウンドノベル風の文章表示を行ってみました。
JavaScript自体そこまで詳しくないため言っていいのかわかりませんが、どうしても汚いコードになってしまうイメージがありました。
しかし今回はTypeScriptを使ってオブジェクト指向的に処理を書くことができたのでだいぶ書きやすくてよかったです。
これから色々と手を加えていこうと思います。
また手法等がまとまりましたら紹介しようと思います。
2015/07/22
その2を追加しました。