반응형

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 길 만들기

반응형
반응형


화면과 같이 유니티 엔진으로 무한 점프 게임을 만들어 보겠습니다



제작게임 해보기 :  Google Play  





Game 씬 화면을 16:9 맞춤니다



Scene 폴더를 만들고 씬을 생성하여 이름을 MainGame이라 합니다



Texture 폴더를 만들고 그림 리소스를 드래그 하여 폴더 안에 넣습니다


  

Audio폴더를 만들고  드래그 하여 사운드 리소스를 넣습니다



MainCamera를 선택하여 그림과 같이 맞춤니다


Sky 텍스쳐를 선택하고 그림과 같이 속성을 바꿈니다


Hierarchy에 Quad를 생성 하여 이름을 Sky라 합니다



Materials폴더를 만들고 Material를 생성하여 Sky라 이름을 붙힙니다


Hierarchy에  Sky를 선택하고 만든 Material을 드래그 하여 붙히고 Shader -> Unlit -> Texture

를 선택합니다 



Select창에 sky Texture를 붙힙니다


Sky 오브젝트를 MainCamera자식으로 이동 합니다

그리고 그림과 같이 스케일과 포지션을 바꿉니다



Scripts폴더를 생성하고 SkyCtrl 스크립트를 생성하여 넣습니다


SkyCtrl 스크립트 작성

Sky를 배경으로 하여 배경 스크롤 하는 오브젝트를 짭니다



using System.Collections;

using System.Collections.Generic;

using UnityEngine;


public class SkyCtrl : MonoBehaviour {


    float speed = 0.03f;


    //------------------------------

    // 화면 스크롤

    //------------------------------

    void Update()

    {

        float ofsX = speed * Time.time;

        transform.GetComponent<Renderer>().material.mainTextureOffset = new Vector2(ofsX, 0);

    }

}




SkyCtrl 스크립트를 Sky 오브젝트에 드래그하여 붙힙니다



배경이 좌로 스크롤 되는 것을 확인합니다


Jump_tile 텍스쳐를 선택하여 그림과 같이 속성을 바꿉니다



Jump_tile 텍스쳐를 선택하여 Hierarchy창에 드래그하여 놓고 이름을 Tile 로 바꿉니다


Tile 오브젝트를 선택하여 Tag를 Tile을 생성하여 선택하고 Layer를 Tile를 만들어 선택합니다




TilCtrl 스크립트를 만들어 저장합니다


TilCtrl 스크립트 작성

그림과 같이 Camera 시선에 좌표를 만들어 화면에 벗어나면 삭제되는 스크립트를 작성합니다


using System.Collections;

using System.Collections.Generic;

using UnityEngine;


public class TilCtrl : MonoBehaviour {


    //-------------------

    // 화면 아래를 벗어나면 제거

    void Update()

    {

        // 월드 좌표를 스크린 좌표로 변환 

        Vector3 view = Camera.main.WorldToScreenPoint(transform.position);

        if (view.y < -50)

        {

            Destroy(gameObject);

        }

    }

}




Tile오브젝트에 Tile스크립틀 붙힙니다


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



tile Sorting Layers tile 을 만들고 선택합니다 Sorting Layer는 아래로 선택할수록 텍스쳐가 위로 보이는 기능입니다 


Hierarchy 창에 Tile 오브젝트를 하나더 만들어서 포지션을 조금 높게 설정하여 저장합니다



rabbit_jump2 텍스쳐를 선택하여 드래그하여 Hierarchy창에 올리고 이름을 Player라 합니다

 

Sorting Layer를 Player를 만들어 선택합니다



 MainCamera를 선택하여 빈 오브젝트를 만들고 이름을 spPoint 라고 하고 포지션을 그림과 같이 합니다


Player를 선택하고 그림과 같이 자식으로 오브젝트를 만들어 StartPos 와 EndPos 이름을 붙힙니다  


그림과 같이 오브젝트포지션을 Player 발끝에 EndPos 를 놓고  바로 위에 StartPos를 놓습니다



Player를 선택하고 Audio Source를 붙힙니다


PlayerCtrl스크립트를 만들고 PlayerCtrl 스크립트를 작성합니다


PlayerCtrl 스크립트 작성




using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;


public class PlayerCtrl : MonoBehaviour {


    public Transform tile;

    public Transform startPos;

    public Transform endPos;


    public AudioClip sndJump;        // 효과음 및 배경음악 

    public AudioClip sndGift;

    public AudioClip sndBird;

    public AudioClip sndStage;

    public AudioClip sndOver;


    Transform spPoint;

    Transform newTile;

    float maxY = 0;


    int speedSide = 10;              // 좌우 이동 속도 

    int speedJump = 16;             // 점프 속도 

    int gravity = 25;                  // 추락 속도 


    Vector3 moveDir = Vector3.zero;


    bool isDead = false;

    Animator anim;

    public Button retryGame;



