-
Notifications
You must be signed in to change notification settings - Fork 0
Description
CHAPTER 3 Space Rocks: Build a 2D Arcade Classic with Physics
Summary overview
- Use custom input actions
- Implementation physics with RIgidBody2D
- Game logic with finite state machine
- Dynamic and flexible UI
- Sounds and musics
- Particles
3.1 Technical Requirements
3.2 Setting up the project
new input actions: rotate_left, rotate_right, thrust, shoot
3.2.1 Rigid body physics
두 오브젝트가 접촉하는 시점 => collision detection
Godot engine은 PhysicsBody2D node type으로 3종류의 physics body를 제공한다.
- StaticBody2D: physics body에 의해 움직이지 않고 collision detection에만 반응한다. 벽이나 지면같이 환경적인 부분의 동적 액션이 필요하지 않은 부분에 사용한다.
- RigidBody2D: physics simulation body, 중력이나 충격등의 힘을 주면 물리 엔진을 통해 collision, bouncing, rotation 등의 효과를 포함한 결과를 계산한다.
- CharacterBody2D: collision detection만 할 수 있고 그 외 물리나 움직임은 코드로 구현해야 하고 collision response 도 직접 구현해야 한다.
특정 physics body를 언제 사용해야 하는지 이해하는 건 게임 제작에서 큰 부분을 차지한다.
예제에서는 space ship and rocks 모두 RigidBody2D node를 사용한다.
RigidBody2D node는 Mass, Friction, Bounce 등의 property가 있다.
기본 중력 값은 980, 방향은 (0, 1)인 아래쪽을 향한다.
하지만 이 게임은 우주 공간이므로 중력이 필요가 없다.
중력 값을 0으로 설정한다.
3.3 The player's ship
3.3.1 Body and physics setup
Note:
Sprite 방향을 90도로 하는 이유 =>
고도에서 0도 기준 방향은 오른쪽이다. 그러므로 스프라이트를 바디의 방향과 일치하게 회전시켜야 한다.
보통 아트 애셋의 경우 위쪽 방향이 흔하므로 이런 방법을 알고 있어야 한다.
Tips:
Scene and scripts file 모두 루트 프로젝트 폴더에 저장하지 말고 각 게임 오브젝트에 따른 폴더로 정리하면 좋다.
프로젝트 규모가 커지고 복잡해 지면 이 방법을 쓰는 게 좋은 습관이다.
3.3.2 Finite machines
3.3.3 Adding player controls
3.3.4 Screen wrap
screen wrap: 플레이어가 화면의 한쪽을 벗어나면 반대편에 나타나는 것
게임에서는 우주선의 위치를 반대편으로 순간 이동하는 식으로 구현한다.
처음에 screensize 설정을 가지고 화면 경계를 넘어갈 때 반대편으로 설정하면 된다고 생각할 수 있다.
RigidBody2D는 직접 position을 설정할 수 없는데, 물리 엔진이 계산하는 이동과 상충하기 때문이다.
흔히 하는 실수로 다음과 같은 코드를 작성하는 것이다.
func _physics_process(delta):
if position.x > screensize.x:
position.x = 0
if position.x < 0:
position.x = screensize.x
if position.y > screensize.y:
position.y = 0
if position.x > screensize.x:
position.x = screensize.x코인 대시 게임은 Area2D 이므로 이 코드를 쓰면 완벽하게 작동한다.
하지만 RigidBody2D는 플레이어가 가장자리에 갇히고 모서리에는 예측 불가능한 글리치glitch가 발생한다.
glitch: 정상 작동은 아니지만 게임이 작동은 하는 사소한 오류
RigidBody2D에 대한 문서 인용
참고: RigidBody2D의 position이나 linear_velocity를 매 프레임은 물론이고 자주 변경해서도 안 된다.
바디 상태에 직접 영향을 줘야 하는 경우는 _integrate_forces를 사용해야 하고 물리 상태에 직접 접근할 수 있다.
reference from:
https://docs.godotengine.org/en/stable/classes/class_rigidbody2d.html
_integrate_forces() 설명 인용
오브젝트의 시뮬레이션 상태를 읽고 안전하게 수정할 수 있다.
바디의 위치나 기타 물리 속성을 직접 변경해야 하는 경우 _physics_process 대신 이 함수를 쓴다.
reference from:
https://docs.godotengine.org/en/stable/classes/class_rigidbody2d.html#class-rigidbody2d-private-method-integrate-forces
_integrate_forces()를 사용하면 바디의 PhysicsDirectBodyState2D에 접근할 수 있는데
바디의 현재 상태에 대한 정보가 있는 godot 오브젝트이다.
여기서 location을 변경해야 하므로 Trasnform2D를 수정한다.
Transfrom은 공간에서 translation, rotation, scaling 등 하나 이상의 transformation을 나타내는 행렬이다.
Translation은 Trasnform2D.origin 속성을 접근한다.
func _integrate_forces(physics_state):
var xform = physics_state.transform
xform.origin.x = wrapf(xform.origin.x, 0, screensize.x)
xform.origin.y = wrapf(xform.origin.y, 0, screensize.y)
physics_state.transform = xformwrapf() 함수는 첫 번째 파라미터 값 을 보고 두 번째 파라미터인 최소값, 세 번째 파라미터인 최대값 사이에 '휘감기wrap'한다.
0 미만의 값은 screensize.x가 되고 반대도 마찬가지가 된다.
파라미터 이름이 physics_state인 이유는 state가 이미 플레이어의 상태를 추적하는데 사용하고 있으므로 혼동을 피하기 위해 만들어진 것이다.
3.3.5 Shooting
shoot action은 bullet이 우주선 앞에 스폰되고 화면에서 사라질 때 까지 일직선으로 이동하게 만든다.
shoot 이후 다음 shoot 까지 cooldown time 이 필요하며 그 사이에 shoot을 할 수는 없다.
- Bullet scene
- Shoot bullet
- Player ship test
shoot action이 있을 때 마다 Bullet scene instance를 생성하면 되는데
Player 단독으로 실행하고 테스트 하기 위해 fixed tree layout을 가정하면 안 된다.
get_parent(), add_chile()를 사용할 수 있긴 하지만, 피하려고 노력해야 한다.
그러면 훨씬 더 모듈화된 디자인이 되고 흔한 실수를 몇 가지 방지할 수도 있다.
Bullet은 게임 최상위 노드, SceneTree에 있는 루트 노드의 자식으로 만들면 좋다.
Main scene 생성 후 Sprite2D로 배경을 만든 후에, Player 인스턴스를 추가한다.
이후 main scene을 실행해서 비행, 사격이 가능한지 테스트해 본다.
3.4 Adding the rocks
우주선과 마찬가지로 바위도 RigidBody2D를 사용한다.
일직선으로 이동하도 서로 부딪히면 튕겨 나가는 효과를 준다.
바위는 큰 것으로 시작하고 레이저에 맞으면 여러 개의 작은 바위로 부서지게 한다.
3.4.1 Scene setup
3.4.2 Variable size rocks
3.4.3 Instantiating rocks
3.4.4 Exploding rocks
총알이 바위의 explode() 메서드를 호출하면 아래 3 가지 일을 해야 한다.
- Remove rocks
- Play explode animations
- Notify to create new small rocks to Main
Implementation and record play video
3.5 Creating the UI
다양한 Control 노드의 사용법을 익히면 세련된 UI를 만드는 부담을 덜 수 있다.
이 게임은 UI가 복잡하지 않으므로 다음과 같이 상호작용 할 수 있는 UI가 있으면 좋다.
- Start button
- Status message("Ready" or "Game Over")
- Score
- Lives counter
3.5.1 Layout
3.5.2 Scripting the UI
3.5.3 The main scene's UI code
Main에서 연결한 HUD scene 인스턴스에서 노드 탭의 시그널 중 start_game을 연결하면
아래와 같은 팝업이 나오는데 Receiver method 옆의 Pick 버튼을 누르면
Main 함수 중에 어떤 함수를 선택할지 고를 수 있다. 여기서는 new_game()을 선택한다.
선택한 후에 new_game 함수가 있는 스크립트를 보면 해당 라인에 connect 되어 있다는 아이콘이 표시가 되고
클릭해 보면 source, signal, target에 대한 정보를 보여준다.
3.5.4 Player code
var lives = 0: set = set_liveslives 변수에 set 이라는 키워드를 통해 setter를 추가한 것이고 lives 값을 변경하려고 할 때 set_lives() 함수가 호출된다.
3.6 Ending the game
Sprite2D.modulate.a 는 알파 채널이다. 0.5로 설정하면 반투명, 1로 설정하면 불투명이 된다.
3.6.1 Detecting collisions between rigid bodies
현재는 ship과 rock이 충돌하면 튕기는데 그 이유는 둘 다 rigidbody이기 때문이다.
두 rigidbody가 충돌할 때 이벤트를 발생시키려면 접촉 모니터링contact monitoring을 활성화한다.
Player의 Solver/Contact Monitor를 사용으로 설정하고
Max Contacts Reported를 1로 설정한다.
그러면 rock과 충돌이 일어날 때 signal을 발생시킬 수 있다.
3.7 Pausing the game
일시 정지는 SceneTree.paused 속성으로 설정할 수 있다.
SceneTree가 일시 정지하면 다음 3가지 일이 발생한다.
- 물리 스레드 실행 중지
- _process(), _physics_process()가 호출되지 않음
- _input(), _input_event() 메서드가 입력이 있어도 호출되지 않음
일시 정지가 되면 게임의 모든 노드는 설정한 값에 따라서 실행한다.
Process/Mode 속성을 통해 다음 값들로 설정할 수 있다.
- Inherit: 부모와 동일한 모드를 사용
- Pausable: SceneTree가 정지하면 노드도 정지
- When paused: 일시 정지된 경우에만 실행
- Always: 일시 정지 무시
- Disabled: 실행도 되지 않고, 일시 정지도 무시
Input map에서 pause 입력 액션을 만들고 P 키로 할당하면 적당하다.
일시 정지가 되면 Main도 포함되므로 _input() 처리를 할 수 없다.
따라서 Main 노드는 Process/Mode를 Always로 설정한다.
3.8 Enemies
3.8.1 Following the path
3.8.2 Enemy scene
3.8.3 Moving the enemy
3.8.4 Spawning enemies
3.8.5 Shooting and collisions
3.9 Player shield
3,.10 Sounds and effects
3.10.1 Sound and music
3.10.2 Particles
CPUParticles2D를 추가한 후에
흰색 점들이 줄줄이 흘러내리는 영상
Screen.Recording.2024-08-18.at.10.53.55.PM.mov
파티클 속성을 모두 설정하고 난 후에
우주선의 배기가 불꽃 처럼 보이는 영상
Screen.Recording.2024-08-18.at.11.07.18.PM.mov
3.10.3 Enemy trail
Enemy scene에서 CPUParticles2D를 추가하고 속성을 설정한 후에
적 비행접시 안에 전체에 걸쳐서 파티클이 나타난다는 부분과 더불어
Sprite2D를 숨기면 파티클이 잘 보인다는 부분에 대한 영상