반응형

이번 시간은 몬스터의 부착 되어있는 HP Bar를 실제 공격 당하였을때 줄어 들게 하는 작업을 하겠습니다

 

먼저 EnemyParams 스크립트를 엽니다

 

 

EnemyParams 스크립트 수정

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI; //유니티 UI 를 생성 할때 추가하는 네임 스페이스


public class EnemyParams : CharacterParams
{
    public string name;
    public int exp { get; set; }
    public int rewardMoney { get; set; }
    public Image hpBar;


    public override void InitParams()
    {
        name = "Monster";
        level = 1;
        maxHp = 50;
        curHp = maxHp;
        attackMin = 2;
        attackMax = 5;
        defense = 1;

        exp = 10;
        rewardMoney = Random.Range(10, 31);

        InitHpBarSize();
    }

    void InitHpBarSize()
    {
        //hpBar 의 사이즈를 원래 자신의 사이즈 ,1배의 사이즈로 초기와시켜 주게 됨
        hpBar.rectTransform.localScale = new Vector3(1f, 1f, 1f);
    }
    protected override void UpdateAfterReceiveAttack()
    {
        base.UpdateAfterReceiveAttack();

        hpBar.rectTransform.localScale = new Vector3((float)curHp / (float)maxHp, 1f, 1f);
    }
}

 

 

 

Enemy 오브젝트를 선택하고 자식으로 두었던 HPFront 를 드래그 드롭하여 EnemyParms 스크립트의 HP Bar 에 올려 놓습니다

 

 

게임을 실행하여 몬스터 HP Bar 가 공격 당할때 줄어드는 것을 확인합니다

0

 

반응형
반응형

슬라이드와 같이 몬스터의 HP Bar를 만들어서 몬스터가 움직여도  HP bar 가 화면 방향을 유지시키는 작업을 하겠습니다 

 

0

 

 

 

Hierarchy 화면에 마우스 오른쪽 키를 눌러서 UI  -> Image 를 만듭니다

 

 

 

 

화면은 2D로 전환한 다음 생성된 Image를 두번 클릭하여 이미지를 가까이 오게 한다 그리고 Anchor Presets 른 눌러서 

Alt와 Shift 키를 동시에 눌러서 왼쪽으로 Anchor를 왼쪽으로 오게 합니다

 

 

 

Image 이름을 HPBack으로 바꾸고 그림과 같이 with  Hight 를 조정하고 색깔을 회색으로 합니다

 

HPBack을 Ctrl +D룰 눌러서 복사한 다음 이름을 HPFront로 바꾼 다음 색깔을 붉은색으로 합니다

 

 

Canvas를 선택하고 Inspector에서  Render Mode를 Warld Space로 바꾼 다음  Canvas Scaler를 체크 해제합니다

 

 

 

Enemy를 선택하고 빈 게임 오브젝트를 생성한 다음 이름을 HPBarPos 라 하고 자식으로 만든 HPBar 그림과 같이 놓습니다

 

 

자식으로 놓은 HPBar의 Canvas를 Inspector에서 설정 버튼을 누르고 Reset을 합니다

 

 

 

그리고 HPBack과 HPFront를 선택하여 설정 버튼을 누르고 Reset Position을 누릅니다

 

그림과 같이 Enemy 중앙 위로 보이도록 위치를 보기 좋게 합니다

 

 

새 스크립트를 만들고 이름을  Billboard 라 합니다

 

 

 

Billboard 스크립트 작성

 

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

public class Billboard : MonoBehaviour
{

    Transform cam;
    void Start()
    {
        cam = Camera.main.transform;
    }


    void Update()
    {
        //항상 카메라를 정면으로 바라볼 수 있도록 로테이션 값이 조정되게 함
        transform.LookAt(transform.position + cam.rotation * Vector3.forward, cam.rotation * Vector3.up);   
    }
}

 

 

 

그리고 Billboard 스크립트를 HP Bar의 Canvas에 붙입니다