    void Start()

    {

        anim = GetComponent<Animator>();


        // 모바일 단말기 설정

        Screen.orientation = ScreenOrientation.LandscapeRight;

        Screen.sleepTimeout = SleepTimeout.NeverSleep;


        spPoint = GameObject.Find("spPoint").transform;


        // Tile 만들기 

        newTile = Instantiate(tile, spPoint.position, spPoint.rotation) as Transform;


        //Cursor.visible = false;     // 커서 감추기 

        retryGame.gameObject.SetActive(false);

    }


    //-------------------

    // 게임 루프

    //-------------------

    void Update()

    {

        if (isDead) return;

        JumpPlayer();          // Player 점프

        MovePlayer();          // Player 이동 

        MoveCamera();          // 카메라 이동 

      

    }



    //-------------------

    // Player 점프

    //-------------------

    void JumpPlayer()

    {

        RaycastHit2D hit;

        hit = Physics2D.Linecast(startPos.position, endPos.position, 1 << LayerMask.NameToLayer("Tile"));


        if (hit)

        {

            moveDir.y = speedJump;

            AudioSource.PlayClipAtPoint(sndJump, transform.position);

        }

    }


    //-------------------

    //  Player 이동

    //-------------------

    void MovePlayer()

    {

        Vector3 view = Camera.main.WorldToScreenPoint(transform.position);


        if (view.y < -50)

        {       // 화면 아래를 벗어나면  

            isDead = true;      // 게임 오버 

            GameOver();

            return;

        }


        moveDir.x = 0;      // Player의 좌우 이동 방향


        // Mobile 처리 

        if (Application.platform == RuntimePlatform.Android ||

            Application.platform == RuntimePlatform.IPhonePlayer)

        {

            // 중력 가속도 센서 읽기 

            float x = Input.acceleration.x;


            // 왼쪽으로 기울였나?

            if (x < -0.2f && view.x > 35)

            {

                moveDir.x = 2 * x * speedSide;

            }


            if (x > 0.2f && view.x < Screen.width - 35)

            {

                moveDir.x = 2 * x * speedSide;

            }


        }

        else

        {   // Keyboard 읽기 

            float key = Input.GetAxis("Horizontal");

            if ((key < 0 && view.x > 35) ||

                (key > 0 && view.x < Screen.width - 35))

            {

                moveDir.x = key * speedSide;

            }

        }


        // 매 프레임마다 점프 속도 감소

        moveDir.y -= gravity * Time.deltaTime;

        transform.Translate(moveDir * Time.smoothDeltaTime);



        //애니메이션 설정

        if (moveDir.y > 0)

        {

            anim.Play("PlayerJump");

        }

        else

        {

            anim.Play("PlayerIdle");

        }

    }


    void MoveCamera()

    {

        // Player 최대 높이 구하기 

        if (transform.position.y > maxY)

        {

            maxY = transform.position.y;


            // 카메라 위치 이동 

            Camera.main.transform.position = new Vector3(0, maxY - 2.5f, -10);

           // score = (int)maxY * 1000;

        }


        // 가장 최근의 Tile과 spPoint와의 거리 구하기

        if (spPoint.position.y - newTile.position.y >= 4)

        {

            float x = Random.Range(-10f, 10f) * 0.5f;

            Vector3 pos = new Vector3(x, spPoint.position.y, 0.3f);

            newTile = Instantiate(tile, pos, Quaternion.identity) as Transform;


            // 나뭇가지의 회전방향 설정 

            int mx = (Random.Range(0, 2) == 0) ? -1 : 1;

            int my = (Random.Range(0, 2) == 0) ? -1 : 1;

            newTile.GetComponent<SpriteRenderer>().material.mainTextureScale = new Vector2(mx, my);

        }

    }


    //게임 오버 설정

    void GameOver()

    {

         retryGame.gameObject.SetActive(true);

        if (GetComponent<AudioSource>().clip != sndOver)

        {

            GetComponent<AudioSource>().clip = sndOver;

            GetComponent<AudioSource>().loop = false;

            GetComponent<AudioSource>().Play();

        }

    }

    // 재시작 

    public void RetryGame()

    {


        Application.LoadLevel("MainGame");

    }


}





Player를 선택하고 Animation을 만든다


Create New Clip 으로 들어가 PlayerIdle 만들고 저장하고 그림과 같이 rabbit_jump2 텍스쳐를 드래그하여 애니메이션을 만든다



Create New Clip 으로 들어가 PlayerJump 이름을 만들고 저장하고 그림과 같이 rabbit_jump 텍스쳐를 드래그하여 애니메이션을 만든다


Main Camera 자식으로 한 Canvas 를 만들어 

Button 을 만듭니다



Canvas 의 설정을 그림과 같이 합니다


Button 밑에 있는  Text 도 그림과 같이 속성을 합니다



버튼 크기를 적당히 그림과 같이 배치하고 크기를 조절합니다


