반응형

그림과 같은 모양을  3D 맥스에서 쉽게 구현을 할 수 있습니다 

건축에서 많이 사용하는데 일일이 오브젝트를 만들기보다 3D 맥스에서 쉽게 하는 방법이 있습니다 

 

먼저 대략 Height Segs 가 5정도 되는 박스 오브젝트를 만들고 오른쪽 마우스를 클릭하여  Convert To -> Convert to Editable Mesh를 클릭합니다

Editable Mesh 를 선택하는 경우는 에지가 사선으로 향하게 하기 위해서입니다 

 

 

 

Editable Mesh 에서 Edge를 클릭한 다음 박스 오브젝트의 전체 Edge를 클릭합니다 그리고 visble를 클릭하여 전체 Edge 가 보이도록 합니다 

아래 그림과 같이 엣지가 사선으로 보이게 하고 에지를 선택하여야 합니다 

 

그리고 Modifer List - > Lattice를 선택합니다 

 

 

 

Lattice의 Parameters 값을 조절하여 원하는 트러스 모양을 갖추도록 조정합니다.

Parameters 값으로 트러스의 굵기와 형태를 조절 할수 있습니다 .

원하는 형태의 Parameters 값을 넣습니다.

이외 여러가지 형태의 트러스 모양을 구현할 수 있습니다 모양이 사각 모양뿐만 아니라 밴드 형태와 놀이공원에서 많이 본 건축 형태의 트러스를 만들 수 있습니다 

유용하게  쓸수 있을 거 같습니다 ㅎㅎ.

반응형
반응형

보통 포토샵 작업할 때 여러 개의 이미지 파일을 한꺼번에 불러들여 포토샵 CC의 레이어에 올려놓고 작업을 하고 싶을 때가 있습니다.

그래서 포토샵 CC 의 유용하게 사용할 팁을 소개하고자 합니다  

 

여러 개의 이미지 파일을 한꺼번에 포토샵으로 불러들이는 방법 

File -> Scipts -> Load Files into Stack 은 누릅니다 

 

그러면 Load Layer 창이 나오고 이미지 파일을 불러 들일 준비를 합니다

 Load Layers 창에  Browse... 을 클릭합니다 

 

 

 

 

 

 

 

 

 

그러면 불러들일 이미지 파일이 보일 것 입니다.

이미지 파일을 전체 선택하여 OK 버튼을 클릭합니다

 

Load Layers 창에 불러올 그림파일의 이름이 Load 된 것을 확인합니다

확인이 끝나면 OK 버튼을 누릅니다

 

 

 

 

 

 

오른쪽 하단의  Layer에 그림 파일이 불러온 걸 확인합니다  

포토샵 CC에서 유용하게 작업할 수 있도록 하는 꿀팁입니다

 포토샵을 사용하면서 가끔 여러 개의 이미지 파일을 레이어에 불러들여야 되는데 고민을 많이 하게 됩니다.

이런 꿀팁을 알아 두면 포토샵 CC 작업할 때 작업 속도를 높이고 작업효율이 한층 수월해 질 수 있습니다 

 

반응형
반응형

포토샵 CC의 오른쪽에 있는 툴바의  올가미 툴 (Lasso Tool)을 선택하여 딸 그림을 대충 선택합니다 

최대한 공간 영역을 좁게 하여 딸 이미지를 올가미 툴로 드래그합니다 

 

툴 바 아래에 있는 마스크 툴을 두번 클릭하면 마스크 될 영역의 색깔과 농도를  선택하여 ok 버튼을 클릭합니다 

 

 

 

브러시 툴을 선택하여 그림을 제외한 마스크 영역 나머지 부분을 색깔 칠 합니다.

세밀하게 이미지 부분만 남겨 놓고 배경 색을 칠합니다 

 

마스크툴을 해제하면 선택영역이 그림만 따로 정밀하게 선택됩니다 

 

 

 

Ctrl + N 을 눌러서 새창을 열과 이동 툴을 이용하여 그림을 이동시켜 봅니다 

