반응형

 

 

 

Game Over 텍스트를 만들어서 게임 오버 환경을 만들겠습니다

하이라키 뷰에 Canvas 를 선택하고 오른쪽 마우스버튼을 눌러 UI -> Text를 만듬니다

 

새로 생긴 Text 오브젝트를 이름을 tetGameOver 로 바꾸고 속성을 아래 그림과 같이 바꿈니다

 

 

 

txtGameOver  오브젝트를 선택하고 Window -> Animation -> Animation 을 선택합니다

 

 

Animation 창이 나오면 Create 버튼을 누릅니다

 

저장할 Animation 폴더를 만들고 이름을 GameOver 라 하고 저장합니다

 

 

 

Animation 창에 Add Property 를 눌러서 Color + 를 선택하여 애니메이션 의 키를 줍니다 

 

애니며여션 창의 Preview 옆의 빨간 버튼을 누르고  첫 프레임의 키를 선택하여 GameOver 텍스쳐의 알파 값을 0 으로 하고  마지막 프레임을 선택하여 알파값을 255 로 줍니다

 

 

 

그리고 Animator 창으로 돌아가서 GameOver 애니메이션의 Loop Time을 체크 해제 합니다

 

그리고 빈 State 를 만들고 선택하여 Set as Layer Defult State 를 선택합니다

 

 

 

Animator 창에 Parameters를 선택하여  Trigger를 만들고 이름을 show 라 합니다

 

Start 와 GameOver  State 에 화살푤르 연결하고  has Exit Time 을 체크 해제하고 Conditons를 show를 선택합니다

 

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

 

 

 

   public Text gameOver;
   Animator animGameOver;

.

.

.

.

 private void Start()
    {
        animGameOver = gameOver.gameObject.GetComponent<Animator>();
        gameOver.enabled = false;
    }

    public void ShowGameOver()
    {
        gameOver.enabled = true;
        animGameOver.SetTrigger("show");
    }

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

public class UIManager : MonoBehaviour
{
    // 언제 어디서나 쉽게 접금할수 있도록 하기위해 만든 정적변수
    public static UIManager instance;

    public Text playerName;
    public Text playerMoney;
    public Image playerHPBar;

    public Text gameOver;

    Animator animGameOver;
    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    private void Start()
    {
        animGameOver = gameOver.gameObject.GetComponent<Animator>();
        gameOver.enabled = false;
    }

    public void ShowGameOver()
    {
        gameOver.enabled = true;
        animGameOver.SetTrigger("show");
    }

    public void UpdatePlayerUI(PlayerParams playerParams)
    {
        playerName.text = playerParams.name;
        playerMoney.text = "Coin : " + playerParams.money.ToString();
        playerHPBar.rectTransform.localScale = 
            new Vector3((float)playerParams.curHp / (float)playerParams.maxHp, 1f, 1f);

    }
    void Update()
    {
        
    }
}

 

 

Canvas의 txtGameOver 오브젝트를 선택하고 UIManager 스크립트의 연결합니다 

 

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

 

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

        UIManager.instance.ShowGameOver();
    }

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);

        curEnemy = GameObject.FindGameObjectWithTag("Enemy");
    }

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

        UIManager.instance.ShowGameOver();
    }

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

        curEnemy = null;
    }

    public void AttackCalculate()
    {
        if (curEnemy == null)
        {
            return;
        }

        print("Attack" + curEnemy.name +"...");

        curEnemy.GetComponent<EnemyFSM>().ShowHitEffect();

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

        //플레이어가 공격할때 나는 소리
        SoundManager.instance.PlayHitSound();
    }

    // 적을 공격하기 위한 함수 
    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;

            GameManager.instance.ChangeCurrentTarget(curEnemy);

            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();
    }
}

 

 

 

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

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

                attackTimer = 0f;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            attackTimer += Time.deltaTime;
        }
    }

 

 

 

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;

    CharacterController controller;

    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;

    public GameObject selectMark;

    //리스폰 시킬 몬스터를 담을 변수 
    GameObject myRespawnObj;

    //리스폰 오브젝트에서 생성된 몇번째 몬스터에 대한 정보
    public int spawnID { get; set; }


    //몬스터가 처음 생성될때의 위치를 저장
    Vector3 originPos;

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

        ChangeState(State.Idle, EnemyAni.IDLE);

        controller = GetComponent<CharacterController>();

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

        hitEffect.Stop();
        HideSelection();
    }

    //몬스터가 리스폰 될때 초기화 상태로 함
    public void AddToWorldAgain()
    {
        //  리스폰 오브젝트에서 처음 생성될때의 위치와 같게 함
        transform.position = originPos;

        GetComponent<EnemyParams>().InitParams();
        GetComponent<BoxCollider>().enabled = true;
    }
    public void HideSelection()
    {
        selectMark.SetActive(false);
    }

    public void ShowSelection()
    {
        selectMark.SetActive(true);
    }

    // 몬스터가 어느 리스폰 오브젝트로부터 만들었졋는지에 대한 정보를 전달 받을 함수
    public void SetRespawnObj(GameObject respawnObj, int spawnID,Vector3 originPos)
    {
        myRespawnObj = respawnObj;
        this.spawnID = spawnID;
        this.originPos = originPos;
    }

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

        //몬스터가 죽은후 아이템 및 동전을 생성한다
        ObjectManager.instance.DropCoinToPosition(transform.position, myParams.rewardMoney);

        player.gameObject.SendMessage("CurrentEnemyDead");

        //몬스터가 사망했을때 나는 소리
        SoundManager.instance.PlayEnemyDie();

        StartCoroutine(RemoveMeFromWorld());
    }

    IEnumerator RemoveMeFromWorld()
    {
        yield return new WaitForSeconds(1f);

        ChangeState(State.Idle, EnemyAni.IDLE);

        //리스폰 오브젝트에 자기 자신을 제거해 달라는 요청
        myRespawnObj.GetComponent<RespawnObj>().RemoveMonster(spawnID);
    }

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

    public void AttackCalculate()
    {
        playerParams.SetEnemyAttack(myParams.GetRandomAttack());
    }

    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 (player.GetComponent<PlayerFSM>().currentState == PlayerFSM.State.Dead)
        {
            ChangeState(State.NoState, EnemyAni.IDLE);
        }
        if (GetDistanceFromPlayer() > reChaseDistance)
        {
            attackTimer = 0f;
            ChangeState(State.Chase, EnemyAni.WALK);
        }
        else
        {
            if (attackTimer > attackDelay)
            {
                transform.LookAt(player.position);
                myAni.ChangeAni(EnemyAni.ATTACK);

                attackTimer = 0f;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            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);

        //몬스터의 이동을 캐릭터 컨트롤러로 바꿈 몬스터가 전방으로 moveSpeed  만큼의 빠르기로 이동하게 됨
        controller.Move(transform.forward * moveSpeed * Time.deltaTime);
    }

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

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

 

 

게임을 실행하고 플레이어가 죽으면  GameOver 텍스쳐가 잘나오는지 확인 합니다

 

 

RPG_Game_Project.z01
10.00MB
RPG_Game_Project.z02
10.00MB
RPG_Game_Project.z03
10.00MB
RPG_Game_Project.z04
10.00MB
RPG_Game_Project.zip
8.63MB

반응형
반응형

XML.zip
0.47MB

엑셀에서 캐릭터의 능력치 데이터를 만들고 유니티에서 엑셀 데이터를 파싱 하여 캐릭터의 능력치를 나타내는 작업을 하겠습니다 

먼저 엑셀 파일로 캐릭터의 데이터를 작성합니다

 

 

만든 엑셀 데이터를 복사 합니다

 

 

사이트  https://shancarter.github.io/mr-data-converter/ 를 가서 엑셀에서 복사한 윗 빈칸에 데이터를 붙여 넣기 하고  아래 Output as를 XML-Nodes로 변경합니다

그러면 XML-Nodes 데이터가 나옵니다

 

 

 

XML-Nodes 데이터를 복사합니다 

 

 

 

메모장을 열어 데이터를 붙여 넣기 합니다

 

파일을 저장하는데 이름을 적고 확장자를  xml로 바꾸고 인코딩 방식을 UTF-8 코드로 하고 저장합니다

 

저장하면 모양이 아래 그림과 같이 됩니다 Windows 7 환경

저장한 파일을 유니티에 Resources폴더를 만들고 드래그하여 옮깁니다

 

 

 

 

하이 라키 뷰에 XMLManager 오브젝트를 만들고 XMLManager 스크립트를 만들어 붙입니다

 

 

 

XMLManager 스크립트 작성

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using System;
public class XMLManager : MonoBehaviour
{
    public static XMLManager instance;

