tag:crieit.net,2005:https://crieit.net/tags/three.js/feed 「three.js」の記事 - Crieit Crieitでタグ「three.js」に投稿された最近の記事 2020-06-23T09:43:27+09:00 https://crieit.net/tags/three.js/feed tag:crieit.net,2005:PublicArticle/15975 2020-06-23T09:37:52+09:00 2020-06-23T09:43:27+09:00 https://crieit.net/posts/game1week 【1週間】Web ゲームを爆速で開発するために実践したこと🐧個人開発向け <p>(内心)<br /> 作ったゲームを沢山の人に触ってもらってリアクションが欲しいなぁ~<br /> そうだ、記事投稿して宣伝しよう!</p> <p>といった<strong>下心で書いた記事です</strong>。すみませんでした 遊んでください😆</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Web Game 処女作🎉シンプルでストレス発散になるゲームが爆誕!敵さんを吹っ飛ばして 1,000 G 以上を目指してみてね😆<a target="_blank" rel="nofollow noopener" href="https://t.co/h8w574aGGQ">https://t.co/h8w574aGGQ</a>★アドバイス急募★『こうしたらもっと面白くなる』など、ご意見ご感想を頂けると嬉しいっす(〃´∪`〃)ゞ<a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/%E4%BB%8A%E6%97%A5%E3%81%AE%E7%A9%8D%E3%81%BF%E4%B8%8A%E3%81%92?src=hash&ref_src=twsrc%5Etfw">#今日の積み上げ</a> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/100DaysOfCode?src=hash&ref_src=twsrc%5Etfw">#100DaysOfCode</a> DAY 21 <a target="_blank" rel="nofollow noopener" href="https://t.co/HJvFj87zTf">pic.twitter.com/HJvFj87zTf</a></p>— hikaru🐧#100DaysOfCode! (@hikaru_firecamp) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hikaru_firecamp/status/1273289660186292224?ref_src=twsrc%5Etfw">June 17, 2020</a></blockquote> <blockquote> <p>ペンギンがサメさんを倒すゲーム<br /> 敵さんを吹っ飛ばして 1,000 G 以上を目指してみてね😆<br /> <a target="_blank" rel="nofollow noopener" href="https://games.westa.io/">https://games.westa.io/</a></p> </blockquote> <h2 id="0⃣ 結論 / 爆速で開発するために実践したこと"><a href="#0%E2%83%A3+%E7%B5%90%E8%AB%96+%2F+%E7%88%86%E9%80%9F%E3%81%A7%E9%96%8B%E7%99%BA%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%E5%AE%9F%E8%B7%B5%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8">0⃣ 結論 / 爆速で開発するために実践したこと</a></h2> <ul> <li>1週間のカウントダウンタイマーを設置する</li> <li>適当な企画書を作って全体を把握しならが作業する</li> <li>新しいことは1個までに制限して知っているものを使う</li> <li>こだわりたい部分を絞り他を捨てる覚悟を持つ</li> </ul> <h2 id="1️⃣ この記事の対象読者"><a href="#1%EF%B8%8F%E2%83%A3+%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E5%AF%BE%E8%B1%A1%E8%AA%AD%E8%80%85">1️⃣ この記事の対象読者</a></h2> <p>この記事では<strong>1週間で Web ゲームを作る為に何を考え何をしたのか</strong>を書きました。<br /> 以下に当てはまる人達に参考になれば嬉しいです。</p> <ul> <li>好きなことへのこだわりが強く<strong>いつまでも作品が完成しない</strong>人</li> <li>いつも<strong>ダラダラ期限を伸ばしてしまう</strong>人</li> <li>このゲームがどんなライブラリ使っているか知りたい人</li> <li>奇特な人</li> </ul> <h2 id="2️⃣ なぜ1週間なの?"><a href="#2%EF%B8%8F%E2%83%A3+%E3%81%AA%E3%81%9C%EF%BC%91%E9%80%B1%E9%96%93%E3%81%AA%E3%81%AE%EF%BC%9F">2️⃣ なぜ1週間なの?</a></h2> <p>個人開発の一番の敵って<strong>いかにモチベーションを保つか</strong>だと思いませんか?</p> <p>私の場合1ヶ月もたてば他に興味あることが出てきてしまうのでモチベーションが移ってしまいます。<br /> でも、<strong>1週間なら全力で頑張れるちょうどよい長さ</strong>かな~と考えました。</p> <p>(それと、1週間ならクオリティ低くても言い訳になるかな と)</p> <h2 id="3⃣ 1週間という短い期間で終わらせるために意識したこと"><a href="#3%E2%83%A3+%EF%BC%91%E9%80%B1%E9%96%93%E3%81%A8%E3%81%84%E3%81%86%E7%9F%AD%E3%81%84%E6%9C%9F%E9%96%93%E3%81%A7%E7%B5%82%E3%82%8F%E3%82%89%E3%81%9B%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AB%E6%84%8F%E8%AD%98%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8">3⃣ 1週間という短い期間で終わらせるために意識したこと</a></h2> <h3 id="(1) 1週間のカウントダウンタイマーを設置する"><a href="#%281%29+%EF%BC%91%E9%80%B1%E9%96%93%E3%81%AE%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%83%80%E3%82%A6%E3%83%B3%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BC%E3%82%92%E8%A8%AD%E7%BD%AE%E3%81%99%E3%82%8B">(1) 1週間のカウントダウンタイマーを設置する</a></h3> <p>いつもズルズル伸ばしてしまう癖があるので <strong>Twitter で公開日を宣言</strong>しました。<br /> そして公開日までの<strong>カウントダウンタイマーを常に目に入る位置に設置</strong>しておきました。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">次に出す Web アプリのリリース日を 6/17 12:00 頃に決めました🎉メリハリ付けるために中途半端でもこのタイミングで出します写真はちょうどいいカウントダウンタイマーが無かったので作ってみました▪コードはこちら<a target="_blank" rel="nofollow noopener" href="https://t.co/HiGjiROP4R">https://t.co/HiGjiROP4R</a>(8,9行目を編集すれば自分用に使えるよ!) <a target="_blank" rel="nofollow noopener" href="https://t.co/HnjXxktDLb">pic.twitter.com/HnjXxktDLb</a></p>— hikaru🐧#100DaysOfCode! (@hikaru_firecamp) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hikaru_firecamp/status/1269807592533979138?ref_src=twsrc%5Etfw">June 8, 2020</a></blockquote> <p>このカウントダウンタイマー、<strong>すっごく効果があった</strong>ように思います。度々目に入るので<strong>程よい緊張感と無機質な圧力</strong>をくれます。<br /> さらにシレっと<strong>周囲の人たちに公開日を宣言</strong>できるのでかなりお勧めです。</p> <blockquote> <p>⚠ 補足 ⚠<br /> さも予定を守ったように言ってますが結局半日ほど遅く公開してしまいました<br /> ほ ん と す み ま せ ん で し た 😗</p> </blockquote> <h3 id="(2) 適当な企画書を作って全体を把握しならが作業する"><a href="#%282%29+%E9%81%A9%E5%BD%93%E3%81%AA%E4%BC%81%E7%94%BB%E6%9B%B8%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E5%85%A8%E4%BD%93%E3%82%92%E6%8A%8A%E6%8F%A1%E3%81%97%E3%81%AA%E3%82%89%E3%81%8C%E4%BD%9C%E6%A5%AD%E3%81%99%E3%82%8B">(2) 適当な企画書を作って全体を把握しならが作業する</a></h3> <p>企画書などが無い状態でコーディングすると目に付いた場所から手を付けてしまいがちで<strong>視野が局所的になりやすく作業の優先度決めが難しく</strong>なります。</p> <p>簡単なものでも<strong>全体像を把握できるもの</strong>があると、次にどの部分を実装するか考えるときに『最低でもここを実装しないとだから優先度高めで』みたいな<strong>スケジュール管理が容易</strong>になります。</p> <p>今回は以下の様な簡易仕様書をあらかじめ書いておりました。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">これから作るミニゲームの簡易仕様書!よくあるクリックゲームですが、ちゃんと作りきれるか不安😇1週間がんばります〜(あ、奇しくも一人 web1week みたいになってる)全然関係ないけど最近 iPad でお絵描きの練習始めました!スマブラやったことあればピンとくるはず😎 <a target="_blank" rel="nofollow noopener" href="https://t.co/oVo5xAUeBe">pic.twitter.com/oVo5xAUeBe</a></p>— hikaru🐧#100DaysOfCode! (@hikaru_firecamp) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hikaru_firecamp/status/1270280818116583425?ref_src=twsrc%5Etfw">June 9, 2020</a></blockquote> <p>これのおかげで実装段階での仕様変更が減り、<strong>一直線にゴールに向かって実装できるので結構大切</strong>なものだと思います。</p> <h3 id="(3) 新しいことは1個までに制限して知っているものを使う"><a href="#%283%29+%E6%96%B0%E3%81%97%E3%81%84%E3%81%93%E3%81%A8%E3%81%AF%EF%BC%91%E5%80%8B%E3%81%BE%E3%81%A7%E3%81%AB%E5%88%B6%E9%99%90%E3%81%97%E3%81%A6%E7%9F%A5%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%82%82%E3%81%AE%E3%82%92%E4%BD%BF%E3%81%86">(3) 新しいことは1個までに制限して知っているものを使う</a></h3> <p>初めて使う道具が多いと勉強することが増えたり些細なことにはまったりして時間を消費しがちなので、<strong>使い慣れた開発環境</strong>と<strong>使った事のあるライブラリ</strong>を中心に選定しました。</p> <h4 id="使い慣れたもの"><a href="#%E4%BD%BF%E3%81%84%E6%85%A3%E3%82%8C%E3%81%9F%E3%82%82%E3%81%AE">使い慣れたもの</a></h4> <ul> <li><strong>VS-Code</strong> ... 開発環境</li> <li><strong>TypeScript</strong> ... 開発言語</li> <li><strong>UIKit</strong> ... UI コンポーネント</li> </ul> <h4 id="以前から実験レベルで遊んでいたもの"><a href="#%E4%BB%A5%E5%89%8D%E3%81%8B%E3%82%89%E5%AE%9F%E9%A8%93%E3%83%AC%E3%83%99%E3%83%AB%E3%81%A7%E9%81%8A%E3%82%93%E3%81%A7%E3%81%84%E3%81%9F%E3%82%82%E3%81%AE">以前から実験レベルで遊んでいたもの</a></h4> <ul> <li><strong>Three.js</strong> ... 3D 描画エンジン</li> <li><strong>Cannon.js</strong> ... 3D 物理エンジン</li> </ul> <h4 id="初めて使うもの"><a href="#%E5%88%9D%E3%82%81%E3%81%A6%E4%BD%BF%E3%81%86%E3%82%82%E3%81%AE">初めて使うもの</a></h4> <ul> <li><strong>Audio API</strong> ... BGM や効果音の再生</li> </ul> <p>今回は遊べる Web ゲームを<strong>短期間で作ることが目的</strong>だったので冒険をしない制約を設けましたが、完成度が低くても<strong>未知なる技術を沢山学びたいならどんどん新しい技術を使ってみたらよい</strong>と思います。</p> <p><strong>目的次第でやり方を臨機応変にする</strong>ことが大事なのかなと😎</p> <h3 id="(4) こだわりたい部分を絞り他を捨てる覚悟を持つ"><a href="#%284%29+%E3%81%93%E3%81%A0%E3%82%8F%E3%82%8A%E3%81%9F%E3%81%84%E9%83%A8%E5%88%86%E3%82%92%E7%B5%9E%E3%82%8A%E4%BB%96%E3%82%92%E6%8D%A8%E3%81%A6%E3%82%8B%E8%A6%9A%E6%82%9F%E3%82%92%E6%8C%81%E3%81%A4">(4) こだわりたい部分を絞り他を捨てる覚悟を持つ</a></h3> <p>こだわりが強いと味のあるイイものができるけど、その代わり<strong>完成が遅くなる</strong>傾向にあると思います。</p> <p>今回は『ローポリキャラ達が<strong>物理演算で予想外の挙動</strong>をする』部分だけこだわりましたが、操作 UI の見た目などはブラウザ標準のプログレスバーを使っていたりと<strong>大部分はかなり適当</strong>です。</p> <p>個人的にはゲームの中に<strong>ブラウザの DOM を混ぜることに違和感</strong>とアレルギーを感じるのですが『<strong>こだわる部分を絞って他は適当</strong>』と大胆に割り切っちゃいました。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67192/ef433b3e-8d54-62b9-94b9-83f98f528ade.png" alt="image.png" /></p> <p>他にも、ペンギン/サメ/コイン/木 の当たり判定はすべて立方体(正六面体)で手抜きをしましたが、これは予想に反して『<strong>サメさんやコインが地面に刺さってる</strong>』ように見えたり『木々があらぶってる』感じになったり<strong>ヘンテコな世界観の演出</strong>に一役買ったように思います。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67192/a1a1764b-8e99-17a7-de03-a410ae195d96.png" alt="2020-06-18_00h32_18.png" /></p> <h2 id="4⃣ おまけ: 実装 Tips"><a href="#4%E2%83%A3+%E3%81%8A%E3%81%BE%E3%81%91%3A+%E5%AE%9F%E8%A3%85+Tips">4⃣ おまけ: 実装 Tips</a></h2> <p>唐突なおまけの実装 Tips です。</p> <h3 id="(1) Cannon.js / サメさんの Z 位置(奥行)を固定"><a href="#%281%29+Cannon.js+%2F+%E3%82%B5%E3%83%A1%E3%81%95%E3%82%93%E3%81%AE+Z+%E4%BD%8D%E7%BD%AE%28%E5%A5%A5%E8%A1%8C%29%E3%82%92%E5%9B%BA%E5%AE%9A">(1) Cannon.js / サメさんの Z 位置(奥行)を固定</a></h3> <p>Cannon.js は 3 次元物理演算なので XYZ 軸分の動きがあるのですが、サメさん(敵)に関しては奥行方向に動かれると攻撃を当てられなくなるので Z 軸を固定しています。</p> <pre><code class="ts">// CannonJs: サメさんの物理演算用の剛体を生成 const sharkBody = new CANNON.Body({ <省略> }); // ThreeJs: アニメーションループを開始 renderer.setAnimationLoop(() => { // CannonJs: 1フレーム分の物理演算を実行 world.step(<省略>); // ★★ サメさんのZ位置を固定する ★★ sharkBody.position.set( sharkBody.position.x, sharkBody.position.y, 0); // Z軸を0に変更 }); </code></pre> <h3 id="(2) Cannon.js / 摩擦設定・反発設定"><a href="#%282%29+Cannon.js+%2F+%E6%91%A9%E6%93%A6%E8%A8%AD%E5%AE%9A%E3%83%BB%E5%8F%8D%E7%99%BA%E8%A8%AD%E5%AE%9A">(2) Cannon.js / 摩擦設定・反発設定</a></h3> <p>初期値だとほとんど滑ることは無く、滑らせた方が面白そうだったので摩擦と反発の設定をいじっています。</p> <p>モデルごとに摩擦係数や反発係数を設定できれば直感的だったのですが、Cannon.js では『モデル1とモデル2に対しての摩擦・反発を設定』といった具合に設定が必要でした。</p> <pre><code class="ts">// // 物理演算ワールドを初期化 // const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); // m/s² const floorBodyMaterial = new CANNON.Material(`FloorModel`); const penguinBodyMaterial = new CANNON.Material(`PenguinModel`); const sharkBodyMaterial = new CANNON.Material(`SharkModel`); // 摩擦反発設定: 床とペンギン world.addContactMaterial(new CANNON.ContactMaterial( floorBodyMaterial, penguinBodyMaterial, { friction: 0.01, // 摩擦設定 (ペンギンが床を滑るように) restitution: 0.8, // 反発設定 } )); // 摩擦反発設定: 床とサメ world.addContactMaterial(new CANNON.ContactMaterial( floorBodyMaterial, sharkBodyMaterial, { friction: 0.05, // 摩擦設定 restitution: 0.3, // 反発設定 contactEquationStiffness: 1e8, contactEquationRelaxation: 3, } )); // 摩擦反発設定: ペンギンとサメ world.addContactMaterial(new CANNON.ContactMaterial( penguinBodyMaterial, sharkBodyMaterial, { friction: 0.01, // 摩擦設定 restitution: 2.0, // 反発設定 (攻撃を受けたサメが吹っ飛びやすいように) } )); </code></pre> <h3 id="(3) Three.js / 同じモデルは使いまわして効率化"><a href="#%283%29+Three.js+%2F+%E5%90%8C%E3%81%98%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AF%E4%BD%BF%E3%81%84%E3%81%BE%E3%82%8F%E3%81%97%E3%81%A6%E5%8A%B9%E7%8E%87%E5%8C%96">(3) Three.js / 同じモデルは使いまわして効率化</a></h3> <p>今回ゲーム内では<strong>同じモデルが大量に出現</strong>します。その際に<strong>毎回モデルをロードしていると実行効率が悪い</strong>ので、モデル管理クラスが必要になります。</p> <p>こういった目的の管理クラス系にはシングルトン実装が最適かと思いますが、TypeScript では <strong><code>module</code> を使うと簡単にシングルトン実装が可能</strong>です。</p> <pre><code class="ts">/** モデル名の型定義 */ export type ModelName = `PENGUIN` | `SHARK` | `TREE` | `COIN`; /** * ゲーム内のモデルを管理するモジュール *  シングルトン実装 *  モデルをあらかじめロードして、ロード済みのモデルを使いまわすことで効率化 */ export module ModelManager { /** ロード済みのモデルを保持 */ const modelMap = new Map<ModelName, THREE.Object3D>(); /** 各種モデルをロードする */ export async function load() { await loadModel(`PENGUIN`, `models/PenguinJumping.glb`); await loadModel(`SHARK`, `models/Shark.glb`); await loadModel(`TREE`, `models/Tree.glb`); await loadModel(`COIN`, `models/Coin.glb`); } /** モデルを複製して取得する */ export function getModel(modelName: ModelName) { return modelMap.get(modelName)!.clone(); } /** ロード済みモデルを開放する */ export function dispose() { // ThreeJs: ロードしたモデルをすべて解放 modelMap.forEach((obj3D, key) => { GameUtils.disposeObject3D(obj3D); }); } async function loadModel(modelName: ModelName, path: string) { // ThreeJs: モデル読み込み const obj3D = await GameUtils.loadGltfModel(path); // リストに追加 modelMap.set(modelName, obj3D); } } </code></pre> <p>使い方</p> <pre><code class="ts">// あらかじめすべてのモデルをロードする await ModelManager.load(); // ペンギンモデルを取得する (内部的にはロード済みのモデルを複製しているので効率的) const penguin1 = ModelManager.getModel(`PENGUIN`); const penguin2 = ModelManager.getModel(`PENGUIN`); const penguin3 = ModelManager.getModel(`PENGUIN`); </code></pre> <h2 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h2> <p>ゲーム開発中に Twitter でいいねやコメントなどでリアクションをくれた方、開発中のゲームを試してヒントをくれた友人方、本当にありがとうございました。<br /> 大変励みになりモチベーションになりました、重ねてお礼申し上げます😆</p> <p>あと、ここまで読んでまだプレイしていない人!!<br /> ↓ やってからリアクションをクレクレ厨😗</p> <blockquote> <p>ペンギンがサメさんを倒すゲーム<br /> 敵さんを吹っ飛ばして 1,000 G 以上を目指してみてね😆<br /> <a target="_blank" rel="nofollow noopener" href="https://games.westa.io/">https://games.westa.io/</a></p> </blockquote> <hr /> <p>P.S. crieit.net で初カノニカル投稿してみました🎉これ書く側としてはイイ仕組みですね</p> hikaru🐧 tag:crieit.net,2005:PublicArticle/15258 2019-07-18T22:03:57+09:00 2019-07-18T22:03:57+09:00 https://crieit.net/posts/A-Frame-5d306e3d1b914 A-Frameでコントローラの角度を正確に取得する <p>A-Frameでコントローラの向きに合わせて処理をしたい時があると思う。コントローラの動きに追従するメニューとかであれば子要素として表示すれば良いだけなので問題ないが、例えば銃を撃って弾を発射する場合などは完全に別オブジェクトとして生成する必要があるためコントローラの角度を取得してそれを利用して弾の角度を初期化したりする必要がある。</p> <p>A-Frameだとrotationという属性があるためそれでオブジェクトの角度を利用することもできるが、それだとうまくいかない。</p> <p><a href="https://crieit.net/posts/Oculus-Quest">Oculus Questのコントローラの角度が謎</a> でも書いている通り、rotationはうまく値が取れない。</p> <p>じゃあどうするかというと、A-Frameは内部的にはthree.jsが使われているため、そのあたりの機能を利用していく。</p> <p>A-Frameコンポーネント内で参照できる要素からは、下記のようにthree.jsのObject3Dが参照できるようになっている。</p> <pre><code class="javascript">this.el.object3D </code></pre> <p>これを利用すればA-Frame単体では難しいような3Dの操作もできるようになる。詳しい傾きについてはQuaternionを利用することができる。たとえば下記のような感じ。</p> <pre><code class="javascript"> const q = new Quaternion() const gunQuaternion = new Quaternion() this.el.object3D.getWorldQuaternion(gunQuaternion) q.multiply(gunQuaternion) bullet.object3D.applyQuaternion(q) </code></pre> だら@Crieit開発者