내가 선택한 그림만 정확하게 이동되어진 모습을 확인할 수 있습니다 

포토샵에서 마법봉 툴 같은 걸로 하면 어느정도 이미지를 딸 수 있어도 정밀하게는 따기가 어려운 게 사실인데 마스크를 활용하면 더욱더 정밀하게 딸 수 있는 것 같습니다 

모션 캡쳐나 이팩트 및 그래픽스 작업할 때도 유용하게 써먹으면 참 좋을 거 같습니다 

반응형
반응형

m16 소총 만들기

m16 소총은 1957년에 개발되어 1967면 에 미국 육군 제식 총으로 채택 

제원   구경 5.56mm 무게 2.9kg 길이 99cm 최대 사거리 2653m 유효사거리 460m 최대 발사속도 분당 700~800발

베트남 전쟁에서 사용된 대표적인 소총 

가늠자는 좌우 조정, 가늠쇠 는 상하 조정을 할 수 있지만 조정이 불편하다 

방아쇠 울이 열림으로 말미암아 겨울철 방한 장갑을 낀 채 사격을 할 수 있게 되어 있다 

 

 

모델링 

 

 

 

UV 맵 피기 

 

포토샵으로 맵핑 그림그리기 

 

 

쉐이더 에서 맵핑 그림 연결 하기 

 

맵핑한 m16 오브젝트 

 

 

 

 

 

맥스 프로젝트 

m16.zip
0.65MB

 

반응형
반응형

맥스 단축키를 알아두면 맥스 모델링하기 쉬워집니다 

 

먼저 오브젝트의 Convert to Editable Poly를 적용한 단축키입니다

 

1. vertex 

 

2. Edge

 

 

 

3. Border

 

4. Polygon

 

 

 

5. Element

 

6. Edit Geometry

 

 

 

7. 상단 왼쪽 메뉴바

 

8. 상단 오른쪽 메뉴바

 

 

 

9. 하단 메뉴바 

 

10. 화면 옵션 및 사용자 인터 페이스 

 

 

 

 

 

 

F4 : 모델링할 때 도형의 선 보이게 하기

V : viewports

X : 찾기 

 

Customize -> Customize User Insterface..  에 들어가면 단축키를 설정할 수 있습니다 

반응형
반응형

1947년 소련에서 개발된 화기 

제2차 세계대전 후 소련에서 개발된 소총으로 소련을 위시한 대부분 공산국가에서 사용된 표준 화기이다

가목식 개머리판으로 구성되어 있어 휴대가 요잉 하며, 탄약은 소련제 30발들이 굽은 탄창을 사용한다

베트남전 당시 베트콩들도 사용한 화기이다

 

작동방식 : 가스 작용식, 자동 및 반자동식 

중량 : 4.3kg   길이 : 880mm  총열 길이 : 415mm  탄약 : 7.62 X  39mm 

연사 속도 : 분당 600발

최대 사거리 : 1500m  유효사거리 300m

 

장점 

롱스 트로트 가스 피스톤 시스템을 사용하여 잦은 청소나 관리가 필요치 않아 신뢰성이 높다

부품 수가 적고 내구성이 높아 고장이 없다

쉽고 간단한 구조 덕분에 설계도면만 있으면 철공소에서도 쉽게 만들 수 있다

 

단점 

무겁고 반동이 심함

대구경 저 속탄이라 공기의 영향을 많이 받아 명중률이 낮은 편 

 

 

 

 

 

AUTODESK 3DMAX  모델링  

 

ak-47 소총을 만들기  박스 오브젝트 하나로 모양을 떠서 대충 모양을 만듭니다 

 

 

 

모델링을 완성하여 맵핑 작업을 합니다 

 

맵핑 완성 

 

 

포토샵에서 맵 그림 

 

UV 맵

 

맵 그림

 

 

맥스에서 맵 소스를 오브젝트에 씌운 모습 ㅎㅎ

 

대충 만들었는데 그럭저럭 잘 나왔네요 ㅎㅎ

 

 

 

 

 

 

 

 

ak-47.zip
0.37MB

반응형
반응형

