ぐーるらいふ

底辺。

【Unity】アクションゲームの「動く床」を実現するには【Tips】

最近ゲームを作ってまして、そこから何かネタが無いかなーと思うのですが、
これと言って目新しいものが無く…。
その開発過程でちょっとだけ気になった個所をメモ代わりに残しておこう。

動く床

アクションゲームにはよくあるギミックとして、

動く床

というものがある。
地面が動き、タイミングを合わせてジャンプしないと先に進めないというよくあるやつだ。

f:id:ghoul_life:20171008233955g:plain

これをunityで作ってみよう。

床を作る

cubeを作成してスケールを10,1,10。これを動く床とします。

f:id:ghoul_life:20171008234236p:plain

アニメーションを床に付与する

animation > createして適当にtransform.positonのxを動かすようなものを作成

f:id:ghoul_life:20171008234404p:plain

プレイヤーを作る

その上に乗っかるプレイヤーを適当に用意する。
今回はQueryChanを使いました。

f:id:ghoul_life:20171008234655p:plain

Control.cs
(仮実装なんでとりあえずやっつけ…。)

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

public class Control : MonoBehaviour {

    private const float SPEED = 5.0f;
    public Animator anim;
    private float jumpTime = 0;
    private bool isJump = false;
	
	// Update is called once per frame
	void Update () {

        var hz = Input.GetAxis("Horizontal");
        var vt = Input.GetAxis("Vertical");

        if(Mathf.Abs(hz + vt) > 0)
        {
            anim.SetBool("Walk", true);
        }
        else
        {
            anim.SetBool("Walk", false);
        }




        if (Input.GetButtonDown("Jump"))
        {
            if (!isJump)
            {
                anim.SetTrigger("Jump");
                isJump = true;
                jumpTime = 0;
            }
        }

        float jumpVal = 0;

        if (isJump)
        {
            if (jumpTime < 1.0f)
            {
                jumpVal += 2 * SPEED * Time.deltaTime;
            }
            else if (jumpTime < 2.0f)
            {
                //pos.y -= Time.deltaTime;
            }
            else
            {
                isJump = false;
            }
            jumpTime += Time.deltaTime;
        }

        var pos = this.transform.position;
        pos.x += hz * SPEED * Time.deltaTime;
        pos.y += jumpVal;
        pos.z += vt * SPEED * Time.deltaTime;
        
        this.transform.position = pos;

        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            transform.eulerAngles = new Vector3(0,0,0);
        }
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            transform.eulerAngles = new Vector3(0, 180, 0);
        }
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            transform.eulerAngles = new Vector3(0, 270, 0);
        }
        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            transform.eulerAngles = new Vector3(0, 90, 0);
        }

    }
}
とりあえず実行してみる

早速実行してみるとこうなる。

f:id:ghoul_life:20171008235015g:plain

おかしい。おかしいぞ。
上に乗っかった時は動く床の影響を受けて欲しい。

解決策

では、どうするか?こうすればOKだ。

子要素にする

コードにするとこんな感じ

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

public class MoveFloor : MonoBehaviour {
    private void OnCollisionEnter(Collision collision)
    {
        collision.gameObject.transform.SetParent(this.transform);
    }

    private void OnCollisionExit(Collision collision)
    {
        collision.gameObject.transform.SetParent(null);
    }
}

これを床に付与して実行してみよう

f:id:ghoul_life:20171008235406g:plain

これでよくある動く床を実装出来た。

別の手段

別の手段としては床をRigidbodyにして、AddForceで動かす
(PhysicsMaterialで滑りを低く)
という手もあるらしいけど、ちょっと試していない。

凄い久しぶりにunityの事書けた…。最近更新も滞ってたし。
もっとメモ代わりにどんどん書きたい。

【Unity】 メッシュをカットした後のcappingを作ろう

以前

以前メッシュカッターについて記事にした事があった。

ghoul-life.hatenablog.com

この時はなるべく簡潔な話にしたく、オブジェクトを切った後の「蓋」については
特に触れなかったのだが、ちょっとやる機会があったので、書いておこうと思う。

メッシュ断面図

メッシュカッターはオブジェクトをポリゴン単位で考え、一つずつ丁寧に処理していく。
カットラインと交点がある場合は、新たに頂点が2つ作られる。
四角形だった場合は以下のような断面になる。
図だと少し分かりにくいが、二つに分かれたオブジェクト両方ともこのようになる。

f:id:ghoul_life:20170913001850p:plain

蓋の考え方

そしてここに蓋を作るのだが、蓋をどう作るか?を考える。
四角形ならどうにでもなりそうだが、円形や人型のオブジェクトだったらどうする?

f:id:ghoul_life:20170913001905p:plain