Player 오브젝트에 PlayerCtrl스크립트를 연결하고 그림과 같이 오브젝트들을 연결합니다



Tile은 프리팹으로 연결합니다 


게임을 실행합니다

영상과 같이 캐릭터가 올라가면 됩니다



bird 스프라이트 하나를 드래그 하여 Hierarchy 창에 올리고 이름을 Bird하 합니다


만든  Bird 오브젝트에 Animation 을 열고  BirdFly  애니메이션 이름을 만들고 bird_107*92*0 ~ 5 스프라이트를 드래그 하여 Animation창에 올려 놓습니다



Bird 오브젝트를 선택하여 Tag를 Bird 를 만들어 선택하고 Sorting Layer를 bird 를 만들어 선택합니다

그리고 Box Coillder2D를 붙힙니다


BirdCtrl 스크립트를 만들어 붙힙니다


BirdCtrl 스크립트 작성



using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;


public class BirdCtrl : MonoBehaviour {


    public Transform txtScore;  // 프리팹 

    int speed;                  // 이동 속도 

    bool isDrop = false; // Use this for initialization


    Animator anim;


    private void Awake()

    {

        anim = transform.GetComponent<Animator>();

    }


    void Update()

    {

        speed = Random.Range(3, 7);


        float amtMove = speed * Time.smoothDeltaTime;


        if (!isDrop)

        {

            AnimateBird();

            transform.Translate(Vector3.right * amtMove);

        }

        else

        {           // 아래로 이동 

            transform.Translate(Vector3.down * amtMove, Space.World);

        }


        // 화면을 벗어난 오브젝트 제거 

        Vector3 view = Camera.main.WorldToScreenPoint(transform.position);

        if (view.y < -50 || view.x > Screen.width + 50)

        {

            Destroy(gameObject);

        }


    }


    void AnimateBird()

    {

        anim.Play("BirdFly");

    }


    void DropBird()

    {

        isDrop = true;


        // 참새 회전 

        transform.eulerAngles = new Vector3(0, 0, 180);


        // 감점 표시 

        Transform obj = Instantiate(txtScore) as Transform;

        obj.GetComponent<Text>().text = "<color=red><size=22>-1000</size></color>";


        // World 좌표를 Viewport 좌표로 변환 

        var pos = Camera.main.WorldToViewportPoint(transform.position);

        obj.position = transform.position;

    }

    // Update is called once per frame

}




스크립트를 작성하고 Bird 프리팹을 만들어 저장합니다 Hierachy 창에 Bird는 삭제합니다


Canvas 자식으로 Text 를 만들고 이름을 TextCtrl이라 하고속성을 그림과 같이 합니다



TextCtrl 스크립트를 만들고 TextCtrl 오브젝트에 붙힙니다


TextCtrl 오브젝트를 Canvas 자식에서 빼냅니다



TextCtrl 오브젝트의 Scale을 변경 하지마시고 TextCtrl 오브젝트를 프리팹을 만들고 Hierarchy창에 있는 TextCtrl을 삭제합니다


Bird 오브젝트를 선택하고 TextCtrl 프리팹을 txtScore에 붙힙니다



gift1 스트라이트를 선택 드래그 하여 Item1오브젝트를 만듭니다


Item1오브젝트를 선택하여 Tag를 Item1 정하고 Sorting Layer를 Item을 만들어 선택합니다

그리고 BoxCoillder2D를 붙힙니다


ItemCtrl 스크립트 작성



using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;


public class ItemCtrl : MonoBehaviour {


    public Transform txtScore;      // 프리팹 


    //-------------------

    // 화면을 벗어난 Gift 제거

    //-------------------

    void Update()

    {

        // World 좌표를 Screen 좌표로 변환 

        Vector3 view = Camera.main.WorldToScreenPoint(transform.position);

        if (view.y < -50)

        {

            Destroy(gameObject);

        }

    }

    //-------------------

    // 점수 표시 - 외부 호출 

    //-------------------

    void DisplayScore(int n)

    {

       

        // 점수 표시용 UIText 만들기 

        Transform obj = Instantiate(txtScore, transform.position, Quaternion.identity) as Transform;

        obj.GetComponent<Text>().text = "+" + n * 500;


        // World 좌표를 Viewport 좌표로 변환 

        var pos = Camera.main.WorldToViewportPoint(transform.position);

        // obj.position = pos;


        // Gift 제거 

        Destroy(gameObject);

    }

}




작성한 스크립트를 Item1에 붙히고 TextCtrl 프리팹을 연결합니다


Item2를 만들어 그림과 같이 합니다



Item3를 만들어 그림과 같이 합니다


Canvas에 Text 3개를 만들어 이름을 그림과 같이 하고 속성을 그림과 같이 합니다



Scene 창에 그림과 같이 대략 적절한 크기와 위치 색깔로 맞춤니다 




