ぐーるらいふ

底辺。

【Unity】攻撃判定タイミングをグラフィカルに作りたい 【Editor】

いきなりですが

GitHubにコード一式あげてます。ちょっと長いので、面倒な方はソースを見ると早いと思います。

github.com

格闘ゲームを作ろうとしてます。
格闘ゲームに限らずアクション要素のあるゲームには攻撃判定というものが大抵あります。

http://www.capcom.co.jp/blog/sf4/img_upload/t11151JRI_02.jpg

この攻撃判定ですが、「常に出続けてOK」なゲームもあれば、
「一時的にだけ表示してほしい」というゲームもあります。
アクションゲームの攻撃で「剣を振る」なども一時的に攻撃判定が出てほしいアクションだと思います。

攻撃判定の制御

パンチ Animation

start ---(攻撃判定発生中) ----->> end

キック Animation

start -------------(攻撃判定発生中) ----->> end

格闘ゲームで考えるとパンチならパンチ、キックならキックのアニメーションがあり、
攻撃判定が発生するタイミングとその長さをそれぞれ設定しなければなりません。

  • アニメーションの数が少ない
  • 一人で開発している、

などといった場合は
一つ一つ再生をスクリプトで拾って

10frame後に30frame間だけ判定を出す

などといったコードを埋め込んでいく事でも実現は可能です。
が、多人数で作っていたり、大規模になったりするとその限りではありません。

グラフィカルに当たり判定を制御したい!

Unity使ってるぞ!Editorだって作れちゃうんだ。
せっかくだからEditorで攻撃判定を制御するツールを作っちゃおうじゃないか。

事前準備

今回もまた以下のAssetsを使用して説明します。


f:id:ghoul_life:20170711171702p:plain
https://www.assetstore.unity3d.com/jp/#!/content/33478

f:id:ghoul_life:20170711171719p:plain
https://www.assetstore.unity3d.com/jp/#!/content/33083

以上のお二方は前回から引き続き。
また、Animationの共通化については以下の記事を参考にしてください。

ghoul-life.hatenablog.com

キャラクターに攻撃判定コライダーをつけよう

f:id:ghoul_life:20170720192725p:plain

f:id:ghoul_life:20170720192810p:plain

こんな感じで手足にCapsuleColliderと以下のスクリプトを付与します。
このCapsuleColliderが攻撃判定の大きさになります。

プログラマー作業ではなく、
ゲームデザイナーに位置や大きさなどの調整を任せる
といった分業も出来るでしょう。

また、タックルやヘッドバットといった別の箇所にも攻撃判定を付ける
といった事も可能です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(CapsuleCollider))]
public class HitCollider : MonoBehaviour {

    public HitType hitType;
    private CapsuleCollider collider;

    public void Disable()
    {
        if (collider == null)
        {
            collider = GetComponent<CapsuleCollider>();
        }
        collider.enabled = false;
    }

    public void Enable()
    {
        if (collider == null)
        {
            collider = GetComponent<CapsuleCollider>();
        }
        collider.enabled = true;
    }

    // colliderをSceneビューで表示してみる…。多少強引な計算があるので、
    // colliderの正確な位置とサイズを取る方法があれば…。
    void OnDrawGizmos()
    {
        if (collider && collider.enabled)
        {

            Vector3 offset = 
                transform.right * (collider.center.x / collider.height / 2) + 
                transform.up * (collider.center.y / collider.height / 2) + 
                transform.forward * (collider.center.z / collider.height / 2);

            Vector3 size = new Vector3(
                (collider.radius / 0.5f) * collider.transform.lossyScale.x,
                (collider.height / 2) * collider.transform.lossyScale.y, 
                (collider.radius / 0.5f) * collider.transform.lossyScale.z
                );

            Quaternion dir;
            switch (collider.direction)
            {
                case 0:
                    dir = Quaternion.Euler(Vector3.forward * 90);
                    break;
                case 1:
                    dir = Quaternion.Euler(Vector3.up * 90);
                    break;
                case 2:
                    dir = Quaternion.Euler(Vector3.right * 90);
                    break;
                default:
                    dir = Quaternion.Euler(Vector3.zero);
                    break;
            }
            Gizmos.color = new Color(1, 0, 0, 0.5f);
            Gizmos.DrawMesh(GetPrimitiveMesh(PrimitiveType.Capsule), collider.transform.position + offset, transform.rotation * dir, size);

        }
    }

    private Mesh GetPrimitiveMesh(PrimitiveType type)
    {

        GameObject gameObject = GameObject.CreatePrimitive(type);
        Mesh mesh = gameObject.GetComponent<MeshFilter>().sharedMesh;
        DestroyImmediate(gameObject);

        return mesh;

    }

}

