12/08/2018, 15:18

Day 25 - Adventure Game Tutorial 4

はい、今日からコード解析に入ります。 と、その前に昨日の夜格闘していた事についてちょっと書きます。 Error 実は昨日、完成したコードを動かしてみたらエラーが出て動かない事態に。 んで、バージョンアップしちゃったから動かないのかな?とか思って色々と試したんですけど このゲーム、開始するためには「Persistent」っていうシーンから開始しないと動かないみたいですね。 SecurityRoomやMarketから開始しようとすると動きません。 普通に動作させてゲームクリアまで動作しました。 こんな変装で通してくれるセキュリティとかヤバいと思うんですけど。 ...

はい、今日からコード解析に入ります。 と、その前に昨日の夜格闘していた事についてちょっと書きます。

Error

実は昨日、完成したコードを動かしてみたらエラーが出て動かない事態に。 んで、バージョンアップしちゃったから動かないのかな?とか思って色々と試したんですけど

このゲーム、開始するためには「Persistent」っていうシーンから開始しないと動かないみたいですね。 SecurityRoomやMarketから開始しようとすると動きません。

普通に動作させてゲームクリアまで動作しました。

こんな変装で通してくれるセキュリティとかヤバいと思うんですけど。

1/6

こちらは以前に半分程度解析を行いましたがその続きがまだかけていませんでした。 なので、その続きです。

コード全文は「こちら」から見て頂ければ

private void Stopping (out float speed)
{
    agent.Stop();
    transform.position = destinationPosition;
    speed = 0f;

    if (currentInteractable)
    {
        transform.rotation = currentInteractable.interactionLocation.rotation;
        currentInteractable.Interact();
        currentInteractable = null;
        StartCoroutine(WaitForInteraction());
    }
}

ここからは、前に出てきたif分の中で指定されていた関数ですね。 Stoppingという関数です。

    agent.Stop();
    transform.position = destinationPosition;
    speed = 0f;

エージェント(プレイヤー)の動きを停止して、現在位置をクリックで指定した位置に。 スピードは当然0です。

if (currentInteractable)
{
    transform.rotation = currentInteractable.interactionLocation.rotation;
    currentInteractable.Interact();
    currentInteractable = null;
    StartCoroutine(WaitForInteraction());
}

もし、プレイヤーが会話可能(調査可能)なオブジェクトに反応する位置に止まったら プレイヤーの向きを対象オブジェクトに向けて、オブジェクトに反応する処理をしたらcurrentInteractableをnullにします。

最後に、コルーチンで処理待ちをして一定時間はオブジェクトに反応しないようにします。

  private void Slowing (out float speed,float distanceToDestination)
    {
        agent.Stop();
        transform.position = 
            Vector3.MoveTowards(transform.position,destinationPosition,slowingSpeed * Time.deltaTime);
        float proportionalDistance = 1f - distanceToDestination / agent.stoppingDistance;
        speed = Mathf.Lerp(slowingSpeed, 0f, proportionalDistance);

        Quaternion targetRotation = 
            currentInteractable ? currentInteractable.interactionLocation.rotation : transform.rotation;
        transform.rotation = Quaternion.Lerp(transform.rotation,targetRotation,proportionalDistance);
    }

Stopping関数終了。

次は、Slowingという関数。歩行速度をだんだんと遅くするよ、という関数ですね。

        agent.Stop();
        transform.position = 
            Vector3.MoveTowards(transform.position,destinationPosition,slowingSpeed * Time.deltaTime);
        float proportionalDistance = 1f - distanceToDestination / agent.stoppingDistance;
        speed = Mathf.Lerp(slowingSpeed, 0f, proportionalDistance);

あれ?ここでもagent.Stop()が出てきた。何回止めようとしてるんだ。 と思ったら、次のtransform.positionで制御しちゃうから、Navmeshでの移動はもう止めちゃえ!ってことらしいです。

んで、そのtransformが何をしているかというと、秒間slowingSpeedという設定した速度で行きたいポジションまで行くという指示。 MoveTowards(今の位置,目的地,速度)っていう書き方で、ある場所に向かっていくような動きを実装できるそうです。 ホーミングライフルとか、ロマンありますね。

で、次の行は目的地までの距離と、指定したstoppingDistanceの比率を求めて1からその比率を引いたものを代入。 で、Mathf.Lerpってのは(A,B,T)って描いた時に、AとBの間のTのところの値、0だったらA、1だったらB、0.5だったらAとBの中間。 その値をspeedに代入しています。distanceToDestinationはだんだんと小さくなっていくので、proportionalDistanceは段々と大きくなっていきます。つまり、Tの値が増えていくので、speedは段々と0に近づく(小さくなる)という感じですね。