PlayerCtrl 스크립트를 그립과 같이 수정합니다


PlayerCtrl 스크립트 수정



using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;


public class PlayerCtrl : MonoBehaviour

{

    public Text tScore;          //점수,높이 ,아이템갯수 

    public Text tHeight;

    public Text tItem;


    public Transform tile;        // 프리팹 

    public Transform[] item;

    public Transform bird;


  

    public Transform startPos;

    public Transform endPos;


    public AudioClip sndJump;       // 효과음 및 배경음악 

    public AudioClip sndGift;

    public AudioClip sndBird;

    public AudioClip sndStage;

    public AudioClip sndOver;


    Transform spPoint;

    Transform newTile;

    float maxY = 0;

    int giftCnt = 0;                // 획득한 선물 수 

    int score = 0;                // 득점 


    int speedSide = 10;             // 좌우 이동 속도 

    int speedJump = 16;             // 점프 속도 

    int gravity = 25;               // 추락 속도 


    Vector3 moveDir = Vector3.zero;

 

    bool isDead = false;

    Animator anim;

    public Button retryGame;



    void Start()

    {

        anim = GetComponent<Animator>();


        // 모바일 단말기 설정

        Screen.orientation = ScreenOrientation.LandscapeRight;

        Screen.sleepTimeout = SleepTimeout.NeverSleep;


        spPoint = GameObject.Find("spPoint").transform;


        // Tile 만들기 

        newTile = Instantiate(tile, spPoint.position, spPoint.rotation) as Transform;


        //Cursor.visible = false;     // 커서 감추기 

        retryGame.gameObject.SetActive(false);

    }


    //-------------------

    // 게임 루프

    //-------------------

    void Update()

    {

        if (isDead) return;

        JumpPlayer();          // Player 점프

        MovePlayer();          // Player 이동 

        MoveCamera();          // 카메라 이동 

        MakeItem();

        UIText();

    }



    //-------------------

    // Player 점프

    //-------------------

    void JumpPlayer()

    {

        RaycastHit2D hit;

        hit = Physics2D.Linecast(startPos.position, endPos.position, 1 << LayerMask.NameToLayer("Tile"));


        if (hit)

        {

            moveDir.y = speedJump;

            AudioSource.PlayClipAtPoint(sndJump, transform.position);

        }

    }


    //-------------------

    //  Player 이동

    //-------------------

    void MovePlayer()

    {

        Vector3 view = Camera.main.WorldToScreenPoint(transform.position);


        if (view.y < -50)

        {       // 화면 아래를 벗어나면  

            isDead = true;      // 게임 오버 

            GameOver();

            return;

        }


        moveDir.x = 0;      // Player의 좌우 이동 방향


        // Mobile 처리 

        if (Application.platform == RuntimePlatform.Android ||

            Application.platform == RuntimePlatform.IPhonePlayer)

        {

            // 중력 가속도 센서 읽기 

            float x = Input.acceleration.x;


            // 왼쪽으로 기울였나?

            if (x < -0.2f && view.x > 35)

            {

                moveDir.x = 2 * x * speedSide;

            }


            if (x > 0.2f && view.x < Screen.width - 35)

            {

                moveDir.x = 2 * x * speedSide;

            }


        }

        else

        {   // Keyboard 읽기 

            float key = Input.GetAxis("Horizontal");

            if ((key < 0 && view.x > 35) ||

                (key > 0 && view.x < Screen.width - 35))

            {

                moveDir.x = key * speedSide;

            }

        }


        // 매 프레임마다 점프 속도 감소

        moveDir.y -= gravity * Time.deltaTime;

        transform.Translate(moveDir * Time.smoothDeltaTime);



        //애니메이션 설정

        if (moveDir.y > 0)

        {

            anim.Play("PlayerJump");

        }

        else

        {

            anim.Play("PlayerIdle");

        }

    }


    void MoveCamera()

    {

        // Player 최대 높이 구하기 

        if (transform.position.y > maxY)

        {

            maxY = transform.position.y;


            // 카메라 위치 이동 

            Camera.main.transform.position = new Vector3(0, maxY - 2.5f, -10);

           // score = (int)maxY * 1000;

        }


        // 가장 최근의 Tile과 spPoint와의 거리 구하기

        if (spPoint.position.y - newTile.position.y >= 4)

        {

            float x = Random.Range(-10f, 10f) * 0.5f;

            Vector3 pos = new Vector3(x, spPoint.position.y, 0.3f);

            newTile = Instantiate(tile, pos, Quaternion.identity) as Transform;


            // 나뭇가지의 회전방향 설정 

            int mx = (Random.Range(0, 2) == 0) ? -1 : 1;

            int my = (Random.Range(0, 2) == 0) ? -1 : 1;

            newTile.GetComponent<SpriteRenderer>().material.mainTextureScale = new Vector2(mx, my);

        }

    }

    void MakeItem()