スクリプトはコライダーのON/OFFを制御するのに使います。
HitTypeは後述。

攻撃判定種別を付けよう

今回は格闘ゲームを題材にしているため、
攻撃判定は複数あることにします。

  • 左手
  • 右手
  • 左足
  • 右足

の4つとします。
ゲームによっては、ひじやひざ、または頭突きや肩なんてものもあるかもしれません。
が、今回はシンプルにこの4つにします。
これを定義しているのが、HitTypeです。

public enum HitType
{
    LEFT_HAND = 1,
    RIGHT_HAND = 2,
    LEFT_LEG = 4,
    RIGHT_LEG = 8
}

こんなただのenumです。
値はbit演算のためにこのようにしてます。
その1で対応したHitColliderに左手なら左手、右足なら右足といったHitTypeをセットしておきます。

まずはAnimationClipを再生するEditorを作る

Editor沼に足を踏み入れたような気がします。
AnimationClipなんて、Inspectorの再生窓使えば見れるやんけ!
という声が聞こえてきそうですが、それをあえて自分で作ってみる。

(EditorWindowの作り方の説明などはいくらでも出てくるので割愛)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

// 単純なサンプルエディタ
public class AnimClipSampleEditor : EditorWindow
{

    // アニメーションクリップを再生させるオブジェクト
    private Animator animObject;
    // 再生したいアニメーションクリップ
    private AnimationClip animClip;

    [MenuItem("Window/AnimClipSampleEditor")]
    static void Open()
    {
        GetWindow<AnimClipSampleEditor>();
    }

    private float value;

    void OnGUI()
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("character : ", GUILayout.Width(110));
        animObject = (Animator)EditorGUILayout.ObjectField(animObject, typeof(UnityEngine.Animator), true);
        GUILayout.EndHorizontal();
        EditorGUILayout.Space();

        GUILayout.BeginHorizontal();
        GUILayout.Label("animation clip : ", GUILayout.Width(110));
        animClip = (AnimationClip)EditorGUILayout.ObjectField(animClip, typeof(UnityEngine.AnimationClip), true);
        GUILayout.EndHorizontal();
        EditorGUILayout.Space();

        if (animObject == null || animClip == null)
        {
            GUILayout.Label("Please Setting character and animation clip");
            EditorGUILayout.Space();
            EditorGUI.BeginDisabledGroup(true);

        }

        GUILayout.BeginHorizontal();
        value = EditorGUILayout.Slider(new GUIContent("TimeLine"), value, 0, 1, GUILayout.Width(300));
        GUILayout.EndHorizontal();

        if (animObject == null || animClip == null)
        {
            EditorGUI.EndDisabledGroup();
        }

        //--------------------------------------------------------------------

        if (animObject != null && animClip != null)
        {
            animClip.SampleAnimation(animObject.gameObject, value);
        }

        Repaint();
    }

}

そして適当にキャラクターをシーンに配置して、
AnimationClipをセットすれば、
スライダーでアニメーションを動かすことが出来ます。

f:id:ghoul_life:20170720193802p:plain

攻撃判定を付与するには

色々なアプローチがあります。

  • 自力Scriptアプローチ、
  • AnimatorからStateBehaviorでUpdateのframeを見る

などなんでも良いのですが、
今回はAnimationClipからEventを呼び出すアプローチで行きたいと思います。

AnimationClipにはEventを付与することが出来ます。

f:id:ghoul_life:20170720195028p:plain

これによって、AnimationClipの最後にEventを付与して、アニメーションの終了判定を行ったり
していた過去もあるかもしれません。

攻撃判定を開始するタイミングでEventを発行して、終了タイミングで再度Eventを発行すれば良さそうです。

HitTypeの判定について

攻撃判定を開始した時

  • 「左手の攻撃判定開始」 -> 「判定終了」
  • 「右足の攻撃判定開始」 -> 「判定終了」
  • 「左手と右手の攻撃判定開始」 -> 「判定終了」

といった命令が送られてくると考えます。

こうしました。

