코드를 통해 게임의 UI를 제어하기

소개

이 튜토리얼에서, 당신은 캐릭터를 체력 막대와 연결하고 체력이 줄어드는 것을 구현할 것입니다.

../../_images/lifebar_tutorial_final_result.gif

이것이 우리가 만들 것입니다: 캐릭터가 맞을 때 움직이는 막대와 카운터. 캐릭터가 죽으면 서서히 사라집니다.

배울 것입니다:

  • 어떻게 캐릭터를 GUI에 시그널을 통해 연결 하는가
  • 어떻게 GUI를 GDScript를 통해 제어 하는가
  • 어떻게 체력 막대를 Tween 노드를 통해 애니메이트(animate) 하는가

어떻게 인터페이스를 설정하는 지를 배우고 싶다면, 단계별 UI 튜토리얼을 참고하세요:

  • 메인 메뉴 화면 만들기
  • 게임 사용자 인터페이스 만들기

게임을 코딩할 때, 당신은 핵심 게임 플레이를 먼저 만들고 싶을 것입니다: 주요 기능, 플레이어 입력, 승패 여부. UI는 나중에나 떠오를 겁니다. 당신은 프로젝트를 만들기 위한 모든 요소들을 가능한 한 분리되도록 하고 싶을 것입니다. 각 캐릭터는 자신의 씬에, 자신의 스크립트를 가지고 있어서, UI 요소가 되어야 합니다. 이것은 버그를 예방하고 프로젝트 관리를 유지하며, 다른 팀 구성원들이 게임의 다른 부분을 작업하도록 만듭니다.

일단 핵심 게임 플레이와 UI가 준비되었다면, 이 들을 어떤 방법으로 연결해야 합니다. 우리의 예제에서는, 우리는 일정한 시간마다 플레이어를 공격하는 적을 가지고 있습니다. 우리는 플레이어가 피해를 입을 때마다 체력 막대가 업데이트 되길 원합니다.

그러기 위해서, 우리는 시그널(signals) 을 사용할 것입니다.

주석

시그널은 옵저버 패턴의 Godot 버전입니다. 그들로 메시지를 전달할 수 있습니다. 다른 노드들은 시그널을 방출(emits) 하고 정보를 받을 수 있는 다른 물체와 연결할 수 있습니다. 이것은 사용자 인터페이스와 업적 달성 시스템에 많이 쓰일 강력한 도구입니다. 그러나 그것들을 남용하기를 권하진 않습니다. 두 노드를 연결하면 노드 간의 커플링이 생겨납니다. 많은 연결이 있다면, 관리하기가 어려워집니다. GDquest의 signals video tutorial 에서 더 많은 정보를 보실 수 있습니다.

시작 프로젝트를 다운로드하고 탐구하기

Godot 프로젝트를 다운로드 하세요: ui_code_life_bar.zip. 시작할 모든 애셋과 스크립트가 포함되어 있습니다. .zip 파일을 풀고 두 개의 폴더가 나옵니다: startend 입니다.

Godot에서 start 프로젝트를 불러옵니다. 파일 시스템 독에서 LevelMockup.tscn을 더블 클릭해서 엽니다. 이것은 두 캐릭터가 마주 보고 있는 RPG 게임의 모형입니다. 분홍색 적이 일정하게 공격하고 초록색 사각형이 맞는 것을 반복합니다, 죽을 때 까지 말이죠. 게임을 한번 해보세요: 기본 전투 구조는 이미 작동합니다. 하지만 캐릭터가 체력 막대와 연결되어 있지 않기 때문에 GUI는 아무것도 하지 않습니다.

주석

