[C++] 3.1 공 튕기기 시뮬레이션

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 게임 만들기 연습 문제 패키지 강의를 듣고 정리한 필기입니다.😀
🌜 공부에 사용된 홍정모 교수님의 코드들 보러가기
🌜 [홍정모의 게임 만들기 연습 문제 패키지] 강의 들으러 가기!


Chapter 3. 게임 물리 맛보기 : 공 튕기기

🔔 전체 코드

📜RigidCircle 클래스

    • 멤버
      • pos 👉 현재 위치
      • vel 👉 현재 속도
      • radius 👉 공의 반지름
    • void update(const float & dt)
      • 공의 위치와 속도를 갱신하는 함수
      • 아래에서 구현할 것

📜Example 클래스

  • RigidCircle rigid_body
    • 공 객체 생성
  • void update() override
    • 매 프레임 실행됨 (Game2D의 update 오버라이딩)
      1. 공의 위치 & 속도 업데이트
        • rigid_body.update(getTimeStep() * 0.1f);
        • RigidCircle 공 객체의 update함수 호출
      2. 벽 그리기
        • drawWall()
      3. 공 그리기
        • rigid_body.draw();
        • RigidCircle 공 객체의 draw 호출
      4. R키 누르면 리셋
        • if (isKeyPressedAndReleased(GLFW_KEY_R)) reset();
        • void reset 공의 위치와 속도를 초기화
#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;
}

가속도에 대하여

image

뉴턴의 제 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이라고 하자.
  1. if(vel.x >= 0.0f)
    • 👉 오른쪽에 부딪치는거니까 오른쪽 방향을 향하고 있는 상태, 즉 vel.x >= 0.0f 인 경우만.
  2. 튕겨져 나갈 때 공의 방향이 뒤집힘</u
    • vel.x *= -1.0f;
    • 속도에 -1를 곱해서 방향을 반대로 뒤집는다.
    • 오른쪽 방향을 향하다가 벽에 부딪쳤으면 왼쪽으로 튕겨나가야 하므로 방향을 바꿈
  3. 공의 질량, 공의 딱딱한 정도 벽의 딱딱한 정도 등등에 따라 튕겨나오는 속도가 달라진다.
    • vel.x *= -1.0f * coef_res ;
    • coef_res 값이 1.0f보다 크게 설정하면 더 강한 속도로 튕겨져 나온다.
    • coef_res 값이 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 를 줄어들게 함
  • 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;
	}
}


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

C++ games 카테고리 내 다른 글 보러가기

댓글 남기기