イベントを送る側(Animation Clip)

    private int CreateHitEvent()
    {
        int result = 0;

        if (toggleLeftHand) result = (result | (int)HitType.LEFT_HAND);
        if (toggleRightHand) result = (result | (int)HitType.RIGHT_HAND);
        if (toggleLeftLeg) result = (result | (int)HitType.LEFT_LEG);
        if (toggleRightLeg) result = (result | (int)HitType.RIGHT_LEG);

        return result;
    }
    
    void OnGUI(){
        //~~
        if (GUILayout.Button("Add Hit Event")){
            var serialied = new SerializedObject(animClip);
            serialied.Update();

            var events = new List<AnimationEvent>();

            onHitEventStart = new AnimationEvent();
            onHitEventStart.time = startTime;
            onHitEventStart.functionName = "OnHitEventStart";
            onHitEventStart.intParameter = CreateHitEvent();
            events.Add(onHitEventStart);

            onHitEventEnd = new AnimationEvent();
            onHitEventEnd.time = endTime;
            onHitEventEnd.functionName = "OnHitEventEnd";
            events.Add(onHitEventEnd);

            // Animation ClipにEventを付与します
            AnimationUtility.SetAnimationEvents((AnimationClip)serialied.targetObject , events.ToArray());
            EditorUtility.SetDirty(serialied.targetObject);

            serialied.ApplyModifiedProperties();
        }
        //~~
    }

イベントを受ける側(Character)

    // hitEvtValue = 0x1111 , 0x1001 といった値が来る
    public void OnHitEventStart(int hitEvtValue)
    {
        // 該当するcolliderをON
        foreach (var hit in hitColliders)
        {
            if (((int)hit.hitType & hitEvtValue) != 0)
            {
                hit.Enable(); // HitColliderをON
            }
        }

    }

ビット演算を使い、そのフラグが立っていたら判定をONというシンプルな作りです。
他にも渡したいものがあればObjectに情報をまとめて渡すなんてのもアリなんですが、
とりあえず思いつかなかったので最小限にしてます。

あとはこれをグラフィカルにEditor化すればOK

必要なパーツは大体揃ったので、後はウィンドウにまとめて値をEditorから操作します。

f:id:ghoul_life:20170721103214p:plain

どうしてもGUI系はコードだらっと長いので、githubのコードを参考にしてください。

github.com

MinMaxSliderの範囲が攻撃判定の範囲ということにしました。
ハンドルが持ちにくく、もう少し見た目が変えられるといいんですが、
GUIStyleとか頑張って作ればいけるのかな。

攻撃判定発生タイミングはAnimationClip側に紐付いているため、
キャラクターを変えても、AnimationClipに攻撃判定発生タイミングは変わらず、
共通で扱うことが出来ます。

f:id:ghoul_life:20170721124224g:plain

そして、攻撃判定の大きさや位置はキャラクター側に紐付いているため、
キャラクター個別に設定することが出来ます。


f:id:ghoul_life:20170721123414g:plain

こうしてEditor沼へと落ちていくのだった

これで作業の分離が出来ました。(?)
出来たのか?

ゲームデザイナーやキャラクターデザイナー
攻撃判定位置や発生フレームを丸投げして、
自分はロジックを組む…という分担が出来ます。

「困難は群れで分け合え」

と、かばんさんも言っていました。

自分は完全に個人なので、自分で作って自分で使う!
という無駄な作業かもしれないのですが、勉強だと思って…。

注意点として

fbxの内側にあるAnimationClipをそのまま使おうとすると、
Eventを上手く付与出来ないかもしれません。(保存したのに反映されないなど)
その場合はfbxからAnimationClipを取り出すと、Editが自由になるため
Eventの付与も出来るはずです。
こちらも参考に!神。

baba-s.hatenablog.com

【Unity】[Tips] character animationのスピード変えたかった

Tipsだよ。本当に一言で終わるよ。

やりたかったことを察してほしい

animatorの再生時に速度を落としたりなんだりしたいな、と
思っていたのでまずは直感でこのように実装した。

   public void OnBtnSpeed100()
    {
        animator.speed = 1.0f;
        animator.SetTrigger("Hikick");
    }

    public void OnBtnSpeed50()
    {
        animator.speed = 0.5f;
        animator.SetTrigger("Hikick");
    }

    public void OnBtnSpeed10()
    {
        animator.speed = 0.1f;
        animator.SetTrigger("Hikick");
    }

この結果がこれ。

え、合ってないんかい!?

docs.unity3d.com



正解はこれ

AnimatorStateにはSpeedパラメータも渡すことが出来る

AnimatorのparameterにfloatでSpeedを追加して

f:id:ghoul_life:20170713153130p:plain

その後AnimationStateのSpeedの所にチェックを入れて
Multiplierの所にSpeedを指定して

f:id:ghoul_life:20170713153139p:plain

    public void OnBtnSpeed100()
    {
        animator.SetFloat("Speed", 1.0f);
        animator.SetTrigger("Hikick");
    }

    public void OnBtnSpeed50()
    {
        animator.SetFloat("Speed", 0.5f);
        animator.SetTrigger("Hikick");
    }

    public void OnBtnSpeed10()
    {
        animator.SetFloat("Speed", 0.1f);
        animator.SetTrigger("Hikick");
    }

