반응형


1. HUD Scene 추가

HUD는 Head Up Display의 약자인데 게임의 요소와는 상관없는 UI 작성을 위한 부분입니다. Scene을 하나 추가한 뒤 CanvasLayer노드를 추가합니다. HUD에서 표시할 내용으로는 Score, "Game Over" 또는 "Get Ready!" 같은 문구, "Start" 버튼입니다. 즉 레이블과 버튼 요소가 필요합니다. 아래의 노드들을 CanvasLayer노드 하위에 자식노드로 추가해줍니다.

 

  • ScoreLabel (Label)
  • Message (Label)
  • StartButton (Button)
  • MessageTimer (Timer)

2. 폰트

폰트를 추가합니다. 경로는 인스펙터 창에서 Control > Theme Overrides > Fonts > 새 DynamicFont를 선택하고, 한번 더 클릭하여 Font > Font Data부분에 dodge_assets > font에 있는 "Xolonium-Regular.ttf"폰트를 넣어줍니다.(드래그 앤 드랍)

Font > Settings > Size는 64로 설정합니다.

 

font >Dyna....의 아래쪽 화살표를 누르면 여러 메뉴가 나오고, "복사" 메뉴가 나옵니다. 이걸 누르면 복사가 가능한데, 만들어두었던 Message 라벨로 가서 동일한 위치에서 "붙여넣기"를 해주면 지금 했던 설정을 복사해 넣게됩니다.




3. 레이아웃

우선 ScoreLabel을 설정해보겠습니다. 상단 오른쪽에 나타나는 레이아웃을 선택하고 '위쪽 넓게'를 클릭합니다. 그리고 인스펙터 창에서는 Text: 0, Align: Center, Valign: Center로 설정해줍니다.

다음으로 Message 라벨을 수정해줍니다.

그리고 StartButton은 "Start"라고 텍스트를 입력하고, 레이아웃은 "아래쪽 중앙", Font도 복하를 해줍니다.

그리고나서  Margin속성에 Top: -200, Button: -100을 입력합니다.


MessageTimer노드에서 Wait Time을 2초로 설정해주고, One Shot속성을 "On"으로 합니다.

 

4. HUD.gd 스크립팅

start_game 시그널은 버튼이 눌렸을 때 Main노드에게 알려주는 시그널로 쓰일 예정입니다.

extends CanvasLayer

signal start_game


전달되는 메시지를 표시하기위한 함수를 작성합니다.

func show_message(text):
    $Message.text = text
    $Message.show()
    $MessageTimer.start()


Player가 충돌하여 죽었을 때 텍스트를 보여주는 함수를 아래와 같이 작성합니다.

func show_game_over():
    show_message("Game Over")
    # Wait until the MessageTimer has counted down.
    yield($MessageTimer, "timeout")

    $Message.text = "Dodge the\nCreeps!"
    $Message.show()
    # Make a one-shot timer and wait for it to finish.
    yield(get_tree().create_timer(1), "timeout")
    $StartButton.show()


그리고 시간이 변할때마다 Main 씬에의해 아래의 코드가 지속적으로 호출되어 점수(시간)을 증가시킵니다.

func update_score(score):
    $ScoreLabel.text = str(score)


그리고 MessageTimer의 timeout()시그널과, StartButton버튼의 pressed()시그널에 함수를 연결하겠습니다. 함수는 아래와 같이 작성해줍니다.

func _on_StartButton_pressed():
    $StartButton.hide()
    emit_signal("start_game")

func _on_MessageTimer_timeout():
    $Message.hide()


5. HUD를 Main에 연결

Main 씬에서 HUD를 불러와 연결하도록 하겠습니다.

Main 씬에서 HUD 씬(노드)를 선택하고, 노드탭으로 가면 연결할 수 있는 HUD의 시그널들이 나타납니다. 여기서 start_game시그널을 연결할 함수로 new_game()이라고 이름짓고 아래의 코드를 추가해줍니다.

$HUD.update_score(score)
$HUD.show_message("Get Ready")


game_over() 함수가 실행될 때 HUD에 표시되는 메시지를 실행하도록 아래 라인을 추가해줍니다.