    //xml 파일
    public TextAsset enemyFileXml;

    //여러개의 변수들을 넣어서 구조체 하나를 한개의 상자처럼 간주하고 사용할수 있음
    struct MonParams
    {
        //xml 파일로 부터 각각의 몬스터의 대해서 이들 파라미터 값을 읽어 들이고 구조체 내부 변수에 저장하고 구조체를 이용하여 각 몬스터에게 파라미터 값으 전달함
        public string name;
        public int level;
        public int maxHp;
        public int attackMin;
        public int attackMax;
        public int defense;
        public int exp;
        public int rewardMoney;
    }

    //딕셔너리의 키값으로 적의이름을 사용할 예정이므로 string타입으로 하고 데이터 값으로는 구조체를 이용함 MonParams로 지정
    Dictionary<string, MonParams> dicMonsters = new Dictionary<string, MonParams>();
    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }        
    }

    private void Start()
    {
        MakeMonsterXML();
    }

    //XML로부터 파라미터 값 읽어 들이기
    void MakeMonsterXML()
    {
        XmlDocument monsterXMLDoc = new XmlDocument();
        monsterXMLDoc.LoadXml(enemyFileXml.text);

        XmlNodeList monsterNodeList = monsterXMLDoc.GetElementsByTagName("row");

        //노드 리스트로부터 각각의 노드를 뽑아냄
        foreach ( XmlNode monsterNode in monsterNodeList)
        {
            MonParams monParams = new MonParams();

            foreach (XmlNode childNode in monsterNode.ChildNodes)
            {
                if (childNode.Name == "name")
                {
                    //<name>smallspider</name>
                    monParams.name = childNode.InnerText; 
                }

                if (childNode.Name == "level")
                {
                    //<level>1</level>    Int16.Parse() 은 문자열을 정수로 바꿔줌
                    monParams.level = Int16.Parse( childNode.InnerText);
                }

                if (childNode.Name == "maxHp")
                {
                    monParams.maxHp = Int16.Parse(childNode.InnerText);
                }

                if (childNode.Name == "attackMin")
                {
                    monParams.attackMin = Int16.Parse(childNode.InnerText);
                }

                if (childNode.Name == "attackMax")
                {
                    monParams.attackMax = Int16.Parse(childNode.InnerText);
                }

                if (childNode.Name == "defense")
                {
                    monParams.defense = Int16.Parse(childNode.InnerText);
                }

                if (childNode.Name == "exp")
                {
                    monParams.exp = Int16.Parse(childNode.InnerText);
                }

                if (childNode.Name == "rewardMoney")
                {
                    monParams.rewardMoney = Int16.Parse(childNode.InnerText);
                }

                print(childNode.Name + ": " + childNode.InnerText);
            }
            dicMonsters[monParams.name] = monParams;
        }
    }

    //외부로부터 몬스터의 이름과, EnemyParams 객체를 전달 받아서 해당 이름을 가진 몬스터의 
    //데이터(XML 에서 읽어 온 데이터)를 전달받은 EnemyParams 객체에 적용하는 역할을 하는 함수
    public void  LoadMonsterParamsFromXML(string monName, EnemyParams mParams)
    {
        mParams.level = dicMonsters[monName].level;
        mParams.curHp = mParams.maxHp = dicMonsters[monName].maxHp;
        mParams.attackMin = dicMonsters[monName].attackMin;
        mParams.attackMax = dicMonsters[monName].attackMax;
        mParams.defense = dicMonsters[monName].defense;
        mParams.exp = dicMonsters[monName].exp;
        mParams.rewardMoney = dicMonsters[monName].rewardMoney;
    }


    void Update()
    {
        
    }
}

 

 

 

 

XMLManager 스크립트에 xml 파일 enemy를 Enemy File Xml에 드래그하여 붙입니다

EnemyParams 스크립트를 열고 수정합니다

 

XMLManager.instance.LoadMonsterParamsFromXML(name, this);

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.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);
        */

        //XMLManager에서 넘겨 받은 이름을 해당하는 몬스터 파라미터를 찾아서 주요 파라미터값을 반영해 주게됨
        XMLManager.instance.LoadMonsterParamsFromXML(name, this);

        isDead = false;

        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 오브젝트를 선택하여 활성화시키고  아래와 같이 속성을 바꾸고 프리 팹을 저장합니다

EnemyParams 스크립트에서 name의 spider 를 철자를 같게 합니다

왜냐 하면 enemy xml 파일의 데이터 노드에서  name 의 스펠링과 같게 해 주어야 하기 때문입니다

 

Enemy 오브젝트의 활성화를 꺼줍니다

 

그리고 Enemy 프리 팹을 선택하여 드래그해서 하이 라키 뷰에 올려놓습니다

 

 

하이 라키 뷰에 올려놓은 Enemy (1) 오브젝트를 기존의 프리 팹의 영향을 끊어야 하기 때문에 오브젝트를 선택하고 마우스 오른쪽 버튼을 눌러서 메뉴가 나오면  Unpack Prefab을 눌러서 프리 팹 연결을 끊습니다

 

Enemy (1) 오브젝트 이름을 EnemyBoss로 변경합니다

 

 

 

EnemyBoss 오브젝트를 선택하여 EnemyParams 스크립트의 name을 spiderboss로 타이핑합니다

그리고 메터리얼을 하나 만들어서 이름을 spiderBlack이라고 하고 인스팩터에서  Legacy Shaders/Diffuse를 선택한 다음 텍스쳐를 spider_01를 선택합니다 그리고 색을 아래 그림처럼 파란색으로 변경하여 어둠 게 합니다

 

 

 

 

EnemyBoss 오브젝트를 선택하여 자식으로 되어있는 Spider01 오브젝트의 아까 만든 spiderBlack 메터리얼을 연결합니다

 

EnemyBoss 오브젝트를 프리 팹 만듭니다  Prefabs 폴더에 드래그하여 프리팹을 만듬니다

 

 

 

하이 라키 뷰에 있는  EnemyBoss 오브젝트의 활성화를 끕니다

 

그리고 ResPawnObj 오브젝트를 선택하여 Ctrl + D를 눌러서 오브젝트를 복사합니다 

아래 그림과 같이 RespawnObj (1) 오브젝트가 생성되면 EnemyBoss 프리 팹을 연결하고 SpawnNumber를 2로 바꿉니다

 

 

 

게임을 실행하여 Xml 데이터에서 만든 캐릭터의 이름과 능력치가 제대로 나오는지 콘솔 창에서 확인합니다

0

반응형
반응형

오브젝트 매니저를 만들어서 코인을 효과적으로 관리 하겠습니다.

먼저 ObjectManager 스크립트를 생성하고 하이라키뷰에 Objectmanager 오브젝트를 만든다음 스크립트를 붙힘니다

 

 

 

ObjectManager 스크립트 작성

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

public class ObjectManager : MonoBehaviour
{
    public static ObjectManager instance;

    public GameObject coinPrefab;
    public int initialCoins = 30;

    List<GameObject> coins = new List<GameObject>();
    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }

        MakeCoins();
    }

    void MakeCoins()
    {
        for (int i = 0; i < initialCoins; i++)
        {
            GameObject tempCoin = Instantiate(coinPrefab) as GameObject;

            //새로 생성된 코인들이 오브젝트 매니저의 자식 오브젝트로 들어감,하이라키뷰에서 관리가 수월함
            tempCoin.transform.parent = transform;

            tempCoin.SetActive(false);
            coins.Add(tempCoin);

        }
    }

    public void DropCoinToPosition(Vector3 pos, int coinValue)
    {
        GameObject reusedCoin = null;
        for (int i = 0; i < coins.Count; i++)
        {
            if (coins[i].activeSelf == false)
            {
                reusedCoin = coins[i];
                break;
            }
        }
        if (reusedCoin == null)
        {
            GameObject newCoin = Instantiate(coinPrefab) as GameObject;
            coins.Add(newCoin);
            reusedCoin = newCoin;
        }
        reusedCoin.SetActive(true);
        reusedCoin.GetComponent<Coin>().SetCoinValue(coinValue);
        reusedCoin.transform.position = new Vector3(pos.x, reusedCoin.transform.position.y, pos.z);
    }



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

 

 

작성한 ObjectManager 스크립트에 동전U 프리팹을 ObjectManager  coinPrefab에 연결합니다

 

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

  private void OnTriggerEnter(Collider col)
    {
        if (col.gameObject.tag == "Player")
        {
            col.gameObject.GetComponent().AddMoney(money);

            SoundManager.instance.PlayPickUpSound();

            // Destroy(gameObject);
            RemoveFromWorld();
        }       
    }

    public void RemoveFromWorld()
    {
        gameObject.SetActive(false);
    }

 

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

