반응형

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. 장애물 신 추가

웹 설명서를 그대로 번역하면 적 신으로 나오는데, 마음에 들지않아 장애물이라고 표현하겠습니다. 장애물에 해당하는 몹(Mob)들은 화면 가장자리 4군데에서 랜덤하게 생성되고, 직선방향으로 움직이도록 할 계획입니다. 우선 Player에서 했던 것과 마찬가지로 새 신(scene)을 추가하고 Rigidbody2D 노드를 추가합니다. 그리고 이름은 Mob으로 바꿔주겠습니다.

 

그리고 아래의 버튼(잠금열쇠 옆에있는거..)을 클릭하여, 추가되는 자식노드만 따로따로 선택되지 않도록 해 줍니다.

 

이어서, Gravity Scale을 0으로 해 줍니다. 즉, 중력에 영향을 받지 않게 해줍니다. 이 값이 0이 아닐경우, Mob들은 중력의 영향을 받도록 계산되고, 화면 아래로 떨어지게됩니다. 조금 아래로 내려와 CollisionObject2D 카테고리의 Mask에 1을 체크 해제합니다. 이렇게 하면 Mob들끼리 서로 충돌하는 상황은 발생하지 않습니다.


2. 애니메이션 추가

Player 씬에서 수행했던 것과 마찬가지로, AnimatedSprite를 Node 하위에 추가하고,

인스펙터에서 Frames > 새 SpriteFrame 을 추가하여 한번 더 클릭하여 Animation 창을 띄워줍니다.


애니메이션은 fly, swim, walk의 세가지가 있으며, 각각의 케이스에 대해 2개의 이미지가 있습니다. 기존에 했던 것과 동일한 방식으로 이미지를 추가해줍니다. 아울러 재생속도를 초당 3프레임으로 변경해줍니다.

또한 Mob들의 사이즈를 조절하기 위해 AnimatedSprite 노드를 한번 더 클릭하고, 인스펙터 창의 Playing 속성을 "사용", Scale속성을 x, y 방향으로 각각 0.75씩 입력해줍니다.


3. 충돌 노드(CollisionShape2D) 추가

Player 씬과 마찬가지로 충돌 감지노드를 추가해야합니다. Mob 노드를 선택한 상태에서 새 노드를 추가해줍니다.

이번엔, CapsuleShape이면서도 Mob의 형상에 맞게 90도 회전하여 잘 커버되도록 조정해줍니다.

 

4. Mob.gd 스크립트

스크립트에서는 Mob이 생성될때 랜덤 형상, 랜덤 속도, 화면 밖으로 나갔을 때 삭제 처리를 할 예정입니다.

extends RigidBody2D

export var min_speed = 150  # Minimum speed range.
export var max_speed = 250  # Maximum speed range.

# Called when the node enters the scene tree for the first time.
func _ready():
	var mob_types = $AnimatedSprite.frames.get_animation_names()
	$AnimatedSprite.animation = mob_types[randi() % mob_types.size()]

func _on_VisibilityNotifier2D_screen_exited():
    queue_free()
	
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
#	pass

mob_types는 애니메이션에서 설정한 3가지의 몹 이름을 배열로 갖게 됩니다. 그리고 실제 보여줄 에니메이션에서는 이 중 랜덤하게 골라서 설정하게 됩니다.

화면에서 해당 객체(생성된 Mob)가 나가게 되면 자체 제거(메모리 회수)를 하게되는데, 이때 사용하는 함수가 queue_free()입니다.

 

- Mob 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 끝 -

반응형
반응형

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

 

---- 끝 ---

반응형
반응형