우리가 포토샵을 이용하여 캐릭터나 사물을 그릴 때 대칭으로 그림을 그릴 때가 있습니다

그래서 대칭으로 한쪽만 그려도 그림전체를 완성시킬 수 있는 포토샵의 기능을 소개합니다

유용하게 써먹을 수 있으니 알아두면 편리합니다

먼저 포토샵 도구 툴에서 브러시를 선택합니다 그리고 윗방에 있는 대칭 모양의 아이콘을 누릅니다 

 

그러면 어떤 대칭을 사용할 것인지 메뉴가 나옵니다 

제일 많이 사용하는 Vertical을 선택합니다 

 

좌우로 대칭을 나눌 수 있는 기준선 모양의 선이 생깁니다

 

기준선을 기준으로 그림을 그려 넣습니다 

대칭 모양의 그림을 완성합니다 

반응형
반응형

Channels의 알파레이어를 만듬니다

 

텍스트 도구 툴을 선택하여 글자를 완성합니다 글자를 완성하고 윗바의 체크박스를 누릅니다

 

 

 

 

Edit -> Transform -> Distort 를 눌러서 텍스트를 글자 모양을 편집합니다

 

 

 

글자 편집이 완성되면 Select -> inverse (단축키 Shift + Ctrl +i) 를 눌러서 선택영역을 반전 시킴니다 

 

 

 

Filter -> Blur -> Gaussian Blur... 를 눌러서 글자의 빛을 주는 이팩트를 설정합니다

 

 

 

Filter -> Noise -> Add Noise 를 눌러서 글자의 이팩트를 줍니다

 

 

 

Ctrl + D 를 눌러서 글자의 선택영역을 삭제하고 알파채널을 끄고 나머지 채널을 켜줍니다 

 

Filter -> Render -> Lightig Effects 를 누릅니다

 

 

 

빛의 영역을 글자에 적당히 조정하고 Rroperties 를 조정하여 Ok 를 누릅니다

 

 

 

Image -> Adustments -> Color Balance.. 를 누르고 컬러를 조정합니다 

 

완성 

반응형
반응형

그림에 따라 패스를 만들고 패스에 따라 문자를 만들 수 있습니다

먼저 펜툴을 선택합니다

 

그림에 따라 펜툴로 패스를 연결합니다

 

 

 

패스 연결이 마치면 텍스트 문자 툴을 누릅니다

 

패스 첫부분을 선택하고 문자를 입력합니다 

 

레이어 창에서 옆에 있는 Paths창을 열고 빈 공간을 클릭하면 패스 표시를 없앨 수 있습니다 

반응형
반응형

포토샵 CC에서 텍스트 글자의 굵기와 테두리 색깔을 넣어서 보기 좋은 텍스트를 완성해 보겠습니다.

보통 텍스트를 사용할 때  텍스트를  써 놓고 포토샵의 이팩트 기능을 활용하는 경우가 많습니다 

하지만 오늘은 다른 경우의 텍스트 활용 기능을 소개합니다

텍스트 문자를 완성할 때 유용하게 써먹을 수 있습니다  

그래픽스 작업하는 사람들에게 유용한 방법을 소개합니다

 

먼저 도구 툴 중에서  텍스트를 만드는 아이콘을 선택하고 Horizontal Type Mask Tool을 선택합니다

그리고 윗부분에서 글자 폰트와 글자 크기 및 색깔을 선택합니다 

 

 

그러면 배경이 약간 붉으스므르 하게 나타나게 됩니다 글자를 영역 안에 넣습니다 

글자를 완성하면 윗 메뉴에서 체크 모양의 아이콘을 클릭합니다 

 

 

 

 

그러면 점선 모양의  텍스트가 나옵니다 

아래 그림과 같이 적절하게 말풍선에 텍스트 글자가 들어가 있네요 ㅎㅎ

 

글씨 안의 색깔을 채워 넣기 위해서 Edit -> Fill (단축키 Shift + F5)를 눌러서 메뉴바가 나오면 Foreground color를 선택하고 ok 합니다

