Win32API 강의 64화. Win32API RigidBody (1)
https://www.youtube.com/watch?v=5i6rbcpZUTg&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=64
< 배우게 된 내용 >
2D에서 사용 할 수 있는 기본적인 물리
모든 물체가 다 물리 적용을 받는것은 아님 ( Component로 구현 )
간단하게 힘을 줬을때 이전 속도를 잃지 않은 상태로 움직이는 물리 구현
#pragma once
class CObject;
class CRigidBody
{
private:
CObject* m_pOwner;
Vec2 m_vForce; // 크기, 방향
Vec2 m_vAccel; // 가속도
float m_fMass; // 질량
Vec2 m_vVelocity; // 속도
// F = M * A;
// V += A * DT -> 현재 속도
public:
void finalupdate();
public:
void AddForce(Vec2 _vF)
{
// 힘 누적
m_vForce += _vF;
}
void SetMass(float _fMass)
{
m_fMass = _fMass;
}
float GetMass()
{
return m_fMass;
}
private:
void Move();
public:
CRigidBody();
~CRigidBody();
friend class CObject;
};
#include "pch.h"
#include "CRigidBody.h"
#include "CObject.h"
#include "CTimeMgr.h"
CRigidBody::CRigidBody()
: m_pOwner(nullptr)
, m_fMass(1.f)
{
}
CRigidBody::~CRigidBody()
{
}
void CRigidBody::finalupdate()
{
// 힘의 크기
float fForce = m_vForce.Length();
if (0.f != fForce)
{
// 힘의 방향
m_vForce.Normalize();
// 가속도의 크기
float m_fAccel = fForce / m_fMass;
// 가속도
m_vAccel = m_vForce * m_fAccel;
// 속도
m_vVelocity += m_vAccel * fDT;
}
// 속도에 따른 이동
Move();
// 누적된 힘을 0으로
m_vForce = Vec2(0.f, 0.f);
}
void CRigidBody::Move()
{
// 이동 속력
float fSpeed = m_vVelocity.Length();
if (0.f != fSpeed)
{
// 이동 방향
Vec2 vDir = m_vVelocity;
vDir.Normalize();
Vec2 vPos = m_pOwner->GetPos();
vPos += m_vVelocity * fDT;
m_pOwner->SetPos(vPos);
}
}
+ 이 가속도 속도 계산에서 마찰이 필요함
Win32API 강의 65화. Win32API RigidBody (2)
https://www.youtube.com/watch?v=vJnkF3BNGgo&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=65
< 배우게 된 내용 >
플레이어의 최대 속도 제한 걸기
스스로 멈출수 있도록 마찰력 추가 ( 속도의 반대 방향으로 )
void CRigidBody::finalupdate()
{
// 힘의 크기
float fForce = m_vForce.Length();
if (0.f != fForce)
{
// 힘의 방향
m_vForce.Normalize();
// 가속도의 크기
float m_fAccel = fForce / m_fMass;
// 가속도
m_vAccel = m_vForce * m_fAccel;
// 속도
m_vVelocity += m_vAccel * fDT;
}
// 마찰력에 의한 반대방향으로의 가속도 적용
if (!m_vVelocity.IsZero())
{
Vec2 vFricDir = -m_vVelocity;
vFricDir.Normalize();
Vec2 vFriction = vFricDir * m_fFricCoeff * fDT;
if (m_vVelocity.Length() <= vFriction.Length())
{
// 마찰 가속도가 본래 속도보다 큰 경우
m_vVelocity = Vec2(0.f, 0.f);
}
else
{
m_vVelocity += vFriction;
}
}
// 속도 제한 검사
if (m_fMaxSpeed <= m_vVelocity.Length())
{
m_vVelocity.Normalize();
m_vVelocity *= m_fMaxSpeed;
}
// 속도에 따른 이동
Move();
// 누적된 힘을 0으로
m_vForce = Vec2(0.f, 0.f);
}
플레이어의 이동방식을 힘을 이용해서 변경
( TAP키를 넣은 이유는 초기 스타트 속도를 한번에 주기 위해서 )
void CPlayer::update()
{
CRigidBody* pRigid = GetRigidBody();
// 힘을 이용해서 좌표를 수정
if(KEY_HOLD(KEY::W))
{
pRigid->AddForce(Vec2(0.f, -200.f));
}
if(KEY_HOLD(KEY::S))
{
pRigid->AddForce(Vec2(0.f, 200.f));
}
if(KEY_HOLD(KEY::A))
{
pRigid->AddForce(Vec2(-200.f,0.f));
}
if(KEY_HOLD(KEY::D))
{
pRigid->AddForce(Vec2(200.f, 0.f));
}
// 초기속도 지정
if (KEY_TAP(KEY::W))
{
pRigid->AddVelocity(Vec2(0.f, -100.f));
}
if (KEY_TAP(KEY::S))
{
pRigid->AddVelocity(Vec2(0.f, 100.f));
}
if (KEY_TAP(KEY::A))
{
pRigid->AddVelocity(Vec2(-100.f, 0.f));
}
if (KEY_TAP(KEY::D))
{
pRigid->AddVelocity(Vec2(100.f, 0.f));
}
if(KEY_TAP(KEY::SPACE))
{
CreateMissile();
}
GetAnimator()->update();
}
Win32API 강의 66화. Win32API RigidBody (3)
https://www.youtube.com/watch?v=6E-iTAn-k5I&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=66
< 배우게 된 내용 >
마우스 클릭한 곳에서 원형으로 힘이 발동 ( 플레이어와 몬스터에게 힘을 줘서 반대방향으로 밀리게 )
void CScene_Start::update()
{
if (KEY_HOLD(KEY::LBTN))
{
m_bUseForce = true;
CreateForce();
}
else
{
m_bUseForce = false;
}
for (UINT i = 0; i < (UINT)GROUP_TYPE::END; ++i)
{
const vector<CObject*>& vecObj = GetGroupObject((GROUP_TYPE)i);
for (size_t j = 0; j < vecObj.size(); ++j)
{
if (!vecObj[j]->IsDead())
{
if (m_bUseForce && vecObj[j]->GetRigidBody())
{
Vec2 vDiff = vecObj[j]->GetPos() - m_vForcePos;
float fLen = vDiff.Length();
if (fLen < m_fForceRadius)
{
// 가까울때 힘을 높게
float fRatio = 1.f - (fLen / m_fForceRadius);
float fForce = m_fForce* fRatio;
vecObj[j]->GetRigidBody()->AddForce(vDiff.Normalize() * fForce);
}
}
vecObj[j]->update();
}
}
}
if (KEY_TAP(KEY::ENTER)) // tool scene 전환
{
ChangeScene(SCENE_TYPE::TOOL); // 이벤트 등록
}
}
Win32API 강의 67화. Win32API RigidBody (4)
https://www.youtube.com/watch?v=jgxTQIaOaAI&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=67
< 오늘 배운 내용 >
애니메이션 전환에 따른 내부 로직 개편 ( 대략적인 흐름만, 구체적 구현 X )
#pragma once
#include "CObject.h"
enum class PLAYER_STATE
{
IDLE,
WALK,
ATTACK,
DEAD,
};
enum class PLAYER_ATTACK_STATE
{
NORMAL_ATT_1,
NORMAL_ATT_2,
NORMAL_ATT_3,
SKILL_ATT_1,
// ....
};
class CTexture;
class CPlayer :
public CObject
{
private:
vector<CObject*> m_vecColObj;
PLAYER_STATE m_eCurState;
PLAYER_STATE m_ePrevState;
int m_iDir;
public :
virtual void update();
virtual void render(HDC _dc);
private:
void CreateMissile();
void update_state();
void update_move();
void update_animation();
CLONE(CPlayer);
public:
CPlayer();
~CPlayer();
};
void CPlayer::update()
{
update_move();
update_state();
update_animation();
if(KEY_TAP(KEY::SPACE))
{
CreateMissile();
}
GetAnimator()->update();
m_ePrevState = m_eCurState;
}
void CPlayer::update_animation()
{
if (m_ePrevState == m_eCurState)
return;
switch (m_eCurState)
{
case PLAYER_STATE::IDLE:
break;
case PLAYER_STATE::WALK:
break;
case PLAYER_STATE::ATTACK:
break;
case PLAYER_STATE::DEAD:
break;
default:
break;
}
}
Win32API 강의 68화. Win32API RigidBody (5)
https://www.youtube.com/watch?v=ST7OjCNy0Z0&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=68
< 오늘 배운 내용 >
Walk와 Idle 상태 전환 애니메이션, 스프라이트는 방향별로 따로 2개씩 둠
CPlayer::CPlayer()
: m_eCurState(PLAYER_STATE::IDLE)
, m_ePrevState(PLAYER_STATE::WALK)
, m_iDir(1)
, m_iPrevDir(1)
{
//// Texture 로딩하기
//m_pTex = CResMgr::GetInst()->LoadTexture(L"PlayerTex", L"texture\\Player.bmp");
CreateCollider();
GetCollider()->SetOffsetPos(Vec2(0.f, 12.f));
GetCollider()->SetScale(Vec2(20.f, 40.f));
CreateRigidBody();
// Texture 로딩하기
CTexture* pLeftTex = CResMgr::GetInst()->LoadTexture(L"PlayerLeft", L"texture\\Player01_L.bmp");
CTexture* pRightTex = CResMgr::GetInst()->LoadTexture(L"PlayerRight", L"texture\\Player01_R.bmp");
CreateAnimator();
GetAnimator()->CreateAnimation(L"IDLE_LEFT", pLeftTex, Vec2(0.f, 0.f), Vec2(50.f, 100.f), Vec2(50.f, 0.f), 0.1f, 9);
GetAnimator()->CreateAnimation(L"IDLE_RIGHT", pRightTex, Vec2(0.f, 0.f), Vec2(50.f, 100.f), Vec2(50.f, 0.f), 0.1f, 9);
GetAnimator()->CreateAnimation(L"WALK_LEFT", pLeftTex, Vec2(0.f, 225.f), Vec2(50.f, 100.f), Vec2(50.f, 0.f), 0.1f, 4);
GetAnimator()->CreateAnimation(L"WALK_RIGHT", pRightTex, Vec2(0.f, 225.f), Vec2(50.f, 100.f), Vec2(50.f, 0.f), 0.1f, 4);
GetAnimator()->Play(L"IDLE_RIGHT", true);
}
Rigidbody에서 속도를 가져와 0이 됐을때 Idle 설정
void CPlayer::update_state()
{
if (KEY_TAP(KEY::A))
{
m_iDir = -1;
m_eCurState = PLAYER_STATE::WALK;
}
if (KEY_TAP(KEY::D))
{
m_iDir = 1;
m_eCurState = PLAYER_STATE::WALK;
}
if (0.f == GetRigidBody()->GetSpeed())
{
m_eCurState = PLAYER_STATE::IDLE;
}
}
void CPlayer::update_move()
{
CRigidBody* pRigid = GetRigidBody();
// 힘을 이용해서 좌표를 수정
if (KEY_HOLD(KEY::A))
{
pRigid->AddForce(Vec2(-200.f, 0.f));
}
if (KEY_HOLD(KEY::D))
{
pRigid->AddForce(Vec2(200.f, 0.f));
}
// 초기속도 지정
if (KEY_TAP(KEY::A))
{
pRigid->AddVelocity(Vec2(-100.f, 0.f));
}
if (KEY_TAP(KEY::D))
{
pRigid->AddVelocity(Vec2(100.f, 0.f));
}
}
void CPlayer::update_animation()
{
if (m_ePrevState == m_eCurState && m_iPrevDir == m_iDir)
return;
switch (m_eCurState)
{
case PLAYER_STATE::IDLE:
{
if(m_iDir == -1)
GetAnimator()->Play(L"IDLE_LEFT", true);
else
GetAnimator()->Play(L"IDLE_RIGHT", true);
}
break;
case PLAYER_STATE::WALK:
{
if (m_iDir == -1)
GetAnimator()->Play(L"WALK_LEFT", true);
else
GetAnimator()->Play(L"WALK_RIGHT", true);
}
break;
case PLAYER_STATE::ATTACK:
break;
case PLAYER_STATE::DEAD:
break;
default:
break;
}
}
중력을 어떻게 구현?
1. 중력을 Scene에서 관리한다
2. 중력 자체를 component로 관리 또는 manager로 관리
일단 임시로 Player에서만 중력을 적용!
항상 아래쪽으로 힘이 적용
( 땅에 있을때는 중력 적용 X , 현실에서는 적용하지만, 게임에서는 중력 계산 X )
점프해서 땅위에서 떨어질때만 중력을 적용한다! && 중력의 가속도는 9.8 ( 인간의 질량은 거의 영향을 미치지 못함 )
Win32API 강의 69화. Win32API RigidBody (6)
https://www.youtube.com/watch?v=ne9Cyu9a6C0&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=69
< 배우게 된 내용 >
-> ground에 있는지를 체크
CGravity를 component로 구현
CGravity의 역할은 땅이 부딪혔는지 확인과 중력 적용
class CObject;
class CGravity
{
private:
CObject* m_pOwner;
bool m_bGround;
public:
void SetGround(bool _b) { m_bGround = _b; }
public:
void finalupdate();
public:
CGravity();
~CGravity();
friend class CObject;
};
땅 CObject를 상속받는 CGround 클래스 구현
땅과 player가 collider가 부딪히면 땅이라고 판단
CGround가 CGravity에게 땅에 닿았음을 알려줌
CGround::CGround()
{
CreateCollider();
GetCollider()->SetScale(Vec2(GetScale()));
}
CGround::~CGround()
{
}
void CGround::start()
{
GetCollider()->SetScale(Vec2(GetScale()));
}
void CGround::update()
{
}
void CGround::OnCollisionEnter(CCollider* _pOther)
{
CObject* pOtherObj = _pOther->GetObj();
if (pOtherObj->GetName() == L"Player")
{
pOtherObj->GetGravity()->SetGround(true);
}
}
생성자는 객체가 만들어지는 그 순간에 호출되는게 생성자
생성자뒤에 객체가 만들어지고 Scene이 시작하기 직전에 초기작업 해주는 함수 필요 -> Start() 함수
( Enter 함수에서 맨 끝에 start() 함수를 호출 )
ex) 콜라이더 크기를 오브젝트 크기와 맞추기 위한 시점
Win32API 강의 70화. Win32API RigidBody (7)
https://www.youtube.com/watch?v=QtUHWOwbNcs&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=70
< 배우게 된 내용 >
땅과 캐릭터가 닿을때 DT만큼 Player가 땅속 안으로 들어갈수 있음
이 차이 만큼을 Player가 올라가야함
( 아래 코드는 위의 문제점을 해결하지만 옆에서 진입하면 바로 위로 올라가버려지는 문제점이 있다 )
void CGround::OnCollision(CCollider* _pOther)
{
CObject* pOtherObj = _pOther->GetObj();
if (pOtherObj->GetName() == L"Player")
{
pOtherObj->GetGravity()->SetGround(true);
Vec2 vObjPos = _pOther->GetFinalPos();
Vec2 vObjScale = _pOther->GetScale();
Vec2 vPos = GetCollider()->GetFinalPos();
Vec2 vScale = GetCollider()->GetScale();
float fLen = abs(vObjPos.y - vPos.y);
float fValue = (vObjScale.y / 2.f + vScale.y / 2.f) - fLen;
vObjPos = pOtherObj->GetPos();
vObjPos.y -= (fValue - 1);
pOtherObj->SetPos(vObjPos);
}
}
땅에서 벗어나면 ( OnCollisionExit ) 땅위에 있다는 bool 값을 false로 변경
중력을 구현할때
원래 물체가 적용받고 있던 가속도에 추가 가속도를 더해주는 개념으로 적용시켜야 함
( m_vAccelA는 추가 가속도 )
땅 위에 있을때는 x 방향은 속도 유지 y 방향은 0으로
중력 적용은 y축으로 800f 정도로 함
Win32API 강의 71화. Win32API RigidBody (8)
https://www.youtube.com/watch?v=X_8ArldKwJw&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=71
< 배우게 된 내용 >
점프 만들기 + 애니메이션 실행
Y축으로 추가속도를 덧셈해주기
void CPlayer::update_state()
{
if (KEY_TAP(KEY::A))
{
m_iDir = -1;
m_eCurState = PLAYER_STATE::WALK;
}
if (KEY_TAP(KEY::D))
{
m_iDir = 1;
m_eCurState = PLAYER_STATE::WALK;
}
if (0.f == GetRigidBody()->GetSpeed())
{
m_eCurState = PLAYER_STATE::IDLE;
}
if (KEY_TAP(KEY::SPACE))
{
m_eCurState = PLAYER_STATE::JUMP;
if (GetRigidBody())
{
GetRigidBody()->AddVelocity(Vec2(0.f, -200.f));
}
}
}
void CPlayer::update_animation()
{
if (m_ePrevState == m_eCurState && m_iPrevDir == m_iDir)
return;
switch (m_eCurState)
{
case PLAYER_STATE::IDLE:
{
if(m_iDir == -1)
GetAnimator()->Play(L"IDLE_LEFT", true);
else
GetAnimator()->Play(L"IDLE_RIGHT", true);
}
break;
case PLAYER_STATE::WALK:
{
if (m_iDir == -1)
GetAnimator()->Play(L"WALK_LEFT", true);
else
GetAnimator()->Play(L"WALK_RIGHT", true);
}
break;
case PLAYER_STATE::JUMP:
if (m_iDir == -1)
GetAnimator()->Play(L"JUMP_LEFT", true);
else
GetAnimator()->Play(L"JUMP_RIGHT", true);
break;
case PLAYER_STATE::ATTACK:
break;
case PLAYER_STATE::DEAD:
break;
default:
break;
}
}
Win32API 강의 72화. Win32API RigidBody (9)
https://www.youtube.com/watch?v=nrRXX0DyXp8&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=72
< 배우게 된 내용 >
애니메이션 전환 문제점들 수정
점프 했을때 Walk 애니메이션으로 전환 X
점프 중에서 끝에 속도가 0일때 Idle로 전환 X
void CPlayer::update_state()
{
if (KEY_HOLD(KEY::A))
{
m_iDir = -1;
if(PLAYER_STATE:: JUMP != m_eCurState)
m_eCurState = PLAYER_STATE::WALK;
}
if (KEY_HOLD(KEY::D))
{
m_iDir = 1;
if (PLAYER_STATE::JUMP != m_eCurState)
m_eCurState = PLAYER_STATE::WALK;
}
if (0.f == GetRigidBody()->GetSpeed() && PLAYER_STATE::JUMP != m_eCurState)
{
m_eCurState = PLAYER_STATE::IDLE;
}
if (KEY_TAP(KEY::SPACE))
{
m_eCurState = PLAYER_STATE::JUMP;
if (GetRigidBody())
{
GetRigidBody()->SetVelocity(Vec2(GetRigidBody()->GetVelocity().x, -300.f));
}
}
}
Player가 땅에 닿을때 진입시 IDLE 애니메이션으로 전환
'Win32API' 카테고리의 다른 글
Win32API Sound (0) | 2025.01.31 |
---|---|
Win32API FSM ( Finite State Machine ) (0) | 2025.01.27 |
Win32API 렌더링 최적화 (0) | 2025.01.27 |
Win32API Alpha Blend (0) | 2025.01.27 |
Win32API 파일 입출력 (0) | 2025.01.26 |