이것은 게임을 코딩하는 일반적인 방법입니다: 먼저 핵심 게임 플레이를 구현합니다, 그리고 플레이어의 사망을 다룹니다, 그런 뒤 인터페이스 만을 추가하는 것입니다. UI는 게임에서 무엇이 일어나는지 듣기 때문입니다. 따라서 다른 시스템이 제 자리를 잡지 못한다면 작동할 수 없습니다. 만일 게임 플레이를 테스트하지 않은 채로 UI를 디자인 한다면, 잘 작동하지 않을 수 있으니 처음부터 다시 만들어야 할 수도 있습니다.

씬에는 배경 스프라이트, GUI, 그리고 두 캐릭터가 있습니다.

../../_images/lifebar_tutorial_life_bar_step_tut_LevelMockup_scene_tree.png

GUI 씬이 자식으로 보이게 놓은 씬 트리

GUI 씬은 게임 사용자 인터페이스 전체를 캡슐화 합니다. 씬 안에 존재하는 노드의 경로를 얻는 스크립트와 함께 제공됩니다:

onready var number_label = $Bars/LifeBar/Count/Background/Number
onready var bar = $Bars/LifeBar/TextureProgress
onready var tween = $Tween
public class Gui : MarginContainer
{
    private Tween _tween;
    private Label _numberLabel;
    private TextureProgress _bar;

    public override void _Ready()
    {
        // C# doesn't have an onready feature, this works just the same.
        _bar = (TextureProgress) GetNode("Bars/LifeBar/TextureProgress");
        _tween = (Tween) GetNode("Tween");
        _numberLabel = (Label) GetNode("Bars/LifeBar/Count/Background/Number");
    }
}
  • number_label 은 체력을 숫자로 보여줍니다. Label 노드입니다
  • bar 는 체력 막대입니다. TextureProgress 노드입니다
  • tween 은 다른 어떤 노드의 값이나 메서드를 제어하고 움직이게 할 수 있는 구성 요소 스타일의 노드입니다

주석

프로젝트는 게임 잼이나 작은 게임을 위해 작업하는 간단한 조직화를 사용합니다.

프로젝트의 뿌리에서, res:// 폴더에서, LevelMockup 을 찾으실 수 있습니다. 이것이 같이 작업할 메인 게임 씬입니다. 게임을 만들기 위한 모든 구성 요소는 scenes/ 폴더에 있습니다. assets/ 폴더는 게임 스프라이트와 HP 카운터를 위한 폰트가 들어있습니다. scripts/ 폴더에서 적, 플레이어, 그리고 GUI 컨트롤러 스크립트를 찾으실 수 있습니다.

편집기에서 씬 트리 오른쪽의 편집 씬 아이콘을 클릭해서 씬을 엽니다. LifeBar와 EnergyBar 가 하위 씬으로 있는 것을 보실 수 있습니다.

../../_images/lifebar_tutorial_Player_with_editable_children_on.png

Player 씬이 자식으로 설정된 씬 트리

플레이어의 max_health로 Lifebar 설정하기

우리는 GUI가 플레이어의 현재 체력이 어떤지 알려주고, 체력 막대의 텍스처를 업데이트하고, 화면 좌측 상단에서 HP 카운터가 남은 체력을 보여주어야 합니다. 그러기 위해 우리는 플레이어가 피해를 입을 때마다 체력을 GUI로 보내야 합니다. GUI는 LifebarNumber 노드를 주어진 값으로 업데이트할 것입니다.

여기서 멈추고 숫자를 보여주러 갈 수 있지만, 그 전에 막대의 max_value 을 초기화해야 정확한 크기로 업데이트할 수 있습니다. 그러므로 먼저 GUI 가 초록색 캐릭터의 max_health 가 어느 정도인지 말해야 합니다.

참고

TextureProgress 막대는 100max_value 를 기본적으로 갖고 있습니다. 캐릭터의 체력을 숫자로 표시하지 않아도 된다면, 그것의 max_value 속성을 바꿀 필요가 없습니다. 대신 Player 의 퍼센트를 GUI 로 보냅니다: health / max_health * 100.

../../_images/lifebar_tutorial_TextureProgress_default_max_value.png