他に影響なく値を変えることも出来るし!いいですね。

これだけ。
なんてこと無いけど面白かったのでブログに残す…。

【Unity】3Dモデルのアニメーションを別の3Dモデルで使い回したい

3Dキャラ同士同じアニメ使えるやろ

  • Unity chanは3Dキャラやん
  • Unity chanにアニメーションあるやん
  • 他の3Dキャラにも同じアニメーションいけるやろ
  • ペタッと
  • ちーん

これとか
f:id:ghoul_life:20170711171210p:plain

こうとか
f:id:ghoul_life:20170711171217g:plain

見たことありませんかね?
「合っとるやろ!?なんでお前は言うことを聞かないんや!?もうええ!」
とsteamを起動していたんですが、いい加減に向き合おうと思いました。

格闘ゲーム作りたくなったんです

パンチとかキックとか良いですよね。カッコイイですよね。
でも自分はバーチャファイターや鉄拳は凄いヘタでして…。
2D格闘世代なので、SFやKOFやGGとかの方が馴染みがあります。
闘神伝トバルNo.1はそっと胸の奥に閉まっておきました。

使用したAsset

ファイティングユニティちゃん 無料体験アセット
f:id:ghoul_life:20170711171702p:plain
https://www.assetstore.unity3d.com/jp/#!/content/33478


Jeremyくん 無料
f:id:ghoul_life:20170711171719p:plain
https://www.assetstore.unity3d.com/jp/#!/content/33083


この二方を利用して、アニメーションの使い回しをしてみます。

Avator RigのAnimation Typeをおさらい

Avator RigのAnimation Typeは以下の3つがある

  • Humanoid
  • Generic
  • Legacy
Humanoid

ボーン構造などの基本的な構造の統一化を図り、リターゲッティングと呼ばれる
さまざまなモデルに同じアニメーションが共有できる仕組みが扱える。
docs.unity3d.com
docs.unity3d.com


Generic

ヒューマノイドアニメーションと言われ、動物などで主に利用される事を想定している。
Mechanimで扱うことは出来るが、共有して扱うことは出来ない。
docs.unity3d.com

Legacy

これはそのまま旧アニメーションでそのオブジェクトに紐づくアニメーション。
Mechanim非対応のアニメーションの場合はコレになるのかな。
docs.unity3d.com

結論

つまり、HumanoidのTypeにしてあげれば共通して使うことが出来るはずだ。

設定を見直そう

Unity chanのmodelのRigを見る

Animation Type = Generic -> Humanoid に変更して Apply

JeremyくんのmodelのRigを見る

Animation Type = Generic -> Humanoid に変更して Apply


f:id:ghoul_life:20170711165803p:plain

あれ、まだ駄目?モデルだけではなく、Animation Clipも編集しよう。
ポチポチとAnimation ClipのRigを見て

Animation Type = Generic -> Humanoid に変更して Apply

f:id:ghoul_life:20170711165812p:plain

Unity chan , Jeremy共に同じAnimator Controllerでいいはずだ。
FightingUnityChan_freeをセット。

f:id:ghoul_life:20170711165918p:plain

実装

こんなスクリプトをButtonで呼び出すようにして

  public Animator player;
  public Animator enemy;

  public void Punch()
  {
      player.SetBool("Jab", true);
      enemy.SetBool("Jab", true);
  }

  public void Kick()
  {
      player.SetBool("SAMK", true);
      enemy.SetBool("SAMK", true);
  }

  // Fighting Unitychan側には無い、Jeremy側のアニメーション。
  // 逆も出来るはずだ、というテスト。
  public void Victory()
  {
      player.SetBool("Victory", true);
      enemy.SetBool("Victory", true);
  }

実行すると

f:id:ghoul_life:20170711165449g:plain

OK!!
(後で気づいたんですが、Jeremyくんパンツ履いてなかった。)

さぁ、steamを起動しよう!

書く(読む)と長いんだけど、やると本当にすぐです。五分ぐらいで終わっちゃう。
ちょっとググったら答えはすぐに出るんだけど、自分なりに調査した結果をメモっておく、って事で。

steamを起動する時は、熱々のピザとキンキンに冷えたコーラを忘れずにね!

【Unity】初心者でもCinemachineを使いたい

まずはこれを見てくれ。