    {

        if (Random.Range(1, 1000) < 990) return;


        // 오브젝트 표시 위치 

        Vector3 pos = Vector3.zero;

        pos.y = maxY + Random.Range(4, 5.5f);


        if (Random.Range(0, 100) < 50)

        {

            // 참새 만들기 

            pos.x = -12f;

            Instantiate(bird, pos, Quaternion.identity);

        }

        else

        {

            for (int i = 1; i < item.Length; i++)

            {

                // 화면의 선물 수를 5개 이내로 제한 

                int n1 = GameObject.FindGameObjectsWithTag("Item1").Length;

                int n2 = GameObject.FindGameObjectsWithTag("Item2").Length;

                int n3 = GameObject.FindGameObjectsWithTag("Item3").Length;


                if (n1 + n2 + n3 >= 5) return;


                // Item 만들기 

                pos.x = Random.Range(-5f, 5f);


                int n = Random.Range(0, 3);

                Transform obj = Instantiate(item[n], pos, Quaternion.identity) as Transform;


            }

        }

    }


    void OnTriggerEnter2D(Collider2D coll)

    {

        switch (coll.transform.tag)

        {

            case "Item1":

                AudioSource.PlayClipAtPoint(sndGift, transform.position);

                // 득점 처리              

                score += 500;

                giftCnt++;


                // 선물 제거 

                coll.transform.SendMessage("DisplayScore", 1);

                break;


            case "Item2":

                AudioSource.PlayClipAtPoint(sndGift, transform.position);

                // 득점 처리 

                score += 1000;

                giftCnt++;


                // 선물 제거 

                coll.transform.SendMessage("DisplayScore", 2);

                break;


            case "Item3":

                AudioSource.PlayClipAtPoint(sndGift, transform.position);

                // 득점 처리 

                score += 1500;

                giftCnt++;


                // 선물 제거 

                coll.transform.SendMessage("DisplayScore", 3);

                break;


            case "Bird":

                if (coll.transform.eulerAngles.z != 0) return;


                AudioSource.PlayClipAtPoint(sndBird, transform.position);

                score -= 1000;


                // 참새 추락 처리 

                coll.transform.SendMessage("DropBird", SendMessageOptions.DontRequireReceiver);

                break;

        }

    }


    void UIText()

    {

        tScore.text = "Score :" + score;

        tHeight.text = "Height :" + (int)maxY;

        tItem.text = "Gift :" + giftCnt;

    }


    //게임 오버 설정

    void GameOver()

    {

         retryGame.gameObject.SetActive(true);

        if (GetComponent<AudioSource>().clip != sndOver)

        {

            GetComponent<AudioSource>().clip = sndOver;

            GetComponent<AudioSource>().loop = false;

            GetComponent<AudioSource>().Play();

        }

    }

    // 재시작 

    public void RetryGame()

    {


        Application.LoadLevel("MainGame");

    }


}





수정한 PlayerCtrl 스크립트에 그림과 같이 리소스를 연결합니다



제작게임 해보기  Google Play



게임을 실행하여 동영상과 같이 실행되는지 확인합니다


Audio_Texture.unitypackage


JumpRabbit.unitypackage



반응형
반응형



가위바위보.zip


유니티3D 엔진으로 간단한 가위 바위 보 게임을 만들어 보겠습니다


먼저 Scenes 폴더를 만들고 씬을 만들어 Game 이라하고 저장합니다



Resources 폴더를 만들어 예제 소스를 다운받아 드래그하여 넣습니다

여기서 Resources 철자를 틀려서는 안됩니다

 


Main Camera 를 선택하고 display를 16:9 로 맞추고 Scene 화면을 2D 로 합니다


hierachy창에 Canvas를 생성하고 속성을 그림 빨간 박스 와 같이 맞춤니다



Canvas 자식으로 Image 두개를 생성하고 그림과 같이 이름을 바꿉니다


Image에 그림과 같이 Resources에 있는 그림 아무거나 연결합니다



Canvas 자식으로 Button을 생성하고 Button 자식으로 있는 Text 속성을 그림 빨간 박스와 같이 바꿉니다



Button 그리과 같이 3개가 되도록 생성하고 Button 이름을 그림과 같이 바꿉니다


Scene 화면에 그림과 같이 Button 을 크기와 위치을 합니다


Text 를 생성하고 속성을 그림 빨간 박스와 같이 바꿉니다



Text 3개가 되도록 그림과 같이 이름을 바꿉니다


.Scene 화면에 그림과 같이 Text를 배열 합니다



Hierachy 창에 빈 게임 오브젝트를 만들어 GameManager라 이름 붙힙니다


새 스크립트를 만들어 이름을 GameManager 로 합니다



GameManager 스크립트 작성


using UnityEngine;

using System.Collections;

using System.Collections.Generic;

using UnityEngine.UI;


public class GameManager : MonoBehaviour

{


    public Image objMy; // 가위바위보 오브젝트 