$HUD.show_game_over()


_on_ScoreTimer_timeout() 함수에 아래 코드를 추가하여 점수 변경시 업데이트되도록 합니다.

$HUD.update_score(score)



6. 오래된 크리프 제거

아직은 Player가 죽고나서 다시 시작할 때 Mob들이 살아있습니다. 그래서 시작버튼을 누르면 전부 사라지도록 수정할 필요가 있습니다.

 

Mob 씬에서 루트노드를 선택하고 노드탭 > Groups탭 으로 갑니다. 그리고 이름을 "mobs"라고하여 Add버튼을 눌러 Mob들의 그룹을 지정합니다. 그리고는 game_over()함수에 아래의 코드를 추가합니다. 해당 그룹에 있는 객체들을 한번에 제거하는 명령입니다.

get_tree().call_group("mobs", "queue_free")

7. 마무리

Background

Main노드 최상단 자식노드로 ColorRect 노드를 추가한다. "Layout" -> "Full Rect" 으로 전체 화면 커버가 가능하다.TextureRect노드를 사용하면 이미지를 불러올 수도 있다.

Sound effects

Main신의 하위로 AudioStreamPlayer노드를 추가하고 이름을 Music이라고 변경합니다. 하나더 추가하여 DeathSound라고 이름을 변경합니다. Stream속성의 Load를 클릭하여 해당되는 오디오 파일을 지정해줍니다.
 
new_game()함수 아래 아래 코드를 추가합니다.
$Music.play()

game_over() 함수 아래 아래코드를 추가합니다.

$Music.stop()
$DeathSound.play()

Keyboard shortcut

HUD씬에서 StartButton을 클릭하고 Shortcut속성을 찾아서 "New Shortcut" > "Shortcut"을 클릭하면 두번째 속성이 나타납니다. "New InputEventAction">"InputEventAction"을 입력하면 Action속성이 나타나는데, ui_select라고 타이핑 입력합니다. 이게 스페이스바와 연동되는 지시어라고 합니다.

 

- 끝 -

 
반응형
반응형

1. Main Scene 생성

모든 요소들을 모을 Main Scene 을 생성합니다. 새 씬 생성후 "Node"라는 이름의 노드를 추가해줍니다.

그리고 "인스턴스화"아이콘을 클릭하여 Player.tscn을 불러옵니다.

그 외에는 자식노드 추가 기능에서 Timer 3개, Position2D 노드를 추가해주고, 각각의 이름을 아래와 같이 수정해줍니다.


MobTimer - 가장자리에서 Mob이 생성되는 시간 통제 (0.5초)
ScoreTimer - 매초마다 점수 증가시킴 (1.0초)
StartTimer - 시작하기 전에 지연 시간 부여 (2.0초)
StartPosition - Player의 시작 위치(240, 450)

2. Mob 생성

Main 노드가 Mob을 가장자리를 따라 랜덤한 위치에서 생성합니다. 이를 위해서는 Main 노드 하위에 Path2D 노드를 추가해줍니다. Path2D노드를 선택하면 아래와 같은 화면으로 변경되는데, 그 전에 "스마트 스냅 사용", "격자 스냅 사용" 을 클릭합니다. 그리고, 새로 생긴 Path관련 버튼 중 가운데 버튼(1: 점추가)을 누른다음 화면의 네 꼭지점을 시계방향(2~5)으로 클릭해줍니다. 마지막으로 마지막 버튼(6: 곡선닫기)을 클릭해주면 경로가 완성됩니다.


다음으로 PathFollow2D 노드를 Path2D노드 하위에 추가해줍니다. 이 노드는 Path를 따라서 어떤 작업을 해줍니다. 이 게임에서는 Mob 생성이겠죠?

그리고 각각의 이름을 MobPath, MobSpawnLocation으로 바꿔줍니다.

 


3. Main.gd 스크립팅

우선 아래의 코드로 시작하겠습니다.

extends Node

export (PackedScene) var Mob
var score

func _ready():
	randomize()

