[Unity3D] 攻撃の実装①

PlayerControllerの作成、フォルダの分類
---
1, EnemyのCapcelを作成。playerControllerスクリプトを作成し、playerにアタッチする。
![6F9544CB-470B-4BFC-9762-7250E9C28467.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/021ea9c0-3e3a-c6a1-f0c1-10b7e4d41d2d.jpeg)

2, Moverに下記スクリプトを書く

```C#:Mover
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class Mover : MonoBehaviour
{
    [SerializeField] Transform taget;

    void Update()
    {
        UpdateAnimator();
    }

    private void MoveToCursor()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        bool hasHit = Physics.Raycast(ray, out hit);
        if (hasHit)
        {
            MoveTo(hit.point);　//関数
        }
    }

// 追加の関数
    public void MoveTo(Vector3 destination)
    {
        GetComponent<NavMeshAgent>().destination = destination;
    }

    private void UpdateAnimator()
    {
        Vector3 velocity = GetComponent<NavMeshAgent>().velocity;
        Vector3 localVelocity = transform.InverseTransformDirection(velocity);
        float speed = localVelocity.z;
        GetComponent<Animator>().SetFloat("fowardSpeed", speed);
    }
}
```

3,　playerControllerに下記スクリプトを書く

```C#:PlayerController
using UnityEngine;
using System.Collections;

public class PlayerController : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetMouseButton(0))
        {
            MoveToCursor();
        }
    }

    private void MoveToCursor()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        bool hasHit = Physics.Raycast(ray, out hit);
        if (hasHit)
        {
            GetComponent<Mover>().MoveTo(hit.point);
        }
    }
}
```

4, 各フォルダを作成し、各namespaceを作成してnamespace内にコードを入れる
![2500A328-0497-46FD-B38B-384339321655.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/13820204-4b6e-989b-c230-47e542ffbdc2.jpeg)

```C#:PlayerController
using UnityEngine;
using System.Collections;
using RPG.Movement;

namespace RPG.control
{
    public class PlayerController : MonoBehaviour
    {
        private void Update()
        {
            if (Input.GetMouseButton(0))
            {
                MoveToCursor();
            }
        }

        private void MoveToCursor()
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            bool hasHit = Physics.Raycast(ray, out hit);
            if (hasHit)
            {
                GetComponent<Mover>().MoveTo(hit.point);
            }
        }
    }
}
```
Tagetの作成
---
1, CombatTargetとFighterを作成し、playerにFighterを EnemyにCombatTargetをアタッチする
![D4DE9E5E-8522-4405-974A-850FB2BCDCC3.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/fb40f5e6-c668-6351-ef9d-42a03b8a398f.jpeg)
![4D51D4FF-F47D-4A1A-8CE6-4C5B013A4DD7.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/a23c69b2-6145-c816-bf22-f7055437a8a5.jpeg)

2, 各スクリプトに下記コードを記述する

```C#:PlayerController
using UnityEngine;
using System.Collections;
using RPG.Movement;
using System;
using RPG.Combat;

namespace RPG.control
{
    public class PlayerController : MonoBehaviour
    {
        private void Update()
        {
            InteractWithCombat();
            InteractWithMovement();
        }

        private void InteractWithCombat()
        {
            RaycastHit[] hits = Physics.RaycastAll(GetMouseRay());
            foreach (RaycastHit hit in hits)
            {
                CombatTarget target = hit.transform.GetComponent<CombatTarget>();
                if (target == null) continue;

                if (Input.GetMouseButtonDown(0))
                {
                    GetComponent<Fighter>().Attack(target);
                }
            }
        }

        private void InteractWithMovement()
        {
            if (Input.GetMouseButton(0))
            {
                MoveToCursor();
            }
        }

        private void MoveToCursor()
        {
            RaycastHit hit;
            bool hasHit = Physics.Raycast(GetMouseRay(), out hit);
            if (hasHit)
            {
                GetComponent<Mover>().MoveTo(hit.point);
            }
        }

        private static Ray GetMouseRay()
        {
            return Camera.main.ScreenPointToRay(Input.mousePosition);
        }
    }
}
```