씬 독에서 GUI 오른쪽의 스크립트 아이콘을 클릭합니다. _ready 함수에서, 우리는 Playermax_health 을 새 변수로 저장하고 그걸로 barmax_value 를 설정합니다:

func _ready():
    var player_max_health = $"../Characters/Player".max_health
    bar.max_value = player_max_health
public override void _Ready()
{
    // Add this below _bar, _tween, and _numberLabel.
    var player = (Player) GetNode("../Characters/Player");
    _bar.MaxValue = player.MaxHealth;
}

분석해봅시다. $"../Characters/Player" 는 씬 트리의 한 노드를 불러옵니다, 그리고 Characters/Player 노드를 검색하는 속기입니다. 그것은 노드에게 진입로를 제공합니다. 명령문의 두 번째 부분은, .max_health 로, Player 노드에 max_health 를 접근합니다.

두 번째 줄을 bar.max_value 의 값을 지정합니다.이 두 줄을 하나로 묶을 수도 있습니다, 하지만 이 튜토리얼에서 player_max_health 를 나중에도 사용해야 할 것입니다.

Player.gd 는 게임 시작 시 healthmax_health 로 설정합니다, 따라서 이것으로 작업할 수 있었습니다. 왜 여전히 max_health 를 사용하냐고요? 두 가지 이유가 있습니다:

health 가 항상 max_health 와 같다는 보장은 없습니다: 게임의 미래 버전에서는 플레이어가 이미 체력을 잃은 채로 레벨을 불러올 지도 모릅니다.

주석

게임에서 씬을 열 때,Godot는 씬 독에서 위에서 아래로, 순서에 따라, 하나 씩 노드를 만들어 냅니다. GUIPlayer 는 같은 노드 분기에 있지 않습니다. 실행 시에 그들이 함께 있게 만들기 위해선, 우리는 _ready 함수를 사용해야 합니다. Godot는 게임이 시작하기 전, 모든 노드를 불러온 후에 _ready 를 부릅니다. 모든 것을 설정하고 게임 세션을 준비하기에 최고의 함수입니다. _ready 에 대해서는 스크립팅 (계속) 를 참고하세요

플레이어가 피해를 입었을 때 시그널로 체력을 업데이트하기

우리의 GUI는 Player 에서 health 을 받을 준비가 되었습니다. 그러기 위해 우리는 시그널(signals) 을 사용합니다.

주석

많은 유용한 내장 시그널이 있습니다, enter_treeexit_tree 는 모든 노드가 각각 만들어지고 없어질 때 방출하는 것입니다. 또한 자신만의 시그널 키워드를 만들어 사용할 수 있습니다. Player 노드에서 두 개의 시그널을 찾으실 수 있습니다: diedhealth_changed 입니다.

_process 함수에서 Player 노드를 바로 가져오지 않고 체력 값을 보죠? 그 방법으로 노드를 접근하는 것은 그들을 단단히 연결합니다. 그걸 원한다면 작동은 할 것입니다. 게임이 커질 수록, 많은 연결을 갖게 될 것입니다. 그 방법으로 노드를 가져오는 것은 복잡해질 것입니다. 그 뿐만이 아니라: 당신은 _process 함수에서 끊임없는 상태 변화를 들어야 합니다. 이 확인은 1초 당 60회이고 코드의 작동 순서 때문에 게임이 꺼질 수도 있습니다.

특정 프레임에서 다른 노드의 속성을 업데이트 되기 전인 것을 볼 수 있습니다: 마지막 프레임에서 준 값을 가져옵니다. 이로 인해 수정하기 어려운 애매한 버그가 발생합니다. 반면에, 시그널은 변화가 일어난 후에 바로 방출합니다. 당신이 깔끔한 정보를 얻는다는 것을 보증합니다 그리고 연결한 노드의 상태가 변화가 일어난 바로 직후 에 업데이트 될 것입니다.