게임을 실행하여 화면에 HP Bar 가 몬스터의 위치 에따라 화면 방향으로  보이는지 확인합니다

 

0

반응형
반응형

0

몬스터가 플레이어를 공격할때 타이밍 맞게 공격하고 플레이어의 사망 처리를 하겠습니다

 

먼저 AniEventControl 스크립트를 만들고 Enemy 오브젝트에 자식으로 되어있는 Spider 오브젝트에 스크립트를 붙힘니다

 

 

AniEventControl 스크립트 작성

 

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

public class AniEventControl : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    public void SendAttackEnemy()
    {
        transform.parent.gameObject.SendMessage("AttackCalculate");
    }
    // Update is called once per frame
    void Update()
    {
        
    }
}

 

 

그리고 Spider 오브젝트를 선택하고 Animation 을 들어갑니다

읽기전용으로 되어 있는 attack1을 바꿔줘야 합니다

 

 

attack1 애니메이션을 Ctrl + D를 눌러서 복사하고 이름을 attack1_new 로 바꾸어서 그것을 드래그하여 animaton에 등록 합니다

 

 

 

 

그리고 Spider 오브젝트를 선택하고  Window ->Animation ->Animation 을 열고 attack1_new 애니메이션에 공격 타이밍에 Add event 를 누른 다음 Function 을  SendAttackEnemy() 함수를 등록합니다

 

 

그리고 EnemyAni 스크립트를 선택하여 수정합니다

 

attack1 이라 되어 있던 이름을 attack1_new 로 바꿈니다

 

플레이어 HP가 공격을 받고 0이되었을때 사망처리 하기 위해서 

그리고 PlayerFSM 스크립트를 열고 수정합니다

 

 

 

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

public class PlayerFSM : MonoBehaviour
{  
    public enum State
    {
        Idle,
        Move,
        Attack,
        AttackWait,
        Dead
    }
    //idle 상태를 기본 상태로 지정
    public State currentState = State.Idle;

    //마우수 클릭 지점,플레이어가 이동할 목적지의 좌표를 저장할 예정
    Vector3 curTargetPos;

    GameObject curEnemy;

    public float rotAnglePerSecond = 360f; //1초에 플레이어의 방향을 360도 회전한다

    public float moveSpeed = 2f; //초당 2미터의 속도로 이동

    float attackDelay = 2f; // 공격을 한번하고 다시 공격할때 까지의 지연시간

    float attackTimer = 0f; //공격을 하고 난 뒤에 경과되는 시간을 계산하기 위한 변수

    float attackDistance = 1.5f; // 공격 거리 (적과의 거리)

    float chaseDistance = 2.5f; // 전투 중 적이 도망가면 다시 추적을 시작 하기 위한 거리

    PlayerAni myAni;

    PlayerParams myParams;

    EnemyParams curEnemyParams;
    void Start()
    {
        myAni = GetComponent<PlayerAni>();
        //  myAni.ChangeAni(PlayerAni.ANI_WALK);

        myParams = GetComponent<PlayerParams>();

        myParams.InitParams();

        myParams.deadEvent.AddListener(ChangeToPlayerDead);

        ChangeState(State.Idle, PlayerAni.ANI_IDLE);
    }

    public void ChangeToPlayerDead()
    {
        print("player was dead");
        ChangeState(State.Dead, PlayerAni.ANI_DIE);
    }

    public void CurrentEnemyDead()
    {
        ChangeState(State.Idle, PlayerAni.ANI_IDLE);
        print("enemy was killed");

        curEnemy = null;
    }

    public void AttackCalculate()
    {
        if (curEnemy == null)
        {
            return;
        }
        curEnemy.GetComponent<EnemyFSM>().ShowHitEffect();

        int attackPower = myParams.GetRandomAttack();
        curEnemyParams.SetEnemyAttack(attackPower);
    }

 

 