    public Image objCom;


    public Text txtMy;

    public Text txtCom;

    public Text txtScore;


    public Text scissors;

    public Text rock;

    public Text paper;


    // int you = 0; // 가위바위보(가위:1, 바위:2, 보:3)

    int com = 0;

    int scMy = 0; // 승패 점수 

    int scCom = 0;


    //--------------------------------

    // 승패 처리

    //--------------------------------

    void CheckScore(int myHand)

    {

        com = Random.Range(1, 4);       // 1~3의 난수

        objCom.GetComponent<Image>().sprite = Resources.Load<Sprite>("Game_" + com) as Sprite;

        objMy.GetComponent<Image>().sprite = Resources.Load<Sprite>("Game_" + myHand) as Sprite;


        // 승패 판정

        int k = myHand - com;


        if (k == 0)

        {

            txtScore.text = "비겼습니다";

        }

        else if (k == 1 || k == -2)

        {

            txtScore.text = "당신이 이겼습니다";

            scMy++;

        }

        else

        {

            txtScore.text = "컴퓨터가 이겼습니다";

            scCom++;

        }


        txtMy.text = "당신 : " + scMy;

        txtCom.text = "컴퓨터 : " + scCom;


        myHand = 0;

    }


    //--------------------------------

    // 화면 처리 

    //--------------------------------



    public void ClickScissors()

    {

        scissors.text = "가위";


        CheckScore(1);

    }


    public void ClickRock()

    {

        rock.text = "바위";

        CheckScore(2);

    }



    public void ClickPaper()

    {

        paper.text = "보";

        CheckScore(3);

    }


}




승패 판정원리

가위 바위 보 게임의 핵심부분이라고 할수 있습니다.

가위 바위 보에 각 각 1~3을 할당해 두고 승패는 다음과 같이 정하면 됩니다. 두값이 같으면 비깁니다.

가위(1) < 바위(2) <보 (3) < 가위(1) <  ~

이긴 값에서 진 값을 빼면 다음과 같은 결과를 얻을 수 있습니다.


바위(2) > 가위(1) -> 2-1 = 1

보(3) > 바위(2) -> 3-2 = 1

가위(1) > 보 (3) -> 1-3 = -2


내 값에서 상대방의 값을 뺀 결과가 1이거나 -2 이면 이기고 그 외에는 진다. 입니다.

이 원리를 이용하여  CheckScore() 함수를 만들어 스크릡트를 짭니다



GameManager 오브젝트에 GameManager 스크립트를  붙히고  그림과 같이 Canvas에 있는 오브젝트 들을 연결합니다 



ButtonScissors 를 선택하고  OnClic()에 GameManager를 연결합니다

그리고 GameManager -> ClicScissors() 를 찾아 연결합니다



ButtonRock 를 선택하고  OnClic()에 GameManager를 연결합니다

그리고 GameManager -> ClickRock() 를 찾아 연결합니다


ButtonPaper 를 선택하고  OnClic()에 GameManager를 연결합니다

그리고 GameManager -> ClickPaper()를 찾아 연결합니다



게임을 실행 하여 버튼을 누르면 컴 퓨터와 가위 바위 보 대결을 하고 점수가 올라 가는지 확인 합니다



 

프로젝트 파일

Rock Paper Scissors.unitypackage


반응형
반응형

이번시간에는 게임을 빌드 시켜 안드로이드 게임앱을 만들고 Player 에 터치 드레그 프로그램을 짜 보겠습니다


먼저 안드로이드 앱을 빌드하기위해선 

안드로디드 스튜디오의 SDK Manager 와 자바 프로그램에 Jdk 가 필요 합니다.

두 프로그램을 다운받아 설치하고 그림과 같이 

Edit ->Preferences -> External Tools  로 가서 연결합니다

자바프로그램은 꼭 환경변수 설정을 하셔야 됩니다 

자바 환경변수 설정은 인터넷에 많이 나오니 참고 하시면 됩니다 



그리고 각각 Sprite Renderer가 있는 오브젝트에 Sorting Layer를 붙힙니다

PC에서는 텍스쳐는 만든 순서 대로 해서 보이겠지만  스마트 기기에 들어가면 Layer 지정이 안되 있어 화면에 텍스쳐가 가릴지도 모릅니다 



그림과 같이 Sorting Layers를 만듭니다

 아래로 내려갈수록 텍스쳐 Layer가 위로 보이게 하는 것입니다


Player오브젝트를 선택하고 Sorting Layer를 Player로 합니다





Enemy 프리팹을 선택하여 Sorting Layer 를 Enemy 로 합니다


Explosion 프리팹을 선택하여 Sorting Layer effect 로 합니다


rocket 프리팹을 선택하여 Sorting Layer 를 Rocket로 합니다



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


Player 에 연결하여 스마트 폰에서 손가락으로 touch 하고 drag 할수 있는 스크립트를 짭니다 