자신이 원하는 색깔을 선택하여 Foreground color를 지정하여 채워 넣기를 합니다.

그러면 깔끔하게 점선 안으로 색깔을 넣게 됩니다 

 

 

 

그러면 텍스트 색깔이 채워지는데 글씨를 두껍게 하거나 다른 색깔로 테두리를 만들려면 Edit -> Stroke를 들어갑니다 

이러면 글자 굵기를 크게 할 수 있고 글자의 테두리를 다른 색으로 넣을 수도 있습니다.

 

stroke 메뉴바가  나오면 글씨 굵기 및 Color를 선택하고 ok 합니다 

적절한 글자의 굵기와 색깔을 지정합니다.

 

 

 

글씨 테두리가 이쁘게 나오네요 ㅎㅎ 

이번 Tutorial 은 예시로 보여 준 것이고 사용자의 따라서 다양하게 텍스트를 이쁘게 완성시킬 수 있습니다 

다양한 방법으로 포토샵 cc의 텍스트 굵기와 테두리의 색깔을 넣는 방법을 소개해 보았습니다

저는 처음 포토샵을 사용할 때 텍스트의 굵기를 어떻게 조절할지 몰라서 궁금하였는데 포토샵 cc의 이런 기능이 있었습니다 

좋은 팁으로 포토샵 cc를 활용하면 좋겠네요 ㅎㅎ

반응형
반응형

 

내가 좋아하는 그림을 열고 Edit -> Define Brush Preset 를 엽니다

 

그러면 등록할수 있게 이름을 설정하고 저장할수 있습니다

 

 

 

 

저장하고 브러시를 사용 합니다

 

Brush Settings 를 여시면 브러시가 등록 되어 있는 것을 확인 할수 있습니다

반응형
반응형

포토샵 도구 툴바 중에 사각 툴을 이용하여 이미지 부분을 드래그합니다 

 

그리고 상단의 Select -> Inverse 를 눌러서 부드럽게 할 바깥쪽 이미지를 선택합니다 단축키는 Shift +Ctrl +i입니다

 

 

 

그리고 CTRL+ALT + R 키를 눌러서 아래 사진과 같이 Feather를 조정하여 부드럽게 할 수치를 올립니다

 

안쪽 사각 영역이 매끄러운 사각 영역으로 변합니다

 

 

 

DEL 키를 눌러서 바깥 부분을 삭제시킵니다 

 

Ctrl + D 키를 눌러서 사각 영역을 해제합니다 

반응형
반응형

 

 

바이 패드를 뼈대로 심고 스킨 한 모습입니다

 

 

 

 

그냥 랜더링하여 이미지를 뽑으면 바이 패드가 보이게 됩니다

 

그래서 모니터 모양의 아이콘을 클릭하고 Bone Objects를 클릭합니다

 

 

다시 랜더링 하면 바이 패드가 안 보이는 이미지를 뽑을 수 있습니다 

 

반응형
반응형

 

 

 

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

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

 

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

 

 

 

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

 

 

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

 

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

 

 

 

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

 

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

 

 

 

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

 

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

 

 

 

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

 

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

 

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

 

 

 

   public Text gameOver;
   Animator animGameOver;

.

.

.