public class Coin : MonoBehaviour
{
    public float rotateSpeed = 180f;

    [System.NonSerialized]
    public int money = 100;
    void Start()
    {
        
    }

    public  void SetCoinValue(int money)
    {
        this.money = money;
    }

    private void OnTriggerEnter(Collider col)
    {
        if (col.gameObject.tag == "Player")
        {
            col.gameObject.GetComponent<PlayerParams>().AddMoney(money);

            SoundManager.instance.PlayPickUpSound();

            // Destroy(gameObject);
            RemoveFromWorld();
        }       
    }

    public void RemoveFromWorld()
    {
        gameObject.SetActive(false);
    }
    // Update is called once per frame
    void Update()
    {
        transform.Rotate(0f, rotateSpeed * Time.deltaTime, 0f);
    }
}

 

EnemyFSM 스크립트를 생성하고 수정합니다

 

 void CallDeadEvent()
    {
        ChangeState(State.Dead, EnemyAni.DIE);

        //몬스터가 죽은후 아이템 및 동전을 생성한다
        ObjectManager.instance.DropCoinToPosition(transform.position, myParams.rewardMoney);

        player.gameObject.SendMessage("CurrentEnemyDead");

        //몬스터가 사망했을때 나는 소리
        SoundManager.instance.PlayEnemyDie();

        StartCoroutine(RemoveMeFromWorld());
    }

 

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;

    CharacterController controller;

    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;

    public GameObject selectMark;

    //리스폰 시킬 몬스터를 담을 변수 
    GameObject myRespawnObj;

    //리스폰 오브젝트에서 생성된 몇번째 몬스터에 대한 정보
    public int spawnID { get; set; }


    //몬스터가 처음 생성될때의 위치를 저장
    Vector3 originPos;

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

        ChangeState(State.Idle, EnemyAni.IDLE);

        controller = GetComponent<CharacterController>();

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

        hitEffect.Stop();
        HideSelection();
    }

    //몬스터가 리스폰 될때 초기화 상태로 함
    public void AddToWorldAgain()
    {
        //  리스폰 오브젝트에서 처음 생성될때의 위치와 같게 함
        transform.position = originPos;

        GetComponent<EnemyParams>().InitParams();
        GetComponent<BoxCollider>().enabled = true;
    }
    public void HideSelection()
    {
        selectMark.SetActive(false);
    }

    public void ShowSelection()
    {
        selectMark.SetActive(true);
    }

    // 몬스터가 어느 리스폰 오브젝트로부터 만들었졋는지에 대한 정보를 전달 받을 함수
    public void SetRespawnObj(GameObject respawnObj, int spawnID,Vector3 originPos)
    {
        myRespawnObj = respawnObj;
        this.spawnID = spawnID;
        this.originPos = originPos;
    }

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

        //몬스터가 죽은후 아이템 및 동전을 생성한다
        ObjectManager.instance.DropCoinToPosition(transform.position, myParams.rewardMoney);

        player.gameObject.SendMessage("CurrentEnemyDead");

        //몬스터가 사망했을때 나는 소리
        SoundManager.instance.PlayEnemyDie();

        StartCoroutine(RemoveMeFromWorld());
    }

    IEnumerator RemoveMeFromWorld()
    {
        yield return new WaitForSeconds(1f);

        ChangeState(State.Idle, EnemyAni.IDLE);

        //리스폰 오브젝트에 자기 자신을 제거해 달라는 요청
        myRespawnObj.GetComponent<RespawnObj>().RemoveMonster(spawnID);
    }

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

    public void AttackCalculate()
    {
        playerParams.SetEnemyAttack(myParams.GetRandomAttack());
    }

    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;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            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);

        //몬스터의 이동을 캐릭터 컨트롤러로 바꿈 몬스터가 전방으로 moveSpeed  만큼의 빠르기로 이동하게 됨
        controller.Move(transform.forward * moveSpeed * Time.deltaTime);
    }

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

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

 

 

하이라키뷰에 동전U 를 제거 합니다

 

게임을 실행하여 몬스터가 죽으면 동전을 남기게 되는진 확인합니다

0

반응형
반응형

0

오늘은 아이템[Coin]을 만들고 플레이어가 Coin 을 획득하는 프로그램을 작성하겠습니다

동전.zip
2.24MB

새 폴더를 만들고 이름을 Coin 이라 하고 동전 파일을 임포트합니다 

그림과 같이 임포트 하면 됩니다

 

동전 맵v 을 선택하여 아래와 같이 속성을 바꿈니다

Materials 폴더에 Material을 생성 하여 그림과 같이 이름을 coin 이라 합니다

 

 

새로 만든 Material 에 아래 그림과 같이 Shader를 Unit/Texture를 선택하고 다운 받은 동전맵v 그림을 연결합니다

동전U 를 선택하고 Import Settings 에서 Materials를 선택하여 coin Material을 연결합니다

 

 

그리고 동전U를 게임 씬에 드래그하여 올려봄니다 

 

하이라키 에 동전U 오브젝트를 선택하여 아래그림과 같이 tag를 Coin 이라 달고 Spher Collider 와 Rigidbody 를 달고 아래그림과 같이 속성을 변경합니다

 

 

새 스크립트를 생성하고 이름을 Coin 이라 합니다

 

Coin 스크립트를 동전U 오브젝트에 붙힘니다

 

 

Coin 스크립트작성

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

public class Coin : MonoBehaviour
{
    public float rotateSpeed = 180f;

    [System.NonSerialized]
    public int money = 100;
    void Start()
    {
        
    }

    public  void SetCoinValue(int money)
    {
        this.money = money;
    }

    private void OnTriggerEnter(Collider col)
    {
        if (col.gameObject.tag == "Player")
        {
            col.gameObject.GetComponent<PlayerParams>().AddMoney(money);

            SoundManager.instance.PlayPickUpSound();

            Destroy(gameObject);
        }       
    }
    // Update is called once per frame
    void Update()
    {
        transform.Rotate(0f, rotateSpeed * Time.deltaTime, 0f);
    }
}

 

 

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

 

 

추가

    public void  AddMoney(int money)
    {
        this.money += money;
        UIManager.instance.UpdatePlayerUI(this);
    }

 

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 = 10;
        attackMax = 40;
        defense = 1;

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

        isDead = false;

        //초기화 할때 헤드업 디스플레이에 플레이어의 이름과 기타 정보들이 제대로 표시되도록함
        UIManager.instance.UpdatePlayerUI(this);
    }

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

        UIManager.instance.UpdatePlayerUI(this);
    }

    public void  AddMoney(int money)
    {
        this.money += money;
        UIManager.instance.UpdatePlayerUI(this);
    }
}

 

 

동전U를 선택하고 Prefabs 폴더에 드래그 드롭하여 프리팹을 만듭니다

게임을 실행하면 동전이 회전 하는지 확인하고 플래이어가 닿으면 소리와 함께 사라지고 Coin 점수가 올라 가는지 확인합니다

 

0

반응형
반응형

Enemy 오브젝트를 다시 활성화 하여 Component ->Physics ->  Character Controller 를 붙힘니다

그리고 아래 그림과 같이  Character Controller 속성을 바꿈니다

 

 

 

 

 

 

 

Enemy 오브젝트를 다시 Overrides 를 눌러서 Apply All 합니다

그리고 비활성화를 시킴니다

 

 

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

 

스크립트의 추가 

 

  controller = GetComponent<CharacterController>();