```C#:Fighter
using UnityEngine;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour
    {
        public void Attack(CombatTarget target)
        {
            Debug.Log("Take that you short, squat peasant!");
        }
    }
}
```

```C#:CombatTarget
using UnityEngine;
using System.Collections;

namespace RPG.Combat
{
    public class CombatTarget : MonoBehaviour
    {

    }
}
```

画面の外を押しても反応しないようにする
---
1, 下記スクリプトを追加する

```C#:PlayerController
using UnityEngine;
using System.Collections;
using RPG.Movement;
using System;
using RPG.Combat;

namespace RPG.control
{
    public class PlayerController : MonoBehaviour
    {
        private void Update()
        { 
            if (InteractWithCombat()) return;
            if (InteractWithMovement()) return;
            Debug.Log("Nothing to do");
        }

        private bool InteractWithCombat()
        {
            RaycastHit[] hits = Physics.RaycastAll(GetMouseRay());
            foreach (RaycastHit hit in hits)
            {
                CombatTarget target = hit.transform.GetComponent<CombatTarget>();
                if (target == null) continue;

                if (Input.GetMouseButtonDown(0))
                {
                    GetComponent<Fighter>().Attack(target);
                }
                return true;
            }
            return false;
        }

        private bool InteractWithMovement()
        {
            RaycastHit hit;
            bool hasHit = Physics.Raycast(GetMouseRay(), out hit);
            if (hasHit)
            {
                if (Input.GetMouseButtonDown(0))
                {
                    GetComponent<Mover>().MoveTo(hit.point);
                }
                return true;
            }
            return false;
        }

        private static Ray GetMouseRay()
        {
            return Camera.main.ScreenPointToRay(Input.mousePosition);
        }
    }
}
```

敵の前で止める
---
1, Fighter,Mover,PlayerControllerに各コードを書く

```C#:Fighter.cs
using UnityEngine;
using RPG.Movement;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour
    {
        [SerializeField] float weaponRange = 2f;

        Transform target;

        private void Update()
        {
            if (target == null) return;

            if (!GetIsInRange())
            {
                GetComponent<Mover>().MoveTo(target.position);
            }
            else
            {
                GetComponent<Mover>().Stop();
            }
        }

        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, target.position) < weaponRange;
        }

        public void Attack(CombatTarget combatTarget)
        {
            target = combatTarget.transform;
        }

        public void Cancel()
        {
            target = null;
        }
    }
}
```

```C#:Mover
using System.Collections;
using System.Collections.Generic;
using RPG.Combat;
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Movement
{
    public class Mover : MonoBehaviour
    {
        [SerializeField] Transform target;

        NavMeshAgent navMeshAgent;

        private void Start()
        {
            navMeshAgent = GetComponent<NavMeshAgent>();
        }

        void Update()
        {
            UpdateAnimator();
        }

        public void StartMoveAction(Vector3 destination)
        {
            GetComponent<Fighter>().Cancel();
            MoveTo(destination);
        }

        public void MoveTo(Vector3 destination)
        {
            navMeshAgent.destination = destination;
            navMeshAgent.isStopped = false;
        }

        public void Stop()
        {
            navMeshAgent.isStopped = true;
        }

        private void UpdateAnimator()
        {
            Vector3 velocity = navMeshAgent.velocity;
            Vector3 localVelocity = transform.InverseTransformDirection(velocity);
            float speed = localVelocity.z;
            GetComponent<Animator>().SetFloat("fowardSpeed", speed);
        }
    }
}
```

