지금 까지 만든 보스 캐릭터로 최종 보스 스테이지를 만들겠습니다.

스테이지에 입장하면 방 안쪽에 보스가 있다 입장후 나올수 없다

보스 캐릭터 뒤에 열쇠가 있는 보물상자가 있다.

반 안쪽에 문이 있다.

보스를 쓸어트리고 보물상자에서 키를 얻어야 방을 나가고 클리어 됩니다.

보스는 무한히 총을 쏠수 있고 플레이어는 돌아 다니면서 화살을 줏어야 보충할 수 있습니다.

화살의 위치는 랜덤으로 몇군데 배치하겠습니다.

최종 보스용씬은 BossStage라는 이름으로 씬을 만들고 BuildSetting에 등록합니다. 타일맵으로 맵을 만듭니다.

BossStage.unity
0.12MB

예제를 다운로드하고 로딩하면 SoundManager에러가 나는데 일단 씬뷰에서 지워줍니다. 전 Cavas도 에러가 났는데 교재에서 다운받아 UIManager에 깔아줬습니다.

Canvas.prefab
0.04MB

 

게임 스테이지로 설정하기 위해 지금까지 만들었던 프리팹을 배치합니다.

Player : 프리팹을 추가하면 됩니다.

RoomManager : UIManager의 Retry Scene Name에 다시 시도할 때 불러올 씬 이름을 설정합니다.

Canvas : Main Camera를 설정합니다.

EventSystem :  +>UI>Event System

 

아이템을 자동으로 배치하는 기능 만들기

이제 아이템(화살)을 자동으로 배치하는 기능을 추가합니다.

화살의 수를 항상 확인한다

화살의 수가 0이 되면 화살을 랜덤위치에 배치합니다.

 

오브젝트 발생기

계측뷰 +>CreateEmpty를 클릭해 빈게임오브젝트를 만들오 Boss폴더에 이름을 ObjGen으로 하고 칼라아이콘을 추가합니다.

 

ObjectGenPoint 스크립트를 만들기

ObjectGenPoint.cs
0.00MB

ObjGen에 어태치합니다. 씬에 배치할 위치와 프리팹을 지정해 오브젝트를 만드는 스크립트입니다.

변수

public GameObject objPrefab;    //발생 시킬 Prefab 데이터

ObjectCreate() 메소드

현재 위치에 프리팹으로 게임오브젝트를 배치합니다. 외부에서 호출합니다.