そういった事なども簡潔に解決する方法として、「中心点を作る」という方法がある。
この2点と中心点を繋いでいく、にすれば簡単な計算で作れそうだ。

f:id:ghoul_life:20170913001920p:plain

実装

まずは交点となった新たに作られた点を別リストとして取っておく

// どちらにもカウントがあるということはplateと交差しているポリゴンということ
if (group1PosList.Count > 0 && group2PosList.Count > 0)
{
    CalcCrossPoint(plane, group1PosList, group2PosList); // 2.planeとの交点を求める

    // 3.両方のグループともに交点を入れる
    group1PosList.Add(_pos1);
    group1PosList.Add(_pos2);

    // capping用の表
    group1CapPosList.Add(_pos1);
    group1CapPosList.Add(_pos2);


    group2PosList.Add(_pos1);
    group2PosList.Add(_pos2);

    // capping用裏側
    group2CapPosList.Add(_pos1);
    group2CapPosList.Add(_pos2);
}

中心点を求める方法はそこまで難しくない。
頂点をすべて足して、頂点数で割ればいい。

// 中心点を求める
Vector3 center = Vector3.zero;
foreach(var v in cappingPosList)
{
    center += v;
}
center = center / cappingPosList.Count;


そして交点となる2点と中心点を繋いでいく…のだけど、
カッターとなるplateのどちら側にあるかによって蓋の向きが変わる。
この事に注意して、indexを割り振っていく。

var idx0 = centerIdx;
var idx1 = i;
var idx2 = i + 1;

var cross = Vector3.Cross(capPosList[idx2] - capPosList[idx0], capPosList[idx1] - capPosList[idx0]);
var inner = Vector3.Dot(cross, plane.normal);

// plateに対してどちら側の蓋かによって計算が変わるのに注意
if (isFront)
{
    if(inner < 0)
    {
        idx0 = idx2;
        idx2 = centerIdx;
    }
}
else
{
    if (inner > 0)
    {
        idx0 = idx2;
        idx2 = centerIdx;
    }
}

完成

後は全ての頂点とindexをまとめて、
オブジェクトを作れば完成だ。

f:id:ghoul_life:20170912233913g:plain

f:id:ghoul_life:20170913143608g:plain

f:id:ghoul_life:20170913143650g:plain

複雑なオブジェクトでもちゃんと蓋が出来ている。
全体をまとめたプロジェクトごとgithubに公開している。
他にもMaterialを付与したり、Rigidbodyを付与したりという遊びも可能だ。
参考にしてほしい。

github.com

【Unity】第5回 unity1week 「フロー」の開発

unity1week襲来

もう大分時間が経ってしまったけど、書く。

naichiさんのunityroomで定期的に開催されているunityの一週間でゲームを作るイベント

f:id:ghoul_life:20170905004123p:plain
https://unityroom.com/unity1weeks


もう5回目にもなる。
unityを始めてまだまだ日が浅いけど、なんか色々パッと出来るようになってきた。
(と思いたい)

見知らぬ、お題

毎回あるお題が出て基本的にそれに沿った形で開発を行う。
今回のお題は「フロー」。

フローとか言われると仕事感が凄いというか、
エスカレーションフローを思い出すというか…。
とにかくイメージが悪い。
(勝手に浮かんでくる自分が悪い。仕事柄しょうがないのだけど…。)

鳴りすぎる、Slack

お題見てからどうしよっかなーと頭抱えたかったのだが、
そんな事とは関係なくSlackが鳴り止まなかった。

どうも8月はなんか忙しい。
毎年4月〜5月は桜の季節だからか全体的に緩いムードなのだが、
梅雨のどんよりした季節から加速していき、夏場にピークを迎える傾向にある。

というわけで結局技術的な追求なども特に出来ず、
やれる事をやる事に。

イデア、風呂の向こうに

フロー…フロー…とりあえず風呂入って考えよう。
といった具合で特にアイデアが出ないまま時間が過ぎていく…。
流れるように水の上を滑る!
みたいなものを作ってみたが、なんというか今一つ面白くならず…。
マリオ64の滑り台パチモンみたいなもんになってしまった。

これにコインでも付けて一番下まで滑ろう!なんて適当案が最初に思いついたのだが、
どうしよっかなぁ、このまま詰めるしか無いのか…と
イデアが降りてくるのを待ったが、やっぱりSlackが鳴り止まない。

ベッドダイバー

もう飛んだ。

朝Slackで起こされる ->
家からリモートで暫定対応して、会社へ ->
午前中MTG ->
午後客先など ->
日付変わるぐらいに帰宅 ->
就寝

というループが続き、気がつけば金曜日。
冷や汗しか出ない。

静止した頭の中で

