【unity1week】「ふえる」に参加しました!


はじめに

2020年8月10日〜16日に unity1week が開催されました。 お題は「ふえる」。投稿数は過去最高で500を超えました! 参加された皆さん、お疲れ様でした!!!

自分が作ったものはコチラになります!

unityroom.com

また、リポジトリも公開しているので、アドバイスなどありましたらよろしくお願いします!!!

github.com

今回のunity1weekではどのように作っていたかを振り返りたいと思います


目標

  • 公開時間(16日20時)に間に合わせる

  • 前回(「つながる」)よりも良い評価をいただく

これだけです!


制作過程

当初の予定では

  • 1日目 企画決め
  • 2~5日目 ゲームフローの作成
  • 6日目 演出・音など
  • 7日目 予備日(公開日)

のように進めるつもりでしたが、だいぶ狂ってしまいました(自ら狂わせたんですけどね...)

実際の制作過程です

1日目 企画決め

簡単なルール / 操作でテンポがいい(リトライしやすい)ゲームにしようと思いましたが、全然思いつきませんでした

そこで!

www.youtube.com

【Unity道場 5月 〜ゲームジャムで役立つ超速ゲーム企画〜】を参考に

と決めました

2日目 ゲームフローの作成①

物理挙動あるあるですね

エッジコライダーとポリゴンコライダーを間違えてました...

3日目 ゲームフローの作成②

「ふやす」ができました

今回のコア部分! 個人的に気に入っています!

4日目 ゲームフローの作成③

ゲームっぽくなりました

が、つまらない... どうすれば面白くできるのか...

5日目 オンライン対戦の追加①

ということで(?)、思いつきでオンライン対戦を組み込むことに!

マッチング周りについてはほぼ過去のコードを再利用しています(以前に作っててよかった〜)

6日目 オンライン対戦の追加②

ターン切り替えのタイミングが悪い結果...

これは仕様です 両者敗北にはならないのでセーフ

7日目 ラストスパート

余裕がなくて進捗ツイートできてなかったのですが、20時には間に合いました!

この日は「見た目」や「演出」を主にやってました

10日目 後日談

これで両者勝者にならなくなりました

が!たまに判定中で止まってしまうことがあり...(コライダーのめり込みが原因?)

コライダーのめり込みが発生しにくいような工夫はしたのですが、解決できませんでした...


結果

f:id:atas10703:20200903210122p:plain

116名の方から評価を、25名の方からコメントをいただきました。(9月3日22時)

遊んでくださった方、実況配信してくださった方、それにコメントしてくださった方

ありがとうございました!!!

さて、気になる評価の結果は...

f:id:atas10703:20200903210551p:plain

  • 楽しさ

    • 3.767
    • 自分でも楽しめるようなゲームにできなかったので、次回は頑張ろうと思います(次回っていつの話になるんだろう?)
  • 絵作り

    • 3.905
    • ふやした時のエフェクトとかでしょうか?
    • 絵作りムズカシイ...
  • サウンド

  • 操作性

    • 4.147 🎉第9位🎉
    • コメント等をみると操作感も含まれているのかな、と思いました
  • 雰囲気

    • 3.81
    • 雰囲気もムズカシイ...
  • 斬新さ

    • 3.44
    • 個人的には皆無だと思っていたので、もっと低いと思っていました

さいごに

目標は達成できましたし、モチベもかなり上がりました、ありがとうございました

次回は「絵作り」「雰囲気」で良い評価をいただけるように頑張りたいと思います

オンライン対戦作るならレート機能作るべきだった

【Unity】ObjectPoolをちょっとだけラクに実装する



はじめに

Unityではオブジェクトの生成・破棄は負荷が大きいため、大量に呼び出すとフレームレートの低下(最悪の場合は処理落ち)してしまいます。

そこでオブジェクトプールというオブジェクトの生成に関するパターンがあります。これはオブジェクトの数を制限し、再利用したい時に利用するパターンです。

少し制限がありますが、その分ちょっとラク?に実装する方法を紹介します。


実装する

ObjectPoolクラス

抽象クラスとして扱います。

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// ObjectPool
/// </summary>
/// <typeparam name="T">Component限定</typeparam>
public abstract class ObjectPool<T> : MonoBehaviour where T : Component
{
    /// <summary>
    /// Poolしたいオブジェクト
    /// </summary>
    [SerializeField] private T poolObject;
    
    /// <summary>
    /// PoolのためのList
    /// </summary>
    private List<T> _poolList;

    /// <summary>
    /// Poolするオブジェクトの親オブジェクト
    /// </summary>
    [SerializeField] private Transform poolParent;
    
    /// <summary>
    /// 初期化時にPoolする数
    /// </summary>
    [SerializeField] private int initializePoolCount;

