Game Develop

[DirectX11] 셰이더 안쓰고 체력바 구현 본문

ComputerGraphics/DirectX

[DirectX11] 셰이더 안쓰고 체력바 구현

MaxLevel 2020. 9. 27. 23:51

-  서론

                                                                                                                                                      

 

포트폴리오 제작 도중 체력바를 구현하였는데 구현도중엔 헷갈려서 조금 헤맸지만 결국 원하는대로 완성되었다.

어떤 정수나 실수값(이번 예제 같은 경우는 몬스터HP) 과 연동해서 그 값의 증감만큼 조절하는 기능을 구현하는 것이기때문에 사실상 프로그레스바를 구현하는거라고 보면 된다.

일부러 셰이더를 안쓰고 구현해보려는게 아니라 그냥 안쓰고 금방 할줄알고 시도해본거였다. 

Directx11로 게임을 만들어보시려는 분들은 절대로 저처럼 하지말고 그냥 픽셀셰이더에서 uv.x값 조절해서 출력하면된다.

 

 

 

 

 

- 체력바구현에 쓰일 이미지.

                                                                                                                                                      

 

Back_HPbar.png

 

 

Front_HPbar.png

 

 

- 사전설명

                                                                                                                                                      

 

 구현에 앞서 미리 이해하기 편하도록 설명을 하자면, 각 텍스쳐 이미지 객체는 Scale값 조정을 통해 실제 화면에 렌더링되는 크기를 조절할 수 있다. Scale의 x값이 줄어들수록 가로로 줄어들것이고 y값이 줄어들수록 세로로 줄어들것이다.

이번에 사용할 체력바같은 경우는 가로로 줄어들게 해야하기 때문에 Scale.x값을 조절할것이다.

 

화면에 렌더링 됐을때의 Scale값은 개발자가 DirectX11에서 텍스쳐를 불러오는 방법을 어떻게 했느냐에 따라서 초기값이 다른데, 현재 내 코드에서는 여러 텍스쳐를 재생시키기위한 클래스(애니메이션용)로 텍스쳐를 설정할 경우

초기Scale값은 그 텍스쳐이미지의 Width(너비), Height(높이)이다. 

 

 

 

Width,Height는 각 이미지 속성에 나와있는 사진 크기를 말한다.

 

하지만 이번 체력바구현에서는 각각 단일이미지만 사용하기때문에 이럴 때 사용하기위한 용도로 텍스쳐를 불러오게 따로 구현된 클래스가 있다. 이렇게 불러올 경우 이미지의 너비,높이값과 상관없이 scale값이 1,1로 설정되어있기 때문에 구현할 때 좀 더 직관적으로 생각하기 편했다.

 

주의할점은 만약 DirectX11을 공부하시는분들께서 보셨을 때

    '아 단일 이미지 불러올때는 무조건 Scale값은 1,1이구나~'

라고 오해하시면 안된다!

위에서 언급했듯이, 텍스쳐를 만들때 개발자가 Scale값을 어떻게 설정하냐에 따라 Scale값을 1,1로 설정할 수도 있고 이미지의 크기대로 설정할 수 있기 때문이다. 

 본 글을 보시는분들께서는 이번 예제에서만큼의 텍스쳐의 각 Scale값은 그냥 1,1이구나... 정도만 알아두어도 된다.

 

그리고 몬스터가 실제 체력이 깎이는 과정을 간단하게 쓰자면 아래와 같다.

 

1. 플레이어 총알과 몬스터는 각자 충돌감지용 컬라이더를 가지고있다.

2. 총알은 몬스터에 대한 포인터를 계속 들고있다. 그럼으로써 좌표계산을 통해 몬스터와 충돌여부를 확인할 수 있다.

3. 만약 충돌할 경우, 몬스터객체의 OnDamage()란 함수를 호출해서 실제로 몬스터의 체력을 깎는다.

   

이렇게 실제로 체력이 깎인만큼 Scale값도 줄어들게 구현할 것이다.

 

 

- 구현

                                                                                                                                                      

 

일단 생각해봤을 때는 몬스터의 체력에 변동사항이 있을 때에만 체력바가 업데이트 되야한다.

체력에 변동사항이 있는 경우는 플레이어의 체력이 몬스터의 OnDamage()함수를 실행시켰을 때다.

그렇다면 몬스터의 OnDamage()함수가 실행했을 때 몬스터와 연결된 체력바객체의 임의의 함수를 실행시켜 

