Prosto

따라하는 유니티 2D 프로젝트ⓑ -6 본문

Programing/Unity 3D

따라하는 유니티 2D 프로젝트ⓑ -6

Prosto 2016. 10. 16. 23:36

 

 

따라하는 유니티 2D 프로젝트ⓑ 강좌 여섯 번째 시간입니다.

 

 

이번에 함께 할 작업은 볼이 떨어지기 전에 키퍼와 충돌하는 경우

떨어지던 볼을 다시 위쪽으로 보내는 처리를 할 것이고 (볼이 죽지 않도록)

다음으로 간단한 체력바를 만들어서 블록의 체력을 표시할 것입니다.

(RPG 몬스터들 체력바와 비슷한 체력 게이지를 나타내는 바 입니다.)

 

 

그럼 시작하겠습니다.

 

 

가장 먼저 저번에 작업했던 프로젝트를 실행합니다.

그리고 순서대로 직접 해보며 따라오시면 됩니다.

 

먼저 지금은 볼키퍼를 만들어 볼이 죽지 않도록 도와주는 객체를 만들 것입니다.

(기존의 객체(생성 전에 보이는 투명한 구)를 이용합니다.)

 

먼저 새로운 스크립트 작성을 하기 위하여

Scripts - Ball 폴더에 Create - C# Script로 스크립트 생성을 해주세요.

 

 

이 스크립트의 이름은 BallKeeper로 했습니다.

생성한 스크립트를 열어주세요.

 

 

BallKeeper.cs 스크립트에 있던 Start, Update 함수는 지워주고

새롭게 void OnTriggerEnter2D(Collider2D) 함수를 작성해줍니다.

(항상 ctrl + s로 저장해주세요.)

 

먼저 기본적으로 충돌 확인을 위해 볼(태그)과 충돌했을 시 키퍼와 충돌했다고 띄워보도록 합니다.

 

 

만든 스크립트를 적용시켜줘야겠죠?

 

저번에 만들어놓은 EmptyNormalBall이라는 프리팹이 있습니다.

여기에 스크립트를 추가해서 기존 객체를 사용할 것입니다.

 

Prefabs 폴더의 EmptyNormalBall 프리팹을 선택해주세요.

 

 

그리고 Scripts - Ball에 있는 BallKeeper 스크립트를

EmptyNormalBall Inspector 아래 부분에 드래그 앤 드랍하여 추가합니다.

 

 

충돌 확인을 위해서 콜리더도 있어야겠죠?

Add Component를 누른 후

collider를 입력해주고

나오는 항목 중 Circle Collider 2D를 선택합니다.

 

 

Circle Collider 2D의 설정으로

트리거 함수를 사용할 것이니

Is Trigger를 체크해주시면 됩니다.

 

 

그리고 실행해보면

이렇게 클릭만 하고있는 경우 생성되는 객체와

볼이 충돌하는 경우

'키퍼와 충돌'이라는 문구가 나오는 것을 확인할 수 있습니다.

 

 

이번에는 수정해서 실제로 볼이 움직일 수 있도록 해볼까요?

기존에 만들어뒀던 함수였던

MoveBall의 TurnTheBall(2)함수를 호출합니다.

2의 경우는 y축 반전이였죠?

 

 

실행 결과 볼과 충돌이 일어나는 경우

y축이 반전되는 것을 확인할 수 있습니다.

 

하지만, 볼이 위로 올라가고 있는 중에 충돌이 일어나면

반대로 내려오게 됩니다.

 

이 부분을 수정해줄까요?

 

MoveBall.cs를 열어

TurnTheBall 함수에

type이 -1인 경우에 대해 추가해줍니다.

 

 

그리고 이렇게 BallKeeper.cs로 돌아와

TurnTheBall의 인자로 -1을 넘겨줍니다.

(그러면 MoveBall에서 설정해준 것처럼

y축 진행 방향이 음수(아래로)인 경우만 반전시켜주겠죠?)

 

 

실제로 실행해보면 이렇게 제대로 작동하는 것을 알 수 있습니다.

위로 올라가는 볼에는 아무리 충돌해도

아무런 반응을 보이지 않고요.

 

 

그럼 이제 충돌 체크하는 객체가 모든 볼을 생성한 후에도 나올 수 있게 바꿔줄까요?

먼저 볼의 수를 현실적인 3개로 바꿔줍니다.

 

 

그리고 바꿔줘야하는 부분은 Update 부분입니다.

(Update에서 볼 생성 준비 - 실제 생성이 이루어졌죠?)

 

touchOn이라는 불 변수가 어떻게 사용되었는지 확인해보시면 좋겠습니다.