    protected virtual void Awake()
    {
        _poolList = new List<T>();

        for (int i = 0; i < initializePoolCount; i++)
        {
            var pool = CreatePool(Vector3.zero, Quaternion.identity);
            pool.gameObject.SetActive(false);
        }
    }

    /// <summary>
    /// Poolを新しく生成
    /// </summary>
    /// <param name="createPosition"></param>
    /// <param name="createRotation"></param>
    /// <returns></returns>
    private T CreatePool(Vector3 createPosition, Quaternion createRotation)
    {
        var newPoolObject = Instantiate(poolObject, createPosition, createRotation);
        newPoolObject.transform.parent = poolParent;
        _poolList.Add(newPoolObject);
        return newPoolObject;
    }

    /// <summary>
    /// Poolしたオブジェクトから再利用 or 新しく生成
    /// </summary>
    /// <param name="setPosition"></param>
    /// <param name="setRotation"></param>
    /// <returns>Poolしたオブジェクト</returns>
    protected T ActivatePool(Vector3 setPosition, Quaternion setRotation)
    {
        foreach (var pool in _poolList)
        {
            if (pool == null)
            {
                continue;
            }

            if (pool.gameObject.activeSelf)
            {
                continue;
            }

            pool.transform.position = setPosition;
            pool.transform.rotation = setRotation;
            pool.gameObject.SetActive(true);
            return pool;
        }

        return CreatePool(setPosition, setRotation);
    }

    /// <summary>
    /// Poolした全オブジェクトの破棄
    /// </summary>
    protected void ClearAllPool()
    {
        foreach (var pool in _poolList)
        {
            Destroy(pool.gameObject);
        }

        _poolList.Clear();
    }
}

ObjectPoolを扱うクラス

Playerなど、Poolするオブジェクトを生成するclassです。

ObjectPool<PoolしたいオブジェクトのComponent>を継承する必要があります。

Poolする対象をComponentに限定しているため、GameObject型などは扱えません。

今回はTestBulletをPoolする対象とします。

using UnityEngine;

public class TestPlayer : ObjectPool<TestBullet>
{
    private Transform _generateTransform;
    
    protected override void Awake()
    {
        // Awakeで何か宣言したい場合↓を呼ぶ必要アリ
        // base.Awake();
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // Poolしたオブジェクトの利用
            var pos = _generateTransform.position;
            var rot = _generateTransform.rotation;
            ActivatePool(pos, rot);
        }
    }
}

Poolする対象

利用時の初めに必要な処理がある場合はOnEnable()を呼びます。

また、破棄したいタイミングではDestroy()の代わりにSetActive(false)を呼びます。

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class TestBullet : MonoBehaviour
{
    private Rigidbody _rigidbody;

    private void Awake()
    {
        _rigidbody = GetComponent<Rigidbody>();
    }

    private void OnEnable()
    {
        _rigidbody.velocity = transform.forward;
    }

    private void OnTriggerEnter(Collider other)
    {
        gameObject.SetActive(false);
    }
}

さいごに

説明不足な部分が多々あると思いますが、ご容赦ください。

また、間違いやアドバイス等ありましたらドシドシお願いします!!!

UniRxにあるObjectPoolの方が便利です(早く覚えないと...!)


【Unity】PlayMaker完全に理解する (1)

(注意)タイトルと本編は一切関係ありません

今回の目標

「PlayMakerを用いて以下の処理を行う」

using UnityEngine;

public class PlayerMovement : MonoBehaviour {
    private void Update() {
        if (Input.GetKey(KeyCode.W)) {
            transform.position += transform.forward;
        } else if (Input.GetKey(KeyCode.S)) {
            transform.position -= transform.forward;
        }
    }
}

目標のプログラムの分解

目標のプログラムを分解すると以下のようになります

using UnityEngine;

public class PlayerMovement : MonoBehaviour {
    private Vector3 currentPosition;
    private Vector3 moveDirection;
    private bool isW;
    private bool isS;

    private void Update() {
        isW = Input.GetKey(KeyCode.W);
        isS = Input.GetKey(KeyCode.S);

        if (isW) {
            currentPosition = transform.position; // get position
            moveDirection = transform.forward;    // set move direction
            currentPosition += moveDirection;     // set next position
            transform.position = currentPosition; // update position
        } else if (isS) {
            currentPosition = transform.position; // get position
            moveDirection = transform.forward;    // set move direction
            currentPosition -= moveDirection;     // set next position
            transform.position = currentPosition; // update position
        }
    }
}

PlayMakerで実装する

(1) PlayerオブジェクトPlayerMovement というFSMを追加👇

f:id:atas10703:20190526033216p:plain

(2) 分解したプログラムを参考に変数を追加👇

