Programming/Dev

[009] 플레이어 컨트롤

LaivY 2023. 6. 6. 20:13

DevLog

이번 게시글에서는 간단하게 플레이어를 조종하는 것을 구현해보려고 한다.

 

 

 

 

class Player : public IGameObject
{
public:
    class InputComponent { ... };

    class PhysicsComponent { ... };

    class AnimationComponent { ... };

public:
    Player();
    ~Player() = default;

    virtual void Update(FLOAT deltaTime);
    virtual void Render(const ComPtr<ID3D12GraphicsCommandList>& commandList) const;

private:
    InputComponent m_inputComponent;
    PhysicsComponent m_physicsComponent;
    AnimationComponent m_animationComponent;
};

Player 클래스는 위와 같다.

나중에 바꿀지도 모르겠지만 일단 컴포넌트 패턴을 사용해보려고 한다.

 

 

 

 

void Player::Update(FLOAT deltaTime)
{
    m_inputComponent.Update(deltaTime);		// 1. 입력 처리
    m_physicsComponent.Update(deltaTime);	// 2. 이동 처리
    m_animationComponent.Update(deltaTime);	// 3. 애니메이션
}

코드를 컴포넌트로 분리했기 때문에 Player의 Update 함수에는 크게 내용이 없다.

단, 컴포넌트의 Update 함수 호출의 순서에 따라 결과가 달라지는 것은 주의해야 한다.

 

 

 

 

void Player::InputComponent::Update(FLOAT deltaTime)
{
    // 좌우 이동
    int dir{ 0 };
    if (GetAsyncKeyState(VK_LEFT) & 0x8000)
        --dir;
    if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
        ++dir;
    m_player->m_physicsComponent.Move(static_cast<PhysicsComponent::Direction>(dir));

    // 점프
    bool isJumped{ false };
    if (GetAsyncKeyState('C') & 0x8000)
    {
        m_player->m_physicsComponent.Jump();
        isJumped = true;
    }

    // 애니메이션 재생
    auto animationType{ AnimationComponent::AnimationType::NONE };
    if (isJumped)
        animationType = AnimationComponent::AnimationType::JUMP;
    else if (m_player->m_animationComponent.GetAnimationType() != AnimationComponent::AnimationType::JUMP && m_player->m_animationComponent.GetAnimationType() != AnimationComponent::AnimationType::FALL)
    {
        if (dir != 0)
            animationType = AnimationComponent::AnimationType::RUN;
        else
            animationType = AnimationComponent::AnimationType::IDLE;
    }

    if (animationType != AnimationComponent::AnimationType::NONE &&
        m_player->m_animationComponent.GetAnimationType() != animationType)
        m_player->m_animationComponent.PlayAnimation(animationType);
}

InputComponent에서는 입력에 대한 처리를 담당한다.

위 코드에서 볼 수 있듯이 각 컴포넌트끼리는 직접 접근하여 통신한다. 통신 방법 역시 나중에 바꿀 수도 있을 것 같다.

 

 

 

 

void Player::PhysicsComponent::Update(FLOAT deltaTime)
{
    FLOAT2 beforePlayerPosition{ m_player->GetPosition() };
    FLOAT2 afterPlayerPosition{};
    const Platform* beforePlatform{ m_platform };
    const Platform* afterPlatform{ nullptr };

    // 이동. 현재 플렛폼의 높이와 플레이어의 높이가 같다면 플렛폼 위에 서있다는 것
    if (m_speed.y < 0.0f && m_platform && m_platform->GetHeight(beforePlayerPosition.x) == beforePlayerPosition.y)
        m_speed.y = 0.0f;
    m_player->Move(FLOAT2{ static_cast<int>(m_direction) * m_speed.x * deltaTime, m_speed.y * deltaTime });

    // 현재 위치에서 가장 높은 플렛폼 계산
    afterPlayerPosition = m_player->GetPosition();
    afterPlatform = GetTopPlatformBelowPosition(afterPlayerPosition);

    // 움직이기 이전, 이후 플레이어 y좌표 사이에 이전 플렛폼 높이가 있다면 착지한 것
    FLOAT platformHeight{ -FLT_MAX };
    if (beforePlatform)
        platformHeight = beforePlatform->GetHeight(beforePlayerPosition.x);
    if (afterPlayerPosition.y < platformHeight && platformHeight < beforePlayerPosition.y)
    {
        afterPlatform = beforePlatform;
        m_platform = beforePlatform;
        OnLanding();
    }
    else
    {
        m_platform = afterPlatform;
    }

    // 이전, 이후 플렛폼이 다르고 이후 플렛폼이 없거나 이전 플렛폼의 높이가 이후 플렛폼의 높이보다 크면 플렛폼을 벗어나 떨어지는 것
    if (beforePlatform != afterPlatform && (!afterPlatform || (beforePlatform && afterPlatform && beforePlatform->GetHeight(beforePlayerPosition.x) > afterPlatform->GetHeight(afterPlayerPosition.x))))
        OnFalling();

    // 중력 적용
    m_speed.y -= 980.0f * deltaTime;
}

PhysicsComponent에서는 이동에 대한 처리를 담당한다.

이동 전, 후를 이용하여 착지했는지, 떨어지는 지를 계산하고 이벤트 함수를 호출한다.

 

 

 

 

void Player::AnimationComponent::Update(FLOAT deltaTime)
{
    m_timer += deltaTime;

    FLOAT interval{ DEFAULT_FRAME_INTERVAL };
    do
    {
        if (auto currFrameInterval{ m_currFrameProp->Get<FLOAT>("interval") })
            interval = *currFrameInterval;

        if (m_timer >= interval)
        {
            if (m_frame >= m_currAniProp->GetChildCount() - 1)
            {
                // OnAnimationEnd에서 PlayAnimation을 호출하여 m_timer값이 0이되버리므로 저장해줬다가 다시 설정해줌
                FLOAT timer{ m_timer };
                OnAnimationEnd();
                m_timer = timer - interval;
                continue;
            }

            ++m_frame;
            m_timer -= interval;
            OnFrameChange();
        }
    } while (m_timer >= interval);
}

AnimationComponent에서는 애니메이션에 대한 처리를 담당한다.

do-while문을 통해 deltaTime이 아무리 커도 그 사이 프레임에 대해 이벤트 함수를 호출 할 수 있도록 했다.

 

 

 

 

작동

아직 플렛폼이 보이게 하지는 않았다.

잘 작동하는 것을 볼 수 있다!