(소스가 바뀐 부분이 많아 헷갈릴 것 같아 소스 첨부합니다.)

    //(BallManager.cs의 Update 부분 + 사용되는 변수 touchOn)

    bool touchOn = false;
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) //버튼을 누름.
        {
            startTouchedPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            startTouchedPos.z = 0;
            //클릭된 위치의 월드포지션을 얻어옵니다.(메인카메라 기준)
            emptyNormalBall.SetActive(true);
            emptyNormalBall.transform.position = startTouchedPos;
            touchOn = true;
        }else if (Input.GetMouseButtonUp(0)) //버튼에서 땜.
        {
            if (nowBallCount < normalBallNum)
            {
                //클릭이 끝난 위치의 월드포지션을 얻어옵니다.(메인카메라 기준)
                endTouchedPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                endTouchedPos.z = 0;

                ActiveBall();
            }else
            {
                emptyNormalBall.SetActive(false);
            }
            touchOn = false;
        }

        if (nowBallCount >= normalBallNum)
        { //볼이 모두 만들어진 상태에서는.(개수 이상)
            if (touchOn) //터치(클릭) 중이라면.
            {
                //드래그 중(마우스 이동 중).
                endTouchedPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                endTouchedPos.z = 0;
                emptyNormalBall.transform.position = endTouchedPos;
                //위치 계속 바꿔줘서 emptyNormalBall이 터치된 위치 따라가게 함.
            }
        }   
    }

 

 

 

실제로 실행해보면

볼을 3개 모두 만든 후에는

자유롭게 이동이 가능한 것을 확인할 수 있습니다.

(코루틴으로 2초 후 이동량 검사 후 이렇게 움직이도록 할 수도 있겠죠?)

 

그러면 일단 볼키퍼는 어느정도 틀이 잡혔습니다.

 

 

다음으로 블록의 체력을 보여주는 게이지를 추가해봅시다.

먼저 NormalBlock 프리팹을 Hierarchy 탭에 드래그 앤 드랍하여

수정할 수 있는 게임 오브젝트로 바꿔줍니다.

 

(Hierarchy 탭에 추가된 게 보이시죠?)

 

 

다음으로 우리에게 필요한 게이지에 사용할 이미지를 추가해줍니다.

(아래의 이미지를 받아서 사용하세요.)

 

사용된 이미지

(우측 클릭 후 다른 이름으로 저장하여 사용하시면 됩니다.)

(512 x 64 크기의 이미지 입니다.)

 

추가한 이미지를 설정해줄 차례입니다.

클릭하면 나오는 Inspector 창에서

Texture Type을 Sprite (2D and UI)로 변경해주시고

Pivot을 Left로 변경,

Generate Mip Maps 체크 해제한 후

Apply를 눌러주세요.

 

 

그리고

1. BlockHP_prosto를 Hierarchy 탭으로 드래그 앤 드랍하여

게임 오브젝트를 만들어주세요.

2. 생성된 BlockHP_prosto 게임 오브젝트를

NormalBlock으로 드래그 앤 드랍하여 하위 객체로 만들어줍니다.

 

이렇게 NormalBlock 밑에 BlockHP_prosto가 있다면

잘 추가된 겁니다.

 

 

그리고 하위에 있는 BlockHP_prosto를 클릭하여 position을 0, 0, 0으로 바꿔보세요.

 

 

0, 0, 0인데도 중앙에서 벗어난 게 보이시나요?

(아 참고로 여기서 0,0,0 position은 월드포지션이 아닌 로컬포지션입니다.

(하위 객체는 상위 부모 위치 기준으로 지역 위치 값을 갖습니다.) )

 

벗어난 이유는 아까 우리가 피봇을 Left로 설정했기 때문입니다.

중심점이 왼쪽으로 설정된 거죠.

실제로는 부모와 같은 위치지만 왼쪽이 시작점으로 객체가 놓인겁니다.

(그렇기 떄문에 객체 scale의 x값이 바뀌면 왼쪽은 그대로 있고 오른쪽이 늘어납니다.)

 

 

중심을 맞춰주고 바닥에 놓기 위해

position x는 -2.56, y는 -0.96을 입력해주세요.

 

이렇게 아래에 딱 맞게 들어갔습니다.

 

 

그러면 불투명한 흰색으로 쓰기는 조금 색상을 바꿔줍니다.

(그냥 좋으신 색상 쓰시면 됩니다.)

(투명도(A)는 조금 넣어주시고요.)

 

 

그리고 스케일 X 값을 0.5로 바꿔보면,

 

이렇게 왼쪽으로 줄어든 것을 확인할 수 있습니다.

(이렇게 체력 게이지로 만들면 쉽게 사용할 수 있겠죠?)

 

 