f:id:atas10703:20190526034007p:plain

(3) Input MoveForward MoveBack の3つの状態を作成👇

f:id:atas10703:20190526034440p:plain

(4) InputW InputS ToInput というイベントを追加👇

f:id:atas10703:20190526035700p:plain

(5) 状態Input に InputW InputS の遷移を追加し、4つのアクションを設定👇

f:id:atas10703:20190526035307p:plain

Get Key Keyを押している間 true、その結果をStore Resultに格納

Bool Changed Bool Variable の値が変化した時、Change Event の状態に遷移

(6) 状態MoveForward に ToInput の遷移を追加し、2つのアクションを設定👇

f:id:atas10703:20190526040216p:plain

(7) ゲームシーンを再生し、Wキーを押すと状態が遷移するのを確認👇

f:id:atas10703:20190526040744p:plain

(8) 標準では transform.forward transform.up transform.right が無いっぽい?ので

Assets/PlayMaker/Actions/Transform に GetDirectionVector.cs を追加👇

// 参考 https://hutonggames.com/playmakerforum/index.php?topic=41.0

using UnityEngine;

namespace HutongGames.PlayMaker {
    [ActionCategory(ActionCategory.Transform)]
    [Tooltip("Get the Direction (Forward, Up, Right) Vector of the Transform")]
    public class GetDirectionVector : FsmStateAction {
        public enum Direction {
            Forward,
            Up,
            Right
        }
        public Direction direction;
        
        [RequiredField] public FsmOwnerDefault gameObject;
        [UIHint(UIHint.Variable)] public FsmVector3 vector;
        [UIHint(UIHint.Variable)] public FsmFloat x;
        [UIHint(UIHint.Variable)] public FsmFloat y;
        [UIHint(UIHint.Variable)] public FsmFloat z;
        public bool everyFrame;

        public override void Reset() {
            gameObject = null;
            vector = null;
            x = null;
            y = null;
            z = null;
            direction = Direction.Forward;
            everyFrame = false;
        }

        public override void OnEnter() {
            DoGetForward();

            if (!everyFrame) Finish();
        }

        public override void OnUpdate() {
            DoGetForward();
        }

        private void DoGetForward() {
            var go = Fsm.GetOwnerDefaultTarget(gameObject);

            if (go == null) return;

            var directionVector = Vector3.zero;

            switch (direction) {
                case Direction.Forward:
                    directionVector = go.transform.forward;
                    break;
                case Direction.Up:
                    directionVector = go.transform.up;
                    break;
                case Direction.Right:
                    directionVector = go.transform.right;
                    break;
            }

            if (directionVector == Vector3.zero) return;

            vector.Value = directionVector;
            x.Value = directionVector.x;
            y.Value = directionVector.y;
            z.Value = directionVector.z;
        }
    }
}

(9) 状態MoveForward に4つのアクションを設定👇

f:id:atas10703:20190526042023p:plain

Get Position GameObject の Space 座標を Vector に格納

Get Direction Vector GameObject の Direction(Right、Up、Forward)をVectorに格納

Vector3 Add Vector Variable に Add Vector を加算

Set Position GameObject の Space 座標に Vector に代入

Wキーを押している間は前方に移動します(やった!)

(10) 状態MoveBack にも状態MoveForward と同様に設定👇

f:id:atas10703:20190526043819p:plain

Vector3 Subtract Vector Variable から Subtract Vector を減算

(閉じているアクションは状態MoveForwardと同じです)

これにて完成!!!

もう少し簡単に実装する

Get Key に拘らなければもう少し簡単に実装することができます

(1) 状態Input のアクションを修正👇

f:id:atas10703:20190526044825p:plain

Get Key Down Key を押した時、Send Event の状態に遷移

(2) 状態MoveForward のアクションを修正👇

f:id:atas10703:20190526045117p:plain

Get Key Up Key を離した時、SendEventの状態に遷移

(3) 状態MoveBack も同様に修正👇

f:id:atas10703:20190526045147p:plain

この修正により、bool型の変数 isW isS が不要になります

完全に理解するまでにはまだまだ時間がかかりそうですが、引き続きガンバリマス!!!

前回👇 atas10703.hatenablog.com

【Unity】PlayMaker完全に理解する (0)

(注意)タイトルと本編は一切関係ありません

PlayMakerのみでゲームを作れるようになることを目標にガンバリマス。

そもそもPlayMakerって何?

Unityのビジュアルスクリプティングのアセットです。

assetstore.unity.com

  • 直感的なビジュアルエディタ

  • デバッグが強力

  • 多くのアドオンが利用可能

などが強みです。

使用ソフトのバージョン

本ブログでの開発環境は以下の通りになります。

  • macOS Mojave 10.14.5

  • Unity 2019.1.0f2

  • PlayMaker 1.9.0.p16