주석

시그널이 파생된, 옵저버 패턴은, 여전히 노드 분기 간의 연결을 추가합니다. 하지만 일반적으로 두 개의 분리된 클래스 사이를 연결하기 위해 직접 노드를 접근하는 것보다 더 가볍고 더 안전합니다. 부모 노드가 자식 노드의 값을 가져올 수 있습니다. 하지만 별도의 가지에서 이 작업을 한다면 시그널을 더 선호할 것입니다. 더 많은 정보는 Observer pattern 에서 Game Programming Patterns 를 읽어보세요. 전체 책 은 온라인에서 무료로 이용할 수 있습니다.

이것을 염두해두고 GUIPlayer에 연결합시다. 씬 독에서 Player 노드를 클릭하여 선택합니다. 인스펙터에서 노드(Node) 탭을 클릭합니다. 여기가 당신이 선택한 시그널을 받기 위해 노드를 연결하는 곳입니다.

첫 번째 섹션은 Player.gd 에 정의된 커스텀 시그널 목록입니다:

  • died 는 캐릭터가 죽었을 때 방출합니다. 이것은 UI를 감추는 순간에 사용할 것입니다.
  • health_changed 는 캐릭터가 피해를 입었을 때 방출됩니다.
../../_images/lifebar_tutorial_health_changed_signal.png

우리는 health_changed 시그널을 연결합니다

health_changed 를 선택하고 오른쪽 아래 연결하기 버튼을 누르고 시그널 연결 창을 엽니다. 왼쪽에서 이 시그널을 듣는 노드를 선택할 수 있습니다. GUI 노드를 선택합니다. 화면 오른쪽은 시그널과 함께 선택적인 값을 보낼 수 있습니다. 우리는 이것을 이미 Player.gd 에서 다루었습니다. 일반적으로 저는 코딩보다 덜 편리하므로 이 창으로 너무 많은 인수를 추가하지 않기를 권장합니다.

../../_images/lifebar_tutorial_connect_signal_window_health_changed.png

GUI 노드가 선택된 시그널 연결 창

참고

선택적으로 코드로부터 노드를 연결할 수 있습니다. 하지만 편집기로 하면 두 가지 이점이 있습니다:

  1. Godot는 연결된 스크립트에서 새 콜백 함수를 쓸 수 있습니다
  2. 씬 독에서 시그널을 방출하는 노드 옆에 방출 아이콘이 나타납니다

창의 아래에 당신이 선택한 노드의 경로를 찾으실 수 있습니다. 우리는 "Method in Node"라 부르는 두 번째 줄에 관심을 둘 겁니다. 이것은 GUI 노드에서 시그널이 방출될 때 호출되는 메서드 입니다. 이 메서드는 시그널과 함께 전송된 값을 받아 처리합니다. 오른쪽을 보시면, "함수 만들기" 버튼이 기본적으로 켜진 채로 있습니다. 창 아래의 연결 버튼을 누릅니다. Godot는 GUI 노드 안에 메서드를 만듭니다. 스크립트 편집기를 열면 새로운 _on_Player_health_changed 함수가 안에 있습니다.

주석

편집기에서 노드를 연결할 때, Godot는 메서드 이름을 다음과 같은 방식으로 만듭니다: _on_방출하는노드이름_시그널_이름. 이미 메서드를 적었다면, "함수 만들기" 설정은 유지될 것입니다. 당신이 원하는 어떤 이름으로도 바꿀 수 있습니다.

../../_images/lifebar_tutorial_godot_generates_signal_callback.png

Godot는 콜백 메서드를 적어 사용자에게 전달합니다

함수 이름 다음의 괄호 안에, player_health 인수를 추가하세요. 플레이어가 health_changed 시그널을 방출할 때, 현재 health와 함께 보내질 것입니다. 코드는 다음과 같아야 합니다:

func _on_Player_health_changed(player_health):
    pass