    // 적을 공격하기 위한 함수 
    public void AttackEnemy(GameObject enemy)
    {
        if (curEnemy != null && curEnemy == enemy)
        {
            return;
        }

        //적(몬스터)의 파라미터를 변수에 저장
        curEnemyParams = enemy.GetComponent<EnemyParams>();

        if (curEnemyParams.isDead == false)
        {
            curEnemy = enemy;
            curTargetPos = curEnemy.transform.position;

            ChangeState(State.Move, PlayerAni.ANI_WALK);
        }
        else
        {
            curEnemyParams = null;
        }
  
    }
    void ChangeState(State newState, int aniNumber)
    {
        if (currentState == newState)
        {
            return;
        }
        myAni.ChangeAni(aniNumber);
        currentState = newState;
    }

    //캐릭터의 상태가 바뀌면 어떤 일이 일어날지 를 미리 정의
    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:

                IdleState();

                break;
            case State.Move:

                MoveState();

                break;
            case State.Attack:

                AttackState();

                break;
            case State.AttackWait:

                AttackWaitState();

                break;
            case State.Dead:

                DeadState();

                break;
            default:
                break;
        }
    }
   void IdleState()
    {

    }

    void MoveState()
    {
        TurnToDestination();
        MoveToDestination();
    }
    void AttackState()
    {
        attackTimer = 0f;

        //transform.LookAt(목표지점 위치) 목표지점을 향해 오브젝트를 회전 시키는 함수
        transform.LookAt(curTargetPos);
        ChangeState(State.AttackWait, PlayerAni.ANI_ATKIDLE);
    }
    void AttackWaitState()
    {
        if (attackTimer > attackDelay)
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);

        }

        attackTimer += Time.deltaTime;
    }
    void DeadState()
    {

    }

 

 


    //MoveTo(캐릭터가 이동할 목표 지점의 좌표)
   public void MoveTo(Vector3 tPos)
    {

//사망 하였을때 움직임 리턴 처리 (여기서 끝냄)
        if (currentState == State.Dead)
        {
            return;
        }
        curEnemy = null;
        curTargetPos = tPos;
        ChangeState(State.Move, PlayerAni.ANI_WALK);
    }
    void TurnToDestination()
    {
        // Quaternion lookRotation(회전할 목표 방향) : 목표 방향은 목적지 위치에서 자신의 위치를 빼면 구함
        Quaternion lookRotation = Quaternion.LookRotation(curTargetPos - transform.position);

        //Quaternion.RotateTowards(현재의 rotation값, 최종목표rotation 값, 최대 회전각)
        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        //Vector3.MoveTowards(시작지점, 목표지점,최대이동거리)
        transform.position = Vector3.MoveTowards(transform.position, curTargetPos,moveSpeed * Time.deltaTime);

        if (curEnemy == null)
        {
            //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령
            if (transform.position == curTargetPos)
            {
                ChangeState(State.Idle, PlayerAni.ANI_IDLE);
            }
        }
        else if (Vector3.Distance(transform.position,curTargetPos) < attackDistance)//Vector3.Distance(A,B):A와B사이의 거리
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
        }
    }
    void Update()
    {
        UpdateState();
    }
}

 

 

게임을 실행시키고 몬스터가 플레이어를 공격하면 플레이어의 HP 가 줄고 0이되면 사망 처리 되는 것을 확인 한다

 

0

반응형
반응형

0

유니티가 제공하는 유니티 이벤트 기능인  UnityEvent 를 만들고 AddListenr() 함수를 등록해 보겠습니다

 

플레이어가 몬스터를 공격하였을때 몬스터가 죽으면 몬스터의 죽는 순간의 이벤트를 처리하고

몬스터가 죽음과 동시에 플레이어의 행동을 다시 Idle로 변환하는 SendMessage() 를 처리하여 보겠습니다

 

CharackterParams 스크립트를 선택하고 스크립트를 수정합니다

//CharackterParams 스크립트수정

 

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;//유니티 이벤트를 사용하기 위해서는 네임스페이스를 추가해야함