導入方法

1 AssetStoreからPlayMakerをインポート

2 以下の手順を踏んでPlayMakerをインストール

f:id:atas10703:20190525213704p:plainf:id:atas10703:20190525213808p:plain

3 メニュー欄から PlayMaker > PlayMaker Editorを選択

下図のようなPlayMakerタブが出てきます。

f:id:atas10703:20190525214453p:plain

以上で導入は終了です。

次回👇

atas10703.hatenablog.com

オススメ資料

ユニティ・テクノロジーズ・ジャパン合同会社様が作成した資料👇

www.slideshare.net

STYLY様が作成した資料👇

styly.cc

【unity1week】「つながる」に参加しました!

はじめに

2019年3月11日〜17日に開催されたunity1weekに参加しました。

今回のお題は「つながる」ということで、ゲーム内容は「つなげる」でしたが、オンラインで「つながる」なので、問題ないはずです。。。

以下制作したゲームです。(2人用)

unityroom.com

制作過程

  • 月 企画が定まらない
  • 火 なんとなく作り始める、プレイヤーの操作とか
  • 水 ゲーム開始の演出、ゲーム性がなさすぎて絶望する
  • 木 UI、ゲームオーバー追加、絶望しつつも制作を続ける
  • 金 少しでもゲーム性を見出すためにギミック追加
  • 土 ギミック追加してもゲーム性を見出せなかったので協力プレイにすることに、Photon導入
  • 日 音・エフェクトの追加

といった感じでなんとか間に合いました!

制作過程の反省

良かった点

  • 汎用性のあるシステムを作っていた
  • Photonを使用するのは3回目であったため、戸惑う部分が少なかった

ダメだった点

  • 企画を定めずに作り始めたこと(唯一かつ最大のダメポイント)
  • デバッグプレイが不十分であった

ゲーム内容の反省

良かった点

  • 協力プレイのため、複数人で楽しめる
  • ゲームのテンポがいいのでリトライしやすい

ダメだった点

  • 球の位置の同期でカクつく
  • コンボ数の同期が不十分
  • 球から真下に出ている線がわかりにくい
  • etc...(言い出したらキリがない)

さいごに

評価してくれた41名の皆様、ありがとうございました。

次回は今回以上に良い評価を得られるようにガンバリマス!!!

【Unity】Stateパターンっぽい何かで状態遷移の実装

はじめに

AをしたらBに、BをしたらCに、CをしたらAに...

このようなケースをどのように処理すればいいかの一例として紹介します(既出の場合はすみません)

 今回は以下のような3点を移動する処理を作成したいと思います

f:id:atas10703:20190216212500g:plain

interfaceの作成

以下のようなインターフェースを作成します

public interface IState {
    void Initialize();  // 各状態での変数の初期化
    void Execution();   // 各状態の実行
}

各状態の作成

原点に移動するコードです

using UnityEngine;

public class ToOrigin : MonoBehaviour, IState {

    private Vector3 originPos;

    public void Initialize() {
        originPos = Vector3.zero;
    }

    public void Execution() {
        // 移動完了後、次のstateへ
        if (transform.position == originPos) {
            NextState();
        }
        transform.position = Vector3.MoveTowards(transform.position, originPos, Time.deltaTime);
    }

    public void NextState() {
        StateManager.NextState();
    }
}

残りの2つの状態は上記のコードと酷似しているため省略します

これらのコードは動かすcubeにアタッチします

状態を管理するマネージャーの作成

using System;
using System.Linq;
using UnityEngine;

// 実行したいstateの数だけ用意
public enum State {
    origin,
    right,
    up,
}

public class StateManager : MonoBehaviour {

    private IState[] stateList;     // 全stateを格納する配列
    private static int stateCount;  // stateの数
    private static State currentState;  // 実行するstate

    private void Start() {
        stateCount = Enum.GetValues(typeof(State)).Length;
        stateList = new IState[stateCount];

        SetStateList();

        // 全stateの初期化
        stateList.ToList().ForEach(state => state.Initialize());

        currentState = State.origin;
    }

    // 全stateの取得
    private void SetStateList() {
        stateList[(int)State.origin] = FindObjectOfType<ToOrigin>();
        stateList[(int)State.right] = FindObjectOfType<ToRight>();
        stateList[(int)State.up] = FindObjectOfType<ToUp>();
    }

    private void Update() { 
        stateList[(int)currentState].Execution();
    }

    // stateの遷移
    public static void NextState() {
        currentState = (int)currentState == stateCount - 1 ? 0 : currentState + 1;
    }
}

説明不足な部分が多々あると思いますが、ご容赦ください

また、何かアドバイス等ありましたらドシドシお願いします!!!