export (PackedScene) var Mob 구문으로 선언하면, 인스펙터 창에서 Mob이라는 속성이 나타나는 것을 확인할 수 있습니다. 여기에는 Scene을 넣어줄 수 있는데, 우리가 구성한 Mob.tscn을 넣어줍니다.(드래그 앤 드랍)

 

다음으로 Player가 Mob과 부딛혔을 때(hit signal) 게임오버를 나타내는 함수를 작성/연결하겠습니다. 조금 헷갈릴 수도 있는데요, Main Scene 하위에 있는 Player Scene을 선택(한번 클릭)하면, 우측의 "노드" 창에 시그널들이 나타납니다. 우리가 만든 hit 시그널도 보입니다. hit()시그널을 더블클릭하면 메서드 연결창이 나타납니다. 작성할 스크립트가 Main인 것을 확인하고, 받는 메서드의 이름을 game_over로 하여 "연결"을 클릭합니다.


추가되어야 하는 코드는 아래와 같습니다. new_game() 함수도 함께 작성합니다.

func game_over():
    $ScoreTimer.stop()
    $MobTimer.stop()

func new_game():
    score = 0
    $Player.start($StartPosition.position)
    $StartTimer.start()


다음으로 각 타이머와 연결된 함수를 만들겠습니다. 우선 StartTimer, ScoreTimer의 timeout() 시그널을 더블 클릭합니다.

각각의 함수는 아래와 같이 코딩합니다.

func _on_StartTimer_timeout():
    $MobTimer.start()
    $ScoreTimer.start()

func _on_ScoreTimer_timeout():
    score += 1

_on_StartTimer_timeout()함수에서는 MobTimer와 ScoreTimer가 시작되며,

_on_ScoreTimer_timeout()함수에서는 시간이 흐름에 따라 1점식 올라가게 될 것입니다.

 

MobTimer의 timeout()시그널을 더블클릭하여 연결함수를 생성합니다. _on_MobTimer_timeout()에서는 mob을(mob 인스턴스를) 생성할 것입니다.

func _on_MobTimer_timeout():
	# Choose a random location on Path2D.
    $MobPath/MobSpawnLocation.offset = randi()
    # Create a Mob instance and add it to the scene.
    var mob = Mob.instance()
    add_child(mob)
    # Set the mob's direction perpendicular to the path direction.
    var direction = $MobPath/MobSpawnLocation.rotation + PI / 2
    # Set the mob's position to a random location.
    mob.position = $MobPath/MobSpawnLocation.position
    # Add some randomness to the direction.
    direction += rand_range(-PI / 4, PI / 4)
    mob.rotation = direction
    # Set the velocity (speed & direction).
    mob.linear_velocity = Vector2(rand_range(mob.min_speed, mob.max_speed), 0)
    mob.linear_velocity = mob.linear_velocity.rotated(direction)

4. 테스트

func _ready():
    randomize()
    new_game()


플레이어 이동, 몹 생성, 충돌시 사라지기가 정상 작동되면 다음 HUD 구성을 위해 new_game()부분은 제거해줍니다.

 

- Main Scene 끝 -

반응형
반응형

1. 프로젝트 설정

프로젝트 > 프로젝트 설정을 클릭한 후, display > window메뉴로 가서 width480, height720으로 설정해줍니다.

그리고, Stretch로 내려가서 Mode: 2d, Aspect: keep으로 변경해줍니다.

2. Resource 폴더 추가

dodge_assets 파일은 링크 에서 다운받을 수 있습니다. Godot엔진 홈페이지의 Your First Game 챕터에서 찾을 수도 있습니다. 압축을 풀어서 생성된 프로젝트 폴더에 집어넣으면 아래와 같이 자동으로 프로젝트에 폴더가 추가됩니다.

3. Player 씬 추가

왼쪽 씬 탐색기에서 다른노드 > Area2D검색 >Area2D 를 선택합니다.

노드를 더블클릭하여 이름을 Player로 바꿔줍니다. 그리고 Player.tscn으로 저장합니다.

 

그리고 자물쇠 아이콘 옆의 아이콘을 클릭해줍니다. 추후 자식 노드들이 추가될텐데, 자식노드들만 따로 선택되는 것을 방지해주는 기능이라고 합니다.