.

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

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

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

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

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

    public Text gameOver;

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

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

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

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

    }
    void Update()
    {
        
    }
}

 

 

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

 

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

 

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

        UIManager.instance.ShowGameOver();
    }

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

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

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

    GameObject curEnemy;

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

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

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

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

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

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

    PlayerAni myAni;

    PlayerParams myParams;

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

        myParams = GetComponent<PlayerParams>();

        myParams.InitParams();

        myParams.deadEvent.AddListener(ChangeToPlayerDead);

        ChangeState(State.Idle, PlayerAni.ANI_IDLE);

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

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

        UIManager.instance.ShowGameOver();
    }

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

        curEnemy = null;
    }

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

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

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

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

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

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

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

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

            GameManager.instance.ChangeCurrentTarget(curEnemy);

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

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

                IdleState();

                break;
            case State.Move:

                MoveState();

                break;
            case State.Attack:

                AttackState();

                break;
            case State.AttackWait:

                AttackWaitState();

                break;
            case State.Dead:

                DeadState();

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

    }

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

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

        }

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

    }

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

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

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

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

 

 

 

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

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

                attackTimer = 0f;

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

            attackTimer += Time.deltaTime;
        }
    }

 

 

 

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

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

    public State currentState = State.Idle;

    EnemyParams myParams;

    EnemyAni myAni;

    Transform player;

    PlayerParams playerParams;

    CharacterController controller;

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

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

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

    public ParticleSystem hitEffect;

    public GameObject selectMark;

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

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


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

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

        ChangeState(State.Idle, EnemyAni.IDLE);

        controller = GetComponent<CharacterController>();

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

        hitEffect.Stop();
        HideSelection();
    }

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

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

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

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

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

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

        player.gameObject.SendMessage("CurrentEnemyDead");

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

        StartCoroutine(RemoveMeFromWorld());
    }

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

        ChangeState(State.Idle, EnemyAni.IDLE);

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

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

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

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

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

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

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

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

                attackTimer = 0f;

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

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

    }

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

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

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

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

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

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

 

 

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

 

 

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

반응형
반응형

XML.zip
0.47MB

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

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

 

 

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

 

 

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

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

 

 

 

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

 

 

 

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

 

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

 

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

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

 

 

 

 

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

 

 

 

XMLManager 스크립트 작성

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

    //xml 파일
    public TextAsset enemyFileXml;

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

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

    private void Start()
    {
        MakeMonsterXML();
    }

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

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

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

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

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

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

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

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

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

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

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

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

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


    void Update()
    {
        
    }
}

 

 

 

 

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

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

 

XMLManager.instance.LoadMonsterParamsFromXML(name, this);

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemyParams : CharacterParams
{
    public string name;
    public int exp { get; set; }
    public int rewardMoney { get; set; }

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

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

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

        isDead = false;

        InitHpBarSize();
    }

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

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

 

 

 

그리고 Enemy 오브젝트를 선택하여 활성화시키고  아래와 같이 속성을 바꾸고 프리 팹을 저장합니다

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

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

 

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

 

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

 

 

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

 

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

 

 

 

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

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

 

 

 

 

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

 

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

 

 

 

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

 

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

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

 

 

 

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

0

반응형
반응형

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

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

 

 

 

ObjectManager 스크립트 작성

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

public class ObjectManager : MonoBehaviour
{
    public static ObjectManager instance;

    public GameObject coinPrefab;
    public int initialCoins = 30;

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

        MakeCoins();
    }

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

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

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

        }
    }

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



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

 

 

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

 

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

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

            SoundManager.instance.PlayPickUpSound();

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

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

 

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

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

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

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

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

            SoundManager.instance.PlayPickUpSound();

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

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

 

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

 

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

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

        player.gameObject.SendMessage("CurrentEnemyDead");

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

        StartCoroutine(RemoveMeFromWorld());
    }

 

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

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

    public State currentState = State.Idle;

    EnemyParams myParams;

    EnemyAni myAni;

    Transform player;

    PlayerParams playerParams;

    CharacterController controller;

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

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

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

    public ParticleSystem hitEffect;

    public GameObject selectMark;

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

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


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

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

        ChangeState(State.Idle, EnemyAni.IDLE);

        controller = GetComponent<CharacterController>();

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

        hitEffect.Stop();
        HideSelection();
    }

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

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

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

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

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

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

        player.gameObject.SendMessage("CurrentEnemyDead");

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

        StartCoroutine(RemoveMeFromWorld());
    }

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

        ChangeState(State.Idle, EnemyAni.IDLE);

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

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

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

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

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

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

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

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

                attackTimer = 0f;

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

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

    }

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

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

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

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

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

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

 

 

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

 

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

0

반응형
반응형

0

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

동전.zip
2.24MB

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

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

 

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

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

 

 

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

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

 

 

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

 

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

 

 

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

 

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

 

 