//CharacterParams는 플레이어의 파라미터 클래스와 몬스터 파라미터 클래스의 부모 클래스 역할을 하게됨
public class CharacterParams : MonoBehaviour
{
    //퍼블릭 변수가 아니라 약식프로퍼티,속성으로 지정
    //퍼블릭 변수와 똑같이 사용할수 있지만 유니티 인스펙터에 노출되는 것을 막고 보안을 위해정식 프로퍼티로 전환이 쉬워짐
    public int level { get; set; } 
    public int maxHp { get; set; }
    public int curHp { get; set; }
    public int attackMin { get; set; }
    public int attackMax { get; set; }
    public int defense { get; set; }
    public bool isDead { get; set; }

    [System.NonSerialized]
    public UnityEvent deadEvent = new UnityEvent();

    void Start()
    {
        InitParams();
    }

    //나중에 CharacterParams 클래스를 상속한 자식클래스 에서 
    //InitParams 함수 에 자신만의 명령어를 추가하기만 하면 자동으로 필요한 명령어들이 실행
    public virtual void InitParams()
    {

    }

    public int GetRandomAttack()
    {
        int randAttack = Random.Range(attackMin, attackMax + 1);
        return randAttack;
    }

    public void SetEnemyAttack(int enemyAttackPower)
    {
        curHp -= enemyAttackPower;
        UpdateAfterReceiveAttack();
    }

    //캐릭터가 적으로 부터 공격을 받은 뒤에 자동으로 실행될 함수를 가상함수로 만듬
    protected virtual void UpdateAfterReceiveAttack()
    {
        print(name + "'s HP: " + curHp);

        if (curHp <= 0)
        {
            curHp = 0;
            isDead = true;
            deadEvent.Invoke();
        }
    }
}

 

EnemyFSM 스크립트를 선택하고 수정합니다

 

 

 

 

 

 

 

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

public class EnemyFSM : MonoBehaviour
{
    public enum State
    {
        Idle,  //정지
        Chase,  //추적
        Attack,  //공격
        Dead,   //사망
        NoState  //아무 일도 없는 상태
    }

    public State currentState = State.Idle;

    EnemyParams myParams;

    EnemyAni myAni;

    Transform player;

    PlayerParams playerParams;

    float chaseDistance = 5f; // 플레이어를 향해 몬스터가 추적을 시작할 거리
    float attackDistance = 2.5f; // 플레이어가 안쪽으로 들오오게 되면 공격을 시작
    float reChaseDistance = 3f; // 플레이어가 도망 갈 경우 얼마나 떨어져야 다시 추적

    float rotAnglePerSecond = 360f; // 초당 회전 각도
    float moveSpeed = 1.3f; // 몬스터의 이동 속도

    float attackDelay = 2f;
    float attackTimer = 0f;

    public ParticleSystem hitEffect;

    void Start()
    {
        myAni = GetComponent<EnemyAni>();
        myParams = GetComponent<EnemyParams>();
        myParams.deadEvent.AddListener(CallDeadEvent);

        ChangeState(State.Idle, EnemyAni.IDLE);

        player = GameObject.FindGameObjectWithTag("Player").transform;
        playerParams = player.gameObject.GetComponent<PlayerParams>();

        hitEffect.Stop();
    }

    //몬스터가 죽는 순간 처리 명령어
    void CallDeadEvent()
    {
        ChangeState(State.Dead, EnemyAni.DIE);
        player.gameObject.SendMessage("CurrentEnemyDead");
    }

