본문 바로가기

Programming/Godot

고도 엔진 횡스크롤 게임 예제 (Godot Platformer) - 3

반응형

1. Title screen제작

  - User Interface(Control) 추가하고 이름을 MainScreen으로 변경합니다.


  - Background 추가 후 TextureRectangle로 변경, 

  - Layout: Full Rect

  - 인스펙터에서 Expand: on, Stretch Mode: Tile 로 설정


  - MainScreen Node에 Label 노드 추가(Title)
    ○ My First Godot Game : Text 변환
    ○ Center Top: Layer 변경, Shift 누른채로 약간 아래로 변경


   - VBoxContainer추가하여 이름을 Menu로 변경하고, 아래와 같이 버튼2개를 추가합니다.
    ○ Layout: Center
    ○ Button 2개 추가 및 텍스트 변경

    ○ Button을 VBoxContainer에 끌어다 놓음
    ○ Box 사이즈 조절
    ○ Button 2개 선택 후 size flags: expand vertically


  - Title/Menu/PlayButton/QuitButton으로 각 요소의 이름을 변경하고,  src/UserInterface폴더를 생성하여 별도 scene으로 각각 저장합니다.


  - Scene저장(res://src/Screens/MainScreen.tscn으로 저장

 

  - Project > Project Settings > Run 메뉴에서 Main Scene을 기존 Level1에서 MainScreen.tscn으로 변경해줍니다. 즉, 직접 레벨이 실행되던것을 UI화면이 뜨고, Play를 누르면 Level1으로 진입하도록 합니다.

 

2. 테마 설정

Title - font에 폰트 추가 대신 Theme기능을 이용하여 Scene 전체적으로 스타일을 적용하는 과정입니다. (제공되는 테마를 사용할 경우 skip 가능)
  - Dynamic Font 추가

      ○ MainScreen노드에서 Add Resource Button을 클릭, Dynamic Font노드를 추가합니다.


    - Font에 font file추가


    - size: 24로 변경
    - Assets 폴더에 new_dynamicfont.tres로 추가


  - Add Resource Button을 한번 더 클릭합니다.
  - Theme 노드 추가


    - new_dynamicfont.tres를 Default Font에 추가


    - Assets 폴더에 ui_theme.tres로 저장


  - MainScreen에서 Theme에 ui_theme.tres를 추가


  - Title.tscn을 열고 Text를 일반적으로 쓸 수 있도록 Title로 바꿔줍니다.

     ○ Align: center
     ○ Font: font_title.tres를 추가해줍니다.


  - 다시 MainScreen으로 와서 top center: 위치 조정

 3. Play / Quit Button에 Script추가

  - PlayButton.tscn을 열고, PlayButton노드에서 새 스크립트 추가를 하여 PlayButton.gd로 이름을 변경합니다.

그리고 Signal: button_up 을 추가합니다.(_on_button_up() 함수 생성)


  - Button이름을 ChangeSceneButton으로 변경합니다.(근데, 스크립트 이름은 안바꾸네요..^^;;)


<PlayButton.gd>

tool #플러그인 사용
extends Button

export(String, FILE) var next_scene_path:="" #string형태로 scene을 받아들이고, 파일 다이얼로그를 보여줌

func _on_button_up():
	get_tree().change_scene(next_scene_path)

func _get_configuration.warning() -> String:
	return "next_screne_path must be set for the button to work" if next_scene_path=="" else ""

  - QuitButton-Script추가


  - Node탭-button_up시그널 추가(_on_button_up)


<QuitButton.gd>

extends Button

func _on_button_up():
	get_tree().quit()

 

4.EndScreen생성

  - new scene-userinterface - EndScreen.tscn저장(screen폴더)

 

  - Merge from scene - mainscreen.tscn에서 textureRect 선택으로 배경 불러오기


  - Merge from scene - mainscreen.tscn에서 Menu 선택으로 메뉴/버튼 불러오기
     ○ Next scene에 mainscreen을 선택
     ○ Play버튼 텍스트를 Play Again으로 변경


  - EndScreen선택 후 resource에서 title검색으로 title.tscn을 가져와서 화면으로 drag & drop
     ○ Title text변경: Congratulations, you finished the game
     ○ Layout: center top
     ○ 아래쪽으로 위치이동


  - Label 노드 추가
     ○Layout: center top
     ○Text수정: 텍스트 오른쪽의  expand button    

your final score is %s
you died times %s

    - 헌번 더 layout 조정, 텍스트 얼라인 center조정


  - Title, Label, Menu를 동시 선택 후 layout - center로 중앙 정렬 후 위치 조정
     ○ 화면 사이즈가 변하더라도 위치변화로 이미지가 겹치지 않는다.

 

5. Score 시스템

  - new scene - Node추가 (Autoload폴더에 PlayerData.tscen으로 저장)
  - Script 생성 - PlayerData.gd로 저장

<PlayerData.gd>

extends Node

signal score_updated
signal player_died

var score: = 0 setget set_score #매 score변경시마다 setter함수 호출
var deaths: = 0 setget set_deaths

func reset() -> void:
	score = 0
	deaths = 0

# 다른 스크립트에서 score가 변경되는 이벤트가 발생하여 +1이 되면 setget으로 set_score함수가 실행되고,
score변수에 +1된 값을 설정함과 동시에 "score_update" 시그널을 보낸다.

func set_score(value: int) -> void:
	score = value
	emit_signal("score_updated")

func set_deaths(value: int) -> void:
	deaths = value
	emit_signal("player_died")


  - Project Settings - autoload -PlayerData.tscn - ADD


  - 스크립트 수정

<Player.gd>

func _on_EnemyDetector_body_entered(body: PhysicsBody2D)->void:
	#queue_free() 수정/제거
    die() #추가 함수 생성

func die()-> void:	#신규함수
	PlayerData.deaths += 1
	queue_free()

<Enemy.gd script 수정>

export var score: = 100

func die() -> void:
	queue_free()
	PlayerData.score += score	# 추가

 

<Coin.gd script 수정>

export var score: = 100

func picked() ->void:
	PlayerData.score += score 	#추가
	anim_player.play("picked")

 

6. UI생성

  - User Interface 추가 - UserInterface로 변경 후 UserInterface폴더에 저장
  - ui_theme.tres를 theme에 추가


  - Label 추가(score용) - layout - top right - text align: right

Score %s


  - Color Rect 노드 추가(PauseOverlay로 이름 변경)
     ○ 색상 black, alpha:63정도로 변경

  - title.tscn을 끌어다 추가, text: Paused  - VBoxContainer추가,  layout center, alt키와 동시에 눌러 사이즈 조정, PauseMenu로 이름 조정  - QuitButton.tscen, SceneChangeButton.tscn을 선택하여 VBox안으로 가져옴(Ctrl+방향키로 버튼위치 변경 가능)


  - SceneChangeButton의 Next Scene을 MainScreen.tscn으로 설정(우클릭-path copy기능으로)


  - SceneChangeButton을 복제, RetryButton으로 변경후 더블클릭(Text: Retry로 변경)


  - Script 제거 후 재 추가


<RetryButton.gd scrip 추가>

Extends Button

func _on_button_up() -> void:
	PlayerData.score = 0
	get_tree().paused = false
	get_tree().get_root().get_node("/root/UserInterface/PauseOverlay").visible = false
	get_tree().reload_current_scene()


  - PauseOverlay visibility를 OFF


  - UserInterface.gd script 작성 

<UserInterface.gd script>

extends Control

onready var scene_tree: = get_tree()  #_ready함수 전에 실행됨
onready var pause_overlay: ColorRect= get_node("PauseOverlay") #ColorRect type임을 명시해줌. 혼선의 여지가 없을 경우는 선언 안하더라도 상관 없음.

var paused: = false setget set_paused

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("pause"):
	self.paused = !paused #(== "paused = not paused") #Self는 gdscript에서 기본이지만, setget함수를 위해서...
	scene_tree.set_input_as_handled() #pause event가 끝날때까지 propagate하지 않고 기다린다.


func set_paused(value: bool) ->void:
	paused = value
	scene_tree.paused = value

  - Project Settings - Input Map - pause생성 - Key 추가(+버튼) - Esc로 설정
  - UserInterface.tscn을 Level01에 추가
     ○ Canvaslayer추가(UserInterface로 변경)
     ○ 상위 노드로 위치를 바꾸고 Layer를 +100정도로 앞쪽으로 한다.
     ○ 그 후 Canvaslayer에 UserInterface.tscn을 하위 자식으로 추가


  -  UserInterface노드 선택 후 Node-Pause-Process로 선택(Inherit(default)/Stop/Process 중)
     ○ 기본으로 되어있는 Inherit는 상위노드를 상속하고 상위노드는 Stop으로 되어있다.
     ○ Stop은 완전 정지로 아무것도 Process할 수 없는 상태임


  - 추가로, UserInterface 씬에서 Label을 hide하고, 각 level에서 인스턴스된 UserInterface 노드에서는 Label을 show해야만 Label이 이중으로 나타나는 현상이 사라진다.

<UserInterface.gd script>

extends Control

onready var scene_tree: = get_tree()  #_ready함수 전에 실행됨
onready var pause_overlay: ColorRect= get_node("PauseOverlay") #ColorRect type임을 명시해줌. 혼선의 여지가 없을 경우는 선언 안하더라도 상관 없음.
onready var score: Label = get_node("Label")
onready var pause_title: Label = get_node("PuaseOverlay/Title")

const DIED_MESSAGE: = "You died"

var paused: = false setget set_paused

func _ready() -> void: #46
	PlayerData.connect("score_updated", self, "update_interface")
	PlayerData.connect("player_died", self, "_on_PlayerData_player_died")
	update_interface()

func _on_PlayerData_player_died() -> void:
	self.paused = true
	pause_title.text = DIED_MESSAGE

func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("pause") and pause_title.text != DIED_MESSAGE: #죽었을 때 esc를 누르면 계속 진행되는 현상 방지
	self.paused = !paused #(== "paused = not paused") #Self는 gdscript에서 기본이지만, setget함수를 위해서...
	scene_tree.set_input_as_handled() #pause event가 끝날때까지 propagate하지 않고 기다린다.

func update_interface() -> void:
	score.text = "Score: %s" % PlayerData.score

func set_paused(value: bool) ->void:
	paused = value
	scene_tree.paused = value

 

7. UI씬을 각 level에 복사


  -  level02 열기
     ○ Merge from scene
     ○ level01의 UserInterface(Canvaslayer) 선택
  - Level template에도 적용

 

8. Endscreen 수정

  -  Endscreen 노드에 script 추가

<Endscreen.gd>

extends Control

onready var label: Label = get_Node("Label")

func _ready() -> void:
	label.text = label.text % [PlayerData.score, PlayerData.deaths]


  - Level02의 nextscene에 Endscreen을 추가


  - Project Settings - windows - Fullscreen으로 변경하고 Play

 

---- 끝 ---

반응형