あのゲームのタップしたときのエフェクトかっこいいなぁ!
ぼくにもできるかな??
大丈夫、解説するからきっとできるよ!
いまやモバイルでは必須となっているタップエフェクト、あるのとないのとでは見た目に大きな差がでます。しかし、ゲーム部分に必死でつい後回しにして忘れてしまいがちですよね。
実装は簡単なので最初に入れておくとTwitterの進捗も豪華になってやる気が上がると思います!
本記事では、タップした場所にエフェクトを出す方法と、その応用でトレイル出す方法を解説します。
まずは結論を簡単にご紹介します。
タップ位置を座標変換してパーティクルを生成
- Input Systemからタップ位置を取得
- タップ位置を3D空間の座標系に変換
- パーティクルを生成
こんな感じです。手順にすると特に難しいことはないのですが、今回はInput Systemでタップ位置を取得しますので慣れていない方はこちらもご覧ください。
WebGL対応 コントローラを使う方法(Input Systemの使い方)
タップエフェクトとは
タップやクリックをしたときに、その場所がわかるように出てくるエフェクトです。モバイルゲームではよくある演出ですね。
なお、動画中のアセットは下記です。
モバイルタッチエフェクトはかわいくてよく使ってます。
タップエフェクトを出す手順
おさらいですが、タップエフェクトは3ステップ実装します。
※Input Systemを使えばマウスとスマホのタップを同時に対応できるので、以降、これらをまとめてPointerと呼ぶことにします。
- Input SystemからPointer位置を取得
- Pointer位置を3D空間の座標系に変換
- パーティクルを生成
Input SystemからPointer位置を取得
なにはともあれ、まずはPointer位置を取得しましょう。今回は新たな標準となりつつあるInput Systemを使います。
Input Actionの作成
Input Action は2つのアクションを作ります。
アクション | Action Type | バインド |
---|---|---|
Pointer | Value Vector2 | Position[Pointer] |
Click | Button | Press[Pointer] |
初めてInput Systemを使う方は下記も併せてごらんください。
WebGL対応 コントローラを使う方法(Input Systemの使い方)
Player Inputの配置
次に、PlayerInputコンポーネントを配置します。
ヒエラルキーにEventSytemが無ければ追加しておいてください。
今回はPlayerInputをEventSystemの下に追加してみました。Actionsのところに、さきほど作ったInput Actionのファイルを入れておきます。
イベントハンドラのスクリプトを作る
Input Actionで設定したPointerのイベントはPointer位置が変更されるときに何かするためのものです。このようにイベントが発火したときに何かをするメソッドのことをイベントハンドラといいます
タップのエフェクトに必要なのはPointer位置が変化したときのイベントハンドラとPointerがクリック/タップされたときのイベントハンドラです。
※旧来の方法ではInput.mousePositionで直接取っていましたが、Input Systemではできないようです
PointEffect.cs
public void OnPointer(InputAction.CallbackContext context)
{
// 位置を移動
if (context.performed)
{
//ここに処理をかく
}
}
public void OnClick(InputAction.CallbackContext context)
{
if (context.performed)
{
//ここに処理をかく
}
}
これをPlayerInput のイベント欄に登録します。
PointerEffect.csスクリプトを適当なオブジェクト(例ではPointEffect)にアタッチして、
Player InputのEvents欄に入れます。
これで、Pointerの位置とクリックのイベントをスクリプトで受け取ることができるようになりました。
タップ位置を3D空間の座標系に変換
Pointerの位置はさきほどのイベントハンドラOnPointer(InputAction.CallbackContext context) で受け取ることができます。
Vector2 tmp = context.ReadValue<Vector2>();
ここで読み取れる座標はスクリーン座標系です。左下がゼロで右上が画面サイズです。
しかも、タッチ位置でしかないので奥行Zの値がありません。
なので、3D空間にエフェクトを出すには座標を変換する必要があります。
変換にはCameraクラスのScreenToWorldPointを使います。
// 座標変換するためのカメラ
[SerializeField] Camera tap_camera;
// 現在のポインター位置(マウス、タップでドラッグ)
public Vector3 cursor_point { private set; get; }
public void OnPointer(InputAction.CallbackContext context)
{
if (context.performed)
{
// 位置を取得
Vector2 tmp = context.ReadValue<Vector2>();
// 座標変換
cursor_point = tap_camera.ScreenToWorldPoint(new Vector3(tmp.x, tmp.y));
// 奥行Zを設定しておく
cursor_point = new Vector3(cursor_point.x, cursor_point.y, trail_z);
}
}
cursor_point はPointerの3D空間位置を保存しておく変数です。タップされたときに参照します。
なお、カメラはOrthographicである必要があり、エフェクトより手前側にはオブジェクトは置けません。もしメインカメラがPerspectiveの場合やOrthographicでも複雑な3Dゲームの場合は、タップエフェクト専用のCameraを追加してください。
エフェクト専用カメラはいくつか設定が必要です。
設定場所 | 設定値 |
---|---|
Clear Flags | Depth only |
Culling Mask | タップエフェクトのレイヤ ※レイヤを新設してください そしてエフェクトのPrefabをそのレイヤにしてください |
Projection | Perspective |
Depth | 99 ※一番大きいカメラが一番手前に表示されます 低いとほかのオブジェクトの後ろにいってしまいます |
パーティクルを生成
ここまでで3D空間のどこにタップエフェクトを出すべきかの座標が取得できました。
次はエフェクト部分を生成していきます。
// タップしたときのエフェクト(インすペクタからセット)
[SerializeField] GameObject TouchEffectsPrefab;
public void OnClick(InputAction.CallbackContext context)
{
if (context.performed)
{
GameObject o = Instantiate(TouchEffectsPrefab);
o.transform.SetParent(transform);
o.transform.position = cursor_point;
}
}
特に難しいことはないと思います。エフェクトをPrefabからInstantiateして、位置を変更しています。
なお、SetParentは野良オブジェクトになるのを嫌っているためで、必須ではありません。
トレイルを出す手順
タップのエフェクトが出せるようになりましたので、次はトレイルを出してみましょう。
ここでいうトレイルはUnity機能のTrailではなく、ドラッグしたときの軌跡を追いかけるエフェクトのことです(一般的な用語ではないかもしれません・・・)。
(準備)トレイル用のエフェクトについて
1点注意があります。トレイル用のエフェクトは単発ではなく無限に続くものを使ってください。
設定方法は本記事の範囲を超えるので割愛させていただきますが、Mobile Touch Effect Packなどの対応したエフェクトを使うのもアリだと思います。私は自作を諦めました。
トレイルの生成
トレイルはタップの場合とは異なり1つだけ生成しておきます。
タップは同時に複数エフェクトが再生中になるのでタップの都度に生成していましたが、トレイルは唯一のため1つだけ用意します。ドラッグしていないのに表示されては困るので、Startで非Activeにしておきます。
PointEffect.cs
// トレイルのエフェクト
[SerializeField] GameObject TrailEffectsPrefab;
// 実体化したトレイル
GameObject _trail;
// トレイル中かどうか
bool isTrailing;
// 現在のポインター位置
public Vector3 cursor_point { private set; get; }
private void Start()
{
_trail = Instantiate(TrailEffectsPrefab);
_trail.transform.SetParent(gameObject.transform);
Trail(false);
}
public void Trail(bool isOn)
{
isTrailing = isOn;
_trail.SetActive(isOn);
_trail.transform.position = cursor_point;
}
}
トレイルの表示
エフェクトの生成ができましたので、次はタップ中はエフェクトが追いかける処理をします。
タップしたときに「トレイル中」のフラグをONにして、タップが離れたときにOFFにします。
エフェクトの位置はUpdateで常に更新しておきます(タップのときに作ったイベントハンドラOnPointerで更新されている位置を使います)
// トレイルのエフェクト
[SerializeField] GameObject TrailEffectsPrefab;
// 実体化したトレイル
GameObject _trail;
// トレイル中かどうか
bool isTrailing;
// 現在のポインター位置(マウス、タップでドラッグ)
public Vector3 cursor_point { private set; get; }
private void Start()
{
_trail = Instantiate(TrailEffectsPrefab);
_trail.transform.SetParent(gameObject.transform);
Trail(false);
}
public void OnClick(InputAction.CallbackContext context)
{
if (context.performed)
{
Trail(true);
} else if (context.canceled)
{
Trail(false);
}
}
private void FixedUpdate()
{
if (isTrailing)
{
_trail.transform.position = cursor_point;
}
}
public void Trail(bool isOn)
{
isTrailing = isOn;
_trail.SetActive(isOn);
_trail.transform.position = cursor_point;
}
}
これでドラッグした位置をエフェクトが追いかけてくれるようになりました。
スクリプト全文
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
public class PointEffect : MonoBehaviour
{
// 座標変換するためのカメラ
[SerializeField] Camera tap_camera;
// タップしたときのエフェクト
[SerializeField] GameObject TouchEffectsPrefab;
// トレイルのエフェクト
[SerializeField] GameObject TrailEffectsPrefab;
// 実体化したトレイル
GameObject _trail;
// トレイル中かどうか
bool isTrailing;
// 現在のポインター位置(マウス、タップでドラッグ)
public Vector3 cursor_point { private set; get; }
// カメラ前面に出したい
[SerializeField] float trail_z = -2;
private void Start()
{
_trail = Instantiate(TrailEffectsPrefab);
_trail.transform.SetParent(gameObject.transform);
Trail(false);
}
public void OnPointer(InputAction.CallbackContext context)
{
// 位置を移動
if (context.performed)
{
// 位置を取得
Vector2 tmp = context.ReadValue<Vector2>();
// 座標変換
cursor_point = tap_camera.ScreenToWorldPoint(new Vector3(tmp.x, tmp.y));
// 奥行Zを設定しておく
cursor_point = new Vector3(cursor_point.x, cursor_point.y, trail_z);
if (DebugText.instance != null)
{
DebugText.instance.Log("ReadValue = " + tmp);
DebugText.instance.AddLog("World Position = " + cursor_point);
}
}
}
public void OnClick(InputAction.CallbackContext context)
{
if (context.performed)
{
GameObject o = Instantiate(TouchEffectsPrefab);
o.transform.SetParent(transform);
o.transform.position = cursor_point;
Trail(true);
} else if (context.canceled)
{
Trail(false);
}
}
private void FixedUpdate()
{
if (isTrailing)
{
_trail.transform.position = cursor_point;
}
}
public void Trail(bool isOn)
{
isTrailing = isOn;
_trail.SetActive(isOn);
_trail.transform.position = cursor_point;
}
}
Unityでタップエフェクトとトレイルを出す方法のまとめ
本記事ではタップした位置にエフェクトを出す方法と応用でトレイルを出す方法を解説しました。
- Input Systemからタップ位置を取得
- タップ位置を3D空間の座標系に変換
- パーティクルを生成
タップエフェクトはゲーム機能としては必須ではないので後回しにしがちですが、あまり難しいことをせずに画面を豪華にできるので最初に実装しておくとよいと思います。
皆様のゲームが豪華になることを祈っております。
かわいい我が子にオリジナルアプリを!
コメント