複数のカメラが切り替わるとグッとゲームらしいです。
これ作るの面倒なんだよね~、なんて方。
ご安心ください。簡単に作れますよ。そうCinemachineならね。

Cinemachine?

UnityがAssetストアで無料で公開しているAssetで、統合カメラシステムです。

https://www.assetstore.unity3d.com/jp/#!/content/79898

Unity 2017.01.から使用可能になる予定となっていて、
今使いたい場合は、β版のUnityが必要です。

unity3d.com

早速使ってみる

Unity Betaを準備してAsset StoreからImportしたら
Examplesから各Sceneを開いて機能を確かめたら良い!
そして、公式のModule Examplesを見るんだ!

Module Examples – Cinemachine

と言いたい所ですがそれは置いておいて
「自分で使う場合はどうやるんだ?」
って所を書こうと思います。

まずは動かしてみよう

CinemachineをImportしているプロジェクトで画面上部メニューから

Cinemachine > Create Virtual Camera

f:id:ghoul_life:20170706184343p:plain

を押すと、Main Cameraに Cinemachine Brainが追加される。
さらにCinemachine Virtual Cameraが追加されたCM vcam1がSceneに追加される。

f:id:ghoul_life:20170706184129p:plain

もうこれだけでCinemachineが動いています。スゴイ。当たり前かw


見たまんまをあえて書きますと、

Main Cameraの位置に関わらず、CM vcam1の位置からカメラ表示するようになっている

Main Cameraの位置を動かそうとしても追加されたCM vcam1の位置が上書きされて動かせなくなっているはず。
また、sub cameraを作っているのではなく、スクリプト制御しているということもわかります。

ちょっと遊んでみる

もう一度

Cinemachine > Create Virtual Camera

を選択して、2つ目のVirtual Cameraを追加しよう。
ちょっと位置を調整して切り替わりのテストをしてみます。
こんな感じで切り替わりがわかる形にしました。(真上からの視点)

f:id:ghoul_life:20170706190409p:plain

切り替わり

1つ目のカメラをCM vcam1
2つ目のカメラをCM vcam2
とします。

CM vcam1 -> CinemachineVirtualCamera enable ON
CM vcam2 -> CinemachineVirtualCamera enable OFF

の状態で実行して、
CM vcam2のenableをON/OFFしてみよう。

f:id:ghoul_life:20170706190551g:plain

CinemachineVirtualCameraのenableやGameObjectのActiveを切り替えるだけで
自動でカメラが切り替わってくれる事がわかりました。

念のために優先度もテストしておこう

CM vcam1 -> priority 11
CM vcam2 -> priority 10

この状態で CM vcam2 のenableをON/OFF切り替えてみても
何も反応しないということがわかります。
想定通り、優先度の値が大きい方が優先になるということですね。

まとめると

この事から

  • GameObjectのActiveかCinemachineVirtualCameraのenableをON/OFFするだけで動く
  • Priorityが大きい方が優先になる
  • Priorityが同じ場合はONにした方が動く

ということがわかった。

ゲームに応用してみよう

Cinemachineがちょっとは理解できた。結構単純な仕組みで動いていそうだ。
ゲームで使いたい場合は、単純なFollow Modeなどでも十分使えるが、
もうちょっと応用してみて、アクションゲームやADVゲーム風のカメラワークを
作ってみたい。

仕様
  • カメラ自体は動かない
  • カメラはキャラクターを見る(LookAt)
  • カメラごとに範囲を指定
  • 指定範囲にプレイヤーが入ったらそのカメラに切り替わる
  • 切り替わる時のアニメーションは個別に設定
  • カメラ追加も出来るだけ容易に
実装

特に実装は要らないかなと思っていたのですが、
こんなのだけは作る必要がありました。

範囲に入れば優先度を上げ、範囲から出れば優先度を下げるだけ。

このスクリプトを各vcamにセットして、
SphereColliderもAddComponentします。(Is Trigger ONを忘れずに)
SphereColliderのRadiusで範囲指定して、実行すればもうそれだけでOK!

(二回目。これはこの範囲指定vcamを利用して作ってます。範囲に入るとカメラが切り替わっている)

カメラの追加はEditorから作るようにして、一気に必要なComponentの追加、
設定項目の設定なども一気にやっちゃうとさらに良さそう。

    public void AddColliderVirtualCamera()
    {
        var go = new GameObject();
        var sphereCollider = go.AddComponent<SphereCollider>();
        var cinema = go.AddComponent<Cinemachine.CinemachineVirtualCamera>();
        var cinemaCollider = go.AddComponent<CinemaCollider>();

        sphereCollider.isTrigger = true;
    }

