# Section 4 : 기본 컴뱃 소개

### 24. 컨트롤 계층 분리

```cs
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);
    }
}
```

Move와 Combat을 관장하는 PlayerController.cs 생성 및 리팩토링

monobehaviour나 Update 작성한 후에 Tab 누르면 스니펫 완성해준다 (원래는 그래야 하는데 내 vscode는 안해줌) 프리팹 옆에 있는 화살표 누르면 프리팹 편집기로 바로 들어갈 수 있다.

### 25. 의존성을 확인하기 위한 네임스페이스

의존성은 안좋다. 하나 망가지면 다른 것도 망가질 수 있음. 사이클은 최악. 다 망가짐.

사이클 같은거 안 생기게 하려면 레이어로 다 뜯어놓으면 됨. Control은 Combat에 의존하고, Combat은 Stats에 의존하고... 하위 객체로 갈 수록 안정성이 증가한다. 변경도 덜 해야됨. 이런거 변경하면 프로젝트가 불안정해짐.

using으로 네임스페이스를 명시해놓으면 의존성 관리를 더 잘 할 수 있게 된다. 네임스페이스에는 관련 있는 것들만 다 넣어놓으면 됨.

```cs
namespace RPG.Control
{
    public class PlayerController : MonoBehaviour
    {
		// 생략
    }
}
```

이제 다른 네임스페이스에 있는 클래스에선 using문을 사용해야 함.

### 26. 컴포넌트에 대한 레이캐스팅

```cs
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 combatTarget = hit.transform.GetComponent<CombatTarget>();
                if (combatTarget != null && Input.GetMouseButtonDown(0))
                {
                    GetComponent<Fighter>().Attack(combatTarget);
                }
            }
        }
```

RaycastAll은 지나가면서 받은 모든 오브젝트를 저장한다. 그래서 RaycastHit\[]를 반환한다. 오브젝트로 가려진 적 클릭해도 공격할 수 있게끔 할 때 사용. `Input.GetMouseButtonDown(0)` 조건 안 넣으면 요청이 계속해서 들어옴.

Ctrl + `.`를 통해 IDE의 인텔리센스 기능들 이용 가능

* 메서드로 추출하거나
* 임시 변수에 담아놓고 쓰던 기능을 메서드에 담았다면 `Inline temporal varialbe`로 코드를 즉각 조정할 수 있다

### 27. 액션 우선순위 구현

공격하는거라면 그냥 이동 시키면 안된다. 일단 print("Attack")으로 해둠.

```cs
  if (combatTarget != null && Input.GetMouseButtonDown(0))
  {
      GetComponent<Fighter>().Attack(combatTarget);
      return true;
  }
```

이를 위해 메서드들이 bool값을 return하도록 바꿀건데, 이런식으로 if문 안에 `return true`를 넣지 않을거다. cursor affordance (대상이 공격 가능한건지, 상호작용 가능한건지, 이동할 곳인지 커서로 표현) 을 구현할 것이기 때문.

```cs
        private void Update()
        {
            if (InteractWithCombat()) return;
            if (InteractWithMovement()) return;
            print("Nothing to do");
        }

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

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

근데 Console.Log()가 아니라 그냥 print() 때려도 되는 건 처음 알았네

### 28. 범위 내 이동

```cs
private void Update()
{
    if (target == null) return;
    bool isInRange = Vector3.Distance(transform.position, target.position) > weaponRange;

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

### 29. 무브먼트로 컴뱃 취소

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

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

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

Mover와 Fighter 사이에 의존성 순환이 생겼다.

### 30. 의존성 순환 분리

미사일을 상속받는 총알, 화살, 로켓이 있다면 미사일 받는다고 선언해놓으면 총알 넣어도 되고 화살 넣어도 된다 이게 치환 원칙

Mover랑 Fighter는 둘 다 MonoBehaviour 상속받으니까 MonoBehaviour로 받겠다고 선언하면 둘 다 받을 수 있음 (PlayerController도 받을 수 있다는게 문제인데, 그건 나중에 해결)

```cs
namespace RPG.Core
{
    public class ActionScheduler : MonoBehaviour
    {
        MonoBehaviour prev_action;
        public void StartAction(MonoBehaviour action)
        {
            if (prev_action == action) return;
            if (prev_action != null)
            {
                print("Canceling " + prev_action);
            }
            prev_action = action;
        }
    }
}
```

Movement와 Combat을 관장하는 스케줄러를 도입하여 의존성 순환을 해결한다. Move 시작하거나 Combat 시작할 때 스케줄러에 말하는 구조

근데 이래버리면 Scheduler가 Movement랑 Combat에 의존을 해버려서 또 다른 의존성 순환이 만들어진다.

```cs
GetComponent<ActionScheduler>().StartAction(this);
```

Mover랑 Fighter에서 행동 시작할 때 this로 자기 자신 넣어서 호출

### 31. 인터페이스로 의존성 역전

IAction이라는 인터페이스를 만들어서 Movement랑 Combat을 묶은 뒤에 Scheduler는 IAction에 의존하면 의존성 방향 바꾸는 의존성 역전 가능

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

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

### 32. 공격 애니메이션 추가

![](https://velog.velcdn.com/images/biomatrix117/post/8b4fc132-b2b1-43ed-b4cc-8a2929413904/image.png)

Transition Duration 또는 아래의 바를 건드려서 애니메이션 전환을 얼마나 자연스럽게 할지 정할 수 있다

### 33. 코드에서 애니메이션 트리거

![](https://velog.velcdn.com/images/biomatrix117/post/cfa218d2-050e-4454-ab0a-a7f633154406/image.png)

애니메이션 파일 선택 > Inspector 하단 > 우측 상단 Avatar 아이콘 > Unity Model 혹은 적용하고 싶은 캐릭터 > 정확한 타격 시점 알아내어 게임 플레이 이벤트를 애니메이션과 동기화할 수 있다

![](https://velog.velcdn.com/images/biomatrix117/post/af63a170-646d-4acf-a6f0-bf685e0ebcf3/image.png)

근데 애초에 애니메이션 자체에 Hit()를 호출하는게 있었음.

```cs
private void AttackBehaviour()
{
    GetComponent<Animator>().SetTrigger("Attack");
}
```

transition에 trigger 설정하고 스크립트로 trigger 체크

### 34. 공격 속도 조절

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

// 생략
}

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

애니메이션 trigger 설정에 쿨타임 두기

### 35. 데미지 적용

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

        public void TakeDamage(float damage)
        {
            health = Math.Max(health - damage, 0);
            print(health);
        }
    }
}
```

Fighter.cs의 Hit()에서 TakeDamage 호출 애니메이션에서 정확히 때리는 타이밍에 Hit()를 호출하기 때문에 휘두르기도 전에 데미지가 가는 상황 일어나지 않음


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lazyartisan.gitbook.io/note/main-page/courses/rpg-unity-c/section-4.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