    public void ShowHitEffect()
    {
        hitEffect.Play();
    }

    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:
                IdleState();
                break;
            case State.Chase:
                ChaseState();
                break;
            case State.Attack:
                AttackState();
                break;
            case State.Dead:
                DeadState();
                break;
            case State.NoState:
                NoState();
                break;          
        }
    }

    public void ChangeState(State newState, string aniName)
    {
        if (currentState == newState)
        {
            return;
        }

        currentState = newState;
        myAni.ChangeAni(aniName);
    }
    void IdleState()
    {
        if (GetDistanceFromPlayer() < chaseDistance)
        {
            ChangeState(State.Chase, EnemyAni.WALK);
        }
    }

    void ChaseState()
    {
        //몬스터가 공격 가능 거리 안으로 들어가면 공격 상태
        if (GetDistanceFromPlayer() < attackDistance)
        {
            ChangeState(State.Attack, EnemyAni.ATTACK);
        }
        else
        {
            TurnToDestination();
            MoveToDestination();
        }   
    }

    void AttackState()
    {
        if (GetDistanceFromPlayer() > reChaseDistance)
        {
            attackTimer = 0f;
            ChangeState(State.Chase, EnemyAni.WALK);
        }
        else
        {
            if (attackTimer > attackDelay)
            {
                transform.LookAt(player.position);
                myAni.ChangeAni(EnemyAni.ATTACK);

                attackTimer = 0f;
            }

            attackTimer += Time.deltaTime;
        }
    }
    void DeadState()
    {
        GetComponent<BoxCollider>().enabled = false;
    }
    void NoState()
    {

    }

    void TurnToDestination()
    {
        Quaternion lookRotation = Quaternion.LookRotation(player.position - transform.position);

        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        transform.position = Vector3.MoveTowards(transform.position, player.position, moveSpeed * Time.deltaTime);
    }

    //플레이어와 거리을 재는 함수
    float GetDistanceFromPlayer()
    {
        float distance = Vector3.Distance(transform.position, player.position);
        return distance;
    }

    // Update is called once per frame
    void Update()
    {
        UpdateState();
    }
}

 

 

 

 

PlayerFSM 스크립트를 선택하고 수정합니다

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

public class PlayerFSM : MonoBehaviour
{  
    public enum State
    {
        Idle,
        Move,
        Attack,
        AttackWait,
        Dead
    }
    //idle 상태를 기본 상태로 지정
    public State currentState = State.Idle;

    //마우수 클릭 지점,플레이어가 이동할 목적지의 좌표를 저장할 예정
    Vector3 curTargetPos;

    GameObject curEnemy;

    public float rotAnglePerSecond = 360f; //1초에 플레이어의 방향을 360도 회전한다

    public float moveSpeed = 2f; //초당 2미터의 속도로 이동

    float attackDelay = 2f; // 공격을 한번하고 다시 공격할때 까지의 지연시간

    float attackTimer = 0f; //공격을 하고 난 뒤에 경과되는 시간을 계산하기 위한 변수

    float attackDistance = 1.5f; // 공격 거리 (적과의 거리)

    float chaseDistance = 2.5f; // 전투 중 적이 도망가면 다시 추적을 시작 하기 위한 거리

    PlayerAni myAni;

    PlayerParams myParams;

    EnemyParams curEnemyParams;
    void Start()
    {
        myAni = GetComponent<PlayerAni>();
        //  myAni.ChangeAni(PlayerAni.ANI_WALK);

        myParams = GetComponent<PlayerParams>();

        myParams.InitParams();

        ChangeState(State.Idle, PlayerAni.ANI_IDLE);
    }

    public void CurrentEnemyDead()
    {
        ChangeState(State.Idle, PlayerAni.ANI_IDLE);
        print("enemy was killed");

        curEnemy = null;
    }

    public void AttackCalculate()
    {
        if (curEnemy == null)
        {
            return;
        }
        curEnemy.GetComponent<EnemyFSM>().ShowHitEffect();

        int attackPower = myParams.GetRandomAttack();
        curEnemyParams.SetEnemyAttack(attackPower);
    }