확실하게 보기 위해서 0.1로 바꿔볼까요?

 

자, 이렇게 됐죠?

피봇 설정에 따라 어떻게 바뀌는지 아시겠나요?

 

 

다시 초기 상태인

스케일을 X 1, Y 1, Z 1로 바꿔줍니다.

 

 

그리고 이름을 BlockHP_prosto에서

HealthBar로 변경해줍니다.

(앞으로 참조해서 쓰려면 간단한 이름이 좋겠죠?)

 

 

자, 그리고 Hierarchy 탭에 새로 만들었던 NormalBlock의 추가작업이 끝났다면

기존의 Prefabs의 NormalBlock 위로 드래그 앤 드랍하여 수정한 내용을 적용시켜줍니다.

 

그리고 성공적으로 Prefabs을 바꿔줘다면

Hierarchy탭에 남은 NormalBlock은 필요 없으니 Delete 키를 눌러 제거해줍니다.

 

 

이렇게 NormalBlock이 바뀌어 HealthBar가 하위에 추가된 것을 확인할 수 있습니다.

(HealthBar를 눌러서 Order in Layer 값을 0에서 1로 바꿔주세요.)

(Order in Layer는 소팅 레이어에서 순서를 말합니다.

높을수록 더 위쪽에 그려집니다. (더 화면에 가깝게) )

 

 

실행해보면 이렇게 블록들의 체력 게이지들이 모두 나오는 것을 확인할 수 있습니다.

 

 

이제 실제로 게이지가 체력에 따라 바뀌도록 설정 해줄까요?

BlockInfo.cs를 열어줍니다.

 

 

먼저 max와 현재 값이 필요하니

max를 추가하고 healthPoint도 private으로 변경해줍니다.

 

그리고 하위 게임 오브젝트의 scale 값을 얻기 위해서

gaugeTf라는 Transform도 선언했습니다.


(바뀐 부분이 많아 아래 스크립트 넣어뒀습니다. 복붙하셔도 됩니다.)

 

gaugeTf를 Awake에서 FindChild를 통해 "HealthBar"이름의 자식 Transform을 얻었습니다.

 

그리고 SetBlockInfo() - 블록이 활성화되면 호출되는 함수

에서 healthPoint를 max 값으로 초기화해주는 것을 확인할 수 있습니다.

 

 

GetDamage(int dam)함수 부분이 데미지를 입는 부분이었죠?

 

여기서 tempScale과 gaugeTf.localScale을 통하여

퍼센트 만큼 체력 게이지의 스케일을 조정하는 것을 확인할 수 있습니다.

 

 

//현재의 BlockInfo 전체.

using UnityEngine;
using System.Collections;

public class BlockInfo : MonoBehaviour {
    public int maxHealthPoint; //최대치.
    private int healthPoint; //블록 현재 체력.

    float xHalfSize;
    float yHalfSize;
    Vector3 thisBlockPos;
    Vector2 thisBlockScale;

    Vector3 tempBallPos;
    Vector3 tempBallScale;
    static float ballRadius = 1.2f; //기본 1.2임...

    Color normalColor; //일반 색상. (공통 사용 static)
    Color beAttackedColor; //피격시 잠깐 바뀔 색상.
    SpriteRenderer thisSR;

    Transform gaugeTf;

    void Awake()
    {
        BoxCollider2D tempCol = gameObject.GetComponent<BoxCollider2D>();
        xHalfSize = transform.localScale.x * (tempCol.size.x*0.5f);
        yHalfSize = transform.localScale.y * (tempCol.size.y * 0.5f);
        thisBlockScale = new Vector2(xHalfSize * 2, yHalfSize * 2);       
        tempCol = null;

        thisSR = gameObject.GetComponent<SpriteRenderer>();
        normalColor = thisSR.color;
        beAttackedColor = normalColor;
        beAttackedColor.r -= 0.1f;
        beAttackedColor.g -= 0.1f;
        beAttackedColor.b -= 0.1f;

        gaugeTf = transform.FindChild("HealthBar");
    }

    public void SetBlockInfo()
    {
        healthPoint = maxHealthPoint;
        thisBlockPos = transform.position;
    }

    float shortestDist; //가장 짧은 값 저장.
    int shortestSide=0; //1왼쪽 2오른쪽 3위 4아래.
    float tempDist; //각 면에서의 거리 확인.

