[C++] 3.1 공 튕기기 시뮬레이션
카테고리: C++ games
인프런에 있는 홍정모 교수님의 홍정모의 게임 만들기 연습 문제 패키지 강의를 듣고 정리한 필기입니다.😀
🌜 공부에 사용된 홍정모 교수님의 코드들 보러가기
🌜 [홍정모의 게임 만들기 연습 문제 패키지] 강의 들으러 가기!
Chapter 3. 게임 물리 맛보기 : 공 튕기기
🔔 전체 코드
📜RigidCircle 클래스
공
- 멤버
- pos 👉 현재 위치
- vel 👉 현재 속도
- radius 👉 공의 반지름
- ✨ void update(const float & dt) ✨
- 공의 위치와 속도를 갱신하는 함수
- 아래에서 구현할 것
- 멤버
📜Example 클래스
- RigidCircle rigid_body
- 공 객체 생성
- void update() override
- 매 프레임 실행됨 (Game2D의 update 오버라이딩)
- 공의 위치 & 속도 업데이트
- rigid_body.update(getTimeStep() * 0.1f);
- RigidCircle 공 객체의 update함수 호출
- 벽 그리기
- drawWall()
- 공 그리기
- rigid_body.draw();
- RigidCircle 공 객체의 draw 호출
- R키 누르면 리셋
- if (isKeyPressedAndReleased(GLFW_KEY_R)) reset();
- void reset 공의 위치와 속도를 초기화
- 공의 위치 & 속도 업데이트
- 매 프레임 실행됨 (Game2D의 update 오버라이딩)
#include "Game2D.h"
#include "Examples/PrimitivesGallery.h"
#include "RandomNumberGenerator.h"
#include <vector>
#include <memory>
namespace jm
{
class RigidCircle
{
public:
vec2 pos;
vec2 vel;
const float radius = 0.1f;
void draw() // 원 그리기
{
beginTransformation();
{
translate(pos);
drawFilledCircle(Colors::hotpink, radius - 1e-3f);
setLineWidth(2.0f);
drawWiredCircle(Colors::black, radius);
}
endTransformation();
}
void update(const float & dt)
{
// numerical integration 수치 적분
// wall collision, friction 벽에 부딪치면 튕겨내기, 마찰력
}
};
class Example : public Game2D
{
public:
RigidCircle rigid_body; // RigidCircle 객체
Example()
: Game2D()
{
reset();
}
void reset()
{
// Initial position and velocity
rigid_body.pos = vec2(-0.8f, 0.3f);
rigid_body.vel = vec2(10.0f, 0.0f);
}
void drawWall()
{
setLineWidth(5.0f);
drawLine(Colors::blue, { -1.0f, -1.0f }, Colors::blue, { 1.0f, -1.0f });
drawLine(Colors::blue, { 1.0f, -1.0f }, Colors::blue, { 1.0f, 1.0f });
drawLine(Colors::blue, { -1.0f, -1.0f }, Colors::blue, { -1.0f, 1.0f });
}
void update() override
{
// physics update
rigid_body.update(getTimeStep() * 0.1f); // 0.1f 은 애니메이션 속도가 될 것. 더 올려보고 내려보고 해보자
// draw
drawWall();
rigid_body.draw();
// reset button
if (isKeyPressedAndReleased(GLFW_KEY_R)) reset();
}
};
}
int main(void)
{
jm::Example().run();
return 0;
}
🔔 RigidCircle의 update() 함수 구현하기 : 공 튕기기
1. 위치 업뎃 : 공을 움직이게 하기
거리 = 속력 × 시간
- pos = pos + vel * dt
- pos는 1프레임당 vel만큼 더해져 갱신된다.
- 시간의 흐름에 따라 위치는 속도값만큼 더해진다.
- 1프레임당 vel만큼 이동하게 되는 것
void update(const float & dt)
{
pos = pos + vel * dt;
}
- 여기까지 구현하면 공이 수평방향으로 계속 날아간다.
2. 속도 업뎃 : 중력
- 중력을 받아 공이 점점 아래로 떨어지게 하자.
void update(const float & dt)
{
static const vec2 gravity = vec2(0.0f, -9.8f);
vel = vel + gravity * dt;
pos = pos + vel * dt;
}
가속도에 대하여
뉴턴의 제 2법칙
: 힘이 가속도를 바꾸고 가속도가 속도를 바꾸고 속도가 위치를 바꾼다.수치 적분
을 통해 풀어나간다 : 힘 👉 가속도 👉 속도 👉 위치
- 가속도
가속도 = 힘 / 질량
으로 구할 수 있다. \(\vec{f} = m\vec{a}\)- 시간에 따라 속도가 변화하는 양
- 아주 아주 작은 시간동안 속도가 얼만큼 변했는가
- vel += accel * dt
- 위치를 시간에 대해서 미분 2번
- 속도
- 시간에 따라 거리가 변화하는 양
- 아주 아주 작은 시간동안 위치가 얼만큼 변했는가
- pos += vel * dt
- 위치를 시간에 대해서 미분 1번
중력 가속도
- 지구의 중력 가속도는
9.8
이다.- 아주 아주 작은 시간동안 속도가 9.8 만큼 변화한다.
- 1 프레임당 vel값이 -9.8 만큼 누적되어 변화한다.
- 9.8이라는 값으로 이미 있으므로 힘은 구하지 않는다 (가속도 = 힘/질량)
- 중력은 y방향 아래로 작용한다.
코드 설명
- static const vec2 gravity = vec2(0.0f, -9.8f);
- 중력 가속도는 y 마이너스 방향으로 영향을 미친다. (중력으로 공이 떨어지니까)
- 중력 가속도 값은 불변이므로
cosnt
로 해주었고 초기화는 한번만 하고 값을 유지시키기 위해static
- vel = vel + gravity * dt;
- 중력에 의한 속도 += 중력가속도 × 한프레임당 시간
- 여기까지 구현하면 공이 수평방향으로 날아가다가 서서히 아래로 떨어지는 애니메이션이 완성된다.
3. 탄성 : 벽에 부딪치면 튕기게 하기
1. 충돌 검사
1.0f - pos.x <= radius
- ‘오른쪽 벽과 충돌했다면’ 이라는 의미.
- x축 수평 방향으로 오른쪽 벽의 위치를 1.0f라고 한다면
- 벽와 공의 중심의 거리 (1.0f - pos.x) 가
- 공의 반지름보다 같거나 작다면
- 매 프레임 공이 이동하고 매 프레임 검사하기 때문에 작을 수 있음. 벽을 살짝 넘어서는 순간이 있을 수 있음.
- 순간적으로 오른쪽 벽과 부딪친 것이다.
- ‘오른쪽 벽과 충돌했다면’ 이라는 의미.
pos.x = 1.0f - radius;
- 오른쪽 벽과 충돌하여 pos.x + radius 가 오른쪽 벽의 위치를 넘어 섰다면 우선 다시 pos.x 를 벽에 딱 붙게 조정해준다.
- 여기까지만 하고 실행하면 공이 벽에 부딪치는 순간 벽에 붙은 체로 중력을 받아 쭉 떨어지는 모양이 된다.
void update(const float & dt)
{
static const vec2 gravity = vec2(0.0f, -9.8f);
// numerical integration
vel = vel + gravity * dt;
pos = pos + vel * dt;
// wall collision, friction
if (1.0f - pos.x <= radius) // right wall
{
pos.x = 1.0f - radius;
}
}
2. 얼마나 빨리 튕겨져 나올 것인지.
void update(const float & dt)
{
static const vec2 gravity = vec2(0.0f, -9.8f);
static const float coef_res = 0.7f;
// numerical integration
vel = vel + gravity * dt;
pos = pos + vel * dt;
// wall collision, friction
if (1.0f - pos.x <= radius) // right wall
{
pos.x = 1.0f - radius;
if(vel.x >= 0.0f) // 안전 장치
vel.x *= -1.0f * coef_res;
}
}
- static const float coef_res = 0.7f;
- 탄성력의 정도를 0.7이라고 하자.
- if(vel.x >= 0.0f)
- 👉 오른쪽에 부딪치는거니까 오른쪽 방향을 향하고 있는 상태, 즉 vel.x >= 0.0f 인 경우만.
- 튕겨져 나갈 때 공의 방향이 뒤집힘</u
- vel.x *=
-1.0f
; - 속도에
-1
를 곱해서 방향을 반대로 뒤집는다. - 오른쪽 방향을 향하다가 벽에 부딪쳤으면 왼쪽으로 튕겨나가야 하므로 방향을 바꿈
- vel.x *=
- 공의 질량, 공의 딱딱한 정도 벽의 딱딱한 정도 등등에 따라 튕겨나오는 속도가 달라진다.
- vel.x *= -1.0f *
coef_res
; coef_res
값이 1.0f보다 크게 설정하면 더 강한 속도로 튕겨져 나온다.coef_res
값이 1.0f보다 작게 설정하면 더 줄어든 속도로 튕겨져 나온다.
- vel.x *= -1.0f *
4. 나머지 다른 벽들도 완성
왼쪽 벽과 땅도 ~
void update(const float & dt)
{
static const vec2 gravity = vec2(0.0f, -9.8f);
static const float coef_res = 1.0f;
// numerical integration
vel = vel + gravity * dt;
pos = pos + vel * dt;
// wall collision, friction
if (1.0f - pos.x <= radius) // right wall
{
pos.x = 1.0f - radius;
if(vel.x >= 0.0f)
vel.x *= -1.0f * coef_res;
}
if (pos.y <= -1.0f + radius) // ground
{
pos.y = -1.0f + radius;
if (vel.y <= 0.0f)
vel.y *= -1.0f * coef_res;
}
if (pos.x <= -1.0f + radius) // left wall
{
pos.x = -1.0f + radius;
if (vel.x <= 0.0f)
vel.x *= -1.0f * coef_res;
}
}
5. 마찰력 : 공이 바닥에 닿으면 서서히 멈춰야함
if (pos.y <= -1.0f + radius) // ground
{
pos.y = -1.0f + radius;
if (vel.y <= 0.0f)
vel.y *= -1.0f * coef_res;
vel.x *= coef_friction; // ✨추가
}
- 마찰력
- 공의 속도를 줄여 공이 멈추게 한다.
- 바닥에서 구를 때 작용된다.
- 바닥에 평행한, x축 방향의 속도에 영향을 준다.
- vel.x 를 줄어들게 함
- 바닥에 평행한, x축 방향의 속도에 영향을 준다.
- static const float coef_friction = 0.9f;
- 0.9처럼 1보다 작은수여야 속도가 줄어들 수 있다.
- vel.x *= coef_friction;
6. 완성
void update(const float & dt)
{
static const vec2 gravity = vec2(0.0f, -9.8f);
static const float coef_res = 0.7f; // coefficient of restitution
static const float coef_friction = 0.9f; // friction (not physical)
// 공의 위치와 속도 업데이트
vel = vel + gravity * dt;
pos = pos + vel * dt;
// 각각의 벽에 부딪쳤을 때 처리
if (1.0f - pos.x <= radius) // right wall
{
pos.x = 1.0f - radius; // 벽을 넘어가지 않게
if(vel.x >= 0.0f)
vel.x *= -1.0f * coef_res; // 탄성력에 따른 속도 조정
}
if (pos.y <= -1.0f + radius) // ground
{
pos.y = -1.0f + radius;
if (vel.y <= 0.0f)
vel.y *= -1.0f * coef_res;
vel.x *= coef_friction; // 바닥에 부딪쳤을땐 특별히 마찰력에 따른 x축 방향의 속도 조정
}
if (pos.x <= -1.0f + radius) // left wall
{
pos.x = -1.0f + radius;
if (vel.x <= 0.0f)
vel.x *= -1.0f * coef_res;
}
}
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기