---------------------------------------------------------------

    public void AddToWorldAgain()
    {
        //  리스폰 오브젝트에서 처음 생성될때의 위치와 같게 함
        transform.position = originPos;

        GetComponent<EnemyParams>().InitParams();
        GetComponent<BoxCollider>().enabled = true;
    }

 

 

 

 

 

 

 

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;

    CharacterController controller;

    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;

    public GameObject selectMark;

    //리스폰 시킬 몬스터를 담을 변수 
    GameObject myRespawnObj;

    //리스폰 오브젝트에서 생성된 몇번째 몬스터에 대한 정보
    public int spawnID { get; set; }


    //몬스터가 처음 생성될때의 위치를 저장
    Vector3 originPos;

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

        ChangeState(State.Idle, EnemyAni.IDLE);

        controller = GetComponent<CharacterController>();

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

        hitEffect.Stop();
        HideSelection();
    }

    //몬스터가 리스폰 될때 초기화 상태로 함
    public void AddToWorldAgain()
    {
        //  리스폰 오브젝트에서 처음 생성될때의 위치와 같게 함
        transform.position = originPos;

        GetComponent<EnemyParams>().InitParams();
        GetComponent<BoxCollider>().enabled = true;

    }
    public void HideSelection()
    {
        selectMark.SetActive(false);
    }

    public void ShowSelection()
    {
        selectMark.SetActive(true);
    }

    // 몬스터가 어느 리스폰 오브젝트로부터 만들었졋는지에 대한 정보를 전달 받을 함수
    public void SetRespawnObj(GameObject respawnObj, int spawnID,Vector3 originPos)
    {
        myRespawnObj = respawnObj;
        this.spawnID = spawnID;
        this.originPos = originPos;
    }

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

        //몬스터가 사망했을때 나는 소리
        SoundManager.instance.PlayEnemyDie();

        StartCoroutine(RemoveMeFromWorld());
    }

    IEnumerator RemoveMeFromWorld()
    {
        yield return new WaitForSeconds(1f);

        ChangeState(State.Idle, EnemyAni.IDLE);

        //리스폰 오브젝트에 자기 자신을 제거해 달라는 요청
        myRespawnObj.GetComponent<RespawnObj>().RemoveMonster(spawnID);
    }

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

    public void AttackCalculate()
    {
        playerParams.SetEnemyAttack(myParams.GetRandomAttack());
    }

    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;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            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);

        //몬스터의 이동을 캐릭터 컨트롤러로 바꿈 몬스터가 전방으로 moveSpeed  만큼의 빠르기로 이동하게 됨
        controller.Move(transform.forward * moveSpeed * Time.deltaTime);
    }

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

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

 

 

 

 

 

 

RespawnObj 스크립트를 수정합니다

 

스크립트의 추가

 

    void SpawnMonster()
    {
        for (int i = 0; i <monsters.Length; i++)
        {
            //몬스터가 리스폰 될때 초기화 될 함수를 찾아 실행
            monsters[i].GetComponent().AddToWorldAgain();
            monsters[i].SetActive(true);
        }
    }

 

 

 

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

public class RespawnObj : MonoBehaviour
{
    List<Transform> spawnPos = new List<Transform>();
    GameObject[] monsters;

    public GameObject monPrefab;
    public int spawnNumber = 1;
    public float respawnDelay = 3f;

    int deadMonsters = 0;
    void Start()
    {
        MakeSpawnPos();
    }
    void MakeSpawnPos()
    {
        //자식의 트랜스폼을 확인하여 태그 붙인(Respawn)을 찾아 리스트(spawnPos)에 넣는다
        foreach ( Transform pos in transform)
        {
            if (pos.tag == "Respawn")
            {
                spawnPos.Add(pos);
            }
        }
        if (spawnNumber > spawnPos.Count)
        {
            spawnNumber = spawnPos.Count;
        }

        monsters = new GameObject[spawnNumber];

        MakeMonsters();
    }

    //프리팹으로 부터 몬스터를 만들어 관리하는 함수
    void MakeMonsters()
    {
        for (int i = 0; i < spawnNumber; i++)
        {
            GameObject mon = Instantiate(monPrefab, spawnPos[i].position, Quaternion.identity) as GameObject;

            mon.GetComponent<EnemyFSM>().SetRespawnObj(gameObject, i, spawnPos[i].position);

            mon.SetActive(false);

            monsters[i] = mon;

            GameManager.instance.AddNewMonsters(mon);
        }
    }

    public void RemoveMonster(int spawnID)
    {
        //Destroy(monsters[spawnID]);

        deadMonsters++;

        monsters[spawnID].SetActive(false);
        print(spawnID + "monster was killed");

        //죽은 몬스터의 숫자가 몬스터 배열의 크기와 같다면 일정시간후에 몬스터를 다시 생성
        if (deadMonsters == monsters.Length)
        {
            StartCoroutine(InitMonsters());
            deadMonsters = 0;
        }
    }

    IEnumerator InitMonsters()
    {
        yield return new WaitForSeconds(respawnDelay);

        GetComponent<SphereCollider>().enabled = true;
    }

    void SpawnMonster()
    {
        for (int i = 0; i <monsters.Length; i++)
        {
            //몬스터가 리스폰 될때 초기화 될 함수를 찾아 실행
            monsters[i].GetComponent<EnemyFSM>().AddToWorldAgain();
            monsters[i].SetActive(true);
        }
    }

    private void OnTriggerEnter(Collider col)
    {
        if (col.gameObject.tag == "Player")
        {
            SpawnMonster();
            GetComponent<SphereCollider>().enabled = false;
        }
    }
    void Update()
    {
        
    }
}

 

 

게임을 실행하고 적 캐릭터가 죽은 다음 리스폰 되어서 초기화 되는지 확인 합니다

 

 

 

반응형
반응형

RPG게임소스.zip
0.04MB

몬스터를 플레이어가 처치하고 난 다음 다시 몬스터를 재 생성시키는 작업을 하겠습니다

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;

    public GameObject selectMark;

    //리스폰 시킬 몬스터를 담을 변수 
    GameObject myRespawnObj;

    //리스폰 오브젝트에서 생성된 몇번째 몬스터에 대한 정보
    public int spawnID { get; set; }


    //몬스터가 처음 생성될때의 위치를 저장
    Vector3 originPos;

    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();
        HideSelection();
    }

    public void HideSelection()
    {
        selectMark.SetActive(false);
    }

    public void ShowSelection()
    {
        selectMark.SetActive(true);
    }

    // 몬스터가 어느 리스폰 오브젝트로부터 만들었졋는지에 대한 정보를 전달 받을 함수
    public void SetRespawnObj(GameObject respawnObj, int spawnID,Vector3 originPos)
    {
        myRespawnObj = respawnObj;
        this.spawnID = spawnID;
        this.originPos = originPos;
    }

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

        //몬스터가 사망했을때 나는 소리
        SoundManager.instance.PlayEnemyDie();

        StartCoroutine(RemoveMeFromWorld());
    }

    IEnumerator RemoveMeFromWorld()
    {
        yield return new WaitForSeconds(1f);

        ChangeState(State.Idle, EnemyAni.IDLE);

        //리스폰 오브젝트에 자기 자신을 제거해 달라는 요청
        myRespawnObj.GetComponent<RespawnObj>().RemoveMonster(spawnID);
    }

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

    public void AttackCalculate()
    {
        playerParams.SetEnemyAttack(myParams.GetRandomAttack());
    }

    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;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            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();
    }
}

 

 

 

RespawnObj 스크립트수정

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

public class RespawnObj : MonoBehaviour
{
    List<Transform> spawnPos = new List<Transform>();
    GameObject[] monsters;

    public GameObject monPrefab;
    public int spawnNumber = 1;
    public float respawnDelay = 3f;

    int deadMonsters = 0;
    void Start()
    {
        MakeSpawnPos();
    }
    void MakeSpawnPos()
    {
        foreach ( Transform pos in transform)
        {
            if (pos.tag == "Respawn")
            {
                spawnPos.Add(pos);
            }
        }
        if (spawnNumber > spawnPos.Count)
        {
            spawnNumber = spawnPos.Count;
        }

        monsters = new GameObject[spawnNumber];

        MakeMonsters();
    }

    //프리팹으로 부터 몬스터를 만들어 관리하는 함수
    void MakeMonsters()
    {
        for (int i = 0; i < spawnNumber; i++)
        {
            GameObject mon = Instantiate(monPrefab, spawnPos[i].position, Quaternion.identity) as GameObject;

            mon.GetComponent<EnemyFSM>().SetRespawnObj(gameObject, i, spawnPos[i].position);

            mon.SetActive(false);

            monsters[i] = mon;

            GameManager.instance.AddNewMonsters(mon);
        }
    }

    public void RemoveMonster(int spawnID)
    {
        //Destroy(monsters[spawnID]);

        deadMonsters++;

        monsters[spawnID].SetActive(false);
        print(spawnID + "monster was killed");

        //죽은 몬스터의 숫자가 몬스터 배열의 크기와 같다면 일정시간후에 몬스터를 다시 생성
        if (deadMonsters == monsters.Length)
        {
            StartCoroutine(InitMonsters());
            deadMonsters = 0;
        }
    }

    IEnumerator InitMonsters()
    {
        yield return new WaitForSeconds(respawnDelay);

        GetComponent<SphereCollider>().enabled = true;
    }

    void SpawnMonster()
    {
        for (int i = 0; i <monsters.Length; i++)
        {
            monsters[i].SetActive(true);
        }
    }

    private void OnTriggerEnter(Collider col)
    {
        if (col.gameObject.tag == "Player")
        {
            SpawnMonster();
            GetComponent<SphereCollider>().enabled = false;
        }
    }
    void Update()
    {
        
    }
}

 

 

 

