はじめに
こんにちは![]()
CYBIRD Advent Calendar 2025 12日目担当の@shiso_cです。
クライアントエンジニアとして、主にUnityを使ったタイトルの開発/運用に携わっています。
11日目は@cy-seiyanさんの「Flashをモバイル端末で再生させる為に使用していたツールや体験記」でした。
Unity普及前の苦労が窺えますね……😳
概要
最近ではAIもすっかり日常に馴染んできましたね。
私自身もここ一年で仕事のことからプライベートのことまで
AIに相談する場面が増えてきたように感じます。
そこで今回はGeminiを相談相手としてゲームを作ってみたときの開発日記をお届けします🎁
よし、Geminiくんにスクリプト全部書いてもらおう
今回もUnity1週間ゲームジャム(通称:Unity1Week、以下u1w)の参加記録からの抜粋です!
プレイヤーの横移動の操作で落下物をキャッチするタイプのよくあるアクションゲームですね。
これをそのまま作ったところで普通すぎて面白くないなーと思いまして……
というわけで、思いついた仕様を片っ端からGeminiくんに渡してコードを書いてもらうことにしました。
前提:ザックリこんな仕様です
- 🔴プレーヤーはマウス追従で操作
- 🔴お腹が減ると動きが遅くなる(体力減)
- 🟢目が乾くと視界がぼやける(うるおい減)
- 🟡3パターンのポーズがある
- 愛を受け止めるポーズ
- カレーを受け止めるポーズ
- 目薬を受け止めるポーズ
- 🔵3種の落下物がある
- 愛(スコア加算)
- カレー(体力回復)
- 目薬(うるおい回復)
- ⭐️対応したポーズで落下物を受け止めると受け止めると良い事がある
- 愛はどのポーズで受け取ってもスコア加算。対応したポーズだとさらに多くのポイント量増加
- カレーをカレー皿で受け取ると、他のポーズに切り替えるタイミングでカレーを食べて体力が回復する。あいがけにすると回復量増加
- 目薬を目で受け取ると視界のぼやけが回復する
- ⭐️間違ったポーズで受け取るとゲームオーバー
- 目でカレーを受け止めると失明する
- カレー皿で目薬を受け止めると腹を壊す
⭐️の部分は全体のゲームサイクルとして自分で実装したのですが、細かい機能面でGeminiを頼ってみました。
🔴プレイヤーの動き:マウス追従オブジェクト
シンプルにマウスを追従するだけなので、とりあえず雑にプロンプトを投げてみます。
unity6でマウスカーソルのx座標だけを追従するオブジェクトにアタッチするスクリプトを生成してください
Geminiの回答
はい、Unity 6でマウスカーソルのX座標だけを追従するオブジェクトにアタッチするC#スクリプトを生成します。新しいC#スクリプトを作成し、以下のコードを記述してください。スクリプトの名前は、例えば FollowMouseX とします。
using UnityEngine;
public class FollowMouseX : MonoBehaviour
{
public float offset = 0f; // 必要に応じてオフセット値を設定
void Update()
{
// マウスのスクリーン座標を取得
Vector3 mousePosition = Input.mousePosition;
// マウスのスクリーン座標をワールド座標に変換(Z軸はオブジェクトの現在のZ軸を維持)
Vector3 worldMousePosition = Camera.main.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, transform.position.z - Camera.main.transform.position.z));
// オブジェクトの新しいX座標をマウスのワールド座標のX座標に設定し、YとZ座標は維持
transform.position = new Vector3(worldMousePosition.x + offset, transform.position.y, transform.position.z);
}
}
スクリプトの説明:
-
using UnityEngine;: Unityの機能を使用するために必要な名前空間をインポートします。 -
public class FollowMouseX : MonoBehaviour:FollowMouseXという名前の新しいクラスを定義し、MonoBehaviourを継承することでUnityのスクリプトとして機能するようにします。 -
public float offset = 0f;: 公開されたfloat型の変数offsetを定義します。これにより、インスペクターからオブジェクトのX座標に対するオフセット値を設定できます。初期値は0です。 -
void Update(): Unityのライフサイクルメソッドの一つで、毎フレーム実行されます。 -
Vector3 mousePosition = Input.mousePosition;: 現在のマウスカーソルのスクリーン座標(ピクセル単位)をmousePosition変数に格納します。 -
Vector3 worldMousePosition = Camera.main.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, transform.position.z - Camera.main.transform.position.z));:-
Camera.mainを使用して、シーン内のメインカメラを取得します。 -
ScreenToWorldPoint()メソッドは、スクリーン座標をワールド空間の座標に変換します。 -
new Vector3(mousePosition.x, mousePosition.y, transform.position.z - Camera.main.transform.position.z):- X座標にはマウスのスクリーンX座標を使用します。
- Y座標にはマウスのスクリーンY座標を使用します(ただし、このスクリプトでは最終的にオブジェクトのY座標は変更しません)。
- Z座標には、オブジェクトの現在のZ座標とカメラのZ座標の差を使用します。これにより、オブジェクトがカメラと同じZ平面上に留まるようにします。必要に応じて調整してください。
-
-
transform.position = new Vector3(worldMousePosition.x + offset, transform.position.y, transform.position.z);:- オブジェクトの
transform.position(ワールド空間での位置)を新しいVector3で更新します。 - 新しいX座標には、変換されたマウスのワールドX座標に
offsetを加えた値を使用します。 - Y座標とZ座標は、オブジェクトの現在の値を維持します。
- オブジェクトの
スクリプトの使用方法:
- UnityプロジェクトのAssetsフォルダ内に新しいC#スクリプト (
FollowMouseX.csなど) を作成し、上記のコードをコピー&ペーストします。 - マウスのX座標に追従させたいGameObjectを選択します。
- 作成した
FollowMouseXスクリプトを選択したGameObjectにドラッグ&ドロップしてアタッチします。 - インスペクターで
Offsetの値を調整することで、追従位置を微調整できます。
このスクリプトをアタッチしたGameObjectは、ゲーム実行中にマウスカーソルの水平方向の動きに合わせて移動し、垂直方向と奥行きの位置は維持されます。
ご丁寧に使用方法まで教えてくれました👏
お腹が減ると動きが遅くなる仕様は後から思いついたので、このコードを改変できるか聞いてみます。
追従するスピードを変数を持たせて調整することはできますか?
Geminiの回答
はい、追従するスピードを変数を持たせて調整することができます。`FollowMouseX` スクリプトを以下のように修正してください。using UnityEngine;
public class FollowMouseX : MonoBehaviour
{
public float offset = 0f;
public float speed = 10f; // 追従速度を調整する変数
void Update()
{
Vector3 mousePosition = Input.mousePosition;
Vector3 worldMousePosition = Camera.main.ScreenToWorldPoint(new Vector3(mousePosition.x, mousePosition.y, transform.position.z - Camera.main.transform.position.z));
// 目標のX座標
float targetX = worldMousePosition.x + offset;
// 現在のX座標から目標のX座標へLerpを使って滑らかに移動
float newX = Mathf.Lerp(transform.position.x, targetX, speed * Time.deltaTime);
// オブジェクトの新しい位置を設定
transform.position = new Vector3(newX, transform.position.y, transform.position.z);
}
}
変更点:
-
public float speed = 10f;: 新しくpublicなfloat型の変数speedを追加しました。この変数をインスペクターから調整することで、追従の速さを制御できます。初期値は10fに設定されていますが、必要に応じて変更してください。 -
float targetX = worldMousePosition.x + offset;: 目標とするX座標(マウスのワールド座標にオフセットを加えた値)をtargetX変数に格納します。 -
float newX = Mathf.Lerp(transform.position.x, targetX, speed * Time.deltaTime);:-
Mathf.Lerp(a, b, t):aからbへtの割合で線形補間を行います。 -
transform.position.x: 現在のオブジェクトのX座標。 -
targetX: 目標とするX座標。 -
speed * Time.deltaTime: 毎フレームどれくらいの割合で目標に近づくかを制御します。-
speed: 公開された速度変数。 -
Time.deltaTime: 前のフレームが完了してから経過した時間(秒単位)。これを使用することで、フレームレートに依存しない一定の速度で移動させることができます。
-
-
-
transform.position = new Vector3(newX, transform.position.y, transform.position.z);: 計算された新しいX座標 (newX) を使ってオブジェクトの位置を更新します。Y座標とZ座標は以前と同様に維持されます。
この変更により、以下のことが可能になります:
- Unityのエディター上で、アタッチしたGameObjectのインスペクターから
Speedの値を変更することで、マウスカーソルへの追従速度をリアルタイムに調整できます。 -
Speedの値を大きくすると、オブジェクトはより速くマウスカーソルのX座標に追いつきます。 -
Speedの値を小さくすると、オブジェクトはゆっくりと滑らかにマウスカーソルのX座標に追従します。
この新しいスクリプトをGameObjectにアタッチして、インスペクターで Speed の値を色々と試してみてください。
このspeedの値をゲームのメインサイクルから変更できれば理想の動きが作れそうです。
あっという間にプレイヤーを動かすスクリプトが完成しました🙌
🟡ポーズの切り替え:Buttonのトグルグループ化
UnityのuGUIにはToggleGroupというコンポーネントもあるのですが、今回はButtonを3つ並べてToggleBottunのように扱うよう実装しました。
というのも、私が普段業務で関わっているイケメンシリーズのゲームでそんな感じの実装をしているから (その実装しか頭になかったとも言う)
業務で使われているのもは独自にクラス化してるのでそのままとは行きませんが、似たようなことはできるだろうと言うことで、とりあえず相談してみます。
Buttonを三つ並べてトグルボタンにしたいです。
サンプルコードを作成してください
Geminiの回答
はい、Unityで3つのButtonを並べてトグルボタンのように機能させるサンプルコードを作成します。基本的な考え方:
- 3つのButtonを用意します。
- それぞれのButtonが選択状態を持つようにします。
- 同時に選択できるButtonは1つだけです。
- Buttonがクリックされたときに、自身の選択状態を切り替え、他のButtonの選択状態を解除します。
- Buttonの状態に応じて、見た目(色、テキストなど)を変更します。
サンプルコード (C#):
新しいC#スクリプト (ToggleButtonGroup.cs など) を作成し、以下のコードを記述してください。
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class ToggleButtonGroup : MonoBehaviour
{
public List<Button> buttons; // インスペクターからButtonを割り当てるリスト
public Color selectedColor = Color.yellow; // 選択時の色
public Color unselectedColor = Color.white; // 非選択時の色
private Button currentSelectedButton;
void Start()
{
// Buttonが正しく割り当てられているか確認
if (buttons == null || buttons.Count != 3)
{
Debug.LogError("Buttonが3つ割り当てられていません。");
return;
}
// 各Buttonにクリック時のリスナーを追加
foreach (Button button in buttons)
{
button.onClick.AddListener(() => OnButtonClick(button));
SetButtonState(button, false); // 初期状態は非選択
}
// 初期選択を設定する場合(最初のButtonを選択状態にするなど)
if (buttons.Count > 0)
{
SetButtonState(buttons[0], true);
currentSelectedButton = buttons[0];
}
}
void OnButtonClick(Button clickedButton)
{
// 現在選択されているButtonがあれば非選択状態にする
if (currentSelectedButton != null && currentSelectedButton != clickedButton)
{
SetButtonState(currentSelectedButton, false);
}
// クリックされたButtonを選択状態にする
SetButtonState(clickedButton, true);
currentSelectedButton = clickedButton;
// ここで選択されたButtonに応じた処理を行うことができます
Debug.Log(clickedButton.name + " が選択されました。");
}
void SetButtonState(Button button, bool isSelected)
{
Image buttonImage = button.GetComponent<Image>();
if (buttonImage != null)
{
buttonImage.color = isSelected ? selectedColor : unselectedColor;
}
// 必要に応じてテキストの色や他の要素も変更できます
}
// 現在選択されているButtonを取得するメソッド(必要に応じて)
public Button GetCurrentSelectedButton()
{
return currentSelectedButton;
}
}
Unityエディタでの設定:
-
Canvasの作成: Hierarchyウィンドウで右クリックし、
UI > Canvasを作成します。 -
Buttonの作成: Canvasを右クリックし、
UI > Buttonを3つ作成します。それぞれ名前をButton1,Button2,Button3など分かりやすい名前に変更しておくと良いでしょう。 -
Buttonの配置: 必要に応じて、Layout Groupコンポーネント (
Horizontal Layout Groupなど) を親の空のGameObjectに追加し、そのGameObjectの子として3つのButtonを配置することで、自動的に横並びにすることができます。 -
スクリプトのアタッチ: 空のGameObject (
ToggleButtonGroupなど) を作成し、作成したToggleButtonGroup.csスクリプトをアタッチします。 -
Buttonの割り当て:
ToggleButtonGroupGameObjectのインスペクターにあるButtonsリストに、Hierarchyウィンドウにある3つのButtonをドラッグ&ドロップして割り当てます。リストの要素数が3になっていることを確認してください。 -
色の設定: インスペクターで
Selected ColorとUnselected Colorを好みの色に設定します。
動作原理:
-
buttonsリストに割り当てられたButtonそれぞれに、クリック時のリスナーが追加されます。 - Buttonがクリックされると
OnButtonClickメソッドが呼び出されます。 -
OnButtonClickメソッド内で、現在選択されているButtonがあれば非選択状態に戻し、クリックされたButtonを選択状態にします。 -
SetButtonStateメソッドは、ButtonのImageコンポーネントの色を選択状態に応じて変更します。必要に応じて、テキストの色や他の視覚的な要素もここで変更できます。 -
GetCurrentSelectedButtonメソッドを使用すると、外部のスクリプトから現在選択されているButtonを取得できます。
このサンプルコードを元に、Buttonのテキストを変更したり、選択状態に応じて他のGameObjectの状態を制御するなど、より複雑なトグルボタンの挙動を実装することができます。
考え方まで教えてくれて手厚いです👏
これをベースに以下の改変もGeminiにやってもらいましょう。
- ボタンは画像で扱いたい
- キー操作もできるようにしたい
Image buttonImage = button.GetComponent<Image>();
if (buttonImage != null)
{
buttonImage.color = isSelected ? selectedColor : unselectedColor;
}
この部分を色ではなく、spriteswapでselect状態にしたいです
Geminiの回答
はい、`Image` コンポーネントの色を変更する代わりに、`SpriteSwap` を使用して選択状態を表現する方法をご説明します。SpriteSwap の設定:
まず、Button の Transition を Sprite Swap に設定し、選択状態に使用する Sprite を割り当てる必要があります。
- Unity エディタで、トグルボタンとして使用する Button オブジェクトを選択します。
- インスペクターで
Button (Script)コンポーネントを見つけます。 -
TransitionドロップダウンをSprite Swapに変更します。 -
Sprite Swapの設定項目が表示されるので、以下の Sprite を割り当てます。- Normal Sprite: 通常時の Sprite
- Highlighted Sprite: マウスオーバー時の Sprite(必要に応じて)
- Pressed Sprite: クリック時の Sprite(必要に応じて)
- Selected Sprite: 選択時の Sprite
スクリプトの修正:
ToggleButtonGroup.cs スクリプトの SetButtonState メソッドを修正して、Selection State を制御するようにします。
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
public class ToggleButtonGroup : MonoBehaviour
{
public List<Button> buttons; // インスペクターからButtonを割り当てるリスト
// 色の設定は不要になったため削除またはコメントアウト
// public Color selectedColor = Color.yellow;
// public Color unselectedColor = Color.white;
private Button currentSelectedButton;
void Start()
{
if (buttons == null || buttons.Count != 3)
{
Debug.LogError("Buttonが3つ割り当てられていません。");
return;
}
foreach (Button button in buttons)
{
button.onClick.AddListener(() => OnButtonClick(button));
SetButtonState(button, false); // 初期状態は非選択
}
if (buttons.Count > 0)
{
SetButtonState(buttons[0], true);
currentSelectedButton = buttons[0];
}
}
void OnButtonClick(Button clickedButton)
{
if (currentSelectedButton != null && currentSelectedButton != clickedButton)
{
SetButtonState(currentSelectedButton, false);
}
SetButtonState(clickedButton, true);
currentSelectedButton = clickedButton;
Debug.Log(clickedButton.name + " が選択されました。");
}
void SetButtonState(Button button, bool isSelected)
{
Selectable selectable = button.GetComponent<Selectable>();
if (selectable != null)
{
if (isSelected)
{
selectable.Select(); // Buttonを選択状態にする
}
else
{
selectable.OnDeselect(null); // Buttonの選択を解除する
}
}
}
public Button GetCurrentSelectedButton()
{
return currentSelectedButton;
}
}
変更点:
-
色の変数の削除:
selectedColorとunselectedColorの変数は、SpriteSwap を使用するため不要になりました。削除するかコメントアウトします。 -
SetButtonStateメソッドの変更:-
Imageコンポーネントを取得して色を変更する代わりに、Selectableコンポーネントを取得します。Button はSelectableを継承しています。 -
isSelectedがtrueの場合、selectable.Select()を呼び出して Button を選択状態にします。これにより、Button のSprite Swap設定で割り当てたSelected Spriteが表示されます。 -
isSelectedがfalseの場合、selectable.OnDeselect(null)を呼び出して Button の選択を解除します。これにより、通常時の Sprite (Normal Sprite) が表示されます。
-
この修正により、Button が選択されると、インスペクターで設定した Selected Sprite に切り替わり、選択が解除されると Normal Sprite に戻るようになります。
注意点:
- Button の
Interactableがtrueになっていることを確認してください。 -
Sprite Swapに適切な Sprite が割り当てられていることを確認してください。 - 他の
Transitionモード(例えばColor TintやAnimation)が選択されていると、このスクリプトは意図した通りに動作しません。必ずSprite Swapを選択してください。
キーボード入力で
←↓→を押した時に各ボタンが押されたのと同じ挙動をされる処理を追加したいです
Geminiの回答
はい、キーボードの ←↓→ キーが押されたときに、それぞれに対応するButtonがクリックされたのと同じ挙動をする処理を `ToggleButtonGroup.cs` スクリプトに追加する方法をご説明します。スクリプトの修正:
ToggleButtonGroup.cs スクリプトに Update メソッドを追加し、キー入力を監視して対応するButtonの onClick イベントを呼び出すようにします。
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using UnityEngine.EventSystems; // ExecuteEvents を使用するために必要
public class ToggleButtonGroup : MonoBehaviour
{
public List<Button> buttons; // インスペクターからButtonを割り当てるリスト
public KeyCode leftKey = KeyCode.LeftArrow; // 左キー
public KeyCode downKey = KeyCode.DownArrow; // 下キー
public KeyCode rightKey = KeyCode.RightArrow; // 右キー
private Button currentSelectedButton;
void Start()
{
if (buttons == null || buttons.Count != 3)
{
Debug.LogError("Buttonが3つ割り当てられていません。");
return;
}
foreach (Button button in buttons)
{
button.onClick.AddListener(() => OnButtonClick(button));
SetButtonState(button, false); // 初期状態は非選択
}
if (buttons.Count > 0)
{
SetButtonState(buttons[0], true);
currentSelectedButton = buttons[0];
}
}
void Update()
{
if (Input.GetKeyDown(leftKey) && buttons.Count > 0)
{
SimulateButtonClick(buttons[0]); // 最初のButtonを左キーに対応させる
}
else if (Input.GetKeyDown(downKey) && buttons.Count > 1)
{
SimulateButtonClick(buttons[1]); // 二番目のButtonを下キーに対応させる
}
else if (Input.GetKeyDown(rightKey) && buttons.Count > 2)
{
SimulateButtonClick(buttons[2]); // 三番目のButtonを右キーに対応させる
}
}
void OnButtonClick(Button clickedButton)
{
if (currentSelectedButton != null && currentSelectedButton != clickedButton)
{
SetButtonState(currentSelectedButton, false);
}
SetButtonState(clickedButton, true);
currentSelectedButton = clickedButton;
Debug.Log(clickedButton.name + " が選択されました。");
}
void SetButtonState(Button button, bool isSelected)
{
Selectable selectable = button.GetComponent<Selectable>();
if (selectable != null)
{
if (isSelected)
{
selectable.Select();
}
else
{
selectable.OnDeselect(null);
}
}
}
public Button GetCurrentSelectedButton()
{
return currentSelectedButton;
}
// Buttonのクリック処理をシミュレートする関数
void SimulateButtonClick(Button button)
{
if (button != null && button.interactable)
{
// ExecuteEvents を使用して、PointerClick イベントをトリガーする
ExecuteEvents.Execute(button.gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);
}
}
}
変更点:
-
キーコードの定義:
public KeyCode leftKey = KeyCode.LeftArrow;public KeyCode downKey = KeyCode.DownArrow;-
public KeyCode rightKey = KeyCode.RightArrow;
インスペクターから対応するキーを変更できるように、publicなKeyCode型の変数を定義しました。初期値はそれぞれ ←、↓、→ キーに設定されています。
-
Updateメソッドの追加:- 毎フレーム実行される
Updateメソッド内で、Input.GetKeyDown()を使用して指定されたキーが押されたかをチェックします。 - 対応するキーが押された場合、
SimulateButtonClick()関数を呼び出し、対応するインデックスのButtonを引数として渡します。
- 毎フレーム実行される
-
SimulateButtonClick関数:- この関数は、指定されたButtonが
nullでなく、かつインタラクト可能 (interactableがtrue) である場合に、そのButtonのクリック処理をシミュレートします。 -
ExecuteEvents.Execute(button.gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);:-
ExecuteEventsは、UI要素に対するイベントをプログラムから実行するためのユーティリティクラスです。UnityEngine.EventSystems名前空間をインポートする必要があります。 -
ExecuteEvents.Execute()を使用して、IPointerClickHandlerインターフェースを実装しているコンポーネント(Buttonなど)にOnPointerClickイベントを送信します。 -
new PointerEventData(EventSystem.current)は、イベントに関するデータを作成します。 -
ExecuteEvents.pointerClickHandlerは、実行するイベントの種類を指定します(ここではクリックイベント)。
-
- この関数は、指定されたButtonが
Unity エディタでの設定:
-
ToggleButtonGroupGameObject のインスペクターに、Left Key,Down Key,Right Keyという項目が表示されていることを確認してください。 - 必要に応じて、デフォルトのキー設定を変更できます。
動作原理:
このスクリプトをアタッチした GameObject がアクティブな状態で、指定されたキー(デフォルトでは ←、↓、→)が押されると、Update メソッドがそれを検知し、対応するButtonの onClick イベントがプログラムから実行されます。これにより、マウスでクリックしたときと同じ挙動(選択状態の切り替え、OnButtonClick 関数の呼び出しなど)がキー入力によっても発生します。
これで、キーボード入力でもトグルボタンを操作できるようになります。
よく使う実装でも正直細かいこと覚えてなくて使うたびに調べながら実装するので、その辺りの手間が省けるのとっても楽でした☺️
🔵落下物:当たり判定とPrefabの生成
colliderをつけたオブジェクトをタグで判断して、"Player"にぶつかるかy座標が0以下になったら自身をdeleteする落下物にアタッチするスクリプトを作成してください
Geminiの回答
はい、Collider をつけたオブジェクトにアタッチして、"Player" タグのオブジェクトと衝突するか、Y 座標が 0 以下になったら自身を削除するスクリプトを作成します。スクリプト (C#):
using UnityEngine;
public class FallingObject : MonoBehaviour
{
void OnCollisionEnter(Collision collision) // 3Dの場合
{
if (collision.gameObject.CompareTag("Player"))
{
Destroy(gameObject);
}
}
void OnTriggerEnter(Collider other) // 3Dの場合、Is Triggerがオンの場合
{
if (other.CompareTag("Player"))
{
Destroy(gameObject);
}
}
void OnCollisionEnter2D(Collision2D collision) // 2Dの場合
{
if (collision.gameObject.CompareTag("Player"))
{
Destroy(gameObject);
}
}
void OnTriggerEnter2D(Collider2D other) // 2Dの場合、Is Triggerがオンの場合
{
if (other.CompareTag("Player"))
{
Destroy(gameObject);
}
}
void Update()
{
if (transform.position.y <= 0f)
{
Destroy(gameObject);
}
}
}
スクリプトの説明:
-
using UnityEngine;: Unity の機能を使用するために必要な名前空間をインポートします。 -
public class FallingObject : MonoBehaviour:FallingObjectという名前の新しいクラスを定義し、MonoBehaviourを継承することで Unity のスクリプトとして機能するようにします。 -
OnCollisionEnter(Collision collision)(3D): 3D Collider が他の Collider と衝突したときに呼び出されます。-
collision.gameObject.CompareTag("Player"): 衝突した相手の GameObject の Tag が "Player" かどうかを判定します。 -
Destroy(gameObject): 自身の GameObject を削除します。
-
-
OnTriggerEnter(Collider other)(3D): 3D Collider が Trigger Collider と接触したときに呼び出されます。(Collider のIs Triggerがチェックされている場合)-
other.CompareTag("Player"): 接触した相手の Collider の GameObject の Tag が "Player" かどうかを判定します。 -
Destroy(gameObject): 自身の GameObject を削除します。
-
-
OnCollisionEnter2D(Collision2D collision)(2D): 2D Collider が他の Collider 2D と衝突したときに呼び出されます。-
collision.gameObject.CompareTag("Player"): 衝突した相手の GameObject の Tag が "Player" かどうかを判定します。 -
Destroy(gameObject): 自身の GameObject を削除します。
-
-
OnTriggerEnter2D(Collider2D other)(2D): 2D Collider が Trigger Collider 2D と接触したときに呼び出されます。(Collider 2D のIs Triggerがチェックされている場合)-
other.CompareTag("Player"): 接触した相手の Collider の GameObject の Tag が "Player" かどうかを判定します。 -
Destroy(gameObject): 自身の GameObject を削除します。
-
-
Update(): 毎フレーム実行されます。-
transform.position.y <= 0f: 自身の Y 座標が 0 以下かどうかを判定します。 -
Destroy(gameObject): 自身の GameObject を削除します。
-
スクリプトの使用方法:
- Unity プロジェクトの Assets フォルダ内に新しい C# スクリプト (
FallingObject.csなど) を作成し、上記のコードをコピー&ペーストします。 - 削除したいオブジェクト(落下物)を選択します。
- 作成した
FallingObjectスクリプトを選択したオブジェクトにドラッグ&ドロップしてアタッチします。 - 選択したオブジェクトに Collider (または Collider 2D) がアタッチされていることを確認します。
- 削除したいオブジェクトが 3D オブジェクトの場合は、
OnCollisionEnterまたはOnTriggerEnterが使用されます。2D オブジェクトの場合は、OnCollisionEnter2DまたはOnTriggerEnter2Dが使用されます。Collider のIs Triggerプロパティの状態に合わせて、適切な関数が呼び出されるようにしてください。 - 削除したいオブジェクトが接触する "Player" オブジェクトに、Tag が "Player" と設定されていることを確認します。
このスクリプトをアタッチしたオブジェクトは、ゲーム実行中に "Player" タグのオブジェクトと衝突するか、Y 座標が 0 以下になると自動的に削除されます。
オブジェクトの落下処理をDotweenで制御したいです。
生成した場所から一定の速度で回転させずに落下させるコードを書いてください
Geminiの回答
はい、オブジェクトの落下処理を Dotween で制御し、生成した場所から一定の速度で回転させずに落下させるコードを記述します。前提条件:
- Dotween が Unity プロジェクトにインストールされている必要があります。インストールしていない場合は、Unity Asset Store から入手するか、Package Manager でインストールしてください。
スクリプト (C#):
using UnityEngine;
using DG.Tweening;
public class FallingObjectDotween : MonoBehaviour
{
public float fallSpeed = 5f; // 落下速度
public float fallDistance = 10f; // 落下距離
public float fallDuration; // 落下にかかる時間
private Vector3 initialPosition; // 生成時の位置
void Start()
{
initialPosition = transform.position; // 生成時の位置を記録
fallDuration = fallDistance / fallSpeed; // 落下時間を計算
// Dotween を使用して落下
transform.DOMoveY(transform.position.y - fallDistance, fallDuration)
.SetEase(Ease.Linear) // 一定速度で落下
.SetAutoKill(true) // アニメーション終了時に自動的に破棄
.OnComplete(() =>
{
Destroy(gameObject); // 落下終了時にオブジェクトを削除
});
}
// 必要に応じて、外部から落下を開始するメソッド
public void StartFalling()
{
Start(); // Start() を呼び出して落下を開始
}
}
スクリプトの説明:
-
using UnityEngine;とusing DG.Tweening;: 必要な名前空間をインポートします。 -
public float fallSpeed = 5f;: 落下速度を制御する公開変数。Inspector から調整できます。 -
public float fallDistance = 10f;: 落下距離を制御する公開変数。Inspector から調整できます。 -
public float fallDuration;: 落下にかかる時間を保持する変数。 -
private Vector3 initialPosition;: オブジェクトが生成された位置を保持する変数。 -
Start():-
initialPosition = transform.position;: オブジェクト生成時の位置を記録します。 -
fallDuration = fallDistance / fallSpeed;: 落下にかかる時間を計算します。 -
transform.DOMoveY(transform.position.y - fallDistance, fallDuration): Dotween のDOMoveY関数を使用して、オブジェクトを指定した距離まで Y 軸方向に移動させます。-
transform.position.y - fallDistance: 落下目標の Y 座標。 -
fallDuration: 落下にかかる時間。 -
.SetEase(Ease.Linear):Ease.Linearを指定することで、一定速度で移動させます。 -
.SetAutoKill(true): アニメーション終了時に Dotween の Tween を自動的に破棄します。 -
.OnComplete(() => { Destroy(gameObject); });: アニメーション終了時に実行するコールバック関数。オブジェクトを削除します。
-
-
-
StartFalling(): 外部から落下を開始させたい場合に呼び出すことができるメソッド。Start()を呼び出すことで落下を開始します。
スクリプトの使用方法:
- このスクリプト (
FallingObjectDotween.csなど) を Unity プロジェクトに作成します。 - 落下させたいオブジェクトにこのスクリプトをアタッチします。
- Inspector で
fallSpeedとfallDistanceを適切な値に設定します。 - 必要に応じて、外部のスクリプトから
StartFalling()メソッドを呼び出して落下を開始させます。
このスクリプトを使用することで、Dotween を使ってオブジェクトを生成位置から一定速度で落下させ、落下終了時に自動的に削除することができます。回転は行いません。
思いついた順にやっていたので機能ごとに分けてしまったのですが、このくらいの内容なら指示出しのプロンプトの時点でまとめてしまった方が出力もまとまって楽だったかも?
ついでに生成処理も書いてもらいます。
このオブジェクトをプレハブ化して、他のスクリプトからランダムに生成したいです。
生成場所のy座標は10固定で、x座標は-2.5~2.5で、獲得スコアに応じて段々と生成時間を早くしたいです
Geminiの回答
はい、オブジェクトをプレハブ化して、他のスクリプトからランダムな位置に、獲得スコアに応じて生成間隔を変化させて生成するコードを作成します。1. 落下オブジェクトのプレハブ化:
- Hierarchy ウィンドウで、落下させたいオブジェクトを準備します。
- そのオブジェクトに、以前作成した
FallingObjectDotween.csスクリプトをアタッチします。 - 必要に応じて、
fallSpeedやfallDistanceなどのパラメータを調整します。 - Project ウィンドウの任意のフォルダに、そのオブジェクトをドラッグ&ドロップしてプレハブを作成します。
- Hierarchy ウィンドウの元のオブジェクトは削除して構いません。
2. 生成を制御するスクリプト (ObjectSpawner.cs など):
using UnityEngine;
using System.Collections;
using DG.Tweening;
public class ObjectSpawner : MonoBehaviour
{
public GameObject fallingObjectPrefab; // 生成する落下物のプレハブ
public float spawnY = 10f; // 生成位置の Y 座標
public float spawnXMin = -2.5f; // 生成位置の X 座標の最小値
public float spawnXMax = 2.5f; // 生成位置の X 座標の最大値
public float initialSpawnInterval = 2f; // 初期生成間隔
public float minSpawnInterval = 0.5f; // 最小生成間隔
public float spawnIntervalDecrease = 0.1f; // スコアごとの生成間隔減少量
public int scoreThreshold = 100; // 生成間隔を短縮するスコアの閾値
private float currentSpawnInterval; // 現在の生成間隔
private float timeUntilNextSpawn; // 次の生成までの残り時間
private int currentScore = 0; // 現在のスコア(外部から設定されることを想定)
void Start()
{
currentSpawnInterval = initialSpawnInterval;
timeUntilNextSpawn = currentSpawnInterval;
}
void Update()
{
timeUntilNextSpawn -= Time.deltaTime;
if (timeUntilNextSpawn <= 0f)
{
SpawnObject();
timeUntilNextSpawn = currentSpawnInterval;
}
}
void SpawnObject()
{
float spawnX = Random.Range(spawnXMin, spawnXMax); // X 座標をランダムに生成
Vector3 spawnPosition = new Vector3(spawnX, spawnY, 0f); // Z 座標は 0 で固定 (2Dの場合)
Instantiate(fallingObjectPrefab, spawnPosition, Quaternion.identity); // プレハブを生成 (回転なし)
}
// 外部からスコアを設定するメソッド
public void SetScore(int score)
{
currentScore = score;
// スコアに応じて生成間隔を短縮
if (currentScore > 0 && currentScore % scoreThreshold == 0 && currentSpawnInterval > minSpawnInterval)
{
currentSpawnInterval -= spawnIntervalDecrease;
currentSpawnInterval = Mathf.Max(currentSpawnInterval, minSpawnInterval); // 最小値を下回らないようにする
}
}
}
3. スクリプトの説明:
-
using UnityEngine;とusing System.Collections;: 必要な名前空間をインポート。 -
public GameObject fallingObjectPrefab;: 生成する落下物のプレハブを Inspector から割り当てるための変数。 -
public float spawnY = 10f;: 生成位置の Y 座標。 -
public float spawnXMin = -2.5f;とpublic float spawnXMax = 2.5f;: 生成位置の X 座標の範囲。 -
public float initialSpawnInterval = 2f;: 初期生成間隔。 -
public float minSpawnInterval = 0.5f;: 最小生成間隔。 -
public float spawnIntervalDecrease = 0.1f;: スコアがscoreThresholdに達するたびに、生成間隔をどれだけ短縮するか。 -
public int scoreThreshold = 100;: 生成間隔を短縮するスコアの閾値。 -
private float currentSpawnInterval;: 現在の生成間隔を保持する変数。 -
private float timeUntilNextSpawn;: 次の生成までの残り時間を保持する変数。 -
private int currentScore = 0;: 現在のスコアを保持する変数(外部から設定されることを想定)。 -
Start():-
currentSpawnIntervalとtimeUntilNextSpawnを初期化。
-
-
Update():-
timeUntilNextSpawnを毎フレーム減らす。 -
timeUntilNextSpawnが 0 以下になったらSpawnObject()を呼び出し、timeUntilNextSpawnをcurrentSpawnIntervalにリセット。
-
-
SpawnObject():-
Random.Range(spawnXMin, spawnXMax)で X 座標をランダムに生成。 -
Vector3 spawnPositionで生成位置を決定 (Y 座標はspawnYで固定、Z 座標は 0)。 -
Instantiate(fallingObjectPrefab, spawnPosition, Quaternion.identity)でプレハブを生成 (Quaternion.identityで回転なし)。
-
-
SetScore(int score):- 外部からスコアを設定するためのメソッド。
- スコアが
scoreThresholdの倍数になったら、currentSpawnIntervalをspawnIntervalDecreaseだけ減らす。 -
Mathf.Max()でcurrentSpawnIntervalがminSpawnIntervalを下回らないように制限。
4. スクリプトの使用方法:
- このスクリプト (
ObjectSpawner.csなど) を、落下物を生成する役割を持つ空の GameObject などにアタッチします。 - Inspector で、
-
Falling Object Prefabに、作成した落下物のプレハブを割り当てます。 -
spawnY、spawnXMin、spawnXMax、initialSpawnInterval、minSpawnInterval、spawnIntervalDecrease、scoreThresholdなどのパラメータを調整します。
-
- スコアを管理している他のスクリプトから、
objectSpawner.SetScore(newScore);のようにSetScore()メソッドを呼び出して、現在のスコアをObjectSpawnerスクリプトに通知します。
このコードを使用することで、落下物を指定範囲の X 座標にランダムに生成し、スコアに応じて生成間隔を短縮することができます。
🟢視界をぼかす:ポストエフェクト
なんとなくポストエフェクトとか使えばできるかなー?と思いつつ、初めての試みなので一旦やり方から聞いてみます。
画面全体にぼやけたようなフィルターをかけることはできますか?
ぼやけ具合はパラメータで調整できるようにしたいです
Geminiの回答
はい、Unityで画面全体にぼやけたようなフィルター(ブラーエフェクト)をかけ、そのぼやけ具合をパラメータで調整できるようにする方法はいくつかあります。一般的な方法:
-
ポストプロセッシングスタック (Post-processing Stack) を使用する: Unityの公式パッケージである Post-processing Stack には、ブラーエフェクトが含まれています。これを使用するのが最も簡単で柔軟性の高い方法の一つです。
-
カスタムのRender TextureとShaderを使用する: より高度な制御が必要な場合や、特定のブラーアルゴリズムを実装したい場合は、Render Texture を使用して画面を一時的なテクスチャにレンダリングし、そのテクスチャに対してブラー処理を行うShaderを作成する方法があります。
1. Post-processing Stack を使用する方法 (推奨):
手順:
-
Post-processing Stack のインストール:
- Unityのメニューから
Window > Package Managerを開きます。 - パッケージリストから
Post Processingを選択し、インストールします。
- Unityのメニューから
-
Post-processing Layer の追加:
- シーン内のメインカメラを選択します。
- インスペクターで
Layerドロップダウンを開き、新しいレイヤーを作成します(例: "PostProcessing")。 - カメラの
Layerを新しく作成したレイヤーに設定します。
-
Post-processing Volume の作成:
- Hierarchyウィンドウで右クリックし、
Create > 3D Object > Post-processing Volumeを作成します。 - 作成した
Post-processing Volumeオブジェクトを選択し、インスペクターのLayerをカメラと同じレイヤー("PostProcessing")に設定します。 -
Is Globalチェックボックスをオンにすると、シーン全体にエフェクトが適用されます。特定の範囲に適用したい場合はオフにして、Collider を設定します。 -
Add Effect...ボタンをクリックし、Blur > Gaussian Blurを選択します。
- Hierarchyウィンドウで右クリックし、
-
ブラーパラメータの調整:
- インスペクターの
Gaussian Blur設定で、以下のパラメータを調整できます。- Radius: ブラーの範囲(ぼかし具合)を制御します。この値を大きくすると、より強くぼやけます。
- Sample Count: ブラーの品質を制御します。値を大きくするとより滑らかになりますが、処理負荷が増加します。
- Downsample: レンダーリング解像度を下げてブラー処理を行うことで、パフォーマンスを向上させます。値を大きくすると画質は低下しますが、処理は軽くなります。
- インスペクターの
-
スクリプトから Radius を制御する (オプション):
-
PostProcessVolumeコンポーネントとGaussianBlurエフェクトのパラメータにアクセスすることで、スクリプトからブラーの強さを動的に変更できます。
using UnityEngine; using UnityEngine.Rendering.PostProcessing; public class BlurController : MonoBehaviour { public PostProcessVolume volume; public float blurIntensity = 0f; public float maxBlur = 10f; public float blurSpeed = 1f; private GaussianBlur blurEffect; void Start() { if (volume == null) { Debug.LogError("PostProcessVolume が割り当てられていません。"); enabled = false; return; } volume.profile.TryGetSettings(out blurEffect); if (blurEffect == null) { Debug.LogError("Gaussian Blur エフェクトが PostProcessVolume に追加されていません。"); enabled = false; return; } } void Update() { // 例: キー入力でぼかし具合を調整 if (Input.GetKey(KeyCode.UpArrow)) { blurIntensity = Mathf.Clamp(blurIntensity + blurSpeed * Time.deltaTime, 0f, maxBlur); } if (Input.GetKey(KeyCode.DownArrow)) { blurIntensity = Mathf.Clamp(blurIntensity - blurSpeed * Time.deltaTime, 0f, maxBlur); } blurEffect.radius.value = blurIntensity; } }- このスクリプトを
Post-processing Volumeオブジェクトにアタッチし、Volumeフィールドに自身を割り当てます。Blur Intensityなどのパラメータを調整してください。
-
2. カスタムの Render Texture と Shader を使用する方法:
この方法はより複雑ですが、より細かい制御が可能です。
手順の概要:
- Render Texture の作成: ぼかし処理の中間結果を保存するための Render Texture を作成します。
- ブラー Shader の作成: 画面テクスチャを入力として受け取り、ぼかし処理を適用する Shader を作成します。ガウシアンブラーなどのアルゴリズムを実装する必要があります。
- Material の作成: 作成した Shader を使用する Material を作成します。
-
スクリプトの作成:
-
OnRenderImage関数を使用して、カメラのレンダリング後に処理を行います。 -
Graphics.Blit関数を使用して、ソーステクスチャ(通常はsourceRenderTexture)を一時的な Render Texture にコピーし、ブラー Shader を適用します。 - ぼかしの強度を制御するパラメータを Shader に渡します。
- 最終的なぼかし画像を
destinationRenderTexture に出力します。
-
- UI RawImage などで表示 (オプション): ぼかし処理後の Render Texture を UI の RawImage などに表示することで、UI 要素のみをぼかすといった応用も可能です。
カスタム Shader の例 (簡易的なボックスブラー):
Shader "Custom/Blur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BlurRadius ("Blur Radius", Float) = 1
_BlurDirectionX ("Blur Direction X", Float) = 1
_BlurDirectionY ("Blur Direction Y", Float) = 0
}
SubShader
{
Cull Off ZWrite Off ZTest Always
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_TexelSize;
float _BlurRadius;
float _BlurDirectionX;
float _BlurDirectionY;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = fixed4(0, 0, 0, 0);
float2 offset = float2(_BlurDirectionX, _BlurDirectionY) * _MainTex_TexelSize.xy * _BlurRadius;
int kernelSize = floor(_BlurRadius * 2 + 1);
float weightSum = 0;
for (int x = -floor(_BlurRadius); x <= floor(_BlurRadius); x++)
{
for (int y = -floor(_BlurRadius); y <= floor(_BlurRadius); y++)
{
float2 sampleUV = i.uv + float2(x * offset.x, y * offset.y);
float weight = 1; // 簡易的な均一な重み
col += tex2D(_MainTex, sampleUV) * weight;
weightSum += weight;
}
}
return col / weightSum;
}
ENDCG
}
}
}
カスタムスクリプトの例:
using UnityEngine;
public class ScreenBlur : MonoBehaviour
{
public Material blurMaterial;
[Range(0, 10)]
public float blurRadius = 2;
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (blurMaterial != null)
{
int rtW = source.width;
int rtH = source.height;
RenderTexture buffer = RenderTexture.GetTemporary(rtW, rtH, 0);
// 水平方向のブラー
blurMaterial.SetFloat("_BlurRadius", blurRadius);
blurMaterial.SetFloat("_BlurDirectionX", 1);
blurMaterial.SetFloat("_BlurDirectionY", 0);
Graphics.Blit(source, buffer, blurMaterial);
// 垂直方向のブラー
blurMaterial.SetFloat("_BlurDirectionX", 0);
blurMaterial.SetFloat("_BlurDirectionY", 1);
Graphics.Blit(buffer, destination, blurMaterial);
RenderTexture.ReleaseTemporary(buffer);
}
else
{
Graphics.Blit(source, destination);
}
}
}
カスタム方法の設定:
- 上記の Shader コードを Unity プロジェクトに保存します(例:
Assets/Shaders/Blur.shader)。 - Shader を選択し、Material を作成します(例:
Assets/Materials/BlurMat.mat)。 - 上記の C# スクリプト (
ScreenBlur.cs) を作成し、メインカメラにアタッチします。 - カメラにアタッチした
ScreenBlurスクリプトのBlur Materialフィールドに、作成した Material をドラッグ&ドロップで割り当てます。 -
Blur Radiusパラメータをインスペクターで調整できます。
どちらの方法を選ぶべきか:
- Post-processing Stack: 簡単な設定で高品質なブラーエフェクトを実現でき、パフォーマンスも最適化されています。特別な理由がない限り、この方法を推奨します。
- カスタムの Render Texture と Shader: より高度な制御や特定のブラーアルゴリズムの実装が必要な場合に有効ですが、Shader の知識やレンダーパイプラインの理解が必要です。
まずは Post-processing Stack を試してみて、要件に合わない場合にカスタムの方法を検討するのが良いでしょう。
やはりPost-processing Stackを試してみるのが良さそうと言うことで、
導入手順に従って入れてみます。
(若干バージョンの違いなどで名称などの表記は違ったのでその辺りは手探りで……)
一つのエフェクトに纏めようとしたらいまいちうまくいかなかったので、二つのエフェクトを重ねがけしました。
VolumeのWeightをゲーム内から変動させると……
だんだんボヤけていく画面を作る事ができました👏
おわりに
Geminiくんのおかげで実質三日くらいでゲームの大枠は完成できました!
(この後ビルドしたら謎のバグに出会ったのですがそれはまた別のお話……)
実を言うと今回作ったゲームは、私がUnityの勉強を始めた頃に作ったシューティングゲームと構成要素がほとんど同じなんですよね。
それもあって、機能ごとに具体的なイメージが自分の中であったので、比較的スムーズにできたのではないかなと思っています。
ある程度欲しい回答を予測して相談を持ちかけないと素っ頓狂な答えが返ってくるので、そう言うところは人間もAIも変わらないですね笑
明日のCYBIRD Advent Calendar 2025 13日目は、@ice_matcha3さんの「Fargate Spotの中断が頻発したためどうにかした話」です!
お楽しみに👋