テキトーにBlenderモデリングする!

  • >豆腐
  • >お箸

テキトーにFractureして壊してみる!

  • >予めオブジェクト分けといて、死んだらRigidbody.AddForceでぶっ飛ばす

苦手なAnimationに挑戦!
→何度か使ってるんだけど、毎回戸惑う!

後は割と過去の遺産を掘り起こしてUIやらなんやらそのまま使いました。

死に至るBlender、そして

豆腐とお箸はこれで作りました。
すげー苦手なBlenderを頑張ってみる。
後で書く

FractureとModel

予め壊れたものにしておいて、AddForceでぶっ飛ばす…だけ

f:id:ghoul_life:20170905003447p:plain

メッシュカッターからcapping、そしてvolonoi法(もどき)について
まとめきれないいいい。

Anchor

unityにはanchorという考えを使いたい場合、ちょっとだけ工夫が必要だ。
色々手はあるが、親子構造にして、親オブジェクトをanchorの代わりにするのが単純だ。
お箸ではこれを使ってる

せめて、ゲームらしく

(これが書きたかっただけ)
出来上がったのがこれ。

最初はすげー難しくて自分で作ってクリア出来ないというひどい出来w
動く床は失敗だった…。
うまく追従してくれなくて、どうしようかなと一旦そのままにしている。

こういうアクションゲーム作ったことある人はunity使いに多いと思うが、
基本的にコード記述量が少ない。
基本誤魔化し誤魔化しでなんとか形にしておいた…程度。

命の選択を

ラクガキしてる方が多くないかw
キーボードよりペンタブ触ってる方が長かったんじゃないかw
違うよこれはただの現実逃避だよ!

とりあえず夏休みを取りたい。
この炎上案件さえ終わればきっと取れる。
そう信じたい。

【Unity】第四回 unity1week 「夏」の開発について【レールの作り方】

第四回 Unity一週間ゲームジャムに参加

https://unityroom.com/assets/unity1week-d28da101073cc56f5238563f033ad780466b71b1605362b9fc351e7135ed1c4b.png
Unity 1週間ゲームジャム | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

一回目から参加し続けてもう四回目にもなってしまった。
参加者もちょっと少なかったのかもしれない。
イカドラクエのせいだ。


結論から言えば、今回も結果は振るわなかったのだけど、
まだまだUnity学びたいし頑張ろうと思っている。

イデアについて

とりあえず

  • イカ
  • 花火
  • お化け

この辺りが夏の記号になりそうだなと漠然と考えた。

で、とりあえずスイカ割らせてみたり。

切り方はMeshCutterで数回ランダムに切っているだけ。
毎回結果が変わるのが面白い所。

だが、スイカ割りは特に人気で被りまくりそう。
花火も上に同じ。

最近気になっていたレール式ランゲームをちょっとやってみたかったので、
サーフィンを題材に今回はそれに。

レール式ランゲーム

決められたレールの上を走るランゲーム。
ジェットコースターやトロッコとかイメージすればOK。
例えば右方向へ向かって走るランゲームと比べて、
3Dを活かしたダイナミックな動きを出すことが容易に出来る。
また制御しやすい。(一回転を作ったり、この曲がり角で敵を出す!とか)

unityに落とし込む

  • ランダムにルートを生成する
  • 無限に走ることが出来ないとならない
  • 敵の配置
  • ゲームとしての体裁を整える

敵の配置やゲームとしての体裁を整える辺りは長くなりすぎるので割愛。
今回はルート生成についてコードレベルで紹介。

ルート生成のアルゴリズム

一言で言えばルートはpointの集合体です。
「次に向かうポイント」
をどんどん点で打っていけばOKです。

流れとしては

  • ポイントをざっくり置く
  • 滑らかに整える
  • デコレーションする
  • 描画する

という流れになります。

ポイントをざっくり置く

まずはじめにポイントをざっくりと置いていきます。

for (var i = 1; i < LINE_CNT; i++)
{
    var distance = Random.Range(1, 5); // 距離の力
    range = Random.Range(-3, 3) * 30; // ランダムな方向を向ける
    lastAngle = Mathf.Clamp(lastAngle + range, 0, 180); // 前回の角度から0度~180度の間でランダムな方向を出す

    float angle = lastAngle * Mathf.PI / 180;
    // 極座標の公式で計算する
    path.LinePoints[i] = path.LinePoints[i - 1] + new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle)) * distance;
}

pointはランダムな方向と、角度から位置を計算します。
極座標の公式

線の長さ(r)と角度(θ)で到達点座標(x,y)が求まる
(x,y) = (r cosθ , r sinθ)

イメージとしてはこんな感じでまだガタガタな点が作られます。

f:id:ghoul_life:20170731181931p:plain