```C#:PlayerController
using UnityEngine;
using System.Collections;
using RPG.Movement;
using System;
using RPG.Combat;

namespace RPG.control
{
    public class PlayerController : MonoBehaviour
    {
        private void Update()
        {
            if (InteractWithCombat()) return;
            if (InteractWithMovement()) return;
            Debug.Log("Nothing to do");
        }

        private bool InteractWithCombat()
        {
            RaycastHit[] hits = Physics.RaycastAll(GetMouseRay());
            foreach (RaycastHit hit in hits)
            {
                CombatTarget target = hit.transform.GetComponent<CombatTarget>();
                if (target == null) continue;

                if (Input.GetMouseButtonDown(0))
                {
                    GetComponent<Fighter>().Attack(target);
                }
                return true;
            }
            return false;
        }

        private bool InteractWithMovement()
        {
            RaycastHit hit;
            bool hasHit = Physics.Raycast(GetMouseRay(), out hit);
            if (hasHit)
            {
                if (Input.GetMouseButtonDown(0))
                {
                    GetComponent<Mover>().StartMoveAction(hit.point);
                }
                return true;
            }
            return false;
        }

        private static Ray GetMouseRay()
        {
            return Camera.main.ScreenPointToRay(Input.mousePosition);
        }
    }
}
```

依存サイクルの分離
---

1, ActionShedulerの作成

```C#:Actionsheduler
//追加クラス
using UnityEngine;
using System.Collections;

namespace RPG.Core
{
    public class ActionScheduler : MonoBehaviour
    {
        MonoBehaviour currentAction;

        public void StartAction(MonoBehaviour action)
        {
            if (currentAction == action) return;
            if (currentAction != null)
            {
                Debug.Log("Cancelling" + currentAction);
            }
            currentAction = action;
        }
    }
}
```

```C#:Mover
using System.Collections;
using System.Collections.Generic;
using RPG.Combat;
using RPG.Core;  //追加
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Movement
{
    public class Mover : MonoBehaviour
    {
        [SerializeField] Transform target;

        NavMeshAgent navMeshAgent;

        private void Start()
        {
            navMeshAgent = GetComponent<NavMeshAgent>();
        }

        void Update()
        {
            UpdateAnimator();
        }

        public void StartMoveAction(Vector3 destination)
        {
            //追加
            GetComponent<ActionScheduler>().StartAction(this);
            GetComponent<Fighter>().Cancel();
            MoveTo(destination);
        }

        public void MoveTo(Vector3 destination)
        {
            navMeshAgent.destination = destination;
            navMeshAgent.isStopped = false;
        }

        public void Stop()
        {
            navMeshAgent.isStopped = true;
        }

        private void UpdateAnimator()
        {
            Vector3 velocity = navMeshAgent.velocity;
            Vector3 localVelocity = transform.InverseTransformDirection(velocity);
            float speed = localVelocity.z;
            GetComponent<Animator>().SetFloat("fowardSpeed", speed);
        }
    }
}
```

```C#:Fighter
using UnityEngine;
using RPG.Movement;
using RPG.Core;  //追加

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour
    {
        [SerializeField] float weaponRange = 2f;

        Transform target;

        private void Update()
        {
            if (target == null) return;

            if (!GetIsInRange())
            {
                GetComponent<Mover>().MoveTo(target.position);
            }
            else
            {
                GetComponent<Mover>().Stop();
            }
        }

        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, target.position) < weaponRange;
        }

        public void Attack(CombatTarget combatTarget)
        {
            //追加
            GetComponent<ActionScheduler>().StartAction(this);
            target = combatTarget.transform;
        }

        public void Cancel()
        {
            target = null;
        }
    }
}
```

インタフェースを用いた依存性反転
---
1, 各スクリプトのコメント部分を追加・変更

```C#:IAction.cs
sing System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace RPG.Core
{
    public interface IAction
    {
        void Cancel();
    }
}
```

```C#:ActionShedule.cs
using UnityEngine;
using System.Collections;

namespace RPG.Core
{
    public class ActionScheduler : MonoBehaviour
    {
        IAction currentAction;  //////

        public void StartAction(IAction action) /////
        {
            if (currentAction == action) return;
            if (currentAction != null)
            {
                currentAction.Cancel();    /////
            }
            currentAction = action;
        }
    }
}
```

