debugging-javascript.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. Title: Three.jsでのJavaScriptデバッグ
  2. Description: THREE.jsでJavaScriptをデバッグする方法
  3. TOC: JavaScriptのデバッグ
  4. この記事のほとんどはTHREE.jsのデバッグと言うより、一般的なJavaScriptのデバッグの内容です。THREE.js初心者にはJavaScript初心者も多いので、この記事を読んで困った時に簡単に解決できるようになると良いと思います。
  5. デバッグは大きなトピックであり、この記事で全てをカバーできませんが、JavaScriptに慣れていない場合はいくつかのヒントを得られると思います。デバッグに関しては、時間をかけて学ぶ事を強くお勧めします。デバッグはあなたの学習を大いに助けてくれます。
  6. ## ブラウザの開発者ツールを学ぶ
  7. 全てのブラウザには開発者ツールがあります。
  8. [Chrome](https://developers.google.com/web/tools/chrome-devtools/),
  9. [Firefox](https://developer.mozilla.org/en-US/docs/Tools),
  10. [Safari](https://developer.apple.com/safari/tools/),
  11. [Edge](https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide).
  12. Chromeでは `⋮` アイコンをクリックし、その他のツール -> デベロッパーツールを選択すると開発者ツールが表示されます。そこにはキーボードのショートカットも表示されています。
  13. <div class="threejs_center"><img class="border" src="resources/images/devtools-chrome.jpg" style="width: 789px;"></div>
  14. Firefoxでは `☰` アイコンをクリックし、"ウェブ開発"から"開発者ツール"を選択します。
  15. <div class="threejs_center"><img class="border" src="resources/images/devtools-firefox.jpg" style="width: 786px;"></div>
  16. Safariでは詳細設定メニューから開発メニューを有効にする必要があります。
  17. <div class="threejs_center"><img class="border" src="resources/images/devtools-enable-safari.jpg" style="width: 775px;"></div>
  18. 次に開発メニューで"Webインスペクタの表示/接続"を選択します。
  19. <div class="threejs_center"><img class="border" src="resources/images/devtools-safari.jpg" style="width: 777px;"></div>
  20. [Chromeを使ってAndroidやタブレットでChrome上で実行されているウェブページをデバッグする事もできます](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/)。
  21. 同様にSafariでは[iPhoneやiPadでSafari上で実行されているウェブページをPCでデバッグする事ができます](https://www.google.com/search?q=safari+remote+debugging+ios)。
  22. 私はChromeを一番よく知ってるのでChromeを例にしますが、ほとんどのブラウザは似たような機能を持っているため、全てのブラウザで簡単に同じ機能を適用できるはずです。
  23. ## キャッシュをオフにする
  24. ブラウザはダウンロードしたデータを再利用します。これはウェブサイトを2回目に訪れた際、サイトを表示するために必要な多くのファイルは再びダウンロードされず、ユーザーにとって素晴らしい事です。
  25. 一方でこれはウェブ開発に悪い影響を与える可能性があります。PC上でファイルを変更しリロードしても、前回ダウンロードしたバージョンを使用しているため変更内容が表示されません。
  26. ウェブ開発中の解決策の1つは、キャッシュをオフにする事です。これによりブラウザは常に最新バージョンのファイルを取得する事ができます。
  27. 最初にデベロッパーツールのSettingsメニューを選択します。
  28. <div class="threejs_center"><img class="border" src="resources/images/devtools-chrome-settings.jpg" style="width: 778px"></div>
  29. 次に "Disable Cache (while DevTools is open)" を選択します。
  30. <div class="threejs_center"><img class="border" src="resources/images/devtools-chrome-disable-cache.jpg" style="width: 779px"></div>
  31. ## JavaScriptコンソールを使用する
  32. 全てのdevtoolsの中には *console* があります。ここには警告やエラーメッセージが表示されます。
  33. **メッセージを読みましょう!!**
  34. 一般的にはメッセージは1つか2つしかありません。
  35. <div class="threejs_center"><img class="border" src="resources/images/devtools-no-errors.jpg" style="width: 779px"></div>
  36. もし他のメッセージがあれば**メッセージを読みましょう**。例えば
  37. <div class="threejs_center"><img class="border" src="resources/images/devtools-errors.jpg" style="width: 779px"></div>
  38. "three"を"threee"とスペルミスしました。
  39. 以下のように `console.log` であなた自身がconsoleに情報を表示する事もできます。
  40. ```js
  41. console.log(someObject.position.x, someObject.position.y, someObject.position.z);
  42. ```
  43. さらにクールな事にオブジェクトのログを記録したり検査する事ができます。例えば[gLTFの記事](threejs-load-gltf.html)からルートシーンのオブジェクトをログに表示できます。
  44. ```js
  45. {
  46. const gltfLoader = new GLTFLoader();
  47. gltfLoader.load('resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
  48. const root = gltf.scene;
  49. scene.add(root);
  50. + console.log(root);
  51. ```
  52. そしてそのオブジェクトをJavaScriptコンソールで展開できます。
  53. <div class="threejs_center"><img class="border" src="resources/images/devtools-console-object.gif"></div>
  54. スタックトレースを含む赤色メッセージを表示する場合は `console.error` を使う事ができます。
  55. ## データを画面に表示させる
  56. もう1つの分かりやすい方法は `<div>` や `<pre>` を追加しデータを入れる事です。
  57. 最も分かりやすい方法はいくつかのHTML要素を作成する事です。
  58. ```html
  59. <canvas id="c"></canvas>
  60. +<div id="debug">
  61. + <div>x:<span id="x"></span></div>
  62. + <div>y:<span id="y"></span></div>
  63. + <div>z:<span id="z"></span></div>
  64. +</div>
  65. ```
  66. キャンバスの上に残るようにスタイルを整えます(キャンバスがページを埋めていると仮定します)。
  67. ```html
  68. <style>
  69. #debug {
  70. position: absolute;
  71. left: 1em;
  72. top: 1em;
  73. padding: 1em;
  74. background: rgba(0, 0, 0, 0.8);
  75. color: white;
  76. font-family: monospace;
  77. }
  78. </style>
  79. ```
  80. そして要素を探して内容を設定します。
  81. ```js
  82. // at init time
  83. const xElem = document.querySelector('#x');
  84. const yElem = document.querySelector('#y');
  85. const zElem = document.querySelector('#z');
  86. // at render or update time
  87. xElem.textContent = someObject.position.x.toFixed(3);
  88. yElem.textContent = someObject.position.y.toFixed(3);
  89. zElem.textContent = someObject.position.z.toFixed(3);
  90. ```
  91. これはリアルタイムな値を見る時はとても便利です。
  92. {{{example url="../threejs-debug-js-html-elements.html" }}}
  93. または画面にデータを貼り付けるのにクリアロガーを作成する方法もあります。私はその言葉を作っただけですが、私が手がけたゲームの多くはこの解決法を使っています。
  94. このアイデアは1フレーム分だけメッセージを表示するバッファを持つ事です。
  95. データを表示したいコードのどの部分でも、フレームごとにバッファにデータを追加する関数を呼び出します。これは上記のデータのピースごとに要素を作成するよりもはるかに少ない作業です。
  96. 例えば上記のHTMLを以下のように変更してみましょう。
  97. ```html
  98. <canvas id="c"></canvas>
  99. <div id="debug">
  100. <pre></pre>
  101. </div>
  102. ```
  103. この*クリアバックバッファ*を管理するための簡単なクラスを作ってみましょう。
  104. ```js
  105. class ClearingLogger {
  106. constructor(elem) {
  107. this.elem = elem;
  108. this.lines = [];
  109. }
  110. log(...args) {
  111. this.lines.push([...args].join(' '));
  112. }
  113. render() {
  114. this.elem.textContent = this.lines.join('\n');
  115. this.lines = [];
  116. }
  117. }
  118. ```
  119. 次にマウスをクリックするたびに2秒間のランダムな方向に移動するメッシュを作成する簡単な例を作ってみましょう。[レスポンシブデザイン](threejs-responsive.html)の記事から例を紹介します。
  120. マウスをクリックするたびに新しい `Mesh` を追加するコードは以下の通りです。
  121. ```js
  122. const geometry = new THREE.SphereGeometry();
  123. const material = new THREE.MeshBasicMaterial({color: 'red'});
  124. const things = [];
  125. function rand(min, max) {
  126. if (max === undefined) {
  127. max = min;
  128. min = 0;
  129. }
  130. return Math.random() * (max - min) + min;
  131. }
  132. function createThing() {
  133. const mesh = new THREE.Mesh(geometry, material);
  134. scene.add(mesh);
  135. things.push({
  136. mesh,
  137. timer: 2,
  138. velocity: new THREE.Vector3(rand(-5, 5), rand(-5, 5), rand(-5, 5)),
  139. });
  140. }
  141. canvas.addEventListener('click', createThing);
  142. ```
  143. このコードは作成したメッシュを移動させログに記録し、タイマーが切れたら削除します。
  144. ```js
  145. const logger = new ClearingLogger(document.querySelector('#debug pre'));
  146. let then = 0;
  147. function render(now) {
  148. now *= 0.001; // convert to seconds
  149. const deltaTime = now - then;
  150. then = now;
  151. ...
  152. logger.log('fps:', (1 / deltaTime).toFixed(1));
  153. logger.log('num things:', things.length);
  154. for (let i = 0; i < things.length;) {
  155. const thing = things[i];
  156. const mesh = thing.mesh;
  157. const pos = mesh.position;
  158. logger.log(
  159. 'timer:', thing.timer.toFixed(3),
  160. 'pos:', pos.x.toFixed(3), pos.y.toFixed(3), pos.z.toFixed(3));
  161. thing.timer -= deltaTime;
  162. if (thing.timer <= 0) {
  163. // remove this thing. Note we don't advance `i`
  164. things.splice(i, 1);
  165. scene.remove(mesh);
  166. } else {
  167. mesh.position.addScaledVector(thing.velocity, deltaTime);
  168. ++i;
  169. }
  170. }
  171. renderer.render(scene, camera);
  172. logger.render();
  173. requestAnimationFrame(render);
  174. }
  175. ```
  176. 以下のサンプルでマウスをクリックして下さい。
  177. {{{example url="../threejs-debug-js-clearing-logger.html" }}}
  178. ## クエリパラメーター
  179. もう1つ覚えておきたいのは、ウェブページにはクエリパラメーターやアンカーを介してデータを渡す事ができます。検索とハッシュと呼ばれる事があります。
  180. https://domain/path/?query#anchor
  181. これを使用しオプション機能やパラメーターを渡す事ができます。
  182. 先ほどの例では次のようにしています。デバッグ機能はURLに `?debug=true` を指定した場合にのみ表示されます。
  183. まず、クエリストリングを解析するコードが必要です。
  184. ```js
  185. /**
  186. * Returns the query parameters as a key/value object.
  187. * Example: If the query parameters are
  188. *
  189. * abc=123&def=456&name=gman
  190. *
  191. * Then `getQuery()` will return an object like
  192. *
  193. * {
  194. * abc: '123',
  195. * def: '456',
  196. * name: 'gman',
  197. * }
  198. */
  199. function getQuery() {
  200. return Object.fromEntries(new URLSearchParams(window.location.search).entries());
  201. }
  202. ```
  203. そうすると、debug要素をデフォルトでは表示しないようにする事ができるかもしれません。
  204. ```html
  205. <canvas id="c"></canvas>
  206. +<div id="debug" style="display: none;">
  207. <pre></pre>
  208. </div>
  209. ```
  210. このコードをみると `?debug=true` が渡された場合のみデバッグ情報を表示するのが分かります。
  211. ```js
  212. const query = getQuery();
  213. const debug = query.debug === 'true';
  214. const logger = debug
  215. ? new ClearingLogger(document.querySelector('#debug pre'))
  216. : new DummyLogger();
  217. if (debug) {
  218. document.querySelector('#debug').style.display = '';
  219. }
  220. ```
  221. `?debug=true` の場合は何も渡さないように `DummyLogger` を作りました。
  222. ```js
  223. class DummyLogger {
  224. log() {}
  225. render() {}
  226. }
  227. ```
  228. 以下のURLを使用して確認する事ができます。
  229. <a target="_blank" href="../threejs-debug-js-params.html">threejs-debug-js-params.html</a>
  230. 上記にはデバッグ情報はありません。
  231. <a target="_blank" href="../threejs-debug-js-params.html?debug=true">threejs-debug-js-params.html?debug=true</a>
  232. こちらにはデバッグ情報があります。
  233. 複数のパラメーターは `somepage.html?someparam=somevalue&someotherparam=someothervalue` のように'&'で区切る事で渡せます。
  234. このようなパラメータを使用するとあらゆる種類のオプションを渡す事ができます。
  235. `speed=0.01` のようにアプリの速度を遅くしてわかりやすくしたり、`showHelpers=true` のように他のレッスンで見られる照明や影、カメラの錐台を表示するヘルパーを追加してもいいかもしれません。
  236. ## デバッガの使い方を学ぶ
  237. どのブラウザにもデバッガがあり、プログラムを1行ごとに一時停止し全ての変数を検査する事ができます。
  238. デバッガの使い方を教えるのはあまりにも大きなトピックなので、ここではいくつかのリンクを紹介します。
  239. * [Chrome DevTools で JavaScript をデバッグする](https://developers.google.com/web/tools/chrome-devtools/javascript/)
  240. * [Debugging in Chrome](https://javascript.info/debugging-chrome)
  241. * [Tips and Tricks for Debugging in Chrome Developer Tools](https://hackernoon.com/tips-and-tricks-for-debugging-in-chrome-developer-tools-458ade27c7ab)
  242. ## デバッガなどで `NaN` がないかチェックする
  243. `NaN` は Not A Numberの略です。これは数学的に意味のない事をした場合、JavaScript が値として代入するものです。
  244. 簡単な例としては
  245. <div class="threejs_center"><img class="border" src="resources/images/nan-banana.png" style="width: 180px;"></div>
  246. 何か開発中に画面に何も表示されない事がよくあるので、私は `NaN` が表示されたらその場所からすぐにいくつかの値を確認します。
  247. 例として最初に[gLTFファイルの読込](threejs-load-gltf.html)の記事でパスを作り始めた時に2次元曲線を作るSplineCurveクラスを使って曲線を作ってみました。
  248. そのカーブを利用してこのように車を動かしました。
  249. ```js
  250. curve.getPointAt(zeroToOnePointOnCurve, car.position);
  251. ```
  252. 内部的には `curve.getPointAt` は第2引数に渡されたオブジェクトに対して `set` 関数を呼び出します。この場合、第2引数は `car.position` であり、これは `Vector3` です。`Vector3` の `set` 関数はx, y, zの3つの引数を必要としますが、`SplineCurve` は2次元曲線なので、xとyだけを指定して `car.position.set` を呼び出します。
  253. その結果、`car.position.set` はxにx、yにy、zに `undefined` をセットします。
  254. デバッガで `matrixWorld` を見てみると `NaN` 値が表示されています。
  255. <div class="threejs_center"><img class="border" src="resources/images/debugging-nan.gif" style="width: 476px;"></div>
  256. 行列を見ると `NaN` が含まれており、`position`、 `rotation`、 `scale` または他の関数に悪い影響を与えるデータがあるのが見えます。これらの悪いデータから逆算すると問題を追跡するのは簡単です。
  257. `NaN` の上には `Infinity` もありますが、これはどこかに数学のバグがあるような気がします。
  258. ## コードの中を見て!
  259. THREE.jsはオープンソースです。コードの中を見る事を恐れないで下さい!
  260. [github](https://github.com/mrdoob/three.js)で内部コードを見れます。
  261. また、デバッガの関数を踏み込んで内部を見る事もできます。その際には `three.min.js` でなく `three.js` を見るようにして下さい。`three.min.js` は最小化・圧縮されたバージョンなので、ダウンロードする際のサイズが小さくなっています。`three.js` はサイズは大きいですが、デバッグしやすいバージョンです。私はよく `three.js` に切り替えて、コードのステップスルーを行い、何が起こっているのかを確認しています。
  262. ## `requestAnimationFrame` はrender関数の一番下へ
  263. 以下のパターンはよく見かけます。
  264. ```js
  265. function render() {
  266. requestAnimationFrame(render);
  267. // -- do stuff --
  268. renderer.render(scene, camera);
  269. }
  270. requestAnimationFrame(render);
  271. ```
  272. 以下のように `requestAnimationFrame` を一番下に置く事をお勧めします。
  273. ```js
  274. function render() {
  275. // -- do stuff --
  276. renderer.render(scene, camera);
  277. requestAnimationFrame(render);
  278. }
  279. requestAnimationFrame(render);
  280. ```
  281. 最大の理由はエラーが発生した場合にコードが停止する事です。
  282. `requestAnimationFrame` を先頭に置くと、既に別のフレームを要求しているためにエラーが発生してもコードを実行し続けます。
  283. IMOを無視するよりも、それらのエラーを見つける方が良いでしょう。これらのエラーは何かが期待したように表示されない原因になりやすいのですが、コードが停止しない限り、気がつかないかもしれません。
  284. ## 単位をチェックして下さい!
  285. 角度やラジアンを使う時の例を知っておく必要があります。
  286. 残念ながらTHREE.jsではどこでも同じ単位を使用している訳ではありません。
  287. すぐに思いつくのだとカメラの視野は度単位です。それ以外の角度は全てラジアン単位です。
  288. もう1つ注目したいのは、世界単位のサイズです。最近の3Dアプリでは好きな単位を選べるようになっています。あるアプリでは1単位=1cmを選択する事があります。もう1つのアプリでは1台=1フィートを選ぶかもしれません。特定のアプリケーションでは必要なユニットを選択する事ができます。three.jsでは1単位=1メートルを想定しています。
  289. これは測定器を使用して照明効果を計算する物理ベースのレンダリングなどで重要です。
  290. スマホがどこにあるか、VRコントローラーがどこにあるかなど、現実世界の単位を扱う必要があるARやVRにとっても重要です。
  291. ## スタックオーバーフローのための *最小で完全で検証可能なサンプルコード* の作成
  292. THREE.jsの質問をする場合、MCVE(Minimal<最小>、Complete<完全>、Verifiable<検証可能>、Example<サンプル>の略)のコードを提供する事が求められます。
  293. **最小**の部分が重要です。[gLTF読込の記事](threejs-load-gltf.html)の最後のサンプルコードでパスの動きに問題があったとしましょう。そのサンプルには多くのパーツがあり、リストアップすると
  294. 1. HTMLの集まり
  295. 2. いくつかのCSS
  296. 3. ライティング
  297. 4. 影
  298. 5. 影を操作するためのDAT.guiコード
  299. 6. GLTFファイルの読込コード
  300. 7. キャンバスのリサイズコード
  301. 8. パスに沿って車を移動させるコード
  302. このコードはかなり大きいです。もし質問がパスの後に続く部分だけであれば、THREE.jsの `<canvas>` と `<script>` タグだけで済むので、ほとんどのHTMLを削除する事ができます。また、CSSとリサイズのコードを削除する事ができます。GLTFのコードもパスだけを気にしているので削除できます。`MeshBasicMaterial` を使用するとライトとシャドウも削除する事ができます。DAT.guiのコードも確実に削除できます。
  303. このコードはテクスチャ付きの地面を作ります。`GridHelper` を使った方が簡単です。
  304. 最終的にもし質問したい事がパス上での移動についてなら、ロードされた車モデルの代わりにパス上にキューブを使用する事ができます。
  305. 以上の事を考慮したミニマムなサンプルコードを紹介します。271行から135行に縮小しました。パスを単純化する事でさらに縮小する事も考られます。3,4点のパスは、21点のパスと同じように動作するかもしれません。
  306. {{{example url="../threejs-debugging-mcve.html" }}}
  307. `OrbitController` を残してるのはカメラを動かして何が起こっているのかを把握するのに便利だからですが、問題によってはこれも削除できるかもしれません。
  308. MCVEを作る上で一番良い点は、自分自身で解決する事が多いという事です。不要なものを取り除いて可能な限り小さなサンプルコードを作って問題を再現する事で、バグにたどり着く事が多いからです。
  309. その上でStack Overflowで自分のコードを見てもらうのは、回答者の時間を尊重する事になります。最小限のサンプルを作る事で、誰かがあなたを助ける事がはるかに簡単になります。また、その過程で以下を学ぶ事ができます。
  310. Stack Overflowに質問を投稿する際、**コードを[スニペット](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/)**にする事が重要です。
  311. もちろん、MCVEを試すためにJSFiddleやCodepen、または同様のサイトを使用する事は歓迎しますが、実際にStack Overflowに質問を投稿するようになったら、**質問自体に**問題を再現するためのコードを記述する必要があります。
  312. スニペットを作る事でその条件を満たしています。
  313. また、このサイト上の全てのライブサンプルはスニペットとして実行されるべきである事に注意して下さい。HTML、CSS、JavaScriptの部分を[スニペットエディタ](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/)のそれぞれの部分にコピーするだけです。ただし、自分の問題に関係のない部分を削除し、必要最低限のコードにするのを忘れないようにして下さい。
  314. これらに従えば、あなたの問題は助けを得る可能性がはるかに高くなります。
  315. ## `MeshBasicMaterial` を使用する
  316. `MeshBasicMaterial` はライトを使用しないので、何かが表示されない理由を取り除く1つの方法です。もしオブジェクトが `MeshBasicMaterial` を使用して表示されない場合は、コードの他の部分ではなくマテリアルやライトに問題がある可能性が高い事がわかります。
  317. ## カメラの `near` と `far` の設定を確認する
  318. `PerspectiveCamera` には `near` と `far` の設定があり、それは[カメラの記事](threejs-cameras.html) で説明しています。
  319. オブジェクトを含む空間に合わせて設定されている事を確認して下さい。
  320. 例えば `near` = 0.001、`far` = 1000000のような大きな値に**一時的に**設定する事もできます。
  321. 奥行き解像度の問題が発生する可能性がありますが、少なくともカメラの前にあるオブジェクトを見る事ができるようになります。
  322. ## カメラの前にシーンがある事を確認する
  323. 時にはカメラの前になく何も出てこない事もあります。カメラを制御できない場合は `OrbitController` のようなカメラコントロールを追加してみて下さい。
  324. あるいは[この記事](threejs-load-obj.html)で紹介されているコードを使ってシーンをフレーミングしてみて下さい。
  325. このコードはシーンの一部のサイズを見つけ、カメラを移動して `near` と `far` の設定を調整し、それが見えるようにします。
  326. ## カメラの前に何かを置く
  327. これは全てに失敗した場合は、動作するものから始めてゆっくりと何かを追加していくという方法です。何もない画面が表示された場合は、直接カメラの前に何かを置いてみて下さい。
  328. 球体や箱を作り `MeshBasicMaterial`のようなシンプルなマテリアルを与えて、それを画面上に表示できるようにします。
  329. その後、少しずつ追加してテストを開始します。最終的にはバグを再現するか、途中で発見するかのどちらかになります。
  330. ---
  331. 以上、JavaScriptのデバッグのヒントでした。[GLSLをデバッグするためのいくつかのヒント](threejs-debugging-glsl.html)も見てみましょう。