1. Coin 만들기

  - new scene > Area2D(Coin) 노드추가, Coin Image추가


  - CollisionShape2D추가, circle shape 선택 후 사이즈 조정합니다. 


  - AnimationPlayer추가 (Objects폴더 생성 후 scene저장)


  - animation - new animation: bouncing


  - timeline 오른쪽에 시간을 1.8로 전체 애니메이션 시간을 설정
  - timeline 오른쪽의 반복기능 on


  - 뷰포트의 rotation mask for inserting keys를 언체크함
  - 뷰포트의 Translation mask for inserting keys만 체크함


  - Coin 노드 선택 후 ins키나 뷰포트의 key를 누르면 애니메이션에 key가 추가됨

  - 0.9초에 key frame추가 후 y 값을 -10으로 설정
  - 첫 key frame의 y값을 +10으로 설정


  - 첫 프레임의 easing 진동하는 속도...를 상향 조정한다.
  - 두번째 프레임의 easing 을 하향 조정한다.


  - animation time을 1.2로 수정하여 짧게 변경하고, 두번째 프레임의 위치도 중간인 0.6정도로 위치를 변경합니다.


  - animation - new animation: fade_out 애니메이션을 추가합니다.


  - Coin(Area2D)선택
  - transform에서 원점인지 확인 후 작업을 진행합니다.


  - canvasItem > visibility > modulate 오른쪽의 key를 클릭하여 0에서, 0.6초에서 프레임을 추가하면  Modulate속성이 애니메이션에 추가됩니다.


  - 0.6초로 key frame이동 후 Modulate 오른쪽의 key를 클릭 후 alpha를 0으로 수정


  - animation 시간을 0.6초로 변경
  - shift+D로 프리뷰 가능

  - bouncing에서 첫 프레임에 modulate에서 key 추가하여 alpha 100%를 추가


  - fade_out에서 마지막 프레임에 add track버튼 - call method track 버튼 - method 추가


  - coin(Area2D)노드 선택 후 프레임의 오른쪽클릭 후 insert method 클릭

  - queue free()선택

  - Coin 스크립트 추가

<coin.gd>

extends Area2D

onready var anim_player: AnimationPlayer = get_node("AnimationPlayer")
#_ready 실행하기 전에 실행

func _on_body_entered(body:PhysicsBody2D) ->void:
	anim_player.play("fade_out")

 

  - Coin에서 layer는 coin, Mask는 player로 선택

  - signal 추가: body_entered(PhysicsBody2D body) 


  - bouncing 클릭 후 오른쪽의 버튼을 누르면 기본 animation으로 선택됨

 


  - 여기까지 따라하면 코인의 움직임이 안보입니다. animation이 Coin(Area2D)에 설정되어있어서 Play를 시작하면 animation이 원점에서 실행되고 있습니다. 이를 방지하려면 coin(sprite) 노드가 애니메이션되도록 해야합니다. 코인의 AnimationPlayer노드로 가서 ".:position" 으로되어있는 부분을 "coin:position"으로 바꿔줍니다.

  - fade_out 에니메이션을 0.4초로 변경

 

2. Portal 생성

 

  - Portal2D(Area2D)노드를 추가하고 Portal 이미지를 추가합니다.


  - CollisionShape2D 추가
  - CapsuleShape 설정


  - TransitionLayer(canvaslayer) 노드를 추가해주고, 다른 어떤 것보다도 앞에 있어야하므로 Layer 값을 +100 정도로 설정해줍니다.


  - TransitionLayer에 ColorRect 노드를 추가하여 Black으로 색깔 변경하고(layout-Full rect)하고 alpha는 0으로하여 기본적으로는 안보이게 설정합니다.


  - AnimationPlayer 추가
     ○ start애니메이션 추가. 시작될 때 검은 화면이 다시 투명하게 바꾸는 역할을 하는 애니메이션입니다.

     ○ autoplay 체크

     ○ ColorRect선택-color key 지정(투명, 처음엔 안나타나게)
     ○ Visible: off(value: off)로 선택하고 key 지정

     ○ 지속시간은 0으로 하여 한번 투명하게 값을 설정하고 끝내도록 합니다.

Visibility off인지 한번 더 확인


     ○ fade_in애니메이션 생성 (START애니메이션 복제(Ctrl+D))

     Q Visibility는 On

     ○ 지속시간 1초

     ○ 1초에 key frame 추가 - color 선택에서 alpha 255%(블랙)으로 설정 후 키 추가


  - Portal2D Monitoring: on, Monitorable: off, Layer: none, Mask: player 로 설정

3. Portal 스크립트

<protal2D.gd>

tool
extends Area2D

onready var anim_player: animationPlayer = $AnimationPlayer

export var next_scene: PackedScene

func _on_body_entered(body: PhysicsBody2D) -> void:
	teleport()

func _get_configuration_warning() -> String:
	return "The next scene property can't be empty" if not next_scene else ""