滑らかに整える

点を細かくして補完していきます。
この計算をどのようにするかによって補完の仕方が変わります。
http://d.hatena.ne.jp/nakamura001/20111117/1321539246
イージングのアルゴリズムを応用して、緩急を作り出したりしても面白い!

/// <summary>
/// 線を滑らかに補完する
/// </summary>
/// <param name="linePoints"></param>
/// <param name="smoothness"></param>
/// <returns></returns>
private Vector3[] SmoothCurve(Vector3[] linePoints , float smoothness)
{
	if (smoothness < 1.0f) smoothness = 1.0f;

	var pointsLength = linePoints.Length;
	var curvedLength = (pointsLength * Mathf.RoundToInt(smoothness)) - 1;
	var curvedPoints = new List<Vector3>(curvedLength);

	float t = 0.0f;
	List<Vector3> points;
	for (int i = 0; i < curvedLength + 1; i++)
	{
		t = Mathf.InverseLerp(0, curvedLength, i);
		points = new List<Vector3>(linePoints);
		for (int j = pointsLength - 1; j > 0; j--)
		{
			for (int k = 0; k < j; k++)
			{
				points[k] = (1 - t) * points[k] + t * points[k + 1];
			}
		}
		curvedPoints.Add(points[0]);
	}

	return (curvedPoints.ToArray());
}

青い線が補完後の線になります。
ちょっと滑らかさが強いかもしれませんね…。

f:id:ghoul_life:20170731182144p:plain

デコレーションする

一旦パスを作った後、ゲーム的な要素を追加します。
敵を置いたり、ループ作ったり。

基本的に完成したパスを再度舐めつつ、
ランダムに処理をしていけばOK

var EFFECT_IDX = 20; // 20個目までの点には効果を出さないように~とか。
for (var i = EFFECT_IDX ; i < path.LinePoints.Length ; i++)
{
    var pos = path.LinePoints[i];
    // 効果がランダムで発動
    switch (Random.Range(1, 5))
    {
    	// 障害物を出す、とか
    	//case XXX:
	    //     ~~~
	    //     break;
	}
}

描画する

無くてもいいのですが、
見せてもいいのです。

LineRendererにまとめて入れるだけ

_lineRenderer.positionCount = _linePoints.Length;
_lineRenderer.SetPositions(_linePoints);

あれ、SetVertexCount だったような…とちょっと気になるヤーツ。

https://docs.unity3d.com/ja/540/ScriptReference/LineRenderer.SetVertexCount.html

変わったようです。

処理速度について

もうここまででもいいんじゃね?って話ですが
ゲームにした場合、気になるのは処理速度です。
どうしても計算はちょっと長いのでストレートに処理していたらプチフリーズしてしまうでしょう。
そして、パスの終点まで到達した時に処理をしていたのでは、遅れてしまうでしょう。

  • 並列に処理する
  • 予め次を計算しておく

この2つを満たせばOKです。

並列に処理するのは何でもいいです。
Coroutineでも、Taskでも、Threadでも…。
慣れ親しんだCoroutine辺りでとりあえずいいのではないでしょうか。

次を用意しておく処理はこんな感じで

// indexが半分を過ぎたら。ここはもっと早くてもいいかも。ゲームスピードとの兼ね合い
var len = currentPath.LinePoints.Length;
if (index > 0.5f * len && isCreateNext)
{
    var lastPoint = currentPath.LastPoint; // 最後のposition
    var lastAngle = currentPath.LastAngle; // 最後の角度
    // 続きのサブを作成しておく
    StartCoroutine(CreateRailPath(lastPoint, lastAngle , subPath));

    isCreateNext = false; // 何度も作らせないために
}

// 最後まで来たらswapする
if (index > len - 1)
{
    // 予め作っておいたサブとカレントを切り替える
    var tmpPath = currentPath;
    currentPath = subPath;
    subPath = tmpPath;
    isCreateNext = true; // 次を作らせるフラグをON
    index = 0;
}

currentとsubを用意しておいて、
終わり際にswapする!というだけ。
後は進捗をチェックしつつ、次を用意させるだけです。

f:id:ghoul_life:20170731183130p:plain

進んでいくと

f:id:ghoul_life:20170731183153p:plain

交互に次が生成されるのがわかると思います。
この辺りだけgithubにまとめておきました。
よく分からない所はこちらを参考に。

github.com

今回の感想

というわけで第四回も終わりました。
今回も大変だった…。手が遅くて後手後手に…。
イデアは相変わらずヒドイもんで、
センスが腐ってんなと思います。

次こそはもうちょっと良いものを作りたい。

【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を起動する時は、熱々のピザとキンキンに冷えたコーラを忘れずにね!