Player 노드 하위에 AnimatedSprite를 검색하여 추가합니다.

AnimatedSprite노드가 추가되면 우측의 Frames속성이 아직 [비었음] 상태입니다. [비었음]을 클릭하여 새 SpriteFrames를 선택합니다.

그리고 선택된 SpriteFrame를 한번 더 눌러주면 아래의 애니메이션 패널이 나타납니다.

4. 애니메이션 프레임 추가

Default를 walk라고 이름을 바꾸고, 새 애니메이션 추가 버튼을 클릭하여 up으로 이름으로 만들어줍니다. 아까 추가한 dodge_assets 폴더로 가보면 art라는 하위폴더가 있는데, 여기에서 PlayerGrey_walk1, 2는 walk프레임에, PlayerGrey_up1, 2는 up프레임에 드래그앤드랍으로 추가해줍니다.

다시 AnimationSprite노드를 클릭하고, 인스펙터 창에서 Node2D > Scale에서 x와 y를 각각 0.5로 입력하여 사이즈를 반으로 줄여줍니다.

5. 충돌감지노드 추가

Player노드에서 우클릭하여 새 노드를 추가합니다.검색창에 CollisionShape2D를 검색하여 추가해줍니다.

이 노드는 현재의 노드가 장애물(Mob)과 충돌을 감지할 수 있는 영역을 만들수 있게 해 줍니다. 즉, 이 노드만으로는 안되고, 실제 감지되는 영역을 지정해줘야합니다. 인스펙터 창에서 Shape > [비었음] 클릭 > 새 CapsuleShape2D 를 클릭하여 추가해줍니다.

그러면 캐릭터에 캡슐형 감지영역이 나타나게 됩니다.  감지영역의 핸들을 조정하여 적당히 커버될 수 있도록 조정합니다. 약간 이미지보다 작게 나타나도록 설정하는 것을 추천합니다.


6. Player 방향키 설정

적당히 진행하며 저장해주시고, 이제 Player의 움직임을 설정해줘야합니다. 여기서부터 코딩이 들어갑니다. Attach Script 버튼을 클릭합니다.

우선 필요한 변수를 선언합니다. (speed, screen_size)

export 키워드는 작업을 굉장히 편리하게 해 줍니다. 변수 앞에 export를 붙여주면 inpector 창에서 나타나게 됩니다. 그럼 inpector에서 값을 직접 설정하여, 테스트할 때 일일이 코드를 보지 않고도 테스트할 수 있도록 도와줍니다. 물론 스크립트에 설정된 기본값은 무시됩니다.

 

처음 게임이 시작되면 _ready()함수를 실행하게 되는데, 게임 창의 크기값을 가져와 screen_size라는 변수에 지정하도록 하겠습니다. 이 값은 나중에 활용할 계획입니다. 기존에 있던 pass 부분은 지워줍니다. 아무것도 안하는 함수일 때 필요한 지시어입니다.

func _ready():
    screen_size = get_viewport_rect().size

이제 진짜 움직임을 코딩합니다.

func _process(delta):
    var velocity = Vector2()  # The player's movement vector.
    if Input.is_action_pressed("ui_right"):
        velocity.x += 1
    if Input.is_action_pressed("ui_left"):
        velocity.x -= 1
    if Input.is_action_pressed("ui_down"):
        velocity.y += 1
    if Input.is_action_pressed("ui_up"):
        velocity.y -= 1
    if velocity.length() > 0:
        velocity = velocity.normalized() * speed
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.stop()

우선 $AnimatedSprite.play() 의$는 get_node()의 단축 표기형입니다. get_node("AnimatedSprite").play()와 동일합니다. 위 코드는 아직 오류가 있는 것이 상하키와 좌우키를 동시에 누르면 1+1=2의 속도로 원래 움직임보다 더 빠르게 움직입니다.
다음으로 화면 밖으로 나가는 것을 방지하기 위해 clamp()함수로 영역을 지정해줍니다. func _process(delta)함수 하부에 이어서 아래 코드를 추가해줍니다.

position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)

이제 "재생 장면" (F6)을 클릭하여 확인해봅니다.