빈 폴더를 생성하고 이름을 Texture 라 합니다

 

Texture 폴더를 선택하고 Import new Asset 를 눌러서 텍스트를 선택하고 임포트합니다

 

 

 

임포트한 텍스트를 선택하고  속성을 Texture type 을 Sprite and UI 로 바꾸고 아래 그림과 같이 바꾼다

 

 

비활성이였던 Enemy 오브젝트를 활성한 다음 Enemy 오브젝트를 선택하고 자식으로 빈 오브젝트를 만듬니다 그리고 이름을 SelectMarkPos 라 하고 자식으로 Quad를 생성합니다

 

 

 

빈 폴더를 생성하여 이름을 Materials 라 하고 폴더 안에 material을 생성하고 이름을 Selection이라 합니다

 

 

Selection 메터리얼을 선택하고 속성에서 Shader 를 Unlit/Transparent 를 선택하고 임포트한 텍스쳐를 드래그하여 그림과같이 올려 놓습니다

 

 

아래 그림과 같이 Enemy를 선택하고 Selection 메트리얼 을 드래그하여 자식으로 있는 Quad 에 드래그하여 올려 놓습니다.그리고 그림과 같이 Quad의 위치와 각도를 몬스터의 아래 방향을 향하도록 변경합니다

 

 

 

그리고 Enemy 오브젝트를 선택하고  수정한 EnemyFSM 스크립트의 Select Mark 부분에 Quad 오브젝트를 드래그 드롭 합니다 그리고 프리팹을 다시 저장 하기 위해서 Override 를 눌러서 Apply All 을 합니다

 

 

Enemy 오브젝트를 다시 비활성 상태로 돌아 감니다

 

 

하이라키에 빈 오브젝트를 만들과 이름을 GameManager 라 합니다

 

 

 

GameManager 스크립트를 생성하고 GameManager 오브젝트에 붙힘니다

 

 

 

GameManager 스크립트작성

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

public class GameManager : MonoBehaviour
{
    public static GameManager instance;

    List<GameObject> monsters = new List<GameObject>();

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
        
    }

    //외부에서 전달된 몬스터가 기존에 리스트에 보관하고 있는 몬스터와 일치하는지 여부를 체크
    public void AddNewMonsters(GameObject mon)
    {

        //인자로 넘어온 몬스터가 기존의 리스트에 존재하면 sameExist = true 아니면 false
        bool sameExist = false;
        for (int i = 0; i < monsters.Count; i++)
        {
            if (monsters[i] == mon)
            {
                sameExist = true;
                
                break;
            }
        }

        if (sameExist == false)
        {
            monsters.Add(mon);
        }
        
    }
         

    public void RemoveMonster(GameObject mon)
    {
        foreach (GameObject monster in monsters)
        {
            if (monster == mon)
            {
                monsters.Remove(monster);
                break;
            }

        }
    }

    //현재 플레이어가 클릭한 몬스터만 선택마크가 표시
    public void ChangeCurrentTarget(GameObject mon)
    {
        DeselectAllMonsters();
        mon.GetComponent<EnemyFSM>().ShowSelection();
    }

    public void DeselectAllMonsters()
    {
        for (int i = 0; i < monsters.Count; i++)
        {
            monsters[i].GetComponent<EnemyFSM>().HideSelection();
        }
    }
    // Update is called once per frame
    void Update()
    {
        
    }
}

 

 

 

그리고  PlayerFSM 스크립트를 선택하고  AttackEnemy(GameObject enemy) 함수 부분을 수정합니다.

 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;

            GameManager.instance.ChangeCurrentTarget(curEnemy);

            ChangeState(State.Move, PlayerAni.ANI_WALK);
        }
        else
        {
            curEnemyParams = null;
        }
  
    }

 

게임을 실행하고 몬스터를 선택 하였을때 몬스터 표시 마크가 생성 되고 몬스터들이 다 죽은후 리스폰 되는지 확인 합니다

 

 

 

 

반응형
반응형

 적 캐릭터를 자동으로 생성 하는 프로그램 짜기

먼저 빈오프젝트를 생성하고 이름을 RespawnObj 라합니다

 

 

RespawnObj 오브젝트를 선택하고 Sphere Collider를 생성합니다

 

 

 

 

RespawnObj 오브젝트의 Inspector에서 Shper colllider 의 속성을 아래 그림과 같이 바꿉니다

 

 

RespawnObj 오브젝트의 자식으로 Pos 오브젝틀 4개를 만든후 tag를 Respawn 을 붙히힘니다

 

 

새로운 스크립트를 생성하여 RespawnObj 라합니다

 

 

 

RespawnObj 스크립트 작성

 

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

public class RespawnObj : MonoBehaviour
{
    List<Transform> spawnPos = new List<Transform>();
    GameObject[] monsters;

    public GameObject monPrefab;
    public int spawnNumber = 1;
    public float respawnDelay = 3f;

    int deadMonsters = 0;
    void Start()
    {
        MakeSpawnPos();
    }
    void MakeSpawnPos()
    {
        foreach ( Transform pos in transform)
        {
            if (pos.tag == "Respawn")
            {
                spawnPos.Add(pos);
            }
        }
        if (spawnNumber > spawnPos.Count)
        {
            spawnNumber = spawnPos.Count;
        }

        monsters = new GameObject[spawnNumber];

        MakeMonsters();
    }

    //프리팹으로 부터 몬스터를 만들어 관리하는 함수
    void MakeMonsters()
    {
        for (int i = 0; i < spawnNumber; i++)
        {
            GameObject mon = Instantiate(monPrefab, spawnPos[i].position, Quaternion.identity) as GameObject;
            mon.SetActive(false);


            monsters[i] = mon;
        }
    }

    void SpawnMonster()
    {
        for (int i = 0; i <monsters.Length; i++)
        {
            monsters[i].SetActive(true);
        }
    }

    private void OnTriggerEnter(Collider col)
    {
        if (col.gameObject.tag == "Player")
        {
            SpawnMonster();
            GetComponent()<SphereCollider>.enabled = false;
        }
    }
    void Update()
    {
        
    }
}

 

 

작성한 RespawnObj 스크립트를 RespawnObj 오브젝트에 붙힘니다

 

 

빈 폴더를 생성하고 이름을 Prefabs 라 합니다

 

 

Enemy 오브젝트를 생성하고 Prefabs 폴더 안에 드래그 드롭하여 프리팹을 생성합니다

 

 

 

그리고 Enemy 프리팹을 RespawnObj 오브젝트에 붙힌 RespawnObj 스크립트의 MonPrefab 에 드래그 드롭하여 넣습니다

 

 

기존의 하이라키의 있던 Enemy는 비활성화 시킵니다

 

그리고 Player 오브젝트를 선택하고 아래 그림과 같이 SphereCollider 를 생성하고 속성을 바꿉니다

 

 

 

RespawnObj 오브젝트를 선택하고 SpawnNumber 를 3으로 합니다

게임을 실행하고 Respawn이 재대로 되는지 확인 합니다

 

 

 

 

 

반응형
반응형

음향소스.zip
0.04MB

 

게임 프로젝트의 사운드 매니저를 만들어서 게임의 음향 효과를 주겠습니다.

먼저 사이트 https://www.bfxr.net  에 들어 감니다

 

사이트에 들어가면 간단한 음향 효과를 만들수 있는 음향 프로그램을 볼수 있습니다

각각의 맞는 음향을 선택하여 Export 합니다

 

 

사진과 같이 4개의 음향을 만들었습니다

 

 

새로운 폴더를 생성하여 이름을 Sound 라 합니다

 

 

 


Sound 폴더를 선택하고 오른쪽 마우스를 클릭하여 Import New Asset 을 클릭합니다

그리고 아까 만들었던 4개의 음향 파일을 선택하여 임포트합니다

 

 

Hierarchy 에  빈오브젝트를 만들고 이름을 SoundManager 라고 합니다

 

 

 

새로운 스크립트를 생성하여 이름을 SoundManager 라 합니다

 

SoundManager 스크립트 작성

 

 

 

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

//자동으로 AudioSource GetComponent 부착
[RequireComponent(typeof(AudioSource))]
public class SoundManager : MonoBehaviour
{

 

//어디서나 접근할수 있는 정적 변수를 만듬니다
    public static SoundManager instance;

    AudioSource myAudio;