Coin 스크립트작성

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

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

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

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

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

            SoundManager.instance.PlayPickUpSound();

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

 

 

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

 

 

추가

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

 

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

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

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

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

        isDead = false;

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

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

        UIManager.instance.UpdatePlayerUI(this);
    }

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

 

 

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

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

 

0

반응형
반응형

금화 같은 십원 짜리 동전은 만들어 보았습니다

 먼저 원통오브젝트를 생성한다음 동전 모양으로 만들고 맵을 펴서 십원 짜릴 동전 사진을 맵에 덮었습니다 

 

 

 

 

 

 

 

금화 같은 빛깔을 보이게 하기 위해서 포토샵에서 
Image -> Adjustment -> Brightness/contranst 를 클릭하여 색상을조절합니다

 

십원짜리 맵핑 작업 사진

 

 

맥스에서 쉐이더 연결 

잘 나옵니다 ㅎㅎ

 

유니티 엔진에서 임포트한 모습 ㅎㅎ

 

 

 

 

동전[Coin]리소스.zip
2.28MB

반응형
반응형

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

 

 

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

 

 

 

반응형
반응형

 

 

 

 

 

 

3DS 맥스로 캐릭터를 만들고 맵핑을 하겠습니다

먼저 캐릭터 머리 오브젝트를 선택 합니다 

 

캐릭터 오브젝트 선택하고 Unwrap UVW 선택

 

 

 

Unwrap UVW 의 Polygon을 선택하여 Map Seams  끄면 오브젝트의 녹색선이 없어집니다 

 

 

Peel 메뉴에서 Point to Point Seams 선택합니다

 

 

캐릭터를 평면으로 보이게 하기 위해서 가위질을 한다고 생각하고 머리를 가위질을 할 부분을 선택하여 선을 긋습니다

 

 

 

 

가위질이 끝나면 Peel 의 Pelt Map을 선택하여 맵을 펴줍니다

 

 

StarPelt를 누릅니다

 

 

 

 

StartPelt 를 누른 후 Sethings를 누르고 Relax By Polygon Angles를 누른 후 StarRelax 맵이 펴질때 까지 기다립니다

 

캐릭터 얼굴 형태에 맞게 버텍스를 움직여서 정리합니다

 

정리한 맵을 UV 안에 넣고 CheckerPatt (Checker)를 선택하고 맵이 잘 펴졌는지 확인합니다 체커 모양이 고르게 잘 나오면 대부분 맵이 잘 펴진 형태입니다

 

 

 

맵을 핀 다음 UV 안에 정리하고 맵을 저장 합니다 

Render UVW Template를 누릅니다

 

Render UVW Template 설정 바가 나오면 width와 Height  설정을 한 후 Render Output으로 저장할 경로 설정하고 Render UV  Template를 누릅니다

저장한 맵을 포토샵이나 그림 툴을 이용하여 맵을 그립니다

 

 

 

 

얼글 맵

 

 

맵핑을 하고 Material Editor를 들어가서 Standerd 매터리얼을 선택합니다

 

 

 

 

Standard 매터리얼의 Bitmap을 선택하여 맵핑한 그림을 선택합니다

 

Bitmap을 Diffuse Color에 연결하고 Assign Material Selection을 누르고 Show Shaded Material Viewport를 클릭합니다

 

 

 

 

 캐릭터 오브젝트 얼굴에 맵핑을 한 모습 

 

 

몸통 맵

 

 

 

몸통도 얼굴과 같이 맵을 핀 다음 포토샵이나 그림 툴로 맵 그림을 그리고 메터리얼을 만들어 비트맵으로 연결한 후 Assign Material 합니다 

정면

 

우 측면 

 

 

 

 

좌 측면

 

 

 

 

반응형
반응형

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

반응형
반응형

마인 크래프트형 3D 캐릭터를 만들고 있습니다

 

 

정면 각도의 캐릭터

이등신 가분수 캐릭터 형 눈과 귀는 매핑으로 때우면 됩니다

 

옆모습

반응형
반응형

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

 

먼저 EnemyParams 스크립트를 엽니다

 

 