public void OnPlayerHealthChanged(int playerHealth)
{
}

주석

엔진은 파스칼 표기법(PascalCase)을 스네이크 표기법(snake_case)으로 변환하지 않기 때문에, C# 예제에서 메서드 이름에는 파스칼 표기법, 메서드 매개 변수에는 카멜 표기법(camelCase)을 사용할 것입니다, 공식 `C# 명명 규칙. <https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions>`_에 따르는 것입니다

../../_images/lifebar_tutorial_player_gd_emits_health_changed_code.png

Player.gd에서, Player가 health_changed 시그널을 방출할 때, 체력 값도 보냅니다

_on_Player_health_changed 안에서, update_health 라는 두 번째 함수를 호출하고 거기에 player_health 변수를 전달하게 해봅시다.

주석

우리는 체력 값을 LifeBarNumber 에 직접 업데이트할 수 있습니다. 메서드를 대신 사용하는 이유는 두 가지 입니다:

  1. 플레이어가 피해를 입을 때, 이름은 체력 카운터를 업데이트 해야 하는 미래의 나와 팀원에게 그것을 명확하게 해줍니다
  2. 이 메서드를 나중에 다시 사용할 것입니다

새로운 update_health 메서드를 _on_Player_health_changed 아래에 만듭니다. new_value를 유일한 인수로 갖습니다:

func update_health(new_value):
    pass
public void UpdateHealth(int health)
{
}

이 메서드에는 필요합니다:

  • Number 노드의 text 를 문자열로 변환된 new_value 로 설정하는 것
  • TextureProgressvaluenew_value 로 설정하는 것
func update_health(new_value):
    number_label.text = str(new_value)
    bar.value = new_value
public void UpdateHealth(int health)
{
    _numberLabel.Text = health.ToString();
    _bar.Value = health;
}

참고

str은 내장 함수로 어느 값을 문자로 변환합니다. Numbertext 속성은 문자열을 요구하기 때문에 new_value를 직접 지정할 수 없습니다

또한 update_health_ready 함수의 마지막에 호출하여 게임의 시작에서 Number 노드의 text 를 적당한 값으로 초기화합니다. F5를 누르면 게임을 테스트합니다: 체력 막대가 매 공격마다 업데이트 됩니다!

../../_images/lifebar_tutorial_LifeBar_health_update_no_anim.gif

Player가 피해를 입을 때 Number 노드와 TextureProgress가 둘 다 업데이트합니다

Tween 노드로 체력이 감소하는 것을 애니메이트하기

인터페이스는 작동하지만, 몇 가지 애니메이션을 사용할 수 있습니다. 지금이 속성을 애니메이트 하는데 필수적인 도구, Tween 노드를 소개하기에 좋은 순간이네요. Tween은 어떤 것이든 처음부터 끝을 일정 기간 동안 애니메이트 합니다. 예를 들어 캐릭터가 피해를 입을 때 Player의 현재 새로운 healthTextureProgress에서 체력을 애니메이트 할 수 있습니다.

GUI 씬에는 이미 tween 변수가 저장된 Tween 노드 갖고 있습니다. 이제 사용할 때입니다. 우리는 update_health 에 몇 가지 변화를 주어야 합니다.

Tween 노드의 interpolate_property 메서드를 사용할 겁니다. 그것은 일곱 개의 인수들을 가집니다:

  1. 애니메이트 할 속성을 소유한 노드에 대한 참조
  2. 속성 식별자의 문자열
  3. 시작 값
  4. 끝 값
  5. 애니메이션의 지속 시간 (초)
  6. 전환(transition) 유형
  7. 방정식과 함께 사용하는 완화.

마지막 두 인수들은 완화 식(easing equation)으로 동등하게 묶입니다. 이것은 시작에서 끝까지 값이 얼마나 증가하는 지를 제어합니다.