```C#:Mover.cs
using System.Collections;
using System.Collections.Generic;
using RPG.Core;
using UnityEngine;
using UnityEngine.AI;
namespace RPG.Movement
{
    public class Mover : MonoBehaviour, IAction /////
    {
        [SerializeField] Transform target;

        NavMeshAgent navMeshAgent;

        private void Start()
        {
            navMeshAgent = GetComponent<NavMeshAgent>();
        }

        void Update()
        {
            UpdateAnimator();
        }

        public void StartMoveAction(Vector3 destination)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            MoveTo(destination);
        }

        public void MoveTo(Vector3 destination)
        {
            navMeshAgent.destination = destination;
            navMeshAgent.isStopped = false;
        }

        public void Cancel()    /////
        {
            navMeshAgent.isStopped = true;
        }

        private void UpdateAnimator()
        {
            Vector3 velocity = navMeshAgent.velocity;
            Vector3 localVelocity = transform.InverseTransformDirection(velocity);
            float speed = localVelocity.z;
            GetComponent<Animator>().SetFloat("fowardSpeed", speed);
        }
    }
}
```

```C#:Fighter
using UnityEngine;
using RPG.Movement;
using RPG.Core;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction   //////
    {
        [SerializeField] float weaponRange = 2f;

        Transform target;

        private void Update()
        {
            if (target == null) return;

            if (!GetIsInRange())
            {
                GetComponent<Mover>().MoveTo(target.position);
            }
            else
            {
                GetComponent<Mover>().Cancel(); /////
            }
        }

        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, target.position) < weaponRange;
        }

        public void Attack(CombatTarget combatTarget)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            target = combatTarget.transform;
        }

        public void Cancel()
        {
            target = null;
        }
    }
}
```

アニメーションの実装
---
1, Attackアニメーションを作成しMotionを選択。Parametersにattackを作成する。
ConditionsにAttackを設定し、MakeTransitionの各設定を行う。
![D24C76F6-1DA4-48AD-9541-80426CA50E0B_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/d3661e81-6224-da57-a8ae-fa52a1daf348.jpeg)

![6AE63C05-9A14-4859-B8E7-069C64DECCBF.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/bd40d081-b443-a720-ade9-fb3579cbf016.jpeg)

2, 下記コメント部を追加する

```C#:Fighter.cs
using UnityEngine;
using RPG.Movement;
using RPG.Core;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction
    {
        [SerializeField] float weaponRange = 2f;

        Transform target;

        private void Update()
        {
            if (target == null) return;

            if (!GetIsInRange())
            {
                GetComponent<Mover>().MoveTo(target.position);
            }
            else
            {
                GetComponent<Mover>().Cancel();
                AttackBehaviour();  /////
            }
        }

        ///
        private void AttackBehaviour()
        {
            GetComponent<Animator>().SetTrigger("attack");
        }

        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, target.position) < weaponRange;
        }

        public void Attack(CombatTarget combatTarget)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            target = combatTarget.transform;
        }

        public void Cancel()
        {
            target = null;
        }

        //Animation Event
        void Hit()
        {

        }
    }
}
```
攻撃を抑える
---
1, 下記コメント部分の追加

```C#:Fighter
using UnityEngine;
using RPG.Movement;
using RPG.Core;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction
    {
        [SerializeField] float weaponRange = 2f;
        [SerializeField] float timeBetweenAttacks = 1f; //追加

        Transform target;
        float timeSinceLastAttack = 0;  //追加

        private void Update()
        {
            timeSinceLastAttack += Time.deltaTime;  //追加　

            if (target == null) return;

            if (!GetIsInRange())
            {
                GetComponent<Mover>().MoveTo(target.position);
            }
            else
            {
                GetComponent<Mover>().Cancel();
                AttackBehaviour();  
            }
        }

        private void AttackBehaviour()
        {
            if (timeSinceLastAttack > timeBetweenAttacks)   //追加
            {
                GetComponent<Animator>().SetTrigger("attack");
                timeSinceLastAttack = 0;    //追加
            }
        }

        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, target.position) < weaponRange;
        }

        public void Attack(CombatTarget combatTarget)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            target = combatTarget.transform;
        }

        public void Cancel()
        {
            target = null;
        }

        //Animation Event
        void Hit()
        {

        }
    }
}
```

ダメージを受ける
---
1, Health.csを作成。下記のコードを書く。Fighter.csに下記コメント部分を追加。