DragScript


using System.Collections;

using System.Collections.Generic;

using UnityEngine;


public class DragScript : MonoBehaviour

{

    float deltaX, deltaY;


    Rigidbody2D rb;


    bool moveAllowed = false;


// Use this for initialization

void Start ()

    {

        rb = GetComponent<Rigidbody2D>();


        PhysicsMaterial2D mat = new PhysicsMaterial2D();

        mat.bounciness = 0.75f;

        mat.friction = 0.4f;

        GetComponent<CircleCollider2D>().sharedMaterial = mat;

}

// Update is called once per frame

void Update ()

    {

        if (Input.touchCount > 0)

        {

            Touch touch = Input.GetTouch(0);


            Vector2 touchPos = Camera.main.ScreenToWorldPoint(touch.position);


            switch (touch.phase)

            {

                case TouchPhase.Began:


                    if (GetComponent<Collider2D>() == Physics2D.OverlapPoint(touchPos))

                    {

                        deltaX = touchPos.x - transform.position.x;

                        deltaY = touchPos.y - transform.position.y;


                        moveAllowed = true;


                        rb.freezeRotation = true;

                        rb.velocity = new Vector2(0, 0);


                        rb.gravityScale = 0;


                        GetComponent<CircleCollider2D>().sharedMaterial = null;

                    }

                    break;

                case TouchPhase.Moved:

                    if (GetComponent<Collider2D>()== 

                        Physics2D.OverlapPoint(touchPos)&& moveAllowed)

                    {                                                    

                        rb.MovePosition(new Vector2(touchPos.x - deltaX, touchPos.y - deltaY));

                    }                                                                       //Player를 좌우로만 움직일경우  touchPos.y - deltaY 대신에  transform.position.y 을 넣는다

                    break;


                case TouchPhase.Ended:


                    moveAllowed = false;

                    rb.freezeRotation = false;

                    rb.gravityScale = 2;


                    PhysicsMaterial2D mat = new PhysicsMaterial2D();

                    mat.bounciness = 0.75f;

                    mat.friction = 0.4f;

                    GetComponent<CircleCollider2D>().sharedMaterial = mat;

                    break;                   

            }


        }

}

}







DragScript를 Player 전투기에 붙힙니다




File -> Build Settings 를 엽니다



Android를 선택하고 Switch Platform을 클릭합니다


Game 씬을 드래그하여 Snenes In Build 에 집어 넣습니다






Player Settings 를 눌러 인스팩터 값을 수정합니다


Default Icon을 게임에 적합한 그림을 찾아 넣습니다 되도록 정사각형의 그림을 찾아넣고 Resolution and Presentation 에 그림과 같이 Landscape Right 와 Landscape Left를 체크 해제합니다







Other Settings 로 들어가서 Package Name을 변경합니다 


설정이 끝나면 빌드를 눌러 압축 APK 파일이 생성되도록합니다


생성한 압축파일 아이콘이 각자에 압축프로그램에 따라 다를수 있습니다

저는 참고로 반디집을 깔아서 저렇게 나오네요 ㅎㅎ


각자 안드로이드 휴대폰에 파일을 넣고 실행시킵니다











프로젝트파일 다운로드 





반응형
반응형

이번시간은 재시작 버튼을 만들어 게임을 재시작하는 프로그램을 짜보겠습니다


Hierarchy 창에 오른쪽 마우스 버튼을 눌러 UI -> Button 을 생성합니다


생성된 Button을  GameOver 오브젝트  밑으로 자식으로 둡니다 그리고 속성을 그림과 같이 합니다



Button 밑에 Text Retry 라 하고 그림과 같이 속성 바꿉니다 




그리과 같이 Button 의 크기와 위치을 하게 합니다


ObjectManager 스크립트를 열어서 수정합니다


총알을 모두 비활성 시키는 함수 ClearBullets()를 만들어 저장합니다





SpawnManager 함수를 열고 수정합니다



적 리스트를 담을 리스트배열 변수를 만들고 생성되는 적오브젝트를 삭제 하는 함수를 만듭니다






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


Restart()함수를 만들어 게임오버 텍스쳐를 비활성 시키고 코루틴함수 ShowReady()를 실행시켜 게임을 재시작 합니다



Player 스크립트를 열어 스크립트를 수정합니다


Player가 격파 되었을때 삭제 하지 않고 비활성으로 만듭니다

그리고 게임이 재시작 되었을때 Player 가 초기 지점에 올수 있도록 transform.position 을 playerPos에 맞춤니다




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


게임 재시작 과 관련된 수정된 스크립트를 불러 들여 게임을 3초후 재시작 하고 Palyer를  활성 시킵니다 




Button 오브젝트를 선택하여 OnClick에 + 를 눌러 그림과 같이 합니다



GameManager 오브젝트를 드래그하여 OnClick()에 넣습니다