    // 적을 공격하기 위한 함수 
    public void AttackEnemy(GameObject enemy)
    {
        if (curEnemy != null && curEnemy == enemy)
        {
            return;
        }

        //적(몬스터)의 파라미터를 변수에 저장
        curEnemyParams = enemy.GetComponent<EnemyParams>();

        if (curEnemyParams.isDead == false)
        {
            curEnemy = enemy;
            curTargetPos = curEnemy.transform.position;

            ChangeState(State.Move, PlayerAni.ANI_WALK);
        }
        else
        {
            curEnemyParams = null;
        }
  
    }
    void ChangeState(State newState, int aniNumber)
    {
        if (currentState == newState)
        {
            return;
        }
        myAni.ChangeAni(aniNumber);
        currentState = newState;
    }

 

 

 


    //캐릭터의 상태가 바뀌면 어떤 일이 일어날지 를 미리 정의
    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:

                IdleState();

                break;
            case State.Move:

                MoveState();

                break;
            case State.Attack:

                AttackState();

                break;
            case State.AttackWait:

                AttackWaitState();

                break;
            case State.Dead:

                DeadState();

                break;
            default:
                break;
        }
    }
   void IdleState()
    {

    }

    void MoveState()
    {
        TurnToDestination();
        MoveToDestination();
    }
    void AttackState()
    {
        attackTimer = 0f;

        //transform.LookAt(목표지점 위치) 목표지점을 향해 오브젝트를 회전 시키는 함수
        transform.LookAt(curTargetPos);
        ChangeState(State.AttackWait, PlayerAni.ANI_ATKIDLE);
    }
    void AttackWaitState()
    {
        if (attackTimer > attackDelay)
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);

        }

        attackTimer += Time.deltaTime;
    }
    void DeadState()
    {

    }

    //MoveTo(캐릭터가 이동할 목표 지점의 좌표)
   public void MoveTo(Vector3 tPos)
    {
        curEnemy = null;
        curTargetPos = tPos;
        ChangeState(State.Move, PlayerAni.ANI_WALK);
    }
    void TurnToDestination()
    {
        // Quaternion lookRotation(회전할 목표 방향) : 목표 방향은 목적지 위치에서 자신의 위치를 빼면 구함
        Quaternion lookRotation = Quaternion.LookRotation(curTargetPos - transform.position);

        //Quaternion.RotateTowards(현재의 rotation값, 최종목표rotation 값, 최대 회전각)
        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        //Vector3.MoveTowards(시작지점, 목표지점,최대이동거리)
        transform.position = Vector3.MoveTowards(transform.position, curTargetPos,moveSpeed * Time.deltaTime);

        if (curEnemy == null)
        {
            //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령
            if (transform.position == curTargetPos)
            {
                ChangeState(State.Idle, PlayerAni.ANI_IDLE);
            }
        }
        else if (Vector3.Distance(transform.position,curTargetPos) < attackDistance)//Vector3.Distance(A,B):A와B사이의 거리
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
        }
    }
    void Update()
    {
        UpdateState();
    }
}

 

게임을 실행하여 몬스터가 HP 를 소모 하면 죽는 행동과 플레이어가 IDLE 상태로 변하는 것을 확인 합니다

0

반응형
반응형

이번 시간에는 전투로 인한 캐릭터 파라미터를 만들고 변경되는 부분을 처리 하겠습니다

 

새로운 스크립트를 생성하고 이름을 CharacterParams 합니다

 

CharacterParams 스크립트 작성

 

 

 

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

//CharacterParams는 플레이어의 파라미터 클래스와 몬스터 파라미터 클래스의 부모 클래스 역할을 하게됨
public class CharacterParams : MonoBehaviour
{
    //퍼블릭 변수가 아니라 약식프로퍼티,속성으로 지정
    //퍼블릭 변수와 똑같이 사용할수 있지만 유니티 인스펙터에 노출되는 것을 막고 보안을 위해정식 프로퍼티로 전환이 쉬워짐
    public int level { get; set; } 
    public int maxHp { get; set; }
    public int curHp { get; set; }
    public int attackMin { get; set; }
    public int attackMax { get; set; }
    public int defense { get; set; }
    public bool isDead { get; set; }