    public AudioClip sndHitEnefmy;
    public AudioClip sndEnemyAttack;
    public AudioClip sndPickUp;
    public AudioClip sndEnemyDie;


    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }
    // Start is called before the first frame update
    void Start()
    {
        myAudio = GetComponent();
    }


    public void PlayHitSound()
    {
        myAudio.PlayOneShot(sndHitEnefmy);
    }

    public void PlayEnemyAttack()
    {
        myAudio.PlayOneShot(sndEnemyAttack);
    }

    public void PlayEnemyDie()
    {
        myAudio.PlayOneShot(sndEnemyDie);
    }

    public void PlayPickUpSound()
    {
        myAudio.PlayOneShot(sndPickUp);
    }
    // Update is called once per frame
    void Update()
    {
        
    }
}

 

 

 

작성한 SoundManager 스크립트를 Hierarchy 의 SoundManager 오브젝트에 드래그하여 붙힘니다

아래 사진과 같이 Audio Source  콤퍼너트 가 자동으로 붙는걸 확인합니다

 

 

그리고 임포트한 4개의 음향 소스를 SoundManager 오브젝트의 붙힌 SoundManager 스크립트 빈 공간에  드래그 드롭하여 그림과 같이 순서 대로 넣습니다

 

 

 

 

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();

        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);

        //플레이어가 공격할때 나는 소리
        SoundManager.instance.PlayHitSound();
    }

    // 적을 공격하기 위한 함수 
    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();
    }
}

 

 

 

 

EnemyFSM을 선택하고 수정합니다

 

 

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");

        //몬스터가 사망했을때 나는 소리
        SoundManager.instance.PlayEnemyDie();
    }

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

    public void AttackCalculate()
    {
        playerParams.SetEnemyAttack(myParams.GetRandomAttack());
    }

    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;

                //몬스터가 공격할때 나는 소리
                SoundManager.instance.PlayEnemyAttack();
            }

            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();
    }
}

 

게임을 실행하고 플레이어 공격과 적의 공격 그리고 적 사망시 음향효과가 제대로 나오는지 확인 합니다

 

 

 

반응형
반응형

0

그림과 같이  Player의 HP Bar 를 만들고 적의 공격을 당했을때 HP Bar 가 감소 되는 작업을 하겠습니다

 

 

새로운 Canvas 를 만들과 새로운 Canvas 아래에 아래 그림과 같이 만듬니다  새로운 image  생성하여 이름을 Panel

새로운 Text 생성하여  PlayerName   새로운 Text 생성하여  PlayerMoney  새로운 image 두개를 생성하여 하나는 색깔을 회색으로 하고 이름을 HPBack 그리고 색깔을 붉은색으로 하여 이름을 HPFront 라 합니다 

 

각각 생성한 다음 아래 그림과 같이 Anchors 를 왼쪽 위로 설정한다

 

 

그리고 UIManager 스크립트를 새로만든다

 

UIManager 스크립트 작성

 

 

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

public class UIManager : MonoBehaviour
{
    // 언제 어디서나 쉽게 접금할수 있도록 하기위해 만든 정적변수
    public static UIManager instance;

    public Text playerName;
    public Text playerMoney;
    public Image playerHPBar;


    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
        }
    }

    public void UpdatePlayerUI(PlayerParams playerParams)
    {
        playerName.text = playerParams.name;
        playerMoney.text = "Coin : " + playerParams.money.ToString();
        playerHPBar.rectTransform.localScale = 
            new Vector3((float)playerParams.curHp / (float)playerParams.maxHp, 1f, 1f);

    }
    void Update()
    {
        
    }
}

 

 

 

새로 만든  Canvas 를 선택하고 새로 만든 UIManager를 드래그 드롭한다

 

 

Canvas 의 UIManager 에 각각 의 빈곳에 PlayerName, PalyerMoney, PlayerHPBar 에 드래그 드롭합니다

 

 

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;

        //초기화 할때 헤드업 디스플레이에 플레이어의 이름과 기타 정보들이 제대로 표시되도록함
        UIManager.instance.UpdatePlayerUI(this);
    }

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

        UIManager.instance.UpdatePlayerUI(this);
    }
}

 

게임을 실행하고 플레이어 HP 가 적의 공격을 받았을때 줄어 드는지 확인 합니다

 

 

 

0

반응형
반응형

이번 시간은 몬스터의 부착 되어있는 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

 

반응형
반응형

플레이어가 공격하였을 때 몬스터에게 충격을 가하는 이팩트를 만들겠습니다

 

Player를 선택하고 Animation을 들어갑니다

 

 

Player의 animation의 Hikkick을 선택합니다

이것은 Player가 공격할 때 취하는 애니메이션입니다

지금 이것이 Read-Only로 읽기 전용으로 되어 있습니다

이것을 바꾸어야 합니다 

 

 

 

 

 

 

 

 

Hikkick 애니메이션을 찾아 Ctrl +D를 눌러서 복사합니다

 

복사본 이름을 Hikick_new로 바꾸고  Animator에 들어가서 attack에 복사본을  motion에 드래그해서 바꾸어 놓습니다

 

PlayerFSM을 선택하고 수정합니다

 

 

 

 

 

 

 

PlayerFSM 함수 추가


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;
    void Start()
    {
        myAni = GetComponent<PlayerAni>();
        //  myAni.ChangeAni(PlayerAni.ANI_WALK);

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


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

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

        ChangeState(State.Move, PlayerAni.ANI_WALK);
    }
    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();
    }
}

 

 

 

 

 

 

 

 

 

그리고 Animation을 활성화시켜서 Player 공격하는 시점 프레임에 Add Event를 추가시켜 AttackCalculate() 함수를 등록시킵니다.

 

 

Asset Store에 들어가서 Simple Partice Pack을 다운로드합니다

 

Simpe Particle Pack에서 Explosions에 Burst를 임포트 합니다

 

 

 

 

 

 

 

 

 

Enemy 오브젝트를 선택하고 자식으로 빈 오브젝트를 만들고 이름을 EffectPos 라 하고  임포트 한 Burst 프리 팹을 찾아 드래그해서 자식으로 놓습니다 

그리고 Enemy 오브젝트에 머리 위로 위치시켜 이팩트가 터지면 잘 보이도록 위치시킵니다 

 

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

 

 

 

 

 

 

 

 

 



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

    public State currentState = State.Idle;

    EnemyAni myAni;

    Transform player;

    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>();
        ChangeState(State.Idle, EnemyAni.IDLE);

        player = GameObject.FindGameObjectWithTag("Player").transform;

        hitEffect.Stop();
    }

    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()
    {

    }
    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();
    }
}

 

 

 

Enemy를 선택하고 자식으로 두었던 Burst 이팩트를 수정한 스크립트에 등록합니다

 

Burst 이팩트를 활성화시킵니다

 

 

 

 

게임을 실행시켜 플레이어를 적에게 공격하면 이팩트가 잘 터지는지 확인합니다

 

https://youtu.be/y79imRjp1rM

 

반응형
반응형

몬스터인 적이 플레이어를 공격하고 추적하는 스크립트를 만들겠습니다

 

먼저 EnemyFSM 스크립트를 만들고 Enemy 오브젝트에 붙힘니다.

 

그리고 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;

    EnemyAni myAni;

    Transform player;

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

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

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

    void Start()
    {
        myAni = GetComponent<EnemyAni>();
        ChangeState(State.Idle, EnemyAni.IDLE);

        player = GameObject.FindGameObjectWithTag("Player").transform;
    }

    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()
    {

    }

    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();
    }
}

 

 

 

 

 

그리고 EnemyAni 스크립트를 만들고 Enemy 오브젝트에 붙힘니다

EnemyAni 스크립트 작성

 

 

 

 

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

public class EnemyAni : MonoBehaviour
{
    public const string IDLE = "idle";
    public const string WALK = "walk";
    public const string ATTACK = "attack1";
    public const string DIE = "death1";

    Animation anim;
    // Start is called before the first frame update
    void Start()
    {
        anim = GetComponentInChildren<Animation>();
    }

    public void ChangeAni(string aniName)
    {
        anim.CrossFade(aniName);
    }
    // Update is called once per frame
    void Update()
    {
        
    }
}

 

 

 

 

게임을 실행시켜서 player가 적에게 다가갔을때 적이 공격하고 Player가 달아 났을 때 적이 추적하는지 확인 합니다

https://youtu.be/w1RNqHVehIc

 

반응형
반응형

적을 공격하는 함수 만들기

 

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

// PlayerFSM 스크립트 수정

 

 

 

 


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;


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

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

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

        curEnemy = enemy;
        curTargetPos = curEnemy.transform.position;

        ChangeState(State.Move, PlayerAni.ANI_WALK);
    }


    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)
        {
            ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
        }

  
    }
    void Update()
    {
        UpdateState();
    }
}

 

 

 

 

 

InputManager 오브젝트를 선택하고 InputManager 스크립트를 수정합니다

//InputManager 스크립트 수정

 