No Function에 GameManager -> ResetGame() 선택합니다 




게임을 실행 시킵니다 

Player가 격파되고  Retry 버튼이 생성되고 버튼 눌렸을때 3초후 게임이 실행되는지 확인 합니다




반응형
반응형

이번시간에는 총알이 계속 생성되고 삭제되면 메모리 누수현상이 일어나는 것을 방지 하기 위한 프로그램을 짜보겠습니다

그림과 같이 비행기 총알을 만들고 계속 삭제 하다 보면 기기에 메모리 누수 현상으로 기기에 무리를 가하게 됩니다

그래서 memory pool을 만들어 활용 하겠습니다

이것은 총알 뿐만 아니라 다른 이팩트나 적을 생성 할때도 같이 활용합니다 



빈 게임오브젝트를 만들어 ObjectManager라 이름 붙힘니다


ObjectManager 스크립트를 만듭니다




ObjectManager 스크립트를 오브젝트에 붙힘니다


ObjectManager 스크립트작성



using System.Collections;

using System.Collections.Generic;

using UnityEngine;


public class ObjectManager : MonoBehaviour

{

    public static ObjectManager instance;


    public GameObject rocketPrefab;


    List<GameObject> bullets = new List<GameObject>();// 총알을 담아둘 리스트를 만듬


    private void Awake()

    {

        if (ObjectManager.instance == null)

        {

            ObjectManager.instance = this;

        }

    }

    void Start ()

    {

        CreateBullets(5); //총알 5개를 생성

}

    void CreateBullets(int bulletCount)

    {

        for (int i = 0; i <  bulletCount; i++)

        {     

            // Instantiate()로 생성한 게임 오브젝트를 변수에 담고자 하면 ,"as + 데이터타임"을 명령어 뒤어 붙여 주어야 함

            GameObject bullet = Instantiate(rocketPrefab) as GameObject;


            bullet.transform.parent = transform;

            bullet.SetActive(false);


            bullets.Add(bullet);

        }

    }

    public GameObject GetBullet(Vector3 pos)

    {

        GameObject reqBullet = null;

        for (int i = 0; i < bullets.Count; i++) 

        {

            if (bullets[i].activeSelf == false)

            {

                reqBullet = bullets[i];// 비활성화 되어있는 총알을 찾아 reqBullet에 담아둠니다


                break;

            }

        }


        if (reqBullet == null)// 추가 총알 생성

        {

            GameObject newBullet = Instantiate(rocketPrefab) as GameObject;

            newBullet.transform.parent = transform;


            bullets.Add(newBullet);

            reqBullet = newBullet;

        }


        reqBullet.SetActive(true); //reqBullet활성


        reqBullet.transform.position = pos;


        return reqBullet;

    }

void Update () {

}

}







메모리 플 스크립트를 작성 하였습니다

메모리 플 스크립트를 총알에 활둉하기 위하여 총알과 연관되어 있는 스크립트를 수정하여야 합니다



Player 스크립트를 엽니다




그림과 같이 스크립트를 수정합니다


Rocket스크립트를 엽니다 




Rocket 스크립트를 수정합니다


Enemy 스크립트를 엽니다


그림과 같이 Enemy 스크립트를 수정합니다


게임을 실행하여 총알의 메모리 풀이 제대로 동작하는지 확인합니다

이번시간은 총알을 활용한 메모리 풀을 만들어 보았는데 같은 방법으로 적기나 이팩트를 메모리 풀로 활용하면 데이터 메모리 누수를 막을 수 있습니다 

 


반응형
반응형

이번시간은 

슈팅게임이 시작하기 전에 게임시작을 알리는 텍스쳐를 만들고 Player가 격파 되었을때 게임오버 텍스쳐를 나타내어 보겠습니다


먼저 Canvas 에  Text를 만들고 이름을 Ready 라고 합니다.

Ready 인스펙터를 그림과 같이 바꿉니다


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



TextContrl 스크립트를 Canvas 에 붙힙니다




TextContrl 스크립트를 작성합니다 코루틴 함수를 넣어 3초간 텍스쳐가 깜박이는 스크립트를 짭니다





Canvas 를 선택하여 Ready텍스쳐를 Text Contrl 스크립트에 연결합니다.



GameOver 텍스트를 만들고 그림과 같이 인스텍터 속성을 바꿉니다





TextContrl 스크립트를 수정합니다



TextContrl 스크립트를 수정합니다


GameOver 텍스트를 TextContrl스크립트에 연결합니다



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


그리과 같이 isPlayerAlive 변수와 KillPlayer() 함수를 추가 합니다 





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



OnTriggerEnter2D() 함수에  GameManager.instance.KillPlayer();

추가하여 저장합니다


게임을 실행합니다 

게임 시작하면 Ready 텍스쳐가 깜박거리고 Player가 격파 되었을때 GameOver텍스쳐가 뜨는지 확인합니다




반응형

+ Recent posts