'체력값이 줄어들었으니 이미지의 Scale값을 조정해야한다!'라고 알려서 실제로 줄어들게 해야한다.

임의의 함수 이름은 UpdateHPbar로 했다.

 

 

Monster::OnDamage()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Gnoll_Mirkwood::OnDamage(float damage)
{
    if (isDie) return;
    UM->Change_MonsterHPbar(hpBar);
    onDamageStateCheckTime = Timer::Get()->GetRunTime() + hitRecovery;
 

    hpBar->UpdateHPbar(hp, hp - damage);
// 피격받았을경우(OnDamage 실행시), 체력바 업데이트


    hp -= damage;
 
    if (hp <= 0)
    {
        hp = 0;
        Die();
    }
}
cs

     

HPbar::UpdateHPbar() 

1
2
3
4
5
6
void HPbar_Monster::UpdateHPbar(float beforeHP, float afterHP)
{
    trigger_UpdateHP = true; // true일 경우에만 hp값 변동가능.
    lerpStart = beforeHP / maxHP; // 변동 전 값. (피격 전 체력)
    lerpEnd = afterHP / maxHP; // 변동 후의 값. (피격 후 체력)
}
cs

 

 

lerpStart는 피격 전 체력의 비율이고, lerpEnd는 피격 후의 체력의 비율이다.

즉 Scale의 x값은 lerpEnd값까지 줄어들게만 하면된다.

  ex) 체력이 100%일 경우 체력바의 스케일값은 1.0이다. 만약 체력이 깎여 체력의 비율이 8/10 이 되었다고 치면, 스케일값도 8/10으로 조정하면 된다.

 

비율은 0.0~1.0 사이의 값으로 표현할 수 있기 때문에 1로 정해져있는 x축 스케일값을 직관적으로 다루기 좋다.

trigger_UpdateHP는 UpdateHPbar라는 함수가 호출되었을때만 실제로 Scale값이 줄어들게 하기 위한 bool값이다.

아래 Update()함수를 보면 알겠지만 이 값이 true일때만 Scale값이 바뀌는 코드가 실행된다. 

 

어쨌든 위의 과정을 통해서 체력바객체는 변동전 체력비율과 변동후 체력비율을 알아내게 된다.

그러면 이제 이 값으로 스케일값 조정만 하면된다.

 

다음은 체력바의 Update()함수이다.

게임개발에서 각 오브젝트의 Update()함수는 프레임마다 실행되는 함수라고 생각하면된다. 프레임은 당연히 컴퓨터의 사양마다 다른데 만약 현재 프레임이 120프레임이라면 Update()함수는 초당 120번 실행되고 200프레임이면 200번 실행된다.

이렇듯 각 컴퓨터마다 당연히 프레임차이가 있기때문에 단순하게 Update()함수에서 어떤값을 1씩더하는 코드를 작성했을 때 120프레임컴퓨터에선 1초후에 값이 120이 되고 200프레임컴퓨터에선 1초후에 값이 200이 된다.

이러한 컴퓨터프레임간의 차이에 따른 값의 다름을 해결하기위해 변동값마다 DELTA값을 곱해주는데 이 DELTA값에 관해선 나중에 따로 포스팅하겠다. 

 

 

체력바의 업데이트함수는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void HPbar_Monster::Update()
{
    if (trigger_UpdateHP) // 이 변수가 true일 경우, 체력바의 Scale값을 줄인다.
    {
        if (front_HPbar->scale.x <= lerpEnd) // Scale값이 목표치만큼 줄어들었다면
        {
            trigger_UpdateHP = false; // 신호를 꺼버린다.
        }
 
        else  // 목표치에 아직 도달하지 않았을 경우(lerpEnd값까지 안줄어들었을 경우)
        {
            front_HPbar->scale.x -= 0.3f * DELTA; // Scale값을 줄인다.
         
            if (front_HPbar->scale.x < 0.0f) // 줄이다가 -값이 되려할경우 0으로 고정.
            {
                front_HPbar->scale.x = 0.0f;
            }
        }
    }

 back_HPbar->pos = { 450,500 }; // 실제 모니터에 표현되는 Back_HPbar 위치.
front_HPbar->pos = back_HPbar->pos + Vector2(15-6);
// 실제 모니터에 표현되는 Front_HPbar. Back_HPbar와 겹치게해야해서 약간의 위치조정(15,-6)
}
   cs
 
 
 

 

 