public class InputManager : MonoBehaviour
{

    GameObject player;
    // Start is called before the first frame update
    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player");
    }

    void CheckClick()
    {
        if (Input.GetMouseButtonDown(0))
        {
            //카메라로부터 화면사의 좌표를 관통하는 가상의 선(레이)을 생성해서 리턴해 주는 함수
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            RaycastHit hit;

            //Physics.Raycast(래이 타입 변수, out 레이캐스트 히트 타입변수) : 
            //가상의 레이저선(래이)이 충돌체와 충돌하면, true(참) 값을 리턴하면서 동시에 레이캐스트 히트 변수에 충돌 대상의 정보를 담아 주는 함수           
            if (Physics.Raycast(ray, out hit))
            {
                if (hit.collider.gameObject.name == "Terrain")
                {
                    // player.transform.position = hit.point;

                    //마우스 클릭 지점의 좌표를 플레이어가 전달받은뒤,상태를 이동상태로 바뀜
                    player.GetComponent<PlayerFSM>().MoveTo(hit.point);
                }
                else if (hit.collider.gameObject.tag == "Enemy")//마우스 클릭한 대상이 적 캐릭터인 경우
                {
                    player.GetComponent<PlayerFSM>().AttackEnemy(hit.collider.gameObject);
                }
            }
        }

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

 

 

 

 

게임을 실행시켜서 플레이어가 적을 공격하는지 확인합니다

0

반응형
반응형

Asset Store를 열고 free Fantasy Spider를 찾아 임포트 합니다

 

 

 

 

 

 

 

 

Assets/fantasySpider/spider_myOldOne.FBX 를 선택하여 Model을 선택하고 Scale Factor를 0.015를 입력합니다

 

 

 

 

 

빈 게임오브젝트를 Hierarchy에 만들고 이름을 Enemy 라 합니다

 

 

 

 

 

 

 

새로 만든 Enemy 오브젝트를 선택하고 Inspector에서 tah를 Enemy 만들고 선택합니다

 

 

 

 

Enemy 오브젝트의 자식으로 Asset Store 에서 다운로드한 Spider_myOldOne을 드래그하여 올려놓습니다

 

 

spider_myOldOne의 이름을 Spider로 바꿉니다

 

 

Hierarchy의 Enemy Spider를 선택하고 기본 애니메이션을 Idle로 바꿉니다

 

 

 

 

 

 

 

Enemy 오브젝트를 선택하고 Component -> Physics -> Box Collider를 생성합니다

 

Box Collider의 Size를 Enemy 캐릭터에 맞게 조정합니다

 

 

Enemy를 선택하고 Component -> Physics -> Rigidbody를 선택하여 RigidBody를 생성합니다

 

Box Collider에서 is Trigger를 클릭하고 RigidBody 애 서 use Gravity를 선택 해제하고 is Kinematic을 클릭합니다 

 

 

 

 

 

 

 

 

 

게임을 실행하면 Enemy Idle 동작을 하는 모습  

0

 

반응형
반응형

 

0
Camera following

 

 

 

 

 

PlayerFSM 스크립트를 열고 각각의 상태가 되면 자동으로 실행되는 함수들을 별도로 만들어 보겠습니다


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

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

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

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

    PlayerAni myAni;


    void Start()
    {
        myAni = GetComponent<PlayerAni>();
        ChangeState(State.Idle, PlayerAni.ANI_IDLE);
    }

    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()
    {

    }

    void AttackWaitState()
    {

    }

    void DeadState()
    {

    }

    //MoveTo(캐릭터가 이동할 목표 지점의 좌표)
   public void MoveTo(Vector3 tPos)
    {
        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);


        //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령
        if (transform.position == curTargetPos)
        {
            ChangeState(State.Idle, PlayerAni.ANI_IDLE);
        }
    }
    void Update()
    {
        UpdateState();
    }
}

 

 

 

CameraControl 스크립트를 새로 만들고 CameraControl 스크립트를 Main Camera에 붙입니다

 

CameraControl 스크립트 생성

 

Main Camera에 붙입니다

 

 

 

 

//CameraControl 스크립트 작성

 

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

public class CameraControl : MonoBehaviour
{
    public Transform player;

    Vector3 offset;
    // Start is called before the first frame update
    void Start()
    {
        offset = player.position - transform.position;
    }

    //카메라가 플레이어 움직임에 한 템포 늦게 움직임을 준다
    void LateUpdate()
    {
        //플레이어의 위치와 카메라의 위치를 최초 저장한 위치  차이만큼 자동으로 유지시켜주게 됨
        transform.position = player.position - offset;
    }
}

 

 

 

CameraControl 스크립트 작성하고 Player 오브젝트를 드래그하여 inspector 변수에 넣습니다

 

게임을 실행시키면 카메라가 플레이어를 따라가는 것을 확인할 수 있습니다

 

반응형
반응형

0

 

플레이어를 이동 시키기 위해서 InputManager 스크립트를 수정 하겠습니다

InputManager 스크립트를 엽니다

 

 

 

 

 

 

 

//InputManager 스크립트 수정

 

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

public class InputManager : MonoBehaviour
{

    GameObject player;
    // Start is called before the first frame update
    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player");
    }

    void CheckClick()
    {
        if (Input.GetMouseButtonDown(0))
        {
            //카메라로부터 화면사의 좌표를 관통하는 가상의 선(레이)을 생성해서 리턴해 주는 함수
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            RaycastHit hit;

            //Physics.Raycast(래이 타입 변수, out 레이캐스트 히트 타입변수) : 
            //가상의 레이저선(래이)이 충돌체와 충돌하면, true(참) 값을 리턴하면서 동시에 레이캐스트 히트 변수에 충돌 대상의 정보를 담아 주는 함수           
            if (Physics.Raycast(ray, out hit))
            {
                if (hit.collider.gameObject.name == "Terrain")
                {
                    // player.transform.position = hit.point;

                    //마우스 클릭 지점의 좌표를 플레이어가 전달받은뒤,상태를 이동상태로 바뀜
                    player.GetComponent<PlayerFSM>().MoveTo(hit.point);                

                 }
            }
        }

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

 

 

 

 

 

 

 

 

 

PlayerFSM 스크립트를 선택하고 스크립트를 엽니다

그리고 수정합니다

 

 

//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;

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

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

    PlayerAni myAni;


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

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


    void ChangeState(State newState, int aniNumber)
    {
        if (currentState == newState)
        {
            return;
        }

        myAni.ChangeAni(aniNumber);
        currentState = newState;
    }

    //캐릭터의 상태가 바뀌면 어떤 일이 일어날지 를 미리 정의
    void UpdateState()
    {
        switch (currentState)
        {
            case State.Idle:
                break;
            case State.Move:
                TurnToDestination();
                MoveToDestination();
                break;
            case State.Attack:
                break;
            case State.AttackWait:
                break;
            case State.Dead:
                break;
            default:
                break;
        }

    }

    //MoveTo(캐릭터가 이동할 목표 지점의 좌표)
   public void MoveTo(Vector3 tPos)
    {
        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);


        //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령
        if (transform.position == curTargetPos)
        {
            ChangeState(State.Idle, PlayerAni.ANI_IDLE);
        }
    }
    void Update()
    {
        UpdateState();
    }
}

 

 

 

 

 

 

스크립트를 수정하고 플레이 버튼을 눌러서 플레이어가 마우스 클릭지점으로 이동하는지 확인 합니다

 

 

0

 

반응형
반응형

이제 애니메이터 컨트롤러에 등록한 애니메이션들을 스크립트로 컨트롤 하겠습니다

Scripts 폴더안에 PlayerAni 스크립트를 만듭니다

 

Player를 선택하고 PlayerAni 스크립트를 붙힘니다

 

PlayerAni.cs 에서 만든 함수를 PlayerFSM 에서 활용해 보도록 하겠습니다

 

 

 

 

 

 

//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; 

    PlayerAni myAni; 


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

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

    void ChangeState(State newState, int aniNumber) 
    { 
        if (currentState == newState) 
        { 
            return; 
        } 

        myAni.ChangeAni(aniNumber); 
        currentState = newState; 
    } 

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

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

 

 

 

//PlayerAni 스크립트 작성

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

public class PlayerAni : MonoBehaviour 
{ 
    // 애니메이터 컨트롤러의 전이 관계에서 설정한 번호에 맞춤니다 
    public const int ANI_IDLE = 0; 
    public const int ANI_WALK = 1; 
    public const int ANI_ATTACK = 2; 
    public const int ANI_ATKIDLE = 3; 
    public const int ANI_DIE = 4; 

    Animator anim; 
    void Start() 
    { 
        anim = GetComponent<Animator>(); 
    } 