Inspectorでボタン押下作れると便利。神、お世話になってます。
baba-s.hatenablog.com

個別にTweenアニメーション指定も出来る…だと?

Cinemachine Brainを改めて見てみる。
Default BlendというEase In Outという表示がセットされている。

f:id:ghoul_life:20170706193635p:plain

これはカメラとカメラを切り替える時のTweenアニメーションを指定している。
ここを個別に設定するには、Custom Blendsを使う。
Custom Blendsはほとんど直感で使えてしまうぐらい簡単だ。

Create Asset -> でMain Camera Blends.assetを作成して、
From , To , Style , Timeを追加していくだけだ。

Fromは「どこから」
Toは「どこまで」
Styleは「どのように」
Timeは「どれくらい」

だ。

f:id:ghoul_life:20170706193650p:plain

FromやToには
「ANY CAMERA」
という項目があり、
これはそのまんま、「いずれかのカメラ」を指す。

StyleのTweenアニメーションもいくつか用意されている

  • Cut -> 即座にパッと切り替わる
  • Linear -> 一定速度で切り替わる
  • Ease In Out -> 開始時と終了時に速度が変化する

など。この辺りは好きに触ってみてほしい。

これをガシガシ追加していけば個別にアニメーションを設定出来る

cinemachine神

ブログに起こすと凄く長くなったが、実際に触ると簡単だし!
でもきっと本気でカットシーン作ろうとしたら凄い苦労するんだろうなぁ。

また、今回作ったサンプルでぐりぐり走ってると結構カメラが激しく動くんでちょっと酔ってしまいました。
大きめなフィールドにしたり、移動速度を調整したり、カメラの切り替わり速度を調整すればその辺は改善できそう。
使うときはいい感じに調整してみて。

よくわからんメモ

CinemachineCollider.cs
といういかにも!なスクリプトがcinemachine側に用意されているのですが、
どうも使い方がよく分からず…自分の思っていた機能ではない?

API Reference無いかなぁ…。

【Unity】UnityのSceneを劇的ビフォーアフターしたい

概要

f:id:ghoul_life:20170703194328p:plain

こうしたい。

いいらいとさんがちょっと教えてくれた。
こういうこと教えてくれる人は少ないです。貴重な御方です。


なるほど、自分もやってみよう!

元のデータを用意

テキトーにSceneを作る。
テキトーにオブジェクトを配置してみる。
ここでは

  • Cube
  • Sphere
  • Cylinder

の3つをFloorに見立てたCubeの上に配置しました。

f:id:ghoul_life:20170703194359p:plain

ダサすぎワロタ。
これをオシャレに変えてみましょう。

まずは空から変えていこう

unityはSkyboxという空間の内側にSceneは描画されます。
そのSkyboxから変えていこう
Shaderで二色のカラーをグラデーションした感じでいかがでしょう。

神がいた。
github.com
もうこれこのままでええやん…?完成形や。

Lerp(A,B,C)はAからBに向かって変化した時に、Cの値で保管する関数

これを利用したMaterialを作って
Windows > Lighting > Setting

f:id:ghoul_life:20170703194442p:plain

f:id:ghoul_life:20170703194450p:plain

SkyboxのMaterialに作ったものをぽいっと設定すると
(眩しすぎるならIntensity Multiplierを減らしてみて)

f:id:ghoul_life:20170703194532p:plain

なんということでしょう、先程まで殺風景だった背面があっという間にオシャレなグラデーションの空間に!

ああ、次はカメラだ

これだけでもオシャレさはあるが、まだいける。
次は
Post Processing Stack(無料)
https://www.assetstore.unity3d.com/jp/#!/content/83912
を使う。
Unity5.5以上が対象になってしまうので、それ以前の人は
Import PackageからImage Effectで似たような機能が使えるはずだ。
(間違ってたらゴメンナサイ)

別で作ったProfileの値でPost Effect(色々描画して、最後にかけるエフェクト)を調整出来る

○Profileの作成
Project -> Create -> Post Processing Profileで作成

f:id:ghoul_life:20170703194633p:plain

○Post Efffectの設定
Post Processing BehaviorスクリプトをMain Cameraにアタッチ

○Post Effect profileの反映
作成したPostProcessing Profileを Post Processing Behavior 紐付けて準備完了だ。

PostProcessing Profileの設定

f:id:ghoul_life:20170703194656p:plain

  • Antialiasing
  • Ambient Occlution
  • Depth of Field
  • Bloom
  • Color Grading

の5つをONにしただけでこんな感じ。

f:id:ghoul_life:20170703194833p:plain

まるで豆腐のように美味しそうだったオブジェクトが、
あっという間に食べられなくなりました。