    void Start()
    {
        InitParams();
    }

    //나중에 CharacterParams 클래스를 상속한 자식클래스 에서 
    //InitParams 함수 에 자신만의 명령어를 추가하기만 하면 자동으로 필요한 명령어드이 실행
    public virtual void InitParams()
    {

    }

    public int GetRandomAttack()
    {
        int randAttack = Random.Range(attackMin, attackMax + 1);
        return randAttack;
    }

    public void SetEnemyAttack(int enemyAttackPower)
    {
        curHp -= enemyAttackPower;
        UpdateAfterReceiveAttack();
    }

    //캐릭터가 적으로 부터 공격을 받은 뒤에 자동으로 실행될 함수를 가상함수로 만듬
    protected virtual void UpdateAfterReceiveAttack()
    {
        print(name + "'s HP: " + curHp);
    }

}

 

 

 

 

그리고 새로운 스크립트를 생성하고 이름을  EnemyParams라 합니다

 

 

 

 

EnemyParams 스크립트 작성

 

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

public class EnemyParams : CharacterParams
{
    public string name;
    public int exp { get; set; }
    public int rewardMoney { get; set; }
    public override void InitParams()
    {
        name = "Monster";
        level = 1;
        maxHp = 50;
        curHp = maxHp;
        attackMin = 2;
        attackMax = 5;
        defense = 1;

        exp = 10;
        rewardMoney = Random.Range(10, 31);

        isDead = false;


    }

    protected override void UpdateAfterReceiveAttack()
    {
        base.UpdateAfterReceiveAttack();
    }
}

 

 

 

 

 

 

그리고 새로운 스크립트를 만들고 이름을 PlayerParams 라 합니다

 

 

PlayerParams 스크립트작성

 

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

public class PlayerParams : CharacterParams
{
    public string name { get; set; }
    public int curExp { get; set; }
    public int expToNextLevel { get; set; }
    public int money { get; set; }

    public override void InitParams()
    {
        name = "hong";
        level = 1;
        maxHp = 100;
        curHp = maxHp;
        attackMin = 5;
        attackMax = 8;
        defense = 1;

        curExp = 0;
        expToNextLevel = 100 * level;
        money = 0;

        isDead = false;
    }

    protected override void UpdateAfterReceiveAttack()
    {
        base.UpdateAfterReceiveAttack();
    }
}

 

 

 

 

 

Player 오브젝트를 선택하고 PlayerParams 스크립트를 Player 에 붙힘니다

 

 

 

 

 

 

Enemy 오브젝트를 선택하고 EnemyParams 스크립트를  Enemy에 붙힘니다

그리고 PlayerFSM 스크립트를 선택하고 수정하겠습니다

 

 

 

 

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

public class PlayerFSM : MonoBehaviour
{  
    public enum State
    {
        Idle,
        Move,
        Attack,
        AttackWait,
        Dead
    }
    //idle 상태를 기본 상태로 지정
    public State currentState = State.Idle;

    //마우수 클릭 지점,플레이어가 이동할 목적지의 좌표를 저장할 예정
    Vector3 curTargetPos;

    GameObject curEnemy;

    public float rotAnglePerSecond = 360f; //1초에 플레이어의 방향을 360도 회전한다

    public float moveSpeed = 2f; //초당 2미터의 속도로 이동

    float attackDelay = 2f; // 공격을 한번하고 다시 공격할때 까지의 지연시간

    float attackTimer = 0f; //공격을 하고 난 뒤에 경과되는 시간을 계산하기 위한 변수

    float attackDistance = 1.5f; // 공격 거리 (적과의 거리)

    float chaseDistance = 2.5f; // 전투 중 적이 도망가면 다시 추적을 시작 하기 위한 거리

    PlayerAni myAni;

    PlayerParams myParams;

    EnemyParams curEnemyParams;