    //애니메이션 번호를 입력 받아서 플레이어의 애니메이션을 해당되는 애니메이션으로 바꿔주는 함수 
    public void ChangeAni(int aniNumber) 
    { 
        anim.SetInteger("aniName", aniNumber); 
    } 
    // Update is called once per frame 
    void Update() 
    { 
         
    } 
}

 

 

스크립트를 작성하고 게임을 실행시키면 플레이어가 Idle 상태로 됩니다

 

0

반응형
반응형

 

Scrips 폴더 안에 PlayerFSM 스크립트를 만듭니다

 

 

PlayerFSM 스크립트를 Player 오브젝트에 붙입니다

 

 

그리고 Asset Store에 들어가서 찾기 에서 HQ Fighting Animation을 찾아 임포트 합니다

 

 

임포트 파일 중에서 아래 사진 세 개의 파일만 선택하고 임포트 합니다

 

 

 

 

 

 

그리고 임포트 한 파일을 선택하여 inspector에서 Rig에서 Animation Type을 Hunanoid로 바꾸고 Apply 누릅니다

 

그리고 Animation으로 들어가서 Bake into Pose를 체크합니다

 

 

 

 

 

 

그리고 PlayerControl에서 등록한 WAIT00의 이름을 idle로 바꿉니다

 

그리고 Assets/unity-chan!/Unity-chan! Model/Art/Animations/WALK00_F을 드래그하여 
Animator에 올려놓습니다

이름을 walk로 바꿉니다

 

 

Parameters에 있는 + 버튼을 눌러서 int를 추가하고 이름을 aniName을 등록합니다 

 

idle   ->  walk 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고 

Conditions에서 aniName , Eqals , 1을 선택한다

 

 

 

 

 

idle  <-  walk 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고 

Conditions에서 aniName , Eqals , 0을 선택한다

 

 

 

Assets/FightingUnityChan_FreeAsset/FightingUnityChan_FreeAsset/Animations/FUCM_04_0001_RHiKick에서 Hikick을 드래그 하여  Animator에 올려놓고 이름을 attack으로 한다 

 

walk ->  attack 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고 

Conditions에서 aniName , Eqals , 2를 선택한다

 

 

 

 

walk <-  attack 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고 

Conditions에서 aniName , Eqals , 1을 선택한다

 

Assets/FightingUnityChan_FreeAsset/FightingUnityChan_FreeAsset/Animations/FUCM05_0000_Idle에서 Idle을 선택하여 Animator에 드래그하고 이름을 attackidle로 바꿉니다

 

 

 

 

 

attackidle <-  attack 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하지 말고

Conditions에서 aniName , Eqals , 3을 선택한다

Has Exit Time을 체크 해제하지 않으면 상태가 바뀌었을 때 애니메이션이 즉각적으로 바뀌지 않고,
기존 애니메이션이 다 플레이된 다음에야 바뀌게 됩니다

 

attackidle -> attack 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 2를 선택한다

 

idle <-  attackidle 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 0을 선택한다

 

 

 

 

 

idle ->  attackidle 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 3을 선택한다

 

walk <-  attackidle 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 1을 선택한다

 

idle <-  attack 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 0을 선택한다

 

idle ->  attack 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 2를 선택한다

 

 

 

 

 

Assets/FightingUnityChan_FreeAsset/FightingUnityChan_FreeAsset/Animations/FUCM02_0025_MYA_TF_DOWN에서 DamageDown을 드래그해서  Animator에 놓고 이름을 die 한다

 

die <-  attack 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 4를 선택한다

 

die <-  attackidle 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 4를 선택한다

 

die <-  idle 사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 4를 선택한다

 

 

 

 

 

die <-  walk사이에  Make Transition을 연결하고 화살표를 선택하여 Has Exit Time을 체크 해제하고

Conditions에서 aniName , Eqals , 4를 선택한다

 

Player의 Animator 등록을 완료하였습니다 

반응형
반응형

 

유니티 Hierarchy에 빈 오브젝트를 만들고 이름을 InputManager 라 합니다

InputManager 만들기

 

 

폴더를 하나 생성하여 이름을 Scripts라고 합니다

Scripts폴더생성

Scripts폴더 안에 InputManager 새 스크립트를 생성하고 스크립트를 드래그하여 InputManager 오브젝트에 붙입니다

 

 

 

 

InputManager Scripts 작성

 

아래와 같이 스크립트를 작성합니다

 

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

public class InputManager : MonoBehaviour 
{ 

    GameObject player; 
    // Start is called before the first frame update 
    void Start() 
    { 
        player = GameObject.FindGameObjectWithTag("Player"); 
    } 

    void CheckClick() 
    { 
        if (Input.GetMouseButtonDown(0)) 
        { 
            //카메라로부터 화면사의 좌표를 관통하는 가상의 선(레이)을 생성해서 리턴해 주는 함수 
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); 

            RaycastHit hit; 

            //Physics.Raycast(래이 타입 변수, out 레이 캐스트 히트 타입변수) :  
            //가상의 레이저선(래이)이 충돌체와 충돌하면, true(참) 값을 리턴하면서 동시에 레이캐스트 히트 변수에 충            돌 대상의 정보를 담아 주는 함수    

     
            if (Physics.Raycast(ray, out hit)) 
            { 
                if (hit.collider.gameObject.name == "Terrain") 
                { 
                    player.transform.position = hit.point; 
                } 
            } 
        } 

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

 

 

 

 

아래 동영상과 같이 동작하면 정상작동입니다

 

 

반응형
반응형

 

window -> Asset Store를 들어갑니다

 

window -> Asset Store

 

Asset Store에서 unity-chan을 입력하고 찾기 버튼을 누릅니다

Asset Store -> unity-chan

 

 

임포트 합니다

import

 

 

 

 

전체 파일 임포트

 

 

 

 

 

 

 

 

project 안에 unity-chan! 폴더가 생성된 것을 확인할 수 있습니다

unity-chan! 폴더 생성

 

 

Models 안에 있는 unity chan 오브젝트를 드래그하여 Hierarchy에 올려놓습니다 

 

 

캐릭터 의 모습

 

 

 

 

 

 

 

 

카메라를 캐릭터 중심에 잡아놓고 GameObject -> Align with View를 클릭합니다 

GameObject -> Align with View 

 

 

unity chan 오브젝트를 선택하고 Inspector 창에 이름과 tag를 Player라고 바꿉니다 

 

 

Project Assets에 새폴더를 만들고 이름을 Animator 라합니다

Animator 폴더 생성

 

Animator 폴더를 선택하고 Assets - > Animator Controller를 클릭합니다

 

 

Animatro Controller 이름을 PlayerControl 라고 바꿉니다

PlayerControl 생성

 

 

PlayerControl를 두 번 클릭하여 들어가면 나오는 화면

 

unity-chan! -> Unity-cha! Model -> Art -> Animations의 폴더를 들어가서 unitychan_WAIT00 -> WAIT00을 드래그하여 그림과 같이 올려놓습니다

 

 

 

 

 

 

 

 

그리고 그림과 같이 Player를 선택하고  PlayerCotrol을 드래그해서 Player의 Inspector에서 Animator에 드래그하여 올려놓습니다 

그리고 플레이 버튼을 누르면 Player 가 기본 동작으로 Idle  하는 모습을 볼 수 있습니다

 

반응형
반응형

 

unity project를 생성하고 Scenes 폴더를 생성 한다음 Scene 이름을 RPGGame 을 하고 저장 합니다

 

씬 저장

 

GameObject -> Terrain 을 생성 합니다

GameObject -> Terrain 

 

 

Terrain 생성한 모습

Terrain 생성

 

그리고 Window -> Asset Store 를 열고 Standard Assets 을 찾아 임포트 합니다

Asset Store 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Standard Assets에서  임포트 하면 unity Asset 에 임포트 할 SurfaceTexture 을 선택하고 import 합니다

SurfaceTextures 만 선택 임포트

 

 

 

그리고 생성된 Terrain 을 선택하고 Inspector 창에서 브러쉬 모양을 선택한후 

 

PaintTexture 를 선택합니다.

그리고 Edit Terrain Layers 를 누르고 create Layer  에서 GrassHillAlbedo 를 선택하여 Terrain 전체를 색깔을 덮습니다

그리고 다시 Edit Terrain Layers 를 누르고 create Layer 에서 GrassRockyAlbedo 를 선택하고 

brush Size 를 19

Opacity 를 100 로 합니다

 

Terrain 속성

 

 

 

Terrain 에 브러쉬를 모양이 나오면 그림과 같이 길을 만듭니다 

Terrain 길 만들기

반응형

+ Recent posts