이렇게 좌상단 구석에 우리 캐릭터가 위치하고 있습니다. 방향키를 눌러보면 움직이는 것이 보일 것입니다. 좀더 세부적인 동작은 아래에서 계속 하겠습니다.


7. 애니메이션 지정

Player가 상하로 움직일 때, 좌우로 움직일 때 각각 다른 애니메이션이 나오도록 지정을 해줘야 합니다. 방향을 역전시키는 것은 flip_v / flip_h 속성으로 간단히 만들 수 있습니다.

if velocity.x != 0:
    $AnimatedSprite.animation = "walk"
    $AnimatedSprite.flip_v = false
    $AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite.animation = "up"
    $AnimatedSprite.flip_v = velocity.y > 0

그리고, _ready() 함수에 아래 코드를 추가하여  게임 시작시 Player를 숨김상태로 시작하게 합니다.

hide()


8. 충돌 감지

이제 signal 기능을 이용하여 장애물과의 충돌을 인식시키도록 할 차례입니다. 스크립트 최상단 - extends Area2D 아래 - 에 아래 코드를 추가합니다.

signal hit

이렇게 하면 사용자가 정의하는 signal을 지정하게 됩니다. Player가 방출할 수 있는 signal의 종류는 Inspector창 옆의 Node탭에 보면 나타나게 됩니다. 방금 지정한 hit 시그널도 확인되는 것을 볼 수 있습니다.

본 게임에서의 장애물(Mob)은 RigidBody2D이고 body_entered(body: Node) 시그널이 발생하도록 할 계획입니다. 즉,

body_entered(body: Node)시그널 -> Player 사라짐

body_entered(body: Node)시그널 -> hit 시그널 -> 게임오버 진행 (Main.tscn에서 작성)

으로 구성할 예정입니다. 하단의 "연결" 또는 마우스 우클릭 후 "연결"을 클릭하고, 자동 생성된 함수명으로 "연결"을 클릭합니다.

그리고 연결되었다는 의미로 해당 함수 앞에 초록색 아이콘이 생성됩니다. 생성된 함수 내부에 아래와 같이 코딩합니다.

func _on_Player_body_entered(body):
    hide()  # 부닫히면 Player를 사라지게..
    emit_signal("hit")
    $CollisionShape2D.set_deferred("disabled", true)


이제 Mob이 Player와 충돌할 때마다 Signal이 방출되게 됩니다.

9. 초기화

마지막으로 새 게임 시작시 Player를 초기화하는 코드를 추가해줍니다.

func start(pos):
    position = pos
    show()
    $CollisionShape2D.disabled = false

 

<전체 코드:Player.gd>

extends Area2D

signal hit
export var speed = 400
var screen_size

# Called when the node enters the scene tree for the first time.
func _ready():
	screen_size = get_viewport_rect().size
	hide()


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	var velocity = Vector2()  # The player's movement vector.
	if Input.is_action_pressed("ui_right"):
		velocity.x += 1
	if Input.is_action_pressed("ui_left"):
		velocity.x -= 1
	if Input.is_action_pressed("ui_down"):
		velocity.y += 1
	if Input.is_action_pressed("ui_up"):
		velocity.y -= 1
	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite.play()
	else:
		$AnimatedSprite.stop()
		
	# position
	position += velocity * delta
	position.x = clamp(position.x, 0, screen_size.x)
	position.y = clamp(position.y, 0, screen_size.y)
	
	# 방향 역전
	if velocity.x != 0:
		$AnimatedSprite.animation = "walk"
		$AnimatedSprite.flip_v = false
		$AnimatedSprite.flip_h = velocity.x < 0
	elif velocity.y != 0:
		$AnimatedSprite.animation = "up"
		$AnimatedSprite.flip_v = velocity.y > 0

func start(pos):
	position = pos
	show()
	$CollisionShape2D.disabled = false

func _on_Player_body_entered(body):
	hide()  # Player disappears after being hit.
	emit_signal("hit")
	$CollisionShape2D.set_deferred("disabled", true)

 

- Player Scene 끝 -

반응형

+ Recent posts