【Unity】unity1week「回」で使った小技集
ご!ピクジンです。
Nintendoオンラインのいっせいトライアルで「スチームワールドディグ2」を遊びはじめました。
面白いので皆さんも遊んでみてください。
さて、今回はunity1week「回」で使った小技のご紹介です。
ゲーム概要
ゲーム内容のおさらいです。
タイトル:GEAR=01
ジャンル:シューティング
自分の動きと連動して敵が回転するシューティングゲーム。
プレイはこちらからどうぞ。
コンセプトについてはこちらをご覧ください。
キーボード操作とタッチ操作の両立
今回の目標の一つは、スマホからも遊べるゲームを作ることでした。
スマホでTwitterを利用してる人が大半なので、アクセス数を増やす狙いです。
両方の操作に対応するために、入力を検知するためのクラスを用意しました。
このクラスをプレイヤーや敵オブジェクトに参照させて動かしました。
スクリプトが汚いのはご愛嬌
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InputManager : MonoBehaviour
{
//右と左、どちらを入力してるか判別する変数
int input_num = 0;
public int Input_num
{
get { return input_num; }
}
//操作可能かどうかのフラグ
bool controlable = false;
public bool Controlable
{
//外部スクリプトからcontrolableを切り替えた時にその他のフラグを初期化
set
{
controlable = value;
input_num = 0;
push_flag = false;
shot_flag = false;
}
}
//UIを押してるかどうかのフラグ
bool push_flag = false;
//攻撃するかのフラグ
bool shot_flag = false;
public bool Shot_flag
{
get { return shot_flag; }
}
void Update()
{
if (!controlable) return;
Rotation();
Shot();
}
//キーボードで水平方向に入力があれば回転
void Rotation()
{
//UIを押してたら無効
if (push_flag) return;
float _horizontal = Input.GetAxisRaw("Horizontal");
input_num = (int)_horizontal;
}
キーボードでSpaceキーを押してたらショット
void Shot()
{
//UIを押してたら無効
if (push_flag) return;
shot_flag = Input.GetKey(KeyCode.Space);
}
//イベントトリガーのPointerDown
public void pointerDown(int num)
{
if (!controlable) return;
if (push_flag) return;
if (input_num != 0) return;
input_num = num;
push_flag = true;
shot_flag = true;
}
//イベントトリガーのPointerUp
public void pointerUp(int num)
{
//UIを押していなければ終了
if (!push_flag) return;
//移動方向と違うボタンだったら終了
if (num != input_num) return;
input_num = 0;
push_flag = false;
shot_flag = false;
}
}
歯車や敵の回転、攻撃処理は次のように行います。
//回転処理
[SerializeField]
private InputManager inputManager = null;
[SerializeField]
private int speed = 120;
void FixedUpdate()
{
transform.Rotate(0, 0, speed * inputManager.Input_num * Time.deltaTime);
}
//攻撃処理
void Shot()
{
if (!inputManager.Shot_flag)
{
current_coolTime = max_coolTime;
return;
}
current_coolTime += Time.deltaTime;
if (current_coolTime < max_coolTime) return;
current_coolTime = 0;
Bullet bullet = bulletPool.bulletSerch();
bullet.ForceSet(transform.position, transform.eulerAngles.z);
SE.PlayOneShot(se_shot);
}
なお、キーボードで操作してもタッチで操作してもボタンの色が変わってることにお気付きでしょうか?
これはボタンUIに以下のようなスクリプトを付けてるためです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class InputButtonChange : MonoBehaviour
{
Image image;
[SerializeField]
private InputManager inputManager = null;
//どちらのボタンかの判定用
[SerializeField]
private int _horizontal = 1;
//現在ボタンの色が変わってるか否かのフラグ
bool change_flag = false;
private void Awake()
{
image = GetComponent<Image>();
}
void Update()
{
if (change_flag)
{
if (_horizontal == inputManager.Input_num) return;
Color color = Color.white;
color.a = 0.7f;
image.color = color;
change_flag = false;
}
else
{
if (_horizontal != inputManager.Input_num) return;
Color color = Color.gray;
color.a = 0.8f;
image.color = color;
change_flag = true;
}
}
}
入力を検知するInputManagerクラスを勝手に監視してるだけなので、複雑なコードにしなくて済みました。
オブジェクトプール
シューティングではおなじみの手法です。
いろいろなサイトにあるシューティングゲームのチュートリアルでこれ使ってるのは見たことありませんが、やっておくに越したことはありません。
(正直このくらい小規模な開発ではあってもなくても同じなので......)
といっても、処理は以前のシューティングゲーム開発時に使った処理を移植しただけです。
使い回しは楽でいいですね。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletPool : MonoBehaviour
{
[SerializeField]
private Bullet bullet_prefab = null;
List<Bullet> child_bullet = new List<Bullet>();
public Bullet bulletSerch()
{
最初の一発目は必ず生成
if (transform.childCount == 0) return bulletCreate();
foreach (var item in child_bullet)
{
//RigidBody2Dのsimulatedがfalseのもの=使ってない弾なので返り値にする
if (!item.rigid_simulated) return item;
}
return bulletCreate();
}
Bullet bulletCreate()
{
Bullet bullet = Instantiate(bullet_prefab, transform.position, Quaternion.identity);
bullet.gameObject.transform.parent = transform;
child_bullet.Add(bullet);
return bullet;
}
}
攻撃するタイミングでbulletSerch()を呼び出せば、生成するなり元からあるものを引っ張り出して来るなりします。
歯車の回転音
カチコチと鳴る歯車ですが、元の音は効果音ラボさんの「振り子時計」です。
そのままだとテンポが遅いので、ピッチを3倍にしています。
オーディオソースのピッチを変えただけでは音の高さまで変わってしまうので、オーディオミキサーのPitch Shifterのピッチを下げます。
そうすれば、音の高さはそのままにテンポだけ変更できます。
(Pitch Shifterは0.5〜2までしかないので、今回の例では少し音が高くなってしまいます。)
効果音の再生方法は以下の通りです。
[SerializeField]
private InputManager inputManager = null;
AudioSource audioSource;
//SEが再生中かどうかのフラグ
bool se_playing = false;
private void Update()
{
if (!se_playing)
{
if (inputManager.Input_num == 0) return;
se_playing = true;
audioSource.Play();
}
else
{
if (inputManager.Input_num != 0) return;
se_playing = false;
audioSource.Stop();
}
}
Cinemachine
いい感じのカメラ追従をしてくれるCinemachine、特定のオブジェクトを追いかける以外にも便利な機能が揃ってます。
今回利用したのは基本機能の一つであるNoiseと、追加機能である画面を揺らすCinemachineImpulseLisnerです。
Noiseを設定すると、カメラがゆらゆらと動きます。
大きく動かれると困るので、動いてるか動いてないかわからないくらいの範囲にしました。
CinemachineImpulseLisnerをVirtualCameraに付けたら、カメラを揺らすトリガーになりそうなオブジェクトにCinemachineImpulseSourceを付けます。
これはプレイヤーオブジェクトに付けたもの。
敵オブジェクトにも付ければ、敵が倒される度に画面が揺れて迫力が出ます。
プレイヤーがミスしたときは更に揺れるのでショックもさらに大きく......
画面を揺らしたいときは以下のようにします。
//敵オブジェクト
CinemachineImpulseSource ImpulseSource;
public void Defeated()
{
ImpulseSource.GenerateImpulse();
Destroy(this.gameObject);
}
実際に動くとこんな感じ。
Timeline&DOTween
アニメーションさせるUIが多かったので、Timelineで管理。
最終スコアを表示する処理(数字がだんだん増えていくやつ)はTimelineだけでは無理なのでDOTweenを使用。
DOTweenPro限定のDOCounterを使いました。
TextMeshProUGUI score_final_text;
score_final_text.DOCounter(0, score_final, 0.3f).SetEase(Ease.Linear);
なお、無料版でもこうすれば同じような処理になります。
DOVirtual.Float(0, score_final, 0.3f, value =>
{
score_final_text.text = (int)value.ToString();
}
終わり
以上になります。
こうして振り返ると今までの経験がちゃんと糧になってるな〜って思います。
右も左も分からなかった時よりも格段に作るの上手くなってます。
またこんど。