FINAL STAGE制作記録
GW期間中から21日間でFINAL STAGEというゲームを作りました。
ゲームを公開したツイートはこちらです。
GWあたりからUnity, Houdini, Substance Designerの学習と実践がてら作っていたゲームを公開しました!
— かねた (@kanetaaaaa) 2021年5月31日
そこそこやりがいがある感じになったのでオンラインランキングのハイスコアを狙ってみてください!!https://t.co/jcmByV0Ul2 pic.twitter.com/tUCW1I8Gcd
ゲーム制作をしたという報告の記事はこちら nanka.hateblo.jp
制作期間は作業ログと動画を毎日記録していました。あまり他人に見せれるような実装やコードではないんですが、備忘録という事でそこから抜粋しつつ21日間どのように制作を進めていたのかを記事として残します。
解説記事ではなく、あくまで制作記録です。
1日目
作るゲームの決定
今回ゲーム制作をするきっかけになった、社内ゲームジャムのテーマが「打ち上げる」でした。
お題が何にしても、地球防衛軍ライクな3Dのシューティングゲームにしようというのは決めていたので、テーマにこじつけるために「ラスボスを唯一倒せるミサイルを打ち上げるまで耐えるゲーム」を作ることにしました。
TPSカメラの作成
TPSカメラはCinemachineの機能に完全に乗っかることでノーコードで作成できました!
良い解説記事があったのでこちらをご参照ください。 nekojara.city
プレイヤーを作成
プレイヤーの挙動はSphereのRigidBodyを利用しました。
キーボードの入力情報を元に、Rigidbody.AddForceを用いて弾を転がして、見た目だけ別に差し替えるというシンプルな方法でホバークラフトっぽい操作感にしました。
var horizontal = Input.GetAxisRaw("Horizontal"); var vertical = Input.GetAxisRaw("Vertical"); var direction = new Vector3(horizontal, 0, vertical).normalized; var targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + camera.eulerAngles.y; if (direction.magnitude >= 0.1f) { var moveDir = Quaternion.Euler(0, targetAngle, 0) * Vector3.forward; rb.AddForce(moveDir * 30.0f, ForceMode.Acceleration); } if (rb.velocity.magnitude > 20.0f) { rb.velocity = rb.velocity.normalized * 20.0f; }
二個目のifブロックで最大速度の制限を行っていますが、この方法だと落下速度まで制限されてしまうので、本当は落下と入力による移動速度は別々で管理しておいた方が良さそうです。
個人的に落下にも速度制限が付いた方がホバークラフトっぽかったのでそのままにしました。(時間が無かったともいう...)
Unity - Scripting API: Rigidbody.AddForce
また、SmoothDampAngleを利用して、入力に応じて自機の見た目に傾きを与えています。
var angle = Mathf.SmoothDampAngle(car.rotation.eulerAngles.z, -horizontal * 10.5f, ref turnSmoothVelocity, turnSmoothTime); var angle2 = Mathf.SmoothDampAngle(car.rotation.eulerAngles.x, vertical * 10.5f, ref turnSmoothVelocity2, turnSmoothTime); car.rotation = Quaternion.Euler(angle2, camera.eulerAngles.y, angle);
Unity - Scripting API: Mathf.SmoothDampAngle
自機から射撃する弾関連の素材作成
弾のモデリング
弾はHoudiniを用いてモデリングしました。
弾の大きさや長さ、分割数などを変更できるデジタルアセットにしておいたので、後ほど弾丸の種類を増やしたりするのにとても便利でした。
画像素材作成
着弾時のパーティクル用画像と、レーザーガン用のマズルフラッシュ画像をSubstance Designerで作成しました
これくらいPhotoshopやGimpで作れという話ですが、SDを利用した方が個人的に速く作れそうなので...
弾の挙動作成
弾自体は発射時の銃の姿勢から一直線に飛んでいくだけとして、着弾時に特殊な処理をしているので紹介します。
カメラ揺れ
最終的には発射時に変更されましたが、1日目では着弾時にカメラ揺れを発生させていました。
こちらもTPSカメラと同様Cinemachineの機能を利用することでかなり簡単に実現できました。
良い解説記事があったのでこちらをご参照ください light11.hatenadiary.com
爆発
着弾時に周囲のRigidbodyを吹き飛ばすような爆発演出を行っています。
これを実現するために、Physics.OverlapSphereとRigidbody.AddExplosionForceという二つの関数を利用しました。
void Explosion(Vector3 position) { Vector3 explosionPos = position; Collider[] colliders = Physics.OverlapSphere(explosionPos, 6.0f); foreach (Collider hit in colliders) { Rigidbody rb = hit.GetComponent<Rigidbody>(); if (rb != null) rb.AddExplosionForce(30000.0f, explosionPos, 6.0f, 0.1f); } }
Unity - Scripting API: Physics.OverlapSphere Unity - Scripting API: Rigidbody.AddExplosionForce
1日目の動画
2日目
マシンガンを作成
1日目にレーザーガンを作成したので、最もベースとなる武器のマシンガンを作成しました。
銃や弾の挙動自体はレーザーガンとほぼ一緒だったので、ほとんど変更するところはありませんでしたが、マズルフラッシュのみ新しく作成しました。
マズルフラッシュは1日目に作った弾丸のデジタルアセットを利用して、円状に配置することで作成できました。
こちらも、火花の数や角度などをパラメーターで設定できるようにしたので、バリエーションの量産が容易に行えました。
ゲームでは3パターンのマズルフラッシュをパーティクルシステムでランダムに表示しています。
敵を作成
二種類の武器ができて試し打ちがしたくなったので敵を追加しました。
敵のモデルはUnityのプリミティブで用意されているSphereとCubeを組み合わせたシンプルなものです。最終的にHoudiniでかっこいい敵を作りたいなと思っていたんですが、意外とこのシンプルな形状が気に入ったので最後までこの姿になりました。
敵の挙動もRigidbodyで制御しています。
見づらいコードで恐縮ですが、挙動自体はとてもシンプルで4つの処理を行っています。
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookRotation((target.position + Vector3.up * 4.0f) - transform.position), Time.deltaTime * rotateSpeed); var force = transform.forward * speed; var dot = Vector3.Dot(force.normalized, rb.velocity.normalized); dot = dot * 0.5f + 0.5f; force += -rb.velocity * (1.0f - dot) * brake; rb.AddForce(9.81f * rb.mass * Vector3.up + force);
- ターゲットの方向を徐々に向くようにする
- 敵は正面方向へ移動し続ける
- 重力を打ち消す力を与え続ける
- 移動しようとしている方向と現在移動している方向が逆の場合はブレーキを掛ける
2日目の動画
3日目
敵をブラッシュアップ
マテリアル設定
SphereとCubeにそれぞれ別のマテリアルを設定して、敵っぽい見た目にすることにした。
ノーマルマップはUnityのデフォルトプロジェクトのもともと入っていたものを利用。
死亡時に爆発エフェクトを追加
こちらのUnity Assetに含まれる爆発エフェクトを利用して、敵が死亡した時に爆発するようにした。
ガトリングガン追加
マシンガンの仕組みを流用して、発射位置の追加と発射レートを変更してガトリングガンを追加
ホーミングガン追加
敵のロックオン
毎フレーム敵をプレイヤーからの距離順でソートして以下3体の敵を自動追尾するようにした
- 一番近い敵
- 近い順で25%の場所にいる敵
- 近い順で50%の場所にいる敵
敵をソートする処理
var enemies = GameObject.FindObjectsOfType<AttackTransformEnemy>(); var cameraPos = CameraManager.Instance.MainCamera.transform.position; sortedEnemies = enemies.Where( v => { return v.IsLive; }).OrderBy(v => { var direction = v.transform.position - cameraPos; return direction.sqrMagnitude; }).ToArray();
敵を選択する処理
public AttackTransformEnemy GetEnemyOrderDistance(float normalizedDistance) { var length = sortedEnemies.Length; if (length == 0) return null; var index = (int)Mathf.Lerp(0, length - 1, Mathf.Clamp01(normalizedDistance)); return sortedEnemies[index]; }
追尾処理
ホーミングレーザーは派手に飛ばしたかったので、発射箇所を6つ設定しました。
他の銃は、カメラが向いてる方向に弾を飛ばすのに対して、ホーミングレーザーは斜め上固定で発射します。
ホーミング処理は、敵の挙動と同じように徐々にターゲットの方向へ向くように回転して、常に直進するようにしています。
動いている敵にあたるように回転速度を調整すると、発射したレーザーがすぐに敵に向かっていってしまい、綺麗に発射しているのように見えなかったので、発射後徐々に回転速度を上げるようにしました。
この方法だと至近距離で弾を発射した時に当たらなくなるので、ターゲットに近づくほど回転速度を上げる調整もしています。
var targetTransform = target.transform;
var rotateSpeedTime = Mathf.Clamp01(1.0f - rotateSpeedTransitionTime / rotateSpeedTransitionDuration);
var rotateSpeed = Mathf.Lerp(startedRotateSpeed, endedRotateSpeed, rotateSpeedTime);
rotateSpeedTransitionTime -= Time.deltaTime + Time.deltaTime * Mathf.Exp(-Vector3.Distance(transform.position, targetTransform.position));
movingRotation = Quaternion.Lerp(movingRotation, Quaternion.LookRotation(targetTransform.position - transform.position), Time.deltaTime * rotateSpeed);
弾の軌跡はTrail Rendererで実装。
パワーアップアイテム追加
こちらのサイトのアイコンをお借りして、パワーアップアイテムの実装を行った
同じタイミングで、弾薬システムも追加したので、このパワーアップアイテムを獲得することで、ホーミングレーザーなどの特殊武器を利用できるようにした。
3日目の動画
4日目
マザーシップのモデリング
ラスボスとなるマザーシップをHoudiniでモデリングした
外側の独特な模様はボロノイで分割後、mirrorノードを利用して模様を対称的にしている。
ベースは球なのであまり時間を掛けずにモデリングできた。
残り弾薬のUI作成
Line Renderer等を用いて、残り弾薬のUIを作成。
UnityのAnimationを用いて、表示・非表示のアニメーションも作成。
弾薬の有無に応じて、アニメーションさせてかっこよくしたかった。
4日目の動画
5日目
武器とプレイヤーのモデリング
武器とプレイヤーもHoudiniでモデリングすることにした。
手動で書いたカーブを入力すると、いい感じに武器っぽい形のメッシュを生成するジェネレーターを作成したので、全種類の武器とプレイヤーのモデルを一瞬で量産することができた。
仕組みはとてもシンプルで以下の手順で生成している
- 手動で書いたカーブに厚みを持たせてメッシュ化(ベースメッシュ)
- ベースメッシュの各頂点にベースメッシュを配置してフラクタル形状にする
- 各頂点に配置したベースメッシュ同士が重なっているところに凹みを追加
特殊武器シェーダーを作成
マシンガン以外の武器に利用するシェーダーをShaderGraphで作成しました。
武器とパワーアップアイテムの関連付けをしやすくするための色付けと武器獲得時、消滅時のディゾルブを実装しています。
自機がかっこよく出来上がったのでうれしくなってスクショを沢山撮影した
武器のアニメーション
UnityのAnimationを用いて、弾発射時の反動アニメーションを作成
反動アニメーションがあるだけでとてもいい感じに遊べるようになってテンションが上がっていた。
5日目の動画
6日目
マザーシップ破壊演出
制限時間を実装したので、マザーシップが破壊される演出を作成しました。
ミサイル
ミサイルのメッシュは以下のUnity Assetを利用させて頂きました。
ミサイルの挙動は到着時刻を指定する形で実装しました。
好みの軌跡を描けるように、ターゲットまでの距離が一定になるまでは直進、その後はホーミングするという実装を行いました。
var targetTransform = target; var distance = ToTargetXZVector.magnitude; if (distance < startFollowRange) { followRotateSpeed += followAddRotateSpeed * Time.deltaTime; movingRotation = Quaternion.Lerp(movingRotation, Quaternion.LookRotation(targetTransform.position - transform.position), Time.deltaTime * followRotateSpeed); } // 現在のターゲットまでの距離と到着までの残り時間からスピードを算出 var speed = ToTargetVector.magnitude / arrivalTime; var velocity = movingRotation * Vector3.forward * speed; transform.position += velocity * Time.deltaTime; transform.rotation = movingRotation; if (arrivalTime <= 0.0f) { isEnd = true; onEnd.Invoke(); } arrivalTime -= Time.deltaTime;
カメラの挙動
カメラは例によってCinemachineの機能を利用しています。
Timelineを使ってみようかなとも思ったんですが、あまり複雑なカメラワークにする予定は無かったので、Blend List Cameraを利用してカメラワークを切り替えました。
Blend List Cameraについてはよい解説記事があったのでこちらをご参照ください。 light11.hatenadiary.com
ミッションクリア演出
こちらも残り弾薬のUIと同じように、uGUIで実装し、挙動はUnityのAnimationで付けました。
6日目の動画
7日目
チュートリアルステージのモデリング
チュートリアルステージもHoudiniでモデリングしました。
遠景が街のように見えればよかったので、Grid上に様々な大きさのCubeを散らばせただけの手抜き制作です。
自分の能力が足りずに、マテリアルをいい感じに設定することができなかったので、下手にテクスチャをタイリングするぐらいなら何もしない方が良いだろうと思い、ノーマルマップだけ設定して、ライティングにビジュアルをお任せする形にしました。
次回ゲームを作るときには、ちゃんとマテリアルを設定できるようになりたい。
7日目の動画
8日目
チュートリアルからタイトル表示へのカメラワークを作成
CinemachineのDollyカメラを利用してタイトル表示のカメラワークを作成。
Dollyカメラについてはこちらの記事が詳しいのでご参照ください。
URPの1オブジェクト4ライト制限を回避するためにステージのメッシュを分割
チュートリアル開始時にトンネルの中からゲームが始まるんですが、この時点ではステージ全体がワンメッシュになっていたため、URPの1オブジェクト4ライトの制限を受けていて、トンネル内部のポイントライトが正しく反映されない状態になっていました。(最終的にステージはMixed Lightにしたのでもしかしたらこの対応は不要だったかもしれない)
自分の知識不足かもしれませんが、Houdiniで書き出したモデルをUnityで読み込んだときにGameObjectを分割されている状態にするためには、Houdini側でGroup化しておく必要がありそうでした。
いろいろ調べたところ、以下の手順で求めているGroup生成ができることが分かりました。
まずはDivideノードのBricker Polygonsを利用してメッシュを特定の範囲で分割します。
これではまだ頂点で分割されただけなので、以下の手順でそれぞれをGroupにします。
Attribute Wrangleを用いてプリミティブにGroupの情報を設定します。
今回は位置のZ座標をfloorでID化してGroup情報としました。
Partitionノードを用いて、アトリビュートからGroupを作成します。
以上の手順でトンネルを一定の長さで分割しGroup化することができました。
これをUnityで読み込むとあらかじめGameObjectが分割された状態で読み込まれました。
8日目の動画
9日目
シナリオのテキストを考える
地球防衛軍みたいな熱い感じのテキストを表示したかったので、過去にプレイしたシリーズを思い出しながら頑張って考えました!
テキストの再生システムを作成
マスターデータの用意
考えたシナリオテキストを、スプレッドシートに必要な情報と合わせて記入。
最終的にテキスト一つに対するデータは以下になりました。
- 発言者名の色
- 発言者名
- 文言
- 表示を開始する時間
- 表示する時間
- 文言表示時に特殊な処理をするためのタグ
スプレッドシートには文言のふりがなも含まれていますが、これは文言を表示する時間を計算するためのものです。
計算方法は、文言のふりがなの文字数で決定しています。
とても単純ですが、結構それっぽい時間文言が表示されるようになりましたし、自分で設定する必要がなくなったのでとても時短になりました。
文言のひらがな化には以下のWeb APIを利用させて頂いています。
Unity側でシナリオデータをAsset化する
Scriptable Objectを利用してシナリオデータをAsset化しました。
Editor拡張を書いてワンボタンでスプレッドシートのデータをScriptable Objectに反映することができるようになっています。
public override void OnInspectorGUI() { base.OnInspectorGUI(); if (GUILayout.Button("Download CSV")) { var novel = target as NovelObject; GoogleSpreadSheetUtility.DownloadCSV(novel.SheetID, novel.SheetName, (request) => { var lines = CSVToLineDataArray(request.downloadHandler.text.Replace("\"", "")); novel.Lines = lines; EditorUtility.SetDirty(novel); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); }); } } public class GoogleSpreadSheetUtility { public static void DownloadCSV(string id, string name, Action<UnityWebRequest> onSuccess = null, Action<UnityWebRequest> onError = null) { UnityWebRequest request = UnityWebRequest.Get("https://docs.google.com/spreadsheets/d/"+id+"/gviz/tq?tqx=out:csv&sheet="+name); request.SendWebRequest(); EditorApplication.CallbackFunction updateFunc = null; updateFunc = () => { if (request.isDone) { if (request.result == UnityWebRequest.Result.ProtocolError || request.result == UnityWebRequest.Result.ConnectionError || !string.IsNullOrEmpty(request.error)) { onError?.Invoke(request); } else { onSuccess?.Invoke(request); } EditorApplication.update -= updateFunc; } }; EditorApplication.update += updateFunc; } }
最初はScriptedImporterを利用して、CSVをAsset化していたんですが、毎回毎回スプレッドシートからCSVをダウンロードしてUnityにインポートするという手間が大変過ぎたので、このような形になりました。
これのおかげで、シナリオを反映させる手間がかなり効率化されました。
街を攻撃する敵を作成
街を攻撃している小型戦闘機はHoudiniで街のメッシュに対してスキャッターしたポイントクラウドを100個程生成して、そこを攻撃させている
地面から高くなるほど点群の密度が高くなるようにしている。(高い場所の方が目立つので)
この点群をCSVで出力して、UnityでAssetとして認識させるためにScriptedImporterを利用して実装しました。
ランタイム時にこのデータを参照して、街を攻撃する敵を配置している。
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor.AssetImporters; using System.IO; [ScriptedImporter(1, "points")] public class PointsImporter : ScriptedImporter { [SerializeField] private float unit = 100.0f; public override void OnImportAsset(AssetImportContext ctx) { var text = File.ReadAllText(ctx.assetPath); StringReader reader = new StringReader(text); var csvDatas = new List<string[]>(); while (reader.Peek() != -1) { string line = reader.ReadLine(); csvDatas.Add(line.Split(',')); } var points = new List<Vector3>(); foreach(var data in csvDatas) { var point = Vector3.zero; point.x = -float.Parse(data[0]) * unit; point.y = float.Parse(data[1]) * unit; point.z = float.Parse(data[2]) * unit; points.Add(point); } var pointsObject = ScriptableObject.CreateInstance<PointsObject>(); pointsObject.Points = points.ToArray(); ctx.AddObjectToAsset("MainAsset", pointsObject); ctx.SetMainObject(pointsObject); } }
9日目の動画
10日目
仮のブリーフィングを作成
ゲーム開始から終了までの流れを確認したかったので、真っ黒な画面にテキストだけ表示する仮のブリーフィングを作成
オブジェクトの更新系をFixedUpdateからUpdateに移動
弾やパワーアップアイテムなどあらゆるオブジェクトの更新処理をなぜかFixedUpdateで統一していたのでUpdateに処理を移動
戦闘開始時の演出を追加
戦闘開始時のカメラワークと戦闘終了演出を流用した戦闘開始演出を追加。
カメラワークの制御は、テキスト再生システムのタグを利用してCinemachine Virtual CameraのON, OFF制御を行った。
10日目の動画
11日目
Cinemachine + Timelineでブリーフィングを作成
Call of Dutyみたいなかっこいいブリーフィング画面を作りたかったので、ここで初めてTimelineを利用してブリーフィング演出を作りました。
背景の素材は基本的にSubstance Designerで作成し、世界地図のみ会社の同僚に素材を作ってもらいました。
11日目の動画
12日目
この日がGWの最終日でした
以降は作業ペースが落ちることを予想できたので、あまり挑戦的なことはやらないことにして細かい調整などを行う事にしました。
ブリーフィングのBGMを追加
ブリーフィングにピッタリのかっこいいBGMがあったのでブリーフィング画面にだけ先行してBGMを追加
戦闘ステージの地面テクスチャを作成
Substance Designerのノイズを良しなに使いまくって砂漠マテリアルを作成
今まで使っていた仮のテクスチャと比べてループ感が軽減されて、見た目の品質が一気に向上した気がする。
12日目の動画
13日目
チュートリアル開始時のカメラワーク作成
例によってCinemachineでカメラワークを作成
13日目の動画
14日目
BGMやSEを設定
BGMやSEはゲーム利用可能なフリーの素材をネットで探して利用させて頂いた。
ゲームにあったBGM・SEを探すのは意外と大変で、音の対応だけで丸一日時間がかかってしまった。
ただし音追加の効果は絶大で、敵を打ち落とすのがとても楽しくなった!
作業の1/4は無駄にテストプレイをしていた気がする。
14日目の動画
15日目
スコアを追加
Call of Duty Black Opsシリーズのゾンビモードのように、敵への攻撃ヒット時と敵撃破時にスコアを加算するようにしました。
スコア獲得のアニメーションも COD:BOのゾンビモードを真似ました。
リザルト画面を作成
スコアが記録できるようになったので、ゲームクリア後にリザルト画面を表示するようにしました。
こちらの表示アニメーションも UnityのAnimationを利用して作成しました。
15日目の動画
16日目
ランキング作成
二フクラのMobile Backend無料枠を利用してオンラインランキングを実装しました。
送信するデータだけ変更して、こちらのランキングチュートリアルをほとんどそのまま利用してます。
容量の関係で日本語フォントを少数しか入れることができていなかったので送信できる名前は正規表現で英数字 + いくつかの記号のみに制限しました。
private string NameValidate(string name) { if (name == "") { return "名前を入力してください"; } if (name.Length > 30) { return "名前は30文字以内にしてください"; } var isAlphaNumericAndSymbolOnly = !Regex.IsMatch(name, @"[^a-zA-z0-9-_.!?\-()\[\]@#$%&+:;{}=/*]"); if (!isAlphaNumericAndSymbolOnly ) { return "英数字のみ入力してください"; } return ""; }
16日目の動画
17日目
チュートリアルステージのブラッシュアップ
トンネル内のディティール不足を感じていたので橋の落下対策も兼ねて手すりを追加することにしました。
17日目の動画
18日目
ダメージエフェクト・瀕死エフェクト・死亡演出を作成
ダメージエフェクトはポストエフェクトのみで表現することにし、グリッチとRGBノイズを利用することにしました。
今回はポストエフェクトシェーダーもShaderGraphで作成しました。
瀕死時・被ダメージ時・死亡時は、これらポストエフェクトの強度を変更して適用することで表現しています。
以下は死亡時の例ですがポストエフェクトを強めに掛けることでゲームオーバー感を出せました。
ダメージの実装に合わせて回復アイテムの追加なども行いました。
18日目の動画
19日目
タイトル画面作成
ほんとうはTimelineを活用してかっこいいタイトル画面を作りたかったんですが、残り時間を考えると全然間に合わなそうだったのでチュートリアルステージをそのまま利用し、カメラの位置だけ調整してタイトル画面とすることにしました。
リソース使いまわしの手抜きですがそれっぽいタイトル画面になった気がしてます。
獲得したアイテムを画面に表示
獲得したアイテムが何かわからないことが多々あるので画面上にアイテム名を表示
自分が何のアイテムを獲得したか確認したい事が何度かあったので、この機能追加で遊びやすくなった。
撃破スコアの高い敵を追加
何人かの友人にテストプレイをしてもらったところ、自分を含めてみんな同じような点数に収束することが判明し、急遽撃破スコアが異なる敵がスポーンするように調整しました。
これによってスコアが多少バラつくようになりました、ゲームのレベル調整難しい...
ダブルスコアアイテムを追加
こちらもスコア収束対策のために、追加しました。
獲得すると一定期間獲得スコアが2倍になるアイテムで、これの追加によって、プレイヤーはHP、弾薬、ダブルスコアを管理しつつ戦うことになったので、ハイスコアを狙うための難易度が丁度いい感じに上がりました。
低フレームレートでカメラがガクガクになる問題修正
これに関してはCinemachineの設定によるものだったようで、以下のスクショの通りCinemachineFreeLookコンポーネントのSpeed設定を「Max Speed」から「Input Value Gain」に変更することでかなりマシになりました。
19日目の動画
20日目
戦闘中、エリア外に落ちないために壁を作成
広めに戦闘エリアを取っていたので落ちることは無いと思いますが、わざと落ちようとするユーザーのためにエリア外に行けないようにするための壁を設置しました。
壁のシェーダーもShaderGraphで実装しています。
低フレームレート時に難易度が激減する問題修正
敵の弾発射の抽選をフレーム単位で行っているため、FPS低下時に抽選回数が減ってしまい全然弾を撃ってこなくなる問題があった。
以下のようにフレームレートに応じて抽選確率を変動させて解消
var baseShotRate = 0.0025f; var lotteryCount = Time.deltaTime * 60.0f; var shotRate = LotteryUtility.GetProbabilityFromBaseProbilityAndCount(baseShotRate, lotteryCount); if (Random.Range(0.0f, 1.0f) < shotRate) { gun.Shot(((AttackTarget.position + AttackRandomOffset) - gun.transform.position).normalized); } public static float GetProbabilityFromBaseProbilityAndCount(float probility, float count) { var oneMinusProbility = 1.0f - probility; var allComeOffProbility = Mathf.Pow(oneMinusProbility, count); return 1.0f - allComeOffProbility; }
20日目の動画
21日目
ダブルスコアを重複取得可能にする
その後も友人とテストプレイを行っていましたが、結局スコアが収束してしまいました。
苦肉の策でダブルスコアを重複取得できるようにしたところ、ダブルスコアのドロップ運にかなり左右されてしまいますがスコアの頭打ちを無くすことができました。
オンラインランキングもあったのでこちらの方がランキング狙い甲斐があるだろうと言う事で重複可能で行くことにしました。
細かい修正や雑務
この日は5/31でゲームジャムの最終日だったので、ずっと放置していたバグ修正などを行いました。
具体的に行った作業は以下です。
結局間に合わなかったタスクもありましたが、おおむねタスク通りに作業が進みました。
21日目の動画
Unityの学習を兼ねて久しぶりにゲーム制作をした
GW期間中にUnityやHoudiniやSubstance Designerの学習を兼ねてシンプルなシューティングゲーム制作を行っていました。
今回は僕の好きな人気ゲーム(地球防衛軍・アーマードコア・Call of Duty)に多大に影響を受けつつ作りました。
いつかオリジナルなゲームを作れるように今後も精進していきます。
GW中にゲーム制作を始めたきっかけは、Unityでゲームを作ったことがなかったので今後のためにもやってみたかったというのもあったのですが、会社でGW+5月いっぱいの期間を利用したゲームジャムが開催されていて、それに乗っかれたというのがとても大きかったです。
GW期間中に球体を打ち落とすシンプルなゲーム制作を通じてUnityの学習をしている pic.twitter.com/PlyVL39DJv
— かねた (@kanetaaaaa) 2021年5月1日
自機をHoudiniでモデリングした pic.twitter.com/pZmxZ1yuW0
— かねた (@kanetaaaaa) 2021年5月3日
4/29から制作を始めて完成したのは5/31、作業した日数にすると21日間で↓のようなゲームになりました。
GWあたりからUnity, Houdini, Substance Designerの学習と実践がてら作っていたゲームを公開しました!
— かねた (@kanetaaaaa) 2021年5月31日
そこそこやりがいがある感じになったのでオンラインランキングのハイスコアを狙ってみてください!!https://t.co/jcmByV0Ul2 pic.twitter.com/tUCW1I8Gcd
お手軽にプレイしてもらいたかったのでWebGLビルドをUnity Roomで公開したところ、想像以上にたくさんの方にプレイして頂きました。さらに、ゲームニュースサイトのGameSparkにもなぜか取り上げてもらいました!びっくりしたのと同時に、昔から見ていたゲームニュースサイトに自分が作ったゲームが乗っているのがとても嬉しくて昔からの友達とかに自慢してしまいました。(ミーハー) www.gamespark.jp
今回のゲーム制作を通して、ゲームの公開から反応を身近で見ることができて、ゲーム制作楽しい!と再認識することができました!
学生時代にいくつかゲームもどきのようなものは作っていましたが、ゲームとして体裁をしっかり整えたゲームを完成させたのは初めてだったので、とても良い機会になりました。
大雑把な粒度ですが、期間中の作業ログが残っているので、それを元に後日制作記録を書こうと思います。
第二回「KLab Expert Camp」のメンターを担当させて頂きました。
当日の様子はハッシュタグ「#KLabExpertCamp」を追ってもらえれば確認できます。 twitter.com
参加者の方々にめちゃくちゃ刺激を貰えていた5日間でしたので、終了して数日たってますが未だにロスを感じています。
皆さんとMeetで会話しながら作業するのはとても楽しかったですし、僕自身も色々と学びになることが多くてよい経験となりました。
KLab Expert Camp(KEC)とは
KECは、技術的に深いテーマに取り組んでいる学生の発掘・育成を目的とした KLab の新しいインターンの取り組みです。
第一回目当時の様子や、どういった経緯で開催されたかといった内容は以下の会社のブログに詳細に書かれているのでよかったら見てください。 dsas.blog.klab.org
色々と事情があり今回の第二回はオンラインでの開催となりました。
開催の経緯
堅苦しいことを書きましたが、シェーダー関連でインターンをしたいという話が最初に会社であって、普段Twitterでシェーダを使って何かしらを制作されている皆さんの(メンター含めた) オフ会 交流の場になればいいなと思って人事部の担当者ともう一人のメンターの @gam0022さんと一緒に色々企画させてもらいました。
弊社で私と @gam0022 がメンターを担当する3日間のインターンが開催されます!
— かねた (@kanetaaaaa) 2020年2月14日
私が担当するGLSL Compoでは3日間シェーダー芸で遊ぶだけのかなり特殊なインターンなので少しでも興味があればお気軽にエントリーしてください!https://t.co/DQoCNxqrc4
最初はオフライン開催でGLSLを用いた映像制作と、Unityを用いた映像制作二つの部門に分けてそれぞれのコースで講義を行いつつ3日間で作品を作ってもらい、部門ごとに投票システムで順位を決めるというデモパーティのようなものを構想していました。
ただ3日間はさすがに期間が短すぎると思っていたのでいろいろ相談した結果、5日間での開催へと変更されました。(それでも短すぎますが....)
また残念ながら延期に延期を重ねてオンラインでの開催となってしまい、運営メンバーが一人しかおらず管理の面で懸念の声があったため、コースを統合しての開催となりました。
当初の予定とは色々と異なってしまいましたが、参加者からはよい交流の機会になったといった声も聴けたので開催させていただいてよかったです。
どういったことをしたのか
事前準備
事前準備として講義資料に利用するアニメーションのシェーダーを作ったり、ライティングの資料を作るために以下の記事でも書いたように復習を兼ねて勉強したりしていました。 nanka.hateblo.jp
講義用の資料制作自体は開催の一か月ほど前から着手し始めて、自分の筆の遅さもあって開催日前日にようやく完成しました。。。
その他いろいろな事務作業であったり、郵送であったりといった大変そうな作業はすべて人事部の方にやっていただきました。ありがとうございます...
期間中
期間中は基本的な進行や管理などは人事部の方に全てお任せさせていただいたので、僕は二度の講義と参加者からの質問対応などに集中していました。
一回目の講義
一回目の講義は「距離関数プリミティブを用いたモデリングとライティング」というタイトルで、昨年の4月ごろに作成したThe cake is a lie.というフラグメントシェーダーオンリーの作品のモデリングとライティング部分をアニメーションを交えつつ解説しました。
本インターンの期間中、全4回の講義を行います。
— Kの27乗 (@oktillion27) 2020年9月8日
初日の本日は、1つ目の講義を実施しました。
明日以降も、個人で開発を進めて頂き、技術メンターによるフォローをさせてもらいつつ、他の講義も行っていきます!#KLabExpertCamp pic.twitter.com/g5KRMIJdwG
そのシェーダーを書いた当時の記事はこちらです。この記事を書きながら見返したんですが既に懐かしいですね... nanka.hateblo.jp
二回目の講義
二回目の講義は、「今日から始める盛り盛りポストエフェクト」というタイトルで、今回のインターン延期直後に、 @lucknknock さんと @butadiene121 さんが主催された Shader1WeekCompo に投稿させてもらった、 Delayed という作品で使用したポストエフェクトの解説を行いました。
3日目の本日は、講義を2つ実施予定です。
— Kの27乗 (@oktillion27) 2020年9月10日
1つ目の講義は、先程終了しました。
質問タイムでは、鋭い内容のものもあり、参加者の皆さんは終始集中して臨んで頂いていたようでした!#KLabExpertCamp pic.twitter.com/MZhlm3NKxC
講義するネタに困っていたときに、ちょうどShader1WeekCompoが開催されたので良い機会でした。とても感謝しています。
講義の内容としては、Delayedで実装した描画パスを一つずつ順番に解説して行き、しょぼい描画結果から徐々に綺麗な画面になっていくところをお見せしていきました。
なんかもっとかっこいいタイトルにしたかったんですが、何にも思いつかなかったのでIQ低そうなタイトルになってしまいました。
感想
参加者のレベル高すぎ
まず最初に出てくる感想は、参加者のレベル高すぎという事です...
4日間というかなり短い期間でしたが、参加者全員がちゃんと作品を完成させて5日目の成果発表でしっかりと発表されていました。
中には精神と時の部屋で作業したのか?と思うほどかなり密度の濃い作品があったりして、メンターは成果発表の前に作品を見る機会があったんですが開いた口が塞がらないというのはこういう事なのかという感じでした...
正直自分では4日間で何かしらを完成させるという事ができる気がしないです。参加者の方々は無茶ぶりに答えてくださって本当にありがとうございます。
かなりニッチなインターンでしたが人が集まってよかった
シェーダー+映像制作というかなりニッチなテーマで人が集まるかどうか、実はすごく不安だったんですが、蓋を開けてみると当日は13名も参加してくださり本当に安心しました。ありがとうございます。
他にも参加予定の方々がいたのですが、イベントそのものの延期等で残念ながら都合が合わずに参加できませんでした...
参加できなかった方々にも、せめてもと思い期間中利用する予定だったネームカード(冒頭の画像のやつです)や、おやつセット等をお送りさせてもらいました。
講義で何かしら知識を持って帰ってもらえてよかった
今まで専門学校やテックイベント等で講義をした経験は何度かあったんですが、ほとんどが初心者向けの講義でしたので、今回既にシェーダーがめっちゃできる参加者の皆様に向けて、簡単すぎる内容にならないように結構プレッシャーを感じていました。
事後のアンケートなどを見ると大半の方には講義を通して何かしらの知識を吸収して頂けたようで本当に良かったです...
講義後にも参加者の方からとても沢山の質問を頂けたので、興味を持ってもらえているんだなと安心しました。
期間中制作した作品に早速今回の講義内容を実践してみた、といった声が、成果発表中に聞けたりしたのでメンターとして参加させていただいて本当にありがたいことでした。
反省点もいくつかある
今回開催させていただいて反省点もいくつかありました。
特に
- 提出物のレギュレーションが定まっていなかった
- 後出しで音声の後付けを許可した
といったような提出物に関わる内容は参加者を混乱させてしまっただけではなく、成果物の品質にも影響を与えてしまったと思います。
次回こういったイベントをやる際はしっかりと固めてからやりたいです。
最後に
特段偉いわけでもないのに、期間中は終始偉そうにアドバイスとかコメントさせていただきましたが、参加者が4日間という超短期間で製作された作品はどれも素晴らしいものでした。僕が同じ期間貰えてもこれだけ素晴らしい作品は作れないと思います。
今回はメンターとインターン参加者という立場でしたが、グラフィックや映像が好きな友人としてこれからも仲よくしてもらえたらうれしいです。
参加者の皆さん5日間本当にお疲れ様でした。
参加者のブログ記事リンク
既に数人のインターン参加者が参加報告をブログに書いてくださっていたので、こちらにリンクを貼っておきます。 発見次第随時追加します。
マイクロファセットBRDF
今までいろんなShadertoyの作品で使ってきてましたが、正直理解が浅すぎたので改めていろいろな記事を参考に再実装しました。
用語の整理等もでき始めたのでそこそこ理解が進んだ気がします。
特にこのシリーズはとても参考にさせていただきました。
2019年雑に振り返り
今年も大晦日になりました。
今まで一年を振り返るというのをやったことが無かったのですが(振り返れるものが無かったともいう)今年はいろいろあったので、RIZINを見ながら軽く振り返ります。
2018年まで
2018年は就職して3年目でした。
基本的には仕事以外でプログラムを書くようなことは無かった上に、この時は、スマートフォンゲームにハマっており、twitterは課金の報告用になっていました。
しかし、2月頃から以前から興味があったTokyo Demo Fest 2018に向けてメガデモを製作するための技術を勉強し始めました。
これを起点に情報発信や作品の制作を行うようになって、2019年に繋がったと思います。
2019年
シェーダー芸の投稿
2018年の12月から今年の9月までコンスタントに月一個以上はShadertoyやNEORTにシェーダー芸を投稿しました。
本当は12月までの一年間続けたかったんですが、仕事やその他のイベントなどで作る時間を捻出できなくなってしまいできませんでした。
ただ、三日坊主で有名な僕が10か月以上も継続できたことが自分でも驚きなので、ポジティブにとらえていきたいです。
また、NEORTに投稿していた作品の一つが渋谷駅に展示されました。
継続的に投稿を行っていないとこのような素晴らしい機会を得ることは絶対にできなかったと思います。
登壇等
2件やりました。
6月に「UnityエンジニアによるShader勉強会!登壇」でなぜかライブコーディングに関する発表を行い、終了後の懇親会では20分のライブコーディングの実演を行い場を盛り上げることができたと思います。
11月にWebGL総本山のdoxas氏が主宰する「GLSLスクール2019」でレイマーチングによるシェーダーアートの制作例とテクニックについて1時間の講義を行いました。
Twitterを今のように活用する前からこのスクールを知っていたので、呼んでいただいて本当に光栄でした。
記事の投稿
Unityやシェーダーに関する記事をいくつか書きました。
いろんな人に見てもらいたいものはQiita、雑なメモや個人的なものはブログに書くように使い分けていました。
いずれもたくさんの方に見て頂けて嬉しかったです。
これから
今年は自分の情報を外に出すようにしたからか、新しい出会いがたくさんありました。
また、仕事でもいい方向にいろいろと環境が変わった年でもありました。
来年からもマイペースに作りたいものを作って、やりたいことを勉強する年にしていきたいです。
2019年9月のシェーダーを紹介する
はじめに
毎月やっている月毎に制作したシェーダーの紹介第8回目です。
今月はShadertoyで一つシェーダーを作ったので簡単な解説をします。
Energy Lab
久しぶりにワンパスレイマーチングをやりました🤔https://t.co/y7oiQkUcvK#shadertoy #raymarching #glsl pic.twitter.com/4Mxksm9Yzd
— かねた (@kanetaaaaa) September 23, 2019
インスピレーション
9月頭あたりにこのシェーダーを見て、金色のケージっぽいオブジェクトかっこいいなと思ったので今回登場させました。
また以前からパイプをエネルギーが伝っているようなシーンを作ってみたかったので合わせて今回のシェーダーになりました。
形状
ケージの形状はトーラスをpmodで二重に複製しています。
vec2 cage(vec3 p) { p.y += sin(time) * 0.1; p.xy *= rot(-pi*0.5); p.yz *= rot(time * 0.5); p.yz = pmod(p.yz, 7.0); p.yx = pmod(p.yx, 7.0); return vec2(torus(p, 0.025, 0.55), MAT_CAGE); }
ステージのモデリングには例に漏れず、Mercuryのhg_sdfを活用させていただいています。 mercury.sexy
特にパイプは fOpPipe
を使用して、元のステージとboxを組み合わせて這うパイプを表現しています。
ライティング
n kbデモを意識してコンパクトでそれなりの見た目になる物を用意して使っています。
Diffuseに正規化Lambert、Specularに正規化BlinPhongを使用したもので、反射率はフレネルを考慮した方が良い結果になりますが、マテリアル毎に一定としています。
vec3 light(vec3 p, vec3 n, vec3 v, vec3 lp, vec3 baseColor, float roughness, float reflectance, float metallic, vec3 radiance) { vec3 ref = mix(vec3(reflectance), baseColor, metallic); vec3 l = lp - p; float len = length(l); l /= len; vec3 h = normalize(l + v); vec3 diffuse = mix(1.0 - ref, vec3(0.0), metallic) * baseColor / pi; float m = roughnessToExponent(roughness); vec3 specular = ref * pow( max( 0.0, dot( n, h ) ), m ) * ( m + 9.0 ) / ( 9.0 * pi ); return (diffuse + specular) * radiance * max(0.0, dot(l, n)) / (len*len); }
今まで、複数回のリフレクションに対応するシェーダーを書いたことが無かったので、今回はそれに対応しました。
シェーダー内では再帰関数を利用できないので、依存関係にならないように処理を小さい粒度で関数化して、レンダリングの後方でトレースをループすることで実現しました。
vec3 shade(vec3 p, vec3 ray, vec2 mat) { vec3 baseColor, emission; float roughness, metallic, reflectance; getSurfaceParams(p, mat, baseColor, emission, roughness, reflectance, metallic); vec3 n = normal(p); vec3 result = evalLights(p, n, ray, baseColor, roughness, reflectance, metallic) + emission; vec3 f0 = vec3(1.0); for(int i=0; i<1; i++) { f0 *= mix(vec3(reflectance), baseColor, metallic); vec3 secondPos; vec2 secondMat; float depth; ray = reflect(ray, n); trace(p + n * 0.001, ray, 100.0, 24, secondPos, secondMat, depth); getSurfaceParams(p, secondMat, baseColor, emission, roughness, reflectance, metallic); n = normal(secondPos); p = secondPos; result += (evalLights(secondPos, n, ray, baseColor, roughness, reflectance, metallic) + emission) * f0; } return result; }
ポストエフェクト
過去に紹介したものばかりなので箇条書きで書きます
- Lens Distortion
- Vignette
- Tone Mapping (ACES)
さいごに
来月はGLSLスクールがあるので頑張って資料作ります!
10月は何を作るかまだ未定ですが、もし何か作ったら紹介します。
2019年8月の成果物を紹介する
はじめに
毎月やっている月毎に制作したシェーダーの紹介第7回目です。
今月はShadertoyでシェーダーを書きませんでしたが、最近触り始めたUnityのさらにHDRPで色々やっていたので紹介します!
新幹線でライブコーディング
会社の出張で大阪に向かった時にライブコーディングをやりました。
大阪行きの新幹線で作ったやつです🤔https://t.co/ADhKI0sNHV#glsl #raymarching pic.twitter.com/aiNThvsuPJ
— かねた (@kanetaaaaa) July 27, 2019
以前Shadertoyでトンネルを二つ組み合わせた面白い表現を見たので、ボリュームライトと組み合わせてライブコーディングでやってみました。
Unity HDRP
土日はHDRPで遊ぶぞ(インドア)
— かねた (@kanetaaaaa) August 2, 2019
8月上旬から中旬はずっとこれをやっていました。
凄い人が作ったであろうHDRPを実際に触って改造してみることで色々な知見を得られるのではないかという作戦で進めました。
実際に今まで知らなかった手法やテクニックがたくさん使われており大変勉強になっております!
HDRPのキャッチアップ
まず手始めにHDRPを改造できる環境を整えました。
以下にその時のメモが残っています。
また改造の練習にHDRPにレンダリングパスを追加してアウトラインを描画するということもやりました。
HDRPレンダリングパス追加の練習でアウトラインパスを追加した(Render Graphが来たら使えなくなるであろうテク🤔) pic.twitter.com/RCZPAoVvBf
— かねた (@kanetaaaaa) August 3, 2019
レイマーチング
HDRPでレイマーチングをするというのが今月の目標でした。
HDRPの改造方法も大体わかってきたのでこの辺りから詩作を始めました。
角ばった球と無限にスムースな球🤔 pic.twitter.com/008ttMo6GK
— かねた (@kanetaaaaa) August 4, 2019
レンダリングパスを追加できるようにしておく等入念に準備していましたが、意外とHDRP側の修正は少なく済みました。
Reflection Probeを焼くときにHDRP側を弄ってDBufferとGBufferの描画順を入れ替えないとデプスプリパスが入る関係で描画がおかしくなるのでカスタマイズ以外の回避方法がわからない
— かねた (@kanetaaaaa) August 4, 2019
この辺りからはHDRPの素敵なライティングでスクショを取るのが楽しすぎて、作業よりもスクショを取ってる時間の方が長かったです。
こっちのほうがかっこいい pic.twitter.com/Ce0HtoOork
— かねた (@kanetaaaaa) August 5, 2019
リファクタによって熱そうなメンガーとカラーバリエーションを表現できるようになった🤔
— かねた (@kanetaaaaa) August 6, 2019
そろそろ忘れないうちに情報をまとめたい... pic.twitter.com/c4bRIJOKyD
スクショ撮るのが楽しくて作業が進まない🤔
— かねた (@kanetaaaaa) August 7, 2019
(SDF AOを追加) pic.twitter.com/BL7wob7olS
メンガーを縦に引き伸ばして遊んでいたらよさげな絵が取れた pic.twitter.com/rs2CNWdd3j
— かねた (@kanetaaaaa) August 8, 2019
そして完成したものがこちらです。
Unity HDRP + Raymarchingのプロジェクト公開しました!
— かねた (@kanetaaaaa) August 11, 2019
興味があれば動かしてみてください!!https://t.co/hcBJKSWzC9
コードの整理ができたらブログに情報を残します#unity3d #raymarching pic.twitter.com/6Jc0HeeOAB
この時に得た知見等はこちらの記事で解説しています。(まだ途中だけど...)
さいごに
今月はやったことをほとんど別記事で書いてしまったので内容が薄くなってしまいました...
もうしばらくUnity + HDRPを触っていろいろやりたいなと考えています。
また、10月に@h_doxas氏主催のGLSLスクールでプラスワン講義を担当することになったので、来月はそちらの資料作りに時間を取りたいとも考えています!
来月ももし何か作ったら紹介します。