EnemyParams 스크립트 수정

 

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


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


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

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

        InitHpBarSize();
    }

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

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

 

 

 

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

 

 

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

0

 

반응형
반응형

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

 

0

 

 

 

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

 

 

 

 

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

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

 

 

 

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

 

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

 

 

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

 

 

 

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

 

 

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

 

 

 

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

 

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

 

 

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

 

 

 

Billboard 스크립트 작성

 

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

public class Billboard : MonoBehaviour
{

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


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

 

 

 

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

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

 

0

반응형
반응형

0

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

 

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

 

 

AniEventControl 스크립트 작성

 

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

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

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

 

 

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

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

 

 

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

 

 

 

 

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

 

 

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

 

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

 

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

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

 

 

 

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

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

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

    GameObject curEnemy;

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

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

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

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

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

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

    PlayerAni myAni;

    PlayerParams myParams;

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

        myParams = GetComponent<PlayerParams>();

        myParams.InitParams();

        myParams.deadEvent.AddListener(ChangeToPlayerDead);

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

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

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

        curEnemy = null;
    }

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

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

 

 


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

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

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

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

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

                IdleState();

                break;
            case State.Move:

                MoveState();

                break;
            case State.Attack:

                AttackState();

                break;
            case State.AttackWait:

                AttackWaitState();

                break;
            case State.Dead:

                DeadState();

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

    }

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

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

        }

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

    }

 

 


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

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

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

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

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

 

 

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

 

0

반응형
반응형

0

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

 

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

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

 

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

//CharackterParams 스크립트수정

 

 

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

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

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

    void Start()
    {
        InitParams();
    }

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

    }

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

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

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

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

 

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

 

 

 

 

 

 

 

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

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

    public State currentState = State.Idle;

    EnemyParams myParams;

    EnemyAni myAni;

    Transform player;

    PlayerParams playerParams;

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

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

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

    public ParticleSystem hitEffect;

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

        ChangeState(State.Idle, EnemyAni.IDLE);

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

        hitEffect.Stop();
    }

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

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

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

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

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

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

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

                attackTimer = 0f;
            }

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

    }

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

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

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

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

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

 

 

 

 

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

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

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

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

    GameObject curEnemy;

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

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

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

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

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

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

    PlayerAni myAni;

    PlayerParams myParams;

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

        myParams = GetComponent<PlayerParams>();

        myParams.InitParams();

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

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

        curEnemy = null;
    }

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

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

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

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

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

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

 

 

 


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

                IdleState();

                break;
            case State.Move:

                MoveState();

                break;
            case State.Attack:

                AttackState();

                break;
            case State.AttackWait:

                AttackWaitState();

                break;
            case State.Dead:

                DeadState();

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

    }

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

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

        }

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

    }

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

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

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

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

 

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

0

반응형
반응형

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

 

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

 

CharacterParams 스크립트 작성

 

 

 

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

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

    void Start()
    {
        InitParams();
    }

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

    }

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

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

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

}

 

 

 

 

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

 

 

 

 

EnemyParams 스크립트 작성

 

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

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

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

        isDead = false;


    }

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

 

 

 

 

 

 

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

 

 

PlayerParams 스크립트작성

 

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

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

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

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

        isDead = false;
    }

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

 

 

 

 

 

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

 

 

 

 

 

 

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

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

 

 

 

 

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

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

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

    GameObject curEnemy;

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

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

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

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

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

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

    PlayerAni myAni;

    PlayerParams myParams;

    EnemyParams curEnemyParams;


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

        myParams = GetComponent<PlayerParams>();

        myParams.InitParams();

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

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

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

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

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

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

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

 

 


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

                IdleState();

                break;
            case State.Move:

                MoveState();

                break;
            case State.Attack:

                AttackState();

                break;
            case State.AttackWait:

                AttackWaitState();

                break;
            case State.Dead:

                DeadState();

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

    }

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

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

        }

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

    }

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

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

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

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

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

 

 

 

 

 

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

https://youtu.be/eKgHYr8TEsw

 

반응형

+ Recent posts