めんどくさいな…でも、違和感を消すために簡単なコードなんて無いことは経験済み、我々は避けては通れないのか…

Quaternion targetRotation = 
    currentInteractable ? currentInteractable.interactionLocation.rotation : transform.rotation;
transform.rotation = Quaternion.Lerp(transform.rotation,targetRotation,proportionalDistance);

残りは、もしアクセス可能なオブジェクトを選択していたらtargetをそのオブジェクトに、それじゃなかったらtargetはそのままの向きという指示。 a ? A : B と書くと、aがbool型変数でtrueならAとなり、falseならBとなります。 なんだか前の関数でも同じような処理をしたような気がしますが… 次の式で対象オブジェクトまで移動するまでに、回転角度をtargetの向きまでproportionalDistanceの値の大きさだけの速さで調整します。 前述したようにproportionalDistanceは段々と大きくなるので、targetRotationの向きまでどんどんと回転していきます。 アクセス可能なオブジェクトを選択していなかったらtargetRotation=transform.rotationなので向きは変わりません。

動きながら回転とはいえど、速度が遅くなってから回転しているので移動の大部分は直線移動ですねー

Slowing関数終了。

次は、Moving関数です。動く。

  private void Moving()
    {
        Quaternion targetRotation = Quaternion.LookRotation(agent.desiredVelocity);
        transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, turnSmoothing * Time.deltaTime);
    }

基本はnavmeshくんが制御してくれるので、回転制御だけをします。 targetRotationってのを、移動方向の回転角度に設定。 んで次の式では移動毎に回転がズレるのを補正してます。その時点でのtransform.rotationからtargetRotationまで導いてあげます。

Moving関数終了。

で、次は地面をクリックした時の動作についてです。

public void OnGroundClick(BaseEventData data)
{
    if (!handleInput)
    {
        return;
    }

    currentInteractable = null;

    PointerEventData pData = (PointerEventData)data;
    NavMeshHit hit;
    if (NavMesh.SamplePosition(pData.pointerCurrentRaycast.worldPosition, out hit, navMeshSampleDistance, NavMesh.AllAreas))
    {
        destinationPosition = hit.position;
    }
    else
    {
            destinationPosition = pData.pointerCurrentRaycast.worldPosition;
    }
    agent.SetDestination(destinationPosition);
    agent.Resume();
}

最初は、handleinputという物がfalseなら以下の処理はしないよ、というもの。 handleinputっていうのはコルーチンのところで出てきます。 んで、currentinteractableをnullにしておきます。地面クリックしてるだけなのでアクセス可能オブジェクトには触れないですもんね。 で、BaseEventDataをPointerEventDataに代入します。 次に、Navmesh上でクリックしたらNavmesh上で移動、他のとこクリックされたらクリックされた位置を移動先として設定します。

これでOnGroundClick関数は終了。

次は、OnInteractableClick関数。 大体名前で分かるようになってきました。アクセス可能なオブジェクトに触れたら何が起きてるのか?って感じですかね

    public void OnInteractableClick (Interactable interactable)
    {
        if (!handleInput)
        {
            return;
        }

        currentInteractable = interactable;
        destinationPosition = currentInteractable.interactionLocation.position;

        agent.SetDestination(destinationPosition);
        agent.Resume();
    }

最初は同じでコルーチンのとこの関数を参照して動作するかしないか。 動作させるのであれば、currentInteractableをinteractableにします。目的地はアクセス可能なオブジェクトの前。

で、OnInteractableClick関数は終了。

最後は簡単なんですけど、コルーチンで使うWaitForInteraction関数。

    private IEnumerator WaitForInteraction()
    {
        handleInput = false;

        yield return inputHoldWait;

        while (animator.GetCurrentAnimatorStateInfo(0).tagHash != hashLocomotionTag)
        {
            yield return null;
        }
            
        handleInput = true;
    }

コルーチンが働いている間、handleInputをfalseにしています。 これによって、コルーチンが働いていたら上のOnGroundClickやらが動作しなくなります。 で、あらかじめ指定しておいたinputHoldWaitという数値分だけ待機します。 次は、アニメーターがロコモーションタグを持つまで待機します。 で、それが終わった後にやっとコルーチン終了、handleInputをtrueにして再度コルーチンを実行出来るようにします。

以上が、プレイヤーを動作させているコードの解読結果ですね。 うーん、色々と新しいものが出てきて何をやっているのかはわかったけど 絶対に真に理解出来てないだろうなあ。このチュートリアルのコード解析が終わったら学んだことを活かしてゲームを作ってやるぞ。

長くなってしまったので、今日はここまで。

0