코드자체는 간단해서 아마 주석만 봐도 이해가 잘 될거라 믿는다.

요약하자면 몬스터체력에 변동이 있을 경우, trigger_UpdateHP는 true가 되어 Scale값이 줄어드는 코드가 실행되고

목표치(lerpEnd)까지 Scale값이 줄어들었을 경우 다시 false가 되어 줄어드는 코드를 멈춘다.

참 간단하다. 그래서 이상태로 실행을 해봤다.

 

 

 

 

엥?

 

이런... 분명 줄어드는 비율자체는 맞게 줄어들지만 텍스쳐의 정점이 텍스쳐의 중간을 중심으로 Scale*0.5 만큼의 네방향의 위치에 각각 찍혀있기 때문에 Scale값을 줄이니까 양쪽에서 줄어들게 된다. (이런 내용은 몰라도된다)

 

만약 2/10만큼 줄어들어야 할 경우, 오른쪽에서 2/10만큼 줄어들어야하는데 양쪽에서 각각 1/10씩 줄어들게 되는것이다. 

 

그러면 어떻게해야할까? 

딱 왼쪽에 비어있는 검정색만큼 빨간색 체력바를 왼쪽으로 이동시켜주면 된다. 줄어드는 동시에 Position.x값을 왼쪽으로 이동시키면 감쪽같이 오른쪽에서만 2/10만큼 줄어들것이다.

 

바로 나올 추가된 코드를 쉽게 설명하자면, 현재 Scale값은 줄어들 때 0.3f 값만큼 줄어들게 해놨다.

근데 실제로 이 0.3f란 값은 오른쪽에서 0.3f씩 줄어드는게 아니라 양쪽에서 0.15f씩 줄어들고 있다.

우리는 왼쪽에서 0.15f씩 줄어들고있다는 정보를 알기 때문에, Scale값이 0.3f값만큼 줄어드는 동시에 Position.x값을 왼쪽으로 0.15f비율만큼 줄어들게하면 된다.

(조심해야할 점은 Position.x값을 0.15f씩 줄어들게하는게 아니라 체력바의 0.15f비율씩 줄어들게해야한다는 것이다.)

 

최종코드는 다음과 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void HPbar_Monster::Update()
{
    if (trigger_UpdateHP)
    {
        if (front_HPbar->scale.x <= lerpEnd)
        {
            trigger_UpdateHP = false;
        }
 
        else  // lerpEnd 까지 줄어들어야함.
        {
            front_HPbar->scale.x -= 0.3f * DELTA;
            reductionSum += 0.15f * DELTA; // 추가된부분 1
 
            if (front_HPbar->scale.x < 0.0f)
            {
                front_HPbar->scale.x = 0.0f;
            }
        }
    }
 
    back_HPbar->pos = { 450,500 };
    
    float tmp = front_HPbar->GetSize().x * reductionSum; // 추가된 부분 2
 
    front_HPbar->pos = back_HPbar->pos + Vector2(15-6);
    front_HPbar->pos.x -= tmp; // 추가된 부분 3
}
cs

 

추가된 부분1.을 보면 스케일값이 감소되는 값(0.3)의 절반만큼(0.15) reductionSum이라는 float변수에 계속 저장해놓는다.  그리고 Front_HPbar의 너비(Width)값에다 줄어든 비율만큼의 값을 곱하면, 왼쪽으로 이동해야할 x값의 절대값을 알 수 있다.  이 값을 빼줌으로써 우리는 원하던 목표를 최종적으로 구현할 수 있게 된다.

(Scale값의 절반만큼의 비율로 텍스쳐의 position값을 -하기)

 

 

드디어 완성...

 

 

-  마치며..

                                                                                                                                                      

 

 저번 포스팅과 마찬가지로 식이나 코드 자체에는 복잡한게 없긴 하지만, 조금이라도 이해하기 쉽고 보기편하게 풀어서 글을 쓰려다보니 생각보다 양이 많아졌다. 쓰고 보니 차라리 안 풀고 간단하게 작성하는게 더 좋았을것 같다..

 

 

- p.s : 위 부분은 픽셀셰이더를 사용안하고 구현하려다가 삽질한 결과이다.. 픽셀셰이더에서 uv의 x좌표가지고 조절하            면 좀 더 쉽게 할 수 있다. ( 2020/10/18 )