後はお好みでパラメータをいじったり、
point lightを入れてみたりするとそれだけでグッとそれっぽい。

f:id:ghoul_life:20170703194732p:plain

いかがでしょうか

オブジェクトには触らずに

  • Skybox
  • PostProcess

この2つを触るだけでグッと良くなるのがUnity。本当に劇的。
まだパラメータが一杯あるのでまだまだ出来ることはありそう。

【Unity】第三回unity1week「積む」に挑戦 後編 (開発編)

ghoul-life.hatenablog.com

ちゃんとゲームらしく

タイトル -> チュートリアル -> ゲーム -> 結果

と画面を作る。
必要な素材も大体ここで描く。
fireAlpacaで適当に描く。

他のゲームもいきなりゲーム画面のやつばかりやんけ!と
甘えから今まで一回もチュートリアルなんて作ってこなかったが、
今回はちゃんと作ろう。少しでもゲームをプレイして貰いたいし。
説明文に頑張って書く!という最終手段もあるけど。

開発

開発はスイスイ進んだ。流石に三回目ともなれば色々と積み重ねたものがある。
2DベースでuGUIでゲームを作るというあんまりやってはならない事かもしれないが、
それで行く事にした。

f:id:ghoul_life:20170627154507p:plain


各パーツと捉えてこれらは全て別々のCanvasにしている。
Canvasを複数作ってON/OFFをスッと切り替えて、画面遷移を制御するようにして、
順番やsortOrder , positionを細かく自由に変更可能にしていた。

縦書き

unityはデフォルトで縦書きに対応はしていない。
が、先人達がすでに挑戦してくれていたのを拝借。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Linq;

public class VerticalText : UIBehaviour, IMeshModifier
{
    private Text textComponent;
    private List<char> nonRotatableCharacters; // 回転させない文字リスト
    private List<char> adjustCharacters; // 位置を調整する文字リスト

    private void InitLimitCharacters()
    {
        if (nonRotatableCharacters == null)
        {
            nonRotatableCharacters = new List<char>();
            nonRotatableCharacters.Add('ー');
        }

        if (adjustCharacters == null)
        {
            adjustCharacters = new List<char>();
            adjustCharacters.Add('ァ');
        }
        
    }

    public new void OnValidate()
    {
        //base.OnValidate(); -> WebGLビルド時のみエラーになる。iOSやaOSは平気なのに…何故?
        textComponent = this.GetComponent<Text>();

        var graphics = base.GetComponent<Graphic>();
        if (graphics != null)
        {
            graphics.SetVerticesDirty(); // 更新有りと伝える
        }
    }

    public void ModifyMesh(Mesh m) { }
    public void ModifyMesh(VertexHelper verts)
    {
        var stream = ListPool<UIVertex>.Get();
        verts.GetUIVertexStream(stream);

        modify(ref stream);

        verts.Clear();
        verts.AddUIVertexTriangleStream(stream);

        ListPool<UIVertex>.Release(stream);
    }

    void modify(ref List<UIVertex> stream)
    {
        if (textComponent == null)
        {
            textComponent = this.GetComponent<Text>();
        }

        var characters = textComponent.text.ToCharArray();
        if (characters.Length == 0)
        {
            return;
        }

        InitLimitCharacters();

        float angle = 90.0f;
        // 頂点を云々する。1テキスト6頂点
        // 6文字ずつ進む
        for (int i = 0, streamCount = stream.Count; i < streamCount; i += 6)
        {
            int index = i / 6;
            if (IsNonrotatableCharactor(characters[index]))
            {
                continue;
            }

            // 文字の中央を取得(上なら[i+1])
            var center = Vector2.Lerp(stream[i].position, stream[i + 3].position, 0.5f);

            if (IsAdjustCharactor(characters[index]))
            {
                center = center + new Vector2(-2.0f , 2.0f);
            }

            // 頂点を回す
            for (int r = 0; r < 6; r++)
            {
                var element = stream[i + r];

                var pos = element.position - (Vector3)center; ;
                var newPos = new Vector2(
                    pos.x * Mathf.Cos(angle * Mathf.Deg2Rad) - pos.y * Mathf.Sin(angle * Mathf.Deg2Rad),
                    pos.x * Mathf.Sin(angle * Mathf.Deg2Rad) + pos.y * Mathf.Cos(angle * Mathf.Deg2Rad));
                element.position = (Vector3)(newPos + center);
                


                stream[i + r] = element;
            }
        }
    }

    bool IsNonrotatableCharactor(char character)
    {
        return nonRotatableCharacters.Any(x => x == character);
    }

