LoginSignup
1
1

More than 3 years have passed since last update.

【Unity(C#)】unity1weekで共同開発に初挑戦した振り返り

Last updated at Posted at 2020-05-17

unity1week

不定期で行われているunity1weekというゲームジャムに参加しました。

Unityを使って1週間でゲームを作るイベントです。
UnityがインストールされたPCとインターネット環境があればどなたでも参加可能です。
月曜0時にお題が発表されます。日曜20時までにゲームを作って投稿しましょう。
お題については多少こじつけでも大丈夫。ゲーム作りを楽しむことを心がけてください。

【引用リンク】:unityroom

共同開発

私自身参加は6回目なのですが、今回の参加においては共同開発に挑戦しました。
こんな感じのノリで始まりました。
GWunity.PNG

この友人のお仕事は、プログラムを書いてお金をもらうお仕事でもないですし、
理系でコンピューターサイエンスを専攻してたとかでもない、"ただただ興味がある"
という状態だったのでまずは一緒に何か動かしてみることにしました。

Day0

unity1week開催前日にDiscordで通話して画面共有を行いながら
同じ手順を踏んでもらう形でボールをキー入力で動かすというゲーム?を作りました。

Mitsu0.gif

using UnityEngine;

/// <summary>
/// Inspectorで動かしたいSphereにAdd Component
/// </summary>
public class BallMove : MonoBehaviour
{
    //ボールを動かす力を調整
    //[SerializeField]でInspectorに表示
    //[Range(最小値,最大値)]でInspectorにスライダーを表示
    [SerializeField, Range(0, 100)] private float power = 10;

    //Rigidbody:物理挙動に関する設定を簡単に扱える便利Component
    //Rigidbodyの入れ物を用意する
    private Rigidbody rb;

    //最初にStart関数内に書いた処理が実行される
    private void Start()
    {
        //先程用意した入れ物にRigidbodyを入れる

        //この.ゲームオブジェクトから.コンポーネントを取得<Rigidbodyを指定>
        //this.gameObject.GetComponent<Rigidbody>();
        rb = this.gameObject.GetComponent<Rigidbody>();
    }

    //Start関数内に書いた処理が実行されたあと、Update関数内に書いた処理が実行される
    //Updateは毎フレーム呼ばれる
    //1秒間に60回フレームが更新されるなら→60fps
    private void Update()
    {
        //Rigidbodyクラスに定義されているAddForceという関数を使う

        //もし、右矢印キーを押していたら、、、
        if (Input.GetKey(KeyCode.UpArrow))
        {
            //ワールド空間の指定した方向に任意の力を加える
            rb.AddForce(Vector3.forward * power);
        }

        //もし、下矢印キーを押していたら、、、
        if (Input.GetKey(KeyCode.DownArrow))
        {
            //ワールド空間の指定した方向に任意の力を加える
            rb.AddForce(Vector3.back * power);
        }

        //もし、右矢印キーを押していたら、、、
        if (Input.GetKey(KeyCode.RightArrow))
        {
            //ワールド空間の指定した方向に任意の力を加える
            rb.AddForce(Vector3.right * power);
        }

        //もし、左矢印キーを押していたら、、、
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            //ワールド空間の指定した方向に任意の力を加える
            rb.AddForce(Vector3.left * power);
        }

}

友人に教えている最中に
あまりにも書いてあることがわからなさすぎて技術書を噛みちぎろうとした日を思い出しました。
(まあまあの値段がしたので踏みとどまりました)

何もわからない状態からなら、丁寧すぎて逆に理解しづらいかな?という懸念は一旦捨てて良い
と仮説を立てて"くどい"くらいにコメントを書きました。

Day1

unity1weekは、開始と同時にお題が発表され、
そのお題に関連性のあるゲームを開発するというルールがあります。

今回のお題は「密」でした。

Day1はどんなゲームを作るかアイデア出しを行いました。

アイデア出しの進め方としては、「密 熟語」で検索して
アイデアのベースになりそうなワードを列挙しました。

そして、そのワードからイメージを派生し、ゲーム性、実現可能性について検討しました。


★実際のメモ

Day1メモ.PNG

アイデア出しに関してはunity1week online共有会 #1(24:10~)
でお話してるので参考にしてみてください。

実際にできたアイデアのデザインモックがこちらです。
Getting Over It With Bennett Foddy × モンスト
をイメージしたゲーム性です。

キャプチャ.PNG

Day2

下記リンクを参考にUnity Collaborateの環境構築を完了させました。

【参考リンク】:【Unity】Unity Collaborateでリアルタイム通信コンテンツの制作フローを効率化

いきなりのGitはハードルがさすがに高いと思ったので試みましたが、
大正解でした。本当に簡単にプロジェクトの共同編集が可能なので、超おススメです。

Day3

この日からはひたすら実装です。
先ほどのデザインモックの段階でゲーム性の関してはある程度固まっていましたが、
"何を"飛ばすかがまだ決まっていませんでした。

キャプチャ.PNG

ただ、引っ付いた際、発射する際のアニメーションを自分で作るのが面倒だったので
手足の無い無機質な生命体の方が楽できそうだなと
実装可能性の観点からこの時点でアイデアは絞れていました。

手足の無い無機質な生命体で思いついたのはスライムです。
壁に密着するというアイデアにシナジーも生まれます。

スライムでアセットストアで検索した際に欲しかった形の
無機質な生命体を発見したので採用しました。
SlimePBR.PNG

ただ、スライムの引っ付いたり弾んだりする質感を実装で再現するのが面倒だったので
ここでもう一段階、実装を楽にするための設定を考えました。

ズバリ、磁力です。

この設定の付与で実装のイメージが一気に固まりました。

この日はキャラクターを"飛ばす"、それに伴う"補助線"の実装で終了しました。

Mitsu1.gif

Mitsu2.gif

Day4

磁力の実装に着手しました。

Mitsu3.gif

磁力の実装には万有引力の公式を利用しています。


F[N]:質量m₁[kg]と質量m₂[kg]の2つの物体の間に働く万有引力の大きさ
r[m]:物体間の距離
G:万有引力定数(G=6.673×10-11N・m2/kg2) ※今回はここを係数にして力を調整可能に

F = G\frac{m₁m₂}{r^2}

ただ、このまま利用するとm₁とm₂の物体間距離が遠くなればなるほど
力が弱くなる
という性質になり、なかなか気持ち良い挙動にはなりませんでした。

なので、m₁とm₂の物体距離が遠くなればなるほど力が強くなる式に変更して利用してみました。

F = Gm₁m₂r^2

望み通りの挙動になったのですが、
質量は今回関与する必要はないので下記が最終的な計算式です。

F = Gr^2

私がほしかったのはただの指数関数だったわけですね。

コード

磁力として扱うオブジェクトにアタッチ
/// <summary>
/// 磁力の表現 万有引力の計算式を利用
/// S,Nでそれぞれベクトルが逆になる
/// 磁力として扱うオブジェクトにアタッチ
/// </summary>
public class MagnetFunction : MonoBehaviour
{
    private enum MagnetState
    {
        S,
        N
    }

    [SerializeField] private float _accelerationScale;
    [SerializeField] private MagnetState _magnetState;

    private Rigidbody _collisionObjRigidbody;
    private Vector3 _direction;

    private float _distance;
    private float _magnetPower;

    private void OnTriggerEnter(Collider other)
    {
        //Start関数内で名指しで取得する実装でも良いかも
        _collisionObjRigidbody = other.gameObject.transform.root.GetComponent<Rigidbody>();
    }

    private void OnTriggerStay(Collider other)
    {
        //衝突したキャラがSかNの判定を入れた方がよさそう
            switch (_magnetState)
            {
                case MagnetState.S:
                    // 星に向かう向きの取得
                    _direction = this.gameObject.transform.position - other.transform.position;
                    // 星までの距離の2乗を取得
                    _distance = _direction.magnitude;
                    _distance *= _distance;
                    // 万有引力計算
                    _magnetPower = _accelerationScale * _distance;
                    // 力を与える
                    _collisionObjRigidbody.AddForce(_magnetPower * _direction.normalized, ForceMode.Force);
                    break;

                case MagnetState.N:
                    // 星に向かう向きの取得
                    _direction =  other.transform.position - this.gameObject.transform.position;
                    // 星までの距離の2乗を取得
                    _distance = _direction.magnitude;
                    _distance *= _distance;
                    // 万有引力計算
                    _magnetPower = _accelerationScale * _distance;
                    // 力を与える
                    _collisionObjRigidbody.AddForce(_magnetPower * _direction.normalized, ForceMode.Force);
                    break;
            }
    }
}

InspectorでS極N極それぞれの役割を変更できるようにしました。
S,Nそれぞれの機能でクラスを用意しても良いかと思います。

磁力としてBox Colliderのみを保持するオブジェクトを用意し、
その子に磁石として扱うオブジェクトを配置します。

磁力のコライダーの範囲内に留まっている間は
磁力が働く仕組みです。

NCube.PNG

Day5

友人から稼働時間を確保できると連絡がきたので
タイトルシーンを作ってもらうことにしました。

簡単なデザインモックを渡した後、
適当に調べてもらって分からないことがあれば
逐一、Slackで聞いてもらう形を取りました。

TitleMock.PNG

すぐ作ってくれたので助かりました。
友人もUnityもすごい。
MitsuMasuo1.gif

私は 磁力の調整、および発射パワー調整を行いました。

Day6

下記を頑張りました。

・ステージの実装(チュートリアルっぽいやつ)
・音探して編集して入れたり
・パワーの入力タイミングに制限を加えた
・パワー入力タイミングに関して画面に出す画像を用意してプレイヤーに伝えられるようにした
・フェードインアウト実装

Mitsu5.gif

フェードインアウト

DoTween(無料版)を使って実装しました。

実際に使ったコードはもっと乱雑でしたが、
次回からはこのようなUtilityを用意しておいて最小限の労力で臨もうかと思ってます。

using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;

/// <summary>
/// 任意のUIを表示、非表示に関するユーティリティークラス
/// アニメーション中はUIいじれない
/// </summary>
public static class UIAppearTweenAnimeUtility
{
    /// <summary>
    /// UI表示
    /// </summary>
    /// <returns>シーケンス</returns>
    public static Sequence AppearUI(RectTransform uiRectTransform)
    {
        EventSystem eventSystem = EventSystem.current;

        float appearDuration = 0.5f;

        return DOTween.Sequence()
            .OnStart(()=>eventSystem.enabled = false)
            .Append(uiRectTransform.DOScale(Vector3.one, appearDuration))
            .OnComplete(()=>eventSystem.enabled = true);
    }

    /// <summary>
    /// UI非表示
    /// </summary>
    /// <returns>シーケンス</returns>
    public static Sequence DisappearUI(RectTransform uiRectTransform)
    {
        EventSystem eventSystem = EventSystem.current;

        float disappearDuration = 0.5f;

        return DOTween.Sequence()
            .OnStart(()=>eventSystem.enabled = false)
            .Append(uiRectTransform.DOScale(Vector3.zero, disappearDuration))
            .OnComplete(()=>eventSystem.enabled = true);
    }
}

ポイントとしては
・メソッドがSequence(シーケンス)を返す
・EventSystemのenableを操作するヤンキーコング1

の二点です。

メソッドがSequenceを返す

DoTweenのSequenceはいろいろな処理を任意の順番、タイミングで実行することができます。
また、SequenceとSequenceを繋いで実行することもできます。

呼び出し側でUIアニメーションの処理に追加して
新しい処理を行わせることが可能になります。

UIのアニメーション処理の終了後に音を再生
AppearUI(uiRectTransform)
    .AppendCallback(()=>_fadeOpenAudioSource.Play())
    .Play();

アセットのインポート直後の初期設定ではSequenceが自動で実行される設定になっているかと思うので、
ツールバーから設定を変更しておくとScriptから実行できます。
DoTweenPanel.png

TweenAutoPlay.PNG

EventSystemのenableを操作するヤンキーコング

UIアニメーションの処理を行っている間は入力を受け付けないようにしたかったので
EventSystemごとenableをオフにしています。
そのため、一時的にすべてのEventSystem関連の入力が受け取れない状態になります。
(Unityゲーム開発者ギルドで教えてくださった方ありがとうございます)

EventSystem eventSystem = EventSystem.current;
eventSystem.enabled = true

Day7

結果的に私は毎度のことのように〆切に間に合わなかったのですが、
この日はリトライ処理の実装やステージ解放の実装を行っていました。

【参考リンク】:【Unity(C#)】覚えゲーのリトライポイント実装
【参考リンク】:【Unity(C#)】PlayerPrefsを使用したステージ解放の実装

Mitsu6.gif

友人にはステージを作ってもらっていました。
ゲーム性を与えるセンス抜群&ツールを使いこなす速度がバケモノで助かりました。
MMGameSS.PNG

振り返り

評価はなかなかシビアな感じでした。
MMReview.PNG

操作性

特に操作性の部分ではGetting Over It With Bennett Foddyを意識した難易度設計から、
難しい→操作しづらい という
印象の変換が無意識下で行われてしまったのではないか、という分析をしています。

Getting Over It With Bennett Foddyは確実に名作ですが、
操作をしやすいか質問して、"はい"という回答は得られにくい
かと思います。

Getting Over It With Bennett Foddyは
操作しづらいことで大きなゲーム性が生まれています。

つまり何が言いたいかというと、
評価項目の何かしらが欠損していることによってゲーム性が拡張されるゲーム
unity1weekで上位を狙いにくいということです。

文字起こししてみると当たり前ですが、
意外と見落としがちな要素かと思います。

操作性を振り返っての結論としては、
unity1weekで上位に食い込めなかったからといって
おもしろくないゲームであるという解釈にはならない
ということです。(暴論)

最後に

自分への慰めも含めて声を大にして言いますが、
みなさん胸を張って自分の作ったゲームに誇りを持ちましょう。

あと、運営、開発、実況等、本当にみなさんお疲れさまでした。
合わせて、お礼の言葉で締めさせていただきます。ありがとうございました。

(技術的な誤り、誤植等ありましたらお気軽にコメントください)

参考リンク

[Unity] 惑星に向かって物体が落ちるようにする
Unity で EventSystem.current が null になってる問題
[Unity] DOTweenのSequenceを使ってアニメーションを結合する

【作ったゲーム】:MAGNET MONSTER


  1. ヤンキーコングはヤンキーコーディングを略した造語です。 

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1