func teleport() ->void:
	anim_player.play("fade_in")
	yield(anim_player, "animation_finished") #anim_player가 끝나서 animation_finished 시그널을 보낼 때까지 기다림
	get_tree().change_scene_to(next_scene)
 

  - nextscene을 export로 추가하고, 없으면 에러메세지 popup띄우는 기능 추가

  - teleport함수 추가
  - body_entered(PhysicsBody2D body) 시그널 연결 : _on_body_entered
  - Level을 우선 복제하여 테스트

#참고로...터치패드에서 대량으로 tilemap을 지울 때에는 우클릭에 해당하는 두손가락 클릭을 하는데,,,한번하면 생성, 두번하면 삭제
#한손가락은 패드도 눌러야하고, 검지손가락은 터치도 해야함...두번씩....

반응형
반응형

 

따라해야할 양이 많다보니 포스팅이 친절하지 못한점 양해바랍니다.^^;;

그럼 시작하겠습니다.


1. 프로젝트 세팅

  - assets 다운로드(예제 소스 링크)

  - platformer폴더 생성 후 assets폴더를 복사해 넣습니다. 
  - assets 폴더에 이미지 등 소스 추가
  - src 폴더 추가

2. Player 생성

  - kinematicbody2D 추가

  - Player로 변경
  - CollisionShape2D추가 ->Rectangle shape 설정
  - Player 이미지를 끌어오면 자동으로 sprite node가 추가되며 이름은 player로 설정됨
  - magnetic을 클릭하여 snap 활성화
  - pixel snap을 활성화하여 맨 바닥이 0에 오도록 설정
  - Collisionshape2D의 크기 조정(캐릭터 이미지보다 약간 작도록 설정, 단 바닥은 0에 닿도록 함) 
  - Player Scene을 src/Actors폴더 안에 저장
  - Player node에 script 추가(Empty template로 설정하여 추가)
  - File menu에서 script 추가하여 Actor.gd(palyer와 enemy가 공유할 base script)를 추가 (player.gd는 현 단계에서는 필요 없음.

------Actor.gd script 1단계-------

extends KinematicBody2D
class_name Actor

const FLOOR_NORMAL: = Vector2.UP #플로어에 있는지에 관한 정보

export var speed: = Vector2(300.0, 1000.0)
export var gravity: = 3000.0

var velocity: = Vector2.ZERO

func _physics_process(delta:float) ->void: #매 프레임마다 수행
	velocity.y += gravity * delta
	#나중에 추가: velocity.y = max(velocity.y, speed.y) //if(velocity.y >speed.y){velocity.y=speed.y}
	velocity = move_and_slide(velocity) #Player 스크립트로 이동

3. Level 생성(2D Node: LevelTemplate) 및 저장

  - Tilemap 추가, Tileset 추가(미리 저장된 것 불러오기, 새로 생성하여 사용 가능)

스텝 사이즈 80으로 설정
Collision지정으로 충돌/정지할 수 있도록 인식영역 설정
셀 사이즈를 타일셋에서 지정한 이미지 사이즈와 동일하게 80으로 설정


  - wall, floor 생성. 적당히 생성 후 Player를 불러와서 실행해본다.

4. 레이서 설정(Editor > Project Settings > 2D Physics)

- 설정의 2d physics 로 가서 Level 설정 추가(어떤 레벨과 반응하는지..충돌 등, 씬 안에있는 instance의 세팅을 변경할 경우 해당 instance만 변경되므로, 해당 씬을 수정해야함)

- Layer Names - 2D Physics
- Layer1: player
- Layer2: enemies
- Layer3: coins
- Layer4: world
-Tilemap: Layer-world, Mask-none
-Player: 

 

예를 들면 TileMap같은 경우에는 Collision > Layer 메뉴의 ... 버튼에서 어떤 레이어인지 직접 지정해줄 수 있다. (마스크는 제외해줘야 함)

 

Player는 우선 4번 (world)를 마스크해서 감지하도록 해야함.

 

5. Project Setting - Input Map

-move_left (A, Left, Devide 0(Stick Left)
-move_right(D, Right, Device 0(Stick Right)
-jump(W, Up, Device 0(PS Cross, xBox A nintendo B)


--------Player.gd script----------
- 1단계 : 키 인식 확인용

extends Actor

func _physics_process(delta: float) -> void:
	var direction: = Vector2(
		Input.get_action_strength("move_right") -
		Input.get_action_strength("move_left"),
		1.0
	)
	velocity = speed * direction
	velocity = move_and_slide(velocity)

 

- 최종 -

extends Actor

export var stomp_impulse: = 1000

func _on_EnemyDetector_area_entered(area: Area2D) ->void:
	_velocity = calculate_stomp_velocity(_velocity, stomp_impulse) #enemy를 밟을때 충격으로 튀어오르는 기능

func _on_EnemyDetector_body_entered(body: PhysicsBody2D) -> void: #enemy와 부딛혔을 때 죽는 기능
	queue_free()

#1단계
func _physics_process(delta: float)->void:
	var is_jump_interrupted: = Input.is_action_just_released("jump") and velocity.y <0.0 #현재 점프상태가 끝나면 jump도 짧게 끝남. 키 누르는 시간에 비례하도록)
	direction: = get_direction()
	velocity = calculate_move_velocity(velocity, direction, speed, is_jump_interrupted) 
	velocity = move_and_slide(velocity, FLOOR_NORMAL) #현재 player가 frame 밖에 있으므로 안쪽으로 옮겨준 뒤 실행가능


#1단계
func get_direction() -> Vector2:
	return Vector2(
		Input.get_action_strength("move_right")-Input.get_action_strength("move_left"), -1.0 if Input.is_action_just_pressed("jump") and is_on_floor() else 1.0) #벽에서 점프한 경우에만 y 방향을 -1로 설정하고, 그 외에는 1로 설정함

#1단계
func calculate_move_velocity(
      linear_velocity: Vector2,
      direction: Vector2,
      speed: Vector2
      is_jump_interrupted: bool
      ) ->Vector2:
	var new_velocity: = linear_velocity
	new_velocity.x = speed.x * direction.x
	new_velocity.y += gravity * get_physics_process_delta_time() //delta값을 호출해줌
	if direction.y == -1.0:
		new_velocity.y = speed.y * direction.y
	if is_jump_interrupted:
		new_velocity.y = 0.0
	return new_velocity

func calculate_stomp_velocity(linear_velocity: vector2, impulse: float) -> Vector2:
	var out: = linear_velocity
	out.y = -impulse
	return out

  - player와 enemy속도 차이를  위해 player 600 설정
  -  Jump 함수 추가(방향함수를 추가하여 가독성 향상)
  - Jump 함수에 FLOOR_NORMAL 상수를 추가로 전달하여야 JUMP가 가능함
  - new_velocity 변수 일괄 변경: 선택-> Ctrl+R(대체)->out
  - Ctrl+Shift+F: 파일 전체(전체프로젝트)에서 찾기: velocity찾기->whole word체크(전체일치)->replace->_velocity 로 변경(private:해당 script/class에서만 쓸 수 있음)

6. Enemy

  - Player.tscn -> duplicate: 이름 변경 to Enemy, sprite 이름 enemy
  - enemy.png를 sprite texture에 놓는다.
  - Collisionshape2D  사이즈 변경

  - extend script - actor.gd - enemy.gd생성

Actor.gd를 상속하고, 이름은 Enemy.gd로 하여 생성

- layer를 enemy layer로, mask를 player/world로


<enemy.gd 스크립트>

extends "res://src/Actors/Actor.gd"

func _ready() ->void:
	set_physices_process(false) #enemy가 밖에 있을 때 작동하지 않도록 하는 기능
	_velocity.x = -speed.x 

func _on_StompDetector_body_entered(body:
		PhysicsBody2D) ->void:
	if body.global_positioy.y < get_node("StompDetector").global_position.y: 
		return
	get_node("CollisionShape2D").disabled = true # 죽이고도 내가 또 죽는 오류를 방지
	queue_free()	# 적 삭제

func _physics_process(delta: float) ->void: #1단계
	_velocity.y +=gravity * delta
	if is_on_wall(): #1단계
		_velocity.x *= -1.0 #1단계
		_velocity.y = move_and_slice(_velocity, FLOOR_NORMAL).y #1단계

  - Frame 밖에 있을 때는 작동하지 않도록 하는 기능 ( 노드 추가: VisibilityEnabler2D )

  - physics Process Par: on,  process parent: on

- "physics Process Par: on" 옵션은 화면 밖으로 나가는 것에대해서만 Trigger역할을 합니다. 즉, 이미 오른쪽 밖에서 안으로 이동하는 녀석에 대해서는 작동하지 않습니다. 계속 왼쪽 (화면 안쪽)으로 이동합니다. 따라서 추가 함수가 필요합니다.

func _ready() ->void:
	set_physices_process(false) #enemy가 이미 밖에 있을 때도 작동하지 않도록 하는 기능
	_velocity.x = -speed.x


  - Frame Trigger box 사이즈/위치 조절(-50, -50, 100, 60) 


  - Enemy에 Area2D 노드 추가(StompDetector로 이름 변경)


  - Layer 체크 삭제
  - Monitorable: off


  - CollisionShape2D 노드 추가: rectangleshape/사이즈 조정


  - StompDetector위치는 위로 올리고, 아래에서부터 부딛힐 때도 죽으면 안되니까..

  - 내부의 CollisionShape2D는 아래로 내려서 위치를 조정한다. 

  -  박스 색깔을 조절한다.



  - signal연결: body_entered(PhysicsBody2d)


7. 적을 밟아 제거하면 튀어오르는 모션 추가

이제 적당히 움직임이 구현되었습니다. 거기에 더해 Player 동작에 적을 밟았을 때 튀어오르는 모션을 추가해보겠습니다.

  - player에 Area2D 노드 추가(EnemyDetector로 이름 변경)


  - Layer: 없음, Mask: enemy
  - Monitorable: off


  - collisionshape2D - rectangleshape추가
  - 사이즈 조절

살짝 크게, 아래쪽으로 살짝 더 내려오게 박스 설정

  - visibility tab에서 색깔 변경(파란색으로)

  - 적 제거후 튀어오르는 애니메이션 구현 코드


8. 적과 충돌시 Player 죽는 기능 구현

  - signal 연결: body_entered(PhysicsBody2D body) 적에 닿으면 죽는 기능 구현

  _on_EnemyDetector_body_entered(body: PhysicsBody2D) 함수 추가

 

9. 적당한 시점에 Level TileMap을 수정해준다.

 

10. 카메라

  - Player 노드에 Camera2D 추가
  - current: on으로 설정해야 카메라 뷰로 보임


  - w키로 프레임 위치 상향 조정

  - 카메라 이동 제한
  - player - camera2D -limit의 값 수정(left: 0, top: 0)
  - Drag Margin: 카메라 이동 없이 player가 움직일 수 있는 공간, left 0, right 0


  - Drag Margin H/V Enabled
  - Smoothing - Enabled:on, Speed:2로 카메라가 따라오는 속도를 낮춤.. 7정도로 유지


  - player-right click - Editable Children - Limit Right:3600으로 해당 레벨에서만 리밋 설정 가능


11. 배경 이미지 설정

  - backgraound 이미지를 Texture Rectangle로 화면 안쪽으로 끌고 들어옵니다. (TextureRect노드안에 설정) 

  - Layout: Full Rect정
  - 맨 위로 올리면 제일 뒤쪽에 보이도록 할 수 있다.


  - CanvasLayer 추가후 background 를 자식으로 추가하면, 일일이 배경을 복사해서 추가하지 않아도, 게임 진행하면서 일관되게 배경을 추력해줍니다.
  - layer에 -100으로 설정



  - visible Collision Shapes 체크를 끔으로써 게임에서 Collision shape들이 안보이게 하여 마무리 해 줍니다.

반응형
반응형

흔히 슈퍼마리오 게임으로 익숙하게 알고있는 횡 스크롤 형태의 게임을 Platformer 라고 하는 것 같습니다. Godot 엔진을 이용한 Platformer 게임만들기를 시작하려고 하는데요, youtube강좌 https://www.youtube.com/watch?v=Mc13Z2gboEk 를 따라해보았습니다. 총 3편에 걸쳐 진행하며 Godot 엔진에 익숙해질 시간을 가져보겠습니다.

 

아래는 예전에 핸드폰에 넣으려고 만들었던 게임의 스크린 샷인데, 지금은 소스가 안남아있네요..ㅠㅠ

어쨌든 시작해보겠습니다.

platformer 기초 강좌 프로젝트를 이미지 변경하고, 모바일용으로 변경

 

반응형

+ Recent posts