public void ObjectCreate()  {
    Vector3 pos = new Vector3(transform.position.x,transform.position.y,-1.0f);
    //Prefab으로 GameObject 만들기
    Instantiate(objPrefab, pos, Quaternion.identity);

Item 폴더의 Arrow 프리팹을 끌어다 Obj Prefab에 지정합니다.

ObjGen을 Boss폴더로 끌어다 프리팹으로 만듭니다.

프리팹을 끌어다 씬뷰에 10개정도 추가합니다. 화살이 생성될 위치입니다.

 

ObjectGenManager스크립트

ObjectGenManager.cs
0.00MB

다음으로 배치된 ObjectGenPoint를 관리하고 화살의 수를 감시해 화살의 갯수가 0이 되면 배치하는 기능입니다.

완성후 하이라키뷰의 RoomManager에 어태치합니다. 프리팹이 아닌 씬에 배치된 오브젝트에 어태치해야 해당 씬에서만 스크립트가 동작합니다.

 

변수

씬에 배치된 objGen들의 참조를 담을 배열입니다.

ObjectGenPoint[] objGens;   //씬에 배치되어있는 ObjectGenPoint 배열

Start()

ObjectGenPoint 타입을 모두 찾아 objGens에 전달합니다.

void Start() {
    objGens = GameObject.FindObjectsOfType<ObjectGenPoint>();
}

Update()  

씬에서 Item을 찾아 화살이 있다면 리턴하고 없고 플레이어도 화살을 가지고 있지 않다면 임의의 위치에 화살을 배치합니다.

void Update()   {
    //ItemData 찾기
    ItemData[] items = GameObject.FindObjectsOfType<ItemData>();
    //반복문으로 화살 찾기
    for (int i = 0; i < items.Length; i++)  {
        ItemData item = items[i];
        if (item.type == ItemType.arrow) {
            return; //화살이 있으면 아무것도 하지 않고 메서드에서 빠져나가기
        }
    }
    //플레이어가 존재하는지와 화살의 수 확인
    GameObject player = GameObject.FindGameObjectWithTag("Player");
    if (ItemKeeper.hasArrows == 0 && player != null) {
        //화살 개수가 0이고 플레이어가 존재하면
        //배열의 개수 범위 안에서 난수 생성
        int index = Random.Range(0, objGens.Length);
        ObjectGenPoint objgen = objGens[index];
        objgen.ObjectCreate();   //아이템 배치
    }
}

 

Main Camera에 어태치된 CameraManager 클래스는 플레이어를 화면 중앙에 오도록 카메라를 조정합니다. 이게보스보드에서는 불편합니다. 따라서 Player폴더의 CameraManager  스크립트를 수정해 플레이어와 보스 중앙을 비추도록 합니다.

 

변수

otherTarget에 외부에서 Boss를 연결합니다.

public GameObject otherTarget;

Update()

두 오브젝트를 사이의 위치를 반환하는 Lerp()메서드의 세번째 인자를 0.5f를 사용해 otherTarget와 Player사이의 중간 지점을 구합니다. 

void Update() {
    GameObject player = GameObject.FindGameObjectWithTag("Player");
    if (player != null)  {
        if(otherTarget != null)  {
            Vector2 pos = Vector2.Lerp(player.transform.position,
                                       otherTarget.transform.position,
                                       0.5f);
            //플레이어 위치와 연동
            transform.position = new Vector3(pos.x, pos.y, -10);
        }  else{
            //플레이어 위치와 연동
            transform.position = new Vector3(player.transform.position.x,
                             player.transform.position.y,
                                             -10);
        }
    }
}

 게임 실행하기

이제 카메라가 보스와 플레이어 중간을 비춥니다.

BossController.cs로 스크립트를 만들어 Boss 폴더에 저장합고 Boss에 어태치합니다.

BossController.cs
0.00MB

변수

hp나 반응거리는 적캐릭터와 같습니다. 발사하는 총알의 속성도 같습니다. inAttack변수는 공격중인지 여부를 판단하는 값입니다.

// 체력
public int hp = 10;
// 반응 거리
public float reactionDistance = 7.0f;

public GameObject bulletPrefab;     //총알
public float shootSpeed = 5.0f;     //총알 속도

//공격중인지 여부
bool inAttack = false;

Update()

"Player" Tag로 Player를 찾아서 공격범위이면 inAttack=true, 공격 애니메이션 Play("BossAttack")등 공격처리를 합니다. 

void Update() {
    if (hp > 0)  {
        //Player 게임 오브젝트 가져오기
        GameObject player = GameObject.FindGameObjectWithTag("Player");
        if (player != null) {
            //플레이어와의 거리 확인
            Vector3 plpos = player.transform.position;
            float dist = Vector2.Distance(transform.position, plpos);
            if (dist <= reactionDistance && inAttack == false)  {
                //범위 안 & 공격 중이 아니면 공격 애니메이션
                inAttack = true;
                // 애니메이션 변경
                GetComponent<Animator>().Play("BossAttack");
            }  else if (dist > reactionDistance && inAttack) {
                inAttack = false;
                // 애니메이션 변경
                GetComponent<Animator>().Play("BossIdle");
            }
        } else {
            inAttack = false;
            // 애니메이션 변경
            GetComponent<Animator>().Play("BossIdle");
        }
    }
}

OnCollisionEnter2D()

화살과 충돌하면 hp--처리하고 사망했다면 Play("BossDead")후 Destry()로 지우는 사망처리를 합니다.

private void OnCollisionEnter2D(Collision2D collision) {
    if (collision.gameObject.tag == "Arrow")  {
        //데미지
        hp--;
        if (hp <= 0)  {
            //사망!
            //충돌 판정 비활성
            GetComponent<CircleCollider2D>().enabled = false;
            // 애니메이션 변경
            GetComponent<Animator>().Play("BossDead");
            //1초 뒤에 제거
            Destroy(gameObject, 1);
        }
    }
}

Attack()

플레이어와의 각도를 구해 총알을 발사합니다.

void Attack()  {
    //발사 위치 오브젝트 가져오기
    Transform tr = transform.Find("gate");
    GameObject gate = tr.gameObject;
    //총알을 발사할 벡터 만들기
    GameObject player = GameObject.FindGameObjectWithTag("Player");
    if (player != null) {
        float dx = player.transform.position.x - gate.transform.position.x;
        float dy = player.transform.position.y - gate.transform.position.y;
        //아크 탄젠트2 함수로 각도(라디안) 구하기
        float rad = Mathf.Atan2(dy, dx);
        //라디안을 각도로 변환
        float angle = rad * Mathf.Rad2Deg;
        //Prefab으로 총알 오브젝트 만들기(진행 방향으로 회전)
        Quaternion r = Quaternion.Euler(0, 0, angle);
        GameObject bullet = Instantiate(bulletPrefab, gate.transform.position, r);
        float x = Mathf.Cos(rad);
        float y = Mathf.Sin(rad);
        Vector3 v = new Vector3(x, y) * shootSpeed;
        //발사
        Rigidbody2D rbody = bullet.GetComponent<Rigidbody2D>();
        rbody.AddForce(v, ForceMode2D.Impulse);
    }
}

 

애니메이션 이벤트 설정하기

씬뷰의 보스 캐릭터를 선택하고 애니메이션 창을 엽니다.  팝업메뉴에서 BossAttack를 선택하고 타임라인을 클릭후 이벤트추가▮+를 클릭하면  이벤트가 추가되고 추가된 이벤트를 선택하면 게임오브젝트에 어태치된 메서드 Attack()을 선택할 수 있습니다.

이제 애니메이션이 해당 프레임에 도달하면 Attack()메소드가 호출되면서 총알이 발사 됩니다.

Boss오브젝트  BossController.cs 컴포넌트의 Bullet Prefab에 프로젝트뷰 Boss폴더의 Bullet Prefab을 끌어다 놓습니다.

 

게임 실행하기

보스캐릭터가 플레이어 캐릭터를 향해 총알을 발사 합니다. 총알속도 반응 거리는 스크립트에서 설정할수 있습니다.

보스캐릭터를 Boss 폴더로 끌어다 프리팹으로 만듭니다.

 

BulletController.cs
0.00MB

보스 캐릭터가 발사하는 총알을 만들어봅니다. 기본적으로 포탄이나 화살과 같습니다.

Images폴더의 Bullet 이미지를 선택하고 Pixel Per Unit:32, Filter Mode: Point(no filter)로 설정합니다.

씬뷰로 끌어서 게임오브젝트를 만듭니다. 

Tag:Enemy, Layer:Bullet으로 설정합니다. 없으면 만들어서 합니다.

Order in Layer를 3으로 설정합니다. Rigidbody2D를 어태치하고 Gravity Scale을 0으로 합니다 CircleCollider2D를 어태치하고 EditCollider를 눌 범위를 조정합니다.

 총알의 접촉 설정

보스 캐릭터와 총알이 접촉하지 않도록 Proejct Settings > Physics2D탭의 LayerCollisionMatrix에서 Enemy와 Bullet이 교차하는 부분의 체크를 해제합니다. Bullet과 Bullet도 체크해제 합니다.

총알을 제어하기 위한 스크립트 만들기

스크립트를 하나 만들고 BulletController로 이름 변경하고 bullet에 어태치합니다.

스크립트는 간단합니다. 발사후 일정시간후 자신을 제거하거나 어딘가 충돌해도 제거합니다.

 

Start()

설정된 시간후 자신을 제거합니다.

void Start() {
    Destroy(gameObject, deleteTime);    //제거 설정
}

OnCollision2D()

접촉이 발생하면  자신을 제거합니다.

private void OnCollisionEnter2D(Collision2D collision)  {
    Destroy(gameObject);   //접촉이 발생하면 즉시 제거
}

 

스테이지 마지막에 싸울 보스 캐릭터와 보스 스테이지를 위한 씬을 만들어봅시다. 보스 캐릭터는 다음과 같이 동작합니다.

플레이어 캐릭터와 접촉하면 플레이어가 대미지를 받는다.

플레이어 캐릭터가 접근하면 총알을 발사해 공격한다.

HP를 설정할 수 있다. 화살을 일정량 맞으면 쓰러진다.

보스 캐릭터와 관련된 데이터는  Boss폴더를 만들고 저장합니다. 사용할 이미지도 Boss 폴더로 옮겨둡니다. 지금 바로 Boss폴더를 만듭니다.

 

보스 캐릭터 게임 오브젝트 만들기

먼저 보스 캐릭터의 이미지 에셋을 준비합니다. Boss가 보스 캐릭터용 이미지입니다.

Boss_0~1:대기, Boss_2~3:공격 ,Boss_4: 사망 애니메이션입니다.

보스 캐릭터의 애니메이션 만들기

대기애니메이션 만들기

Boss_0~1을 씬뷰에 끌어다 놓으면 애니메이션 데이터를 만듭니다. 클립 이름을 BossIdel로 합니다. 배치한 보스 캐릭터 게임 오브젝트의 이름은 Boss, 애니메이션 컨트롤러의 이름은 BossAnime로 이름을 변경합니다.

Sprite Renderer의 Order in Layer를 2로 설정하고, Circle Collider2D를 어태치 합니다. 보스 캐릭터는 움직이지 않으니 Rigidbody2D는 쓰지 않습니다. 보스 캐릭터에 Enemy태그와 Enemy Layer를 만들어 적용합니다. 나중에 보스가 발사하는 총알의 충돌 판정에 사용합니다.

CreateEmpty를 하나만들어 gate로 이름 바꾸고 Boss의 자식으로 하고 가슴쯤에 배치합니다. 씬뷰에서 구별하기 쉽게 칼라아이콘을 설정합니다. 총알이 발사될 위치로 사용합니다.

BossIdle 애니메이션을 열고 하이라키의 Boss를 선택한후 Samples를 2로 설정합니다. ▶클릭해서 확인해봅니다.

공격 애니메이션

Animation창에서 BossIdel을 클릭하고 Create New Clip을 선택하고 창이 뜨면 이름은 BossAttack으로 합니다.

Boss_2~3을 끌어 씬 뷰에 끌어다 애니메이션 클립을 만듭니다. 

Sample을 4로 합니다. 약간 포즈를 취한뒤 공격을 하기 위해 0프레임의 키프레임을 카피해 그 다음 프레임어 넣어줍니다.

 

사망 애니메이션

Boss_4 이미지를 이용해 사망애니메이션을 만들고 이름을 BossDead로 합니다. Sprite Renderer.Color Property를 추가하가 마지막 프레임은 알파값이 0이 되도록 저장합니다.

 

애니메이션 컨트롤러

BossAnime을 열고 3개의 애니메이션이 다 있는지 확인합니다. 없으면 끌어다 놓습니다.

 

 

+ Recent posts