    void Start()
    {
        myAni = GetComponent<PlayerAni>();
        //  myAni.ChangeAni(PlayerAni.ANI_WALK);

        myParams = GetComponent<PlayerParams>();

        myParams.InitParams();

        ChangeState(State.Idle, PlayerAni.ANI_IDLE);
    }

    public void AttackCalculate()
    {
        if (curEnemy == null)
        {
            return;
        }
        curEnemy.GetComponent<EnemyFSM>().ShowHitEffect();

        int attackPower = myParams.GetRandomAttack();
        curEnemyParams.SetEnemyAttack(attackPower);
    }

    // 적을 공격하기 위한 함수 
    public void AttackEnemy(GameObject enemy)
    {
        if (curEnemy != null && curEnemy == enemy)
        {
            return;
        }

        //적(몬스터)의 파라미터를 변수에 저장
        curEnemyParams = enemy.GetComponent<EnemyParams>();

        if (curEnemyParams.isDead == false)
        {
            curEnemy = enemy;
            curTargetPos = curEnemy.transform.position;

            ChangeState(State.Move, PlayerAni.ANI_WALK);
        }
        else
        {
            curEnemyParams = null;
        }
  
    }
    void ChangeState(State newState, int aniNumber)
    {
        if (currentState == newState)
        {
            return;
        }
        myAni.ChangeAni(aniNumber);
        currentState = newState;
    }

 

 


    //캐릭터의 상태가 바뀌면 어떤 일이 일어날지 를 미리 정의
    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:

                IdleState();

                break;
            case State.Move:

                MoveState();

                break;
            case State.Attack:

                AttackState();

                break;
            case State.AttackWait:

                AttackWaitState();

                break;
            case State.Dead:

                DeadState();

                break;
            default:
                break;
        }
    }
   void IdleState()
    {

    }

    void MoveState()
    {
        TurnToDestination();
        MoveToDestination();
    }
    void AttackState()
    {
        attackTimer = 0f;

        //transform.LookAt(목표지점 위치) 목표지점을 향해 오브젝트를 회전 시키는 함수
        transform.LookAt(curTargetPos);
        ChangeState(State.AttackWait, PlayerAni.ANI_ATKIDLE);
    }
    void AttackWaitState()
    {
        if (attackTimer > attackDelay)
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);

        }

        attackTimer += Time.deltaTime;
    }
    void DeadState()
    {

    }

    //MoveTo(캐릭터가 이동할 목표 지점의 좌표)
   public void MoveTo(Vector3 tPos)
    {
        curEnemy = null;
        curTargetPos = tPos;
        ChangeState(State.Move, PlayerAni.ANI_WALK);
    }

    void TurnToDestination()
    {
        // Quaternion lookRotation(회전할 목표 방향) : 목표 방향은 목적지 위치에서 자신의 위치를 빼면 구함
        Quaternion lookRotation = Quaternion.LookRotation(curTargetPos - transform.position);

        //Quaternion.RotateTowards(현재의 rotation값, 최종목표rotation 값, 최대 회전각)
        transform.rotation = Quaternion.RotateTowards(transform.rotation, lookRotation, Time.deltaTime * rotAnglePerSecond);
    }

    void MoveToDestination()
    {
        //Vector3.MoveTowards(시작지점, 목표지점,최대이동거리)
        transform.position = Vector3.MoveTowards(transform.position, curTargetPos,moveSpeed * Time.deltaTime);

        if (curEnemy == null)
        {
            //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령
            if (transform.position == curTargetPos)
            {
                ChangeState(State.Idle, PlayerAni.ANI_IDLE);
            }
        }
        else if (Vector3.Distance(transform.position,curTargetPos) < attackDistance)//Vector3.Distance(A,B):A와B사이의 거리
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
        }
    }
    void Update()
    {
        UpdateState();
    }
}

 

 

 

 

 

게임을 실행하고 플레이어가 적을 공격하였을때 콘솔창에 적의 HP 가 깍이는 것을 확인합니다

https://youtu.be/eKgHYr8TEsw

 

반응형

+ Recent posts