```C#:Health.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace RPG.Combat
{
    public class Health : MonoBehaviour
    {
        [SerializeField] float health = 100f;

        public void TakeDamage(float damage)
        {
            health = Mathf.Max(health - damage, 0);
            Debug.Log(health);
        }
    }
}
```

```C#:Fighter.cs
using UnityEngine;
using RPG.Movement;
using RPG.Core;

namespace RPG.Combat
{
    public class Fighter : MonoBehaviour, IAction
    {
        [SerializeField] float weaponRange = 2f;
        [SerializeField] float timeBetweenAttacks = 1f;
        [SerializeField] float weaponDamage = 5f;   //追加

        Transform target;
        float timeSinceLastAttack = 0; 

        private void Update()
        {
            timeSinceLastAttack += Time.deltaTime; 

            if (target == null) return;

            if (!GetIsInRange())
            {
                GetComponent<Mover>().MoveTo(target.position);
            }
            else
            {
                GetComponent<Mover>().Cancel();
                AttackBehaviour();  
            }
        }

        private void AttackBehaviour()
        {
            if (timeSinceLastAttack > timeBetweenAttacks) 
            {
                GetComponent<Animator>().SetTrigger("attack");
                timeSinceLastAttack = 0;
            }
        }

        //追加
        void Hit()
        {
            Health healthComponent = target.GetComponent<Health>();
            healthComponent.TakeDamage(weaponDamage);
        }

        private bool GetIsInRange()
        {
            return Vector3.Distance(transform.position, target.position) < weaponRange;
        }

        public void Attack(CombatTarget combatTarget)
        {
            GetComponent<ActionScheduler>().StartAction(this);
            target = combatTarget.transform;
        }

        public void Cancel()
        {
            target = null;
        }
    }
}
```
入れ子式プレハブとバリアント
---
1, PlayerをCharacterのnameに変更し、選択する。
![89037011-B8F3-410F-ADCF-23EAD047C09C_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/4c664c94-ecb1-deae-eb02-017156b50605.jpeg)

2, CreateのPrefabVariantを選択し、PlayerのPrefabを作成する
![1FAFD3DB-DE37-40BE-B68F-09B045CE82A0.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/904bfdd6-af29-f228-e64a-ca0fb9c8275d.jpeg)

3, 同様にEnemyも作成する
![1F59436A-165A-4012-8962-329E53E613FF_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/4e8275bf-7818-8b55-2b78-af0e0b21dbf9.jpeg)

4, CharacterPrefabにキャラがいると編集できないので一旦キャラだけ削除する
![16889702-0B40-4E38-8CBA-FA22081EFE2E_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/86c81428-e9bf-b19c-05cb-b0ceff583236.jpeg)

5, playerとEnemyのPrefabにキャラをセットする。
![482A459D-B7B2-4AF8-A892-9817E3D6C934.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/cd384c15-fb61-1a1c-1bf8-5a7c9c9fdbe4.jpeg)

6, CharacterのPlayerControllerをコピーし、RemoveConponentで削除する。
![6EA78B2C-E29B-4837-94F2-0657368E09EE_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/8a5fc2b5-a8c0-9549-30ad-726a3e695a26.jpeg)
![0C5005A8-D48B-4DD4-BFB4-582F15FA9230_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/6ffaac6f-ea95-3c88-d3a3-535aa5b160e7.jpeg)

7, playerにペーストComponentして加えてあげる
![CF3ECF20-4782-438C-8A3E-EA65A1E049E5_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/b6dc94fd-381d-e87c-d2c2-9d9165336cda.jpeg)

8, 同様の手順でEnemyにはCombatを付ける
![6E5F84FE-4588-4FB5-BDA3-8ED2A0E19BC3_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/cb2a0e54-dc3e-e928-317c-b9885d9e0380.jpeg)

9, 同様の手順でCharacterにはHealthを付ける
![38A403CF-9C96-4B37-94B6-A81A5F4FE7D9_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/940521/11a9ec16-c829-469f-fd38-a7e7db2d40f9.jpeg)