    void CheckCollisionSide() //충돌한 면이 어떤 면인지 확인해주는 함수.
    {
        //x - 좌 우.
        shortestDist = Mathf.Abs(tempBallPos.x + (tempBallScale.x * ballRadius) - thisBlockPos.x - xHalfSize); //왼쪽면에 대한 값.
        shortestSide = 1;
        tempDist = Mathf.Abs(tempBallPos.x - (tempBallScale.x * ballRadius) - thisBlockPos.x + xHalfSize);//오른쪽면에 대한 값.
        if (shortestDist > tempDist)
        {
            shortestDist = tempDist;
            shortestSide = 2;//오른쪽.
        }
        //y - 위 아래.
        tempDist = Mathf.Abs(tempBallPos.y - (tempBallScale.y * ballRadius) - thisBlockPos.y + yHalfSize);//위쪽면에 대한 값.
        if (shortestDist > tempDist)
        {
            shortestDist = tempDist;
            shortestSide = 3; //위쪽.
        }
        tempDist = Mathf.Abs(tempBallPos.y + (tempBallScale.y * ballRadius) - thisBlockPos.y - yHalfSize);//아래면에 대한 값.
        if (shortestDist > tempDist)
        {
            shortestDist = tempDist;
            shortestSide = 4; //아래쪽.
        }
    }

    Vector3 tempScale;
    void GetDamage(int dam)
    {
        healthPoint -= dam;//피해량에 따라 체력 줄임.

        if(healthPoint < 1)
        {//최소치에 대한 부분 처리와 블록 제거 처리.
            healthPoint = 0;
            gameObject.SetActive(false);//비활성화.
        }else
        {//체력 남았으면 피격효과 보여줌.
            StartCoroutine("BeAttackedEffect");

            tempScale = gaugeTf.localScale;//체력 게이지 줄임.
            tempScale.x = (float)healthPoint / maxHealthPoint;
            gaugeTf.localScale = tempScale;
        }
    }
    IEnumerator BeAttackedEffect()
    {//피격 효과로 잠깐동안 색상바꿈.
        thisSR.color = beAttackedColor;
        yield return new WaitForSeconds(0.1f);
        thisSR.color = normalColor;
    }
    void OnTriggerEnter2D(Collider2D col)
    {
        if(col.CompareTag("Ball"))//볼과 충돌한 경우만.
        {
            Debug.Log("충돌");
            tempBallPos = col.transform.position;
            tempBallScale = col.transform.localScale;

            CheckCollisionSide();

            MoveBall tempMoveBall = col.gameObject.GetComponent<MoveBall>();
            if (!tempMoveBall.frameCollisionCheck)
            {
                if (shortestSide < 3)
                {//충돌한 면이 좌우이면 -> 이동 방향 y축 전환시킴.
                    tempMoveBall.TurnTheBall(2);
                }
                else
                {//충돌한 면이 상하이면 -> 이동 방향 x축 전환시킴.
                    tempMoveBall.TurnTheBall(1);
                }
                tempMoveBall.frameCollisionCheck = true;
                tempMoveBall = null;
               
                GetDamage(col.gameObject.GetComponent<BallInfo>().damage);
            }
            else
            {
                tempMoveBall = null;
            }
           
            shortestSide = 0;
        }
    }
}

 

 

마지막으로 실행하기 전에 이번에 스크립트에서 바꿔줬던

NormalBlock의 MaxHealthPoint만 설정해줍시다.

이렇게 NormalBlock 프리팹을 클릭하여

MaxHealthPoint를 5로 수정해주고

게임을 실행해봅니다.

 

정상적으로 체력 게이지가 표시되고 볼과 충돌이 일어나는 경우

체력 게이지가 줄어드는 것을 확인할 수 있습니다.

 

 

 

 

지금까지 따라하는 유니티 2D 프로젝트ⓑ -6번 강좌를 보셨습니다.

이번 시간에는 볼 키퍼를 통하여 볼이 죽지 않게끔 처리를 했고,

기존의 객체를 변경하여 새로운 기능을 하도록 만들었습니다.

또, 블록의 체력 게이지를 표시하여 현재 체력이 어느정도 남았는지

확인할 수 있도록 했습니다.

(이 게이지 부분은 사실 부드럽게 줄어들도록 할 수도 있는데

지금은 기본 형태로 만들었습니다.)

 

 

다음 시간에는 체력 게이지를 피해가 받은 블록만 일정 시간 보이도록 변경하고,

다른 방식으로도 블록이 생성될 수 있도록 하겠습니다.

그리고 볼이 모두 없어진 경우 게임오버가 될 수 있도록 만들겠습니다.

 

 

고생하셨습니다. 같이 따라하며 진행되니 어떤가요? 도움은 좀 됐나요?

그럼 프로젝트의 다음 단계를 진행하며 나오는 새로운 것들에 대해서는

지금과 같이 설명하며 진행하도록 하겠습니다.

 

 

궁금한 점 있으시면 댓글이나 따로 메일로 질문하시면 시간되는 대로 답변드리겠습니다. ( 연락 )

Comments