    bool IsAdjustCharactor(char character)
    {
        return adjustCharacters.Any(x => x == character);
    }
}

OnValidate
https://docs.unity3d.com/ja/540/ScriptReference/MonoBehaviour.OnValidate.html

ModifyMesh
https://docs.unity3d.com/ja/current/ScriptReference/UI.IMeshModifier.ModifyMesh.html

簡単に言えばUIの更新イベントフックして、そこで回転させている。
縦書きにするとァやー(長音符)の位置が少しズレてしまい、違和感を感じるので、
それを調整するようにしました。
また、ListPoolはUnityのソースから拝借。

神様ありがとうございます!
http://madnesslabo.net/utage/?page_id=6042

注意点としては、
WebGLビルドした時に

error CS0117: `UnityEngine.EventSystems.UIBehaviour' does not contain a definition for `OnValidate'

とbase.OnValidate()が怒られる。
これなんでだろー?iOSやaOSビルドは問題が無かった。
とりあえずコメントアウトしていて、特に影響なかったのでそのまま…。

チュートリアル

一番苦労したのはマスタとかコメントに書いたけど、
実装側でちょっと困ったのはこれ。
長くなっちゃったので別記事で

ghoul-life.hatenablog.com

また1つ勉強になった。

リリース

後は体裁を整えたりデバッグしたりで日曜日夜に提出。

締め切りは守る。
これゼッタイ。
みんな決められた時間内で平等に頑張ってる。(と思っている)

感想

変なゲームを好きに作ろうとしたら
被ったー!盛大に被ったー!しかも結構多いw

もうすでに三回目になり、
付いていくのもギリギリですが、
毎回本当に勉強になるなぁ。
少しずつ出来ることが広がっていくのを感じる。

今まではなんかゴチャ混ぜ感があって、
適当に詰め込んだ感じがあったけど今回は割りとまとまった方だと思う。
ゲーム部分をもっと詰めたいな。なんか本とか読んでみよう。

そろそろTweet機能を付けてみてもええか…。

第三回 unity1week 「積む」に挑戦 前編 (企画編)

先週また@naichilabさんのunityroomにて、unity1weekが開催されました。
unityの勉強は継続してやっていて、このブログも出来るだけ続けていきたいなと思っているので
今回もチャレンジしていく事にしました。

積みゲーけしけし | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

企画について

積むって。

もーこれが難しい。だって最初にパッと浮かんだのアレですよ、アレ!
f:id:ghoul_life:20170627020126p:plain
もうずーっと上位にいるネズミとかクマとかファンシーで可愛いアレのイメージが強すぎる。

とりあえずその考えを頭から振り払って、
他のことを考えよう。
荷物を積む、オブジェクトを積み上げて塔を作る、
など上にベクトルを向けるものがイメージとしてすぐに浮かぶが、
これも一杯出るだろうなぁ、と一旦振り払う。

積むんだよな、ゲームだよな、とシンプルに考えだした時
「積む+ゲーム=積みゲーをゲームにしたらどうだろう」
と思い立つ。

詳細をちょっとだけ詰める

積みゲーがただあるだけではダメだ。
それをどうにかしてゲームに落とし込まないと。
積みゲーを消化するって言うよな…。
シンプルに消化していくゲームはどうだろう。

  • 積みゲーから一本ゲームを選ぶ
  • そのゲームにパラメータが割り振ってある
  • ゲームにはクリアまでの所要時間がある
  • プレイヤーは所要時間を消費しそのパラメータ分成長する
  • 称号が手に入る(アクションゲームばかりクリアしていくとアクションマスターなど)
  • 一定期間過ぎた時に結果を振り返ることが出来る(一ヶ月など)

というゲームはどうだろうか。

だめだー

簡単にモックにしてやってみるが、全然駄目。

  • ゲーム拘束時間が長くて、辛い。
  • 長い割に別に大した結果が出るわけでもない。
  • やれる事に比べて周りを固める要素が多く、分かりにくい。

普通の人ならちょっと触ったらもうブラウザ閉じてるわーこれは。
もうこの時点で金曜日の深夜。だけど考え直そう。

無理矢理な方向転換

もっとシンプルにしよう。所要時間も称号も捨てよう。
パラメータももっとシンプルでいい。
パズルゲーム風に要素を調整して…。

・お題が出る
・それを選ぶ
・正解なら+1 不正解なら0

としたらグッと触り心地が良くなった。
(面白いかどうかは一旦置いておいて)
後はこれでなんとか完成まで持っていこう。

開発編につづく
ghoul-life.hatenablog.com