GUI 노드 옆에 스크립트 아이콘을 클릭해서 다시 엽니다. Number 노드는 스스로 업데이트하는 텍스트가 필요합니다, 그리고 Bar 는 실수 또는 정수가 필요합니다. 우리는 interpolate_property 를 사용해서 숫자를 애니메이트 할 것입니다, 텍스트에 직접 하진 않을 겁니다. 우리는 이것을 animated_health 라는 새로운 GUI 변수를 애니메이트 하는데 이용할 것입니다.

스크립트 맨 위에서, 새 변수를 정의합니다, 이름은 animated_health 입니다, 그리고 값을 0으로 설정합니다. 다시 update_health 메서드로 돌아와서 내용물을 지웁니다. animated_health 값을 애니메이트 해 봅시다. Tween 노드의 interpolate_property 메서드를 호출합니다:

func update_health(new_value):
    tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6, Tween.TRANS_LINEAR, Tween.EASE_IN)
// Add this to the top of your class.
private float _animatedHealth = 0;

public void UpdateHealth(int health)
{
    _tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
        Tween.EaseType.In);
}

호출한 것을 파헤쳐 봅시다:

tween.interpolate_property(self, "animated_health", ...

animated_health 를, self 에, 즉 GUI 노드를 타깃으로 합니다. Tween 의 interpolate_property는 속성의 이름을 문자열로 가져옵니다. 이것이 우리가 "animated_health" 로 쓴 이유입니다.

... _health", animated_health, new_value, 0.6 ...

시작 점은 막대의 현재 값입니다. 이 부분도 코딩을 해야 하지만, 곧 animated_health 가 될 것입니다. 애니메이션의 끝 점은 health_changed` 이후의 ``Playerhealth 가 됩니다: 즉, new_value 입니다. 그리고 0.6 은 애니메이션의 지속 시간입니다.

...  0.6, tween.TRANS_LINEAR, Tween.EASE_IN)

마지막 두 인수는 Tween 클래스의 상수입니다. TRANS_LINEAR 는 애니메이션이 선형적인 것을 의미합니다. EASE_IN 선형 변환에선 아무것도 하지 않지만, 꼭 이 마지막 인수를 채워야 에러가 나지 않습니다.

애니메이션은 Tween 노드가 tween.start() 로 활성화되기 전 까지 재생되지 않습니다. 노드가 활성화되어있지 않으면 이 작업만 하면 됩니다. 이 코드를 마지막 줄 이후에 추가하세요:

if not tween.is_active():
    tween.start()
if (!_tween.IsActive())
{
    _tween.Start();
}

주석

비록 Player 에서 health 속성을 애니메이트 할 수 있을지라도, 그렇게 해서는 안 됩니다. 캐릭터는 피해를 입었을 때 즉시 체력이 줄어야 합니다. 한 명이 언제 죽었는 지 알 수 있는 것처럼, 이 방식이 그들의 상태를 다루기에 훨씬 더 쉽습니다. 당신은 애니메이션이 항상 분리된 데이터 컨테이너나 노드에 있기를 원합니다. tween 노드는 코드로 제어되는 애니메이션에 완벽합니다. 수제 애니메이션은, AnimationPlayer 를 확인하세요.

animated_health를 LifeBar에 지정하기

이제 animated_health 변수가 애니메이트 하지만 실제 BarNumber 노드는 더 이상 업데이트 할 수 없습니다. 이것을 고칩시다.

지금까지, update_health 메서드는 이렇게 보입니다:

func update_health(new_value):
    tween.interpolate_property(self, "animated_health", animated_health, new_value, 0.6, Tween.TRANS_LINEAR, Tween.EASE_IN)
    if not tween.is_active():
        tween.start()
public void UpdateHealth(int health)
{
    _tween.InterpolateProperty(this, "_animatedHealth", _animatedHealth, health, 0.6f, Tween.TransitionType.Linear,
        Tween.EaseType.In);

    if(!_tween.IsActive())
    {
        _tween.Start();
    }
}

특정한 경우에서, number_label 이 텍스트를 차지하기 때문에, 우리는 _process 메서드로 그것을 애니메이트 해야 합니다. 이제 _process` 안에 ``NumberTextureProgress 노드를 전과 같이 업데이트 합시다:

func _process(delta):
    number_label.text = str(animated_health)
    bar.value = animated_health
public override void _Process(float delta)
{
    _numberLabel.Text = _animatedHealth.ToString();
    _bar.Value = _animatedHealth;
}

주석

number_labelbarNumberTextureProgress 노드에 참조로 저장된 변수들입니다.

게임을 실행하시면 막대가 부드럽게 애니메이트 하는 것을 보실 수 있습니다. 하지만 텍스트는 소수 숫자로 보여지고 난잡해 보입니다. 그리고 게임의 스타일을 고려해서, 체력 막대가 더 멋진 방식으로 움직이면 좋을 것입니다.

../../_images/lifebar_tutorial_number_animation_messed_up.gif

애니메이션은 부드럽지만 숫자는 망가졌습니다

animated_health 를 반올림해서 이 문제들을 동시에 해결할 수 있습니다. round_value 라는 지역 변수를 사용해서 반올림 된 animated_health 를 저장합니다. 그런 뒤 그것을 number_label.textbar.value 에 지정합니다:

func _process(delta):
    var round_value = round(animated_health)
    number_label.text = str(round_value)
    bar.value = round_value
public override void _Process(float delta)
{
    var roundValue = Mathf.Round(_animatedHealth);
    _numberLabel.Text = roundValue.ToString();
    _bar.Value = roundValue;
}

게임을 다시 실행하면 멋진 블록 애니메이션을 볼 수 있습니다.

../../_images/lifebar_tutorial_number_animation_working.gif

animated_health를 반올림 해서 일석이조의 효과를 냈습니다

참고

매 시간 플레이어가 피해를 입을 때, GUI_on_Player_health_changed 를 호출합니다, 그리고 그것은 update_health 를 호출합니다. 이것은 애니메이션을 업데이트 하고 number_labelbar_process 에서 따라옵니다. 체력이 점차 줄어드는 애니메이트 된 체력 막대는 트릭입니다. 그것은 GUI가 살아있는 것처럼 느끼도록 만듭니다. Player 가 3 데미지를 입으면, 즉각적으로 발생합니다.

Player가 죽을 때 막대가 사라지게 하기

초록색 캐릭터가 죽을 때, 죽음 애니메이션을 재생하고 사라집니다. 이 점에서, 우리는 인터페이스를 더 이상 표시해서는 안됩니다. 캐릭터가 죽을 때 처럼 막대를 사라지게 해봅시다. 우리는 Tween 노드가 여러 애니메이션을 동시에 관리하므로 같은 노드를 다시 사용할 것입니다.

먼저, GUIPlayerdied 시그널에 연결해서 언제 죽었는 지를 알아야 합니다. F1 키를 눌러 2D 작업 공간으로 돌아갑니다. 씬 독에서 Player 노드를 선택하고 인스펙터 옆의 노드 탭을 클릭합니다.

died 시그널을 찾아서, 선택하고, 연결하기 버튼을 누릅니다.

../../_images/lifebar_tutorial_player_died_signal_enemy_connected.png

시그널에는 이미 Enemy와 연결되어 있어야 합니다

시그널 연결 창에서, GUI 노드를 다시 연결합니다. 노드 경로는 ../../GUI 가 되어야 하고 Method in Node는 _on_Player_died 가 되어야 합니다. 함수 만들기 설정을 둔 채로 창 아래 연결 버튼을 누릅니다. 이걸로 Script 작업 공간에서 GUI.gd 파일로 진입하게 됩니다.

../../_images/lifebar_tutorial_player_died_connecting_signal_window.png

시그널 연결 창에서 이 값들을 받아야 합니다

주석

지금부터는 패턴을 보아야 합니다: 매 순간 GUI가 새로운 정보가 필요하면, 우리는 새 시그널을 방출합니다. 그들을 현명하게 사용하세요: 더 많은 연결을 추가할수록, 추적하는 일은 더 어려워집니다.

UI 요소가 사라지도록 애니메이트 하기 위해, 우리는 그것의 modulate 속성을 사용해야 합니다. modulateColor 로 텍스처의 색상을 곱합니다.

주석

modulateCanvasItem 클래스에서 나옵니다. 모든 2D와 UI 노드는 여기에 상속됩니다. 이것은 노드의 가시성을 켜고 끌 수 있고, 셰이더를 지정하고, modulate 에서 색상을 사용하여 수정할 수 있습니다.

modulate는 4개의 채널로 Color 값을 갖습니다: 빨강, 초록, 파랑, 그리고 알파입니다. 처음 세 개의 채널들 중 하나를 어둡게 하면 그것은 인터페이스에서 어두워집니다. 알파 채널을 낮추면 인터페이스는 희미해집니다.

우리는 두 색상 값을 tween할 것입니다: 완전한 불투명을 의미하는, 흰색이고 1 의 알파 값에서, 완전한 투명을 의미하는, 흰색이지만 0 의 알파 값까지 입니다. 두 변수를 _on_Player_died 메서드에 추가하고 이 둘을 각각 start_colorend_color 라고 이름짓습니다. Color() 생성자(constructor)를 사용해서 두 Color 값을 만듭니다.

func _on_Player_died():
    var start_color = Color(1.0, 1.0, 1.0, 1.0)
    var end_color = Color(1.0, 1.0, 1.0, 0.0)
public void OnPlayerDied()
{
    var startColor = new Color(1.0f, 1.0f, 1.0f);
    var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);
}

