【Unity2d】区間スクロールを実装しよう
こんにちは。なおキーヌです。
ブログ毎日更新は358日目になります。
基本的なマップ構造が出来上がったのでプレイヤーがエリア移動用のイベントを踏んだら
画面をスクロールするようにしてみましょう。
イベントが発動したら移動状態にステータスが切り替わってカメラをスクロールする感じです。
スクロールを実装してみる
衝突判定を利用してイベントを実装する訓練も兼ねてやってみましょう。
手順としては
- 衝突判定にプレイヤーが触れる
- イベントが発動
- キー操作を無効にする
- カメラの座標をスクロールさせる
- プレイヤーを進行方向に1マス分移動させる
- イベント終了
- キー操作を有効にする
こんな感じです。
まずは触れた時に発動するイベントの仕組みを作りましょう。
触れたら
衝突判定に触れた時に発動する関数はもう使っているのでサクサクと作っていきます。
イベントスイッチは地面に設置しますが、そのスクリプトはスイッチを切り替えるだけです。
後は他のスクリプトに処理を任せてしまわないとスクリプト整理をした意味がありません。
プレイヤーのステータスに操作状況を保存する変数を作りましょう。
0がプレイヤー操作可能
1が画面スクロール
みたいな感じで数値によってプレイヤーのステータスを決めてしまいましょう。
State_Managerに以下のクラス変数を作成します。
// 現在のシーン状態
protected static int _gameSceneState = 0;
public int GameSceneState {
get {
return State_Player._gameSceneState;
}
}
private int GameSceneChange(int sceneNo) {
set {
_gameSceneState = sceneNo;
}
}
これでシーン状態を管理していきます。
変更はイベント側で通知を行い、State側で数値を変更します。
基本的に外部からは変更できないようにしておかないとバグに繋がってしまうので、
この辺の数値変更管理は徹底しておきます。
次に通知を受け取ったらシーン変数を変更する関数を作りましょう。
// シーン変数チェンジ
public void setSceneControl(int sceneNo) {
State_Manager._gameSceneState = sceneNo;
}
結局これを使えばどこでも書き替えられてしまうのですが、
原因がどこか掴みやすくなるのは大違いです。
本来はもうちょっとセキュリティをあげておいたほうがいいのでしょうが、
あまりガチガチにしてしまうと制作の本質を見失ってしまうのでほどほどにしておきましょう。
踏んだらステートチェンジ
空オブジェクトに衝突判定だけ持たせてそれを踏んだらプレイヤー側で衝突判定を取って、
そのオブジェクトが持つ値を見てステートを変更します。
これだとセッター作った意味なくない?ってなるかと思うのですが、
後にイベント中に値を書き替えたい場合に結局作るハメになるので問題ありません。
早速空オブジェクトを配置しましょう。
衝突判定はBoxCollider2Dを使います。
最初は2つ作って踏んだら向いてる方向でどちらかに生かせようかと思ったのですが、
後ろ向きに入ったときの対応が面倒なので降れたとにプレイヤーが中心部から上下左右どこにいるのかで移動判定を行おうと思います。
なのでエリア移動のところはオブジェクトは1つで済みます。
大きさは上下に移動する場所なら横幅はブロックにめり込むぐらいでいいでしょう。
縦幅は8pxとしました。
あまり大きいと予期せぬところで衝突判定がとれてしまうのでなるべくエリア移動の
くぼみにプレイヤーが入って少し進んだらエリア移動するようにします。
画像で見るとこんな感じになります。
そしてこのオブジェクトにエリアスクロールと判別するためのタグを付けます。
スクリプトで値を持っても良かったのですが、そこまで値を多く持っていないのでタグで判別します。
プレイヤー側のスクリプトで衝突判定の処理を取りましょう。
現状プレイヤーはスクリプトを持っていないので、他のオブジェクトにも付けられる汎用クラスを作りましょう。
「_ObjectCollision.cs」
を作成してください
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class _ObjectCollision : MonoBehaviour
{
// プレイヤーステートスクリプトの取得
private State_Player _State;
// Start is called before the first frame update
void Start()
{
_State = GameObject.FindGameObjectsWithTag("State_Manager")[0].GetComponent<State_Player>();
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter2D(Collider2D other) {
// 移動モード変数
int sceneNo = 0;
// タグにより移動モードを決定
switch (other.tag) {
case "AreaChangeV":
// 上下判定
if (transform.position.y > other.transform.position.y) {
// プレイヤーが上にいる場合は下スクロール
sceneNo = 1;
} else {
// プレイヤーが下にいる場合は下スクロール
sceneNo = 2;
}
// モードのステートを設定
_State.setSceneControl(sceneNo);
break;
case "AreaChangeH":
// 上下判定
if (transform.position.x < other.transform.position.x) {
// プレイヤーが左にいる場合右はスクロール
sceneNo = 3;
} else {
// プレイヤーが右にいる場合左はスクロール
sceneNo = 4;
}
// モードのステートを設定
_State.setSceneControl(sceneNo);
break;
default:
break;
}
}
}
プレイヤーがAreaChangeVかAreaChangeHのタグを持っているオブジェクトに触れれば、
イベントが発動する仕掛けにしました。
エリア移動オブジェクトの衝突判定の座標が中央なのでそれよりプレイヤーが上に居るか下に居るか、
もしくは左にいるか右にいるかの判定をしてスクロール方向を決めます。
これで判定を取れるようになったので、ステートが変わったらキー操作とかを無効にします。
State_Playerの以下の関数を変更してください。
protected override void Update() {
switch(this.GameSceneState) {
// 操作可能モード
case 0:
// 十字キーが一つでも格納されてたら稼働
if (_cm.PushedArrowKeyList.Count > 0) { setDirection(_cm.PushedArrowKeyList); }
// ステートをセットする
setState(_cm.PushedKeyList,_cm.PushedArrowKeyList);
// 向きとステートを元にアニメーションを設定
setNowAnim(_animator, _hammer);
// 移動タイプ変更
setMoveType();
break;
// エリアスクロールモード
case 1:
case 2:
case 3:
case 4:
break;
}
}
1~4の時はなにも動かさないようにしましたが、このままでは移動状態が固定されてしまい
スクロールと一緒に動いてしまいます。
なのでエリアスクロール中は移動を強制的にとめるようにしましょう。
キー操作を空にして呼び出そうと思いましたが、ステートを強制的に0(停止状態)にする関数を作りました。
こうすると何かと使えそうなのでいい判断かもしれません。
State_ManagerにsetStateReset関数を作ります。
// 強制的にステートを停止にする
protected void setStateReset() {
_actState = 0;
}
アニメーションは_actStateを見て切り替えられるので特にリセット関数は無くても大丈夫でしょう。
setMoveTypeは今のところモノを引きずるか押すかでないと動かないので一旦スルーします。
しかしこのままだと触れた瞬間にとまってしまいますね。
スクロールが終わった瞬間、またスクロールイベントが発生してしまいます。
これではスイッチ処理を忘れたツクールの自動実行イベントみたいになってしまうので修正案を考えてみましょう。
私はこの時点で2つ方法を考え付きました。
1つ目は衝突判定から抜けるまでは移動処理をそのままにしておいて、
抜けたら移動を強制的に止める方法。
2つ目は触れた時に発動ではなく、触れて判定から出た時に駆動するようにするか。
後者は判定に入って進まずに戻って出た場合、面倒くさいことになりそうなので前者にしましょう。
まずは_actStateのセッターを作ります。
そして先ほど作った関数も一応それを通して値を書き替えるようにしましょう。
// _actState用プロパティ
public int ActState{
get{
return State_Manager._actState;
}
set {
State_Manager._actState = value;
}
}
// 強制的にステートを停止にする
public void setStateReset() {
ActState = 0;
}
あまりよろしくはない方法ですが、プレイヤーの持つ_ObjectCollision側でStateを変更しようと思います。
いい方法を思いついたらまた書き直しますので一旦進めましょう。
衝突判定から出るまでは歩かせたいので、出た時にステートを変更する処理を作ります。
private void OnTriggerExit2D(Collider2D other) {
// エリアスクロール中は判定から抜けたらステートを強制0にする
if (_State.GameSceneState >= 1 || _State.GameSceneState <= 4) {
_State.ActState = 0;
}
}
これによりステート強制0の関数を外してこうなります。
protected override void Update() {
switch(this.GameSceneState) {
// 操作可能モード
case 0:
// 十字キーが一つでも格納されてたら稼働
if (_cm.PushedArrowKeyList.Count > 0) { setDirection(_cm.PushedArrowKeyList); }
// ステートをセットする
setState(_cm.PushedKeyList,_cm.PushedArrowKeyList);
// 向きとステートを元にアニメーションを設定
setNowAnim(_animator, _hammer);
// 移動タイプ変更
setMoveType();
break;
// エリアスクロールモード
case 1:
case 2:
case 3:
case 4:
// 向きとステートを元にアニメーションを設定
setNowAnim(_animator, _hammer);
break;
}
}
これでエリアスクロール中にのみ発動するようにすれば衝突判定から出るまでは前進してくれます。
スクロール処理を作る
スクロールする準備はできたので次は実際のスクロール処理を作りましょう。
カメラを動かすのでMove_Cameraスクリプトを作成してください。
内容はこんな感じになります。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move_Camera : Move_Manager
{
// カメラオブジェクト
private GameObject _MainCamera;
// プレイヤーステートスクリプトの取得
private State_Player _State;
private float _ScrollSpeed = 30;
// 縦スクロール量
private int _vScroll = 192;
private int _vDefaultScroll = 136;
// 横スクロール量
private int _hScroll = 288;
private int _hDefaultScroll = 528;
// 更新処理
protected override void Start() {
// プレイヤーオブジェクトの取得
_MainCamera = GameObject.FindGameObjectsWithTag("MainCamera")[0];
_State = GameObject.FindGameObjectsWithTag("State_Manager")[0].GetComponent<State_Player>();
}
protected override void Update() {
_areaScroll(_State.GameSceneState);
}
// ステートが1-4だとスクロールする
private void _areaScroll(int scrollNo) {
switch(scrollNo) {
case 1:
// カメラオブジェクトを移動させる
_MainCamera.transform.Translate(0, -_ScrollSpeed/10, 0);
// _vScrollで割り切れたらスクロール停止(切り捨て
if( (Math.Floor(_MainCamera.transform.position.y)-_vDefaultScroll) % _vScroll == 0) {
_State.setSceneControl(0);
}
break;
case 2:
_MainCamera.transform.Translate(0, +_ScrollSpeed/10, 0);
// _vScrollで割り切れたらスクロール停止(切り上げ
if( (Math.Ceiling(_MainCamera.transform.position.y)-_vDefaultScroll) % _vScroll == 0) {
Debug.Log(Math.Ceiling(_MainCamera.transform.position.y));
_State.setSceneControl(0);
}
break;
}
}
}
スクロールする量と位置は完全に固定になるので、数値をメモしておくとよいでしょう。
私の場合はYが192、Xが240ずつスクロールします。
初期値がYが135、Xが528になります。
初期値が割り切れない数値なので、各数値で割り切る場合は初期値の座標を記憶しておき
計算する時はマイナスしてあげなければいけません。
このスクリプトは同じくしてMoveManagerにアタッチします。
カメラは最初からMainCameraというタグが付いているので特に設定しなくても大丈夫です。
これで実行すると
よっしゃ 良い感じにエリアスクロール実装できた
衝突判定から抜けるまでは歩き続けるようにしたら判定から抜けるまで歩くようにしてスクロールイベント再発防止してくれる
これしなかったらツクールのスイッチきり忘れ自動実行みたいにスクロールし続けてしまった
Unity便利だぁ pic.twitter.com/MKRP6Or8py
— なおキーヌ@ゲームクリエイターLv5 (@naokeyzmt) December 23, 2019
良い感じにスクロールできてますね。
ステートの変更をステート以外でやっちゃったのが心残りですが……
ステート内で完結しようとすると逆にソースが複雑になりそうなので、
基本的に単独のスクリプトの場合は例外としてOKとして進めます。
次回ゲームを作るときはこれをNGにしてみれば成長しそうですね。
たまには可読性を取って妥協することも大事です。
チーム開発だとよろしくはないですがね……
個人開発なのでヨシ!
マネージャースクリプト補足
そういえばゲーム全体を制御するスクリプトを作ってなくて後悔しました。
なのですべてのマネージャーを統括するシーンマネージャークラスがあった方が良さそうですね。
製品版を作るときにはしっかりと実装します。
まぁステートで管理しておけば別になくてもいいんですがね。
体験版ではステートクラスだけで問題ないかのチェックも試みたいのでこのまま進めます。
それでは!