Color(1.0, 1.0, 1.0) 는 흰색과 같습니다. 네 번째 인수는, start_colorend_color 에서 각각 1.00.0 인, 알파 채널입니다.

우리는 Tween 노드의 interpolate_property 메서드를 다시 호출해야 합니다:

tween.interpolate_property(self, "modulate", start_color, end_color, 1.0, Tween.TRANS_LINEAR, Tween.EASE_IN)
_tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
  Tween.EaseType.In);

이번엔 modulate 속성을 바꾸고 그것이 start_color에서 end_color로 애니메이트 해야 합니다. 지속 시간은 1초로 선형 전환입니다. 다시 말하지만, 전환이 선형이기 때문에 완화는 아무 문제가 없습니다. 이것이 완전한 _on_Player_died 메서드 입니다:

func _on_Player_died():
    var start_color = Color(1.0, 1.0, 1.0, 1.0)
    var end_color = Color(1.0, 1.0, 1.0, 0.0)
    tween.interpolate_property(self, "modulate", start_color, end_color, 1.0, Tween.TRANS_LINEAR, Tween.EASE_IN)
public void OnPlayerDied()
{
    var startColor = new Color(1.0f, 1.0f, 1.0f);
    var endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);

    _tween.InterpolateProperty(this, "modulate", startColor, endColor, 1.0f, Tween.TransitionType.Linear,
        Tween.EaseType.In);
}

그리고 됬습니다. 이제 게임을 실행해서 최종 결과물을 봅시다!

../../_images/lifebar_tutorial_final_result.gif

최종 결과물. 여기까지 오신 것에 축하합니다!

주석

똑같은 기법을 사용해서, Player가 독에 걸렸을 때 막대의 색상을 바꿀 수 있고, 체력이 천천히 줄어들 때 막대가 붉게 바뀌거나, 크리티컬 피해를 입을 때 UI가 흔들리게 하거나... 원리는 같습니다: Player 의 정보를 GUI 로 보내고 GUI 가 이를 처리하는 시그널을 방출합니다.