본문 바로가기
Godot

[GodotDocs][Your First 2D Game] 9. HUD 씬 코딩(C#)

by 채식금지 2024. 3. 31.
728x90

본 게시글은 고도엔진 공식문서에 작성된 Your first 2D game를 정리하였습니다.

 

스크립트 추가

  • res://HUD.tscn 파일을 열어 HUD 노드를 선택한다.
  • Attach Script... 를 선택하여 res://HUD.cs 스크립트를 추가한다.

 

스크립트 코딩

  • res://HUD.cs 파일을 열어 스크립트를 추가할 때 자동으로 생성된 _Ready 메소드와 _Process 메소드를 제거한다.
  • StartGameEventHandler 시그널을 추가한다.
  • C#에서 시그널을 추가할 때는 반드시 시그널 이름 뒤에 EventHandler까지 표기해줘야 된다.
[Signal]
public delegate void StartGameEventHandler();

 

  • ShowMessage 메소드를 추가한다.
  • ShowMessage 메소드는 게임에서 원하는 메시지를 2초동안 출력하는 용도로 사용한다.
public void ShowMessage(string text)
{
    var messageLabel = GetNode<Label>("Message");
    messageLabel.Text = text;
    messageLabel.Show();
    GetNode<Timer>("MessageTimer").Start();
}

 

  • ShowGameOver 함수를 추가한다.
  • ShowGameOver 함수는 플레이어가 사망하였을 경우 2초동안 Game Over 메시지를 출력한 후 Dodge the Creeps! 메시지와 시작버튼을 출력하는 용도로 사용한다.
public async void ShowGameOver()
{
    ShowMessage("Game Over");
    await ToSignal(GetNode<Timer>("MessageTimer"), Timer.SignalName.Timeout);

    var messageLabel = GetNode<Label>("Message");
    messageLabel.Text = "Dodge the Creeps!";
    messageLabel.Show();
    await ToSignal(GetTree().CreateTimer(1), Timer.SignalName.Timeout);

    GetNode<Button>("StartButton").Show();
}
  • C#에서 await 키워드를 사용하려면 메소드에 async를 선언해야 된다.
  • await ToSignal([Timer 노드], Timer.SignalName.Timeout)를 입력하면 Timeout 시그널이 호출될 때까지 대기하게 된다.
  • GetTree().CreateTimer() 메소드를 사용하여 스크립에서 바로 타이머를 만들 수도 있다.

 

  • UpdateScore 메소드를 추가한다.
  • UpdateScore 메소드는 게임 중 현재 점수를 출력할 때 사용한다.
    public void UpdateScore(int score)
    {
        GetNode<Label>("ScoreLabel").Text = score.ToString();
    }

 

  • StartButton 노드의 pressed 시그널을 HUD 노드와 연결한다.
  • 문법규칙을 맞추기 위해 연결할 메소드 이름은 OnStartButtonPressed로 변경한다.

 

  • C#은 시그널을 연결해도 메소드가 자동추가되지 않기 때문에 HUD 클래스에 직접 OnStartButtonPressed 메소드를 추가한다.
  • OnStartButtonPressed 메소드 안에 아래 내용을 추가한다.
  • 시작버튼을 누르면 시작버튼을 화면에서 감춘 후 StartGame 시그널을 발생시키도록 구현한다.
public void OnStartButtonPressed()
{
    GetNode<Button>("StartButton").Hide();
    EmitSignal(SignalName.StartGame);
}

 

  • MessageTimer 노드의 timeout 시그널을 HUD 노드와 연결한다.
  • 문법규칙을 맞추기 위해 연결할 메소드 이름은 OnMessageTimerTimeout로 변경한다.

 

  • C#은 시그널을 연결해도 메소드가 자동추가되지 않기 때문에 HUD 클래스에 직접 OnMessageTimerTimeout 메소드를 추가한다.
  • OnMessageTimerTimeout 메소드 안에 아래 내용을 추가한다.
  • 메시지를 출력하고 2초후에 메시지를 감추도록 구현한다.
public void OnMessageTimerTimeout()
{
    GetNode<Label>("Message").Hide();
}

 

  • res://HUD.cs 파일들을 저장한 후 오론쪽 상단의 Build Project를 클릭하여 스크립트 내용을 적용한다.
  • res://HUD.tscn 파일들을 저장한다.

 

게임 씬과 연결

  • res://Main.tscn 씬을 열고, res://HUD.tscn 씬을 Main 노드의 자식으로 추가한다.

 

  • HUD 노드의 StartGame 시그널을 Main 클래스에 추가했던 NewGame 메소드와 연결한다.

 

  • Main 클래스를 아래 내용에 맞춰 수정한다.
  • NewGame 메소드에 아래 내용을 추가한다.
  • NewGame 메소드에 내용을 추가하면 아래 내용이 적용된다.
    • 게임이 시작될 때 초기화된 점수를 화면에 출력한다.
    • 게임이 시작되면 Get Ready 메시지를 2초동안 출력한다.
var hud = GetNode<HUD>("HUD");
hud.UpdateScore(Score);
hud.ShowMessage("Get Ready");

 

  • GameOver 메소드에 아래 내용을 추가한다.
  • 게임이 끝나면 Game Over 메시지를 출력한 후 시작버튼을 출력한다.
GetNode<HUD>("HUD").ShowGameOver();

 

  • OnScoreTimerTimeout 메소드에 아래 내용을 추가한다.
  • 점수가 증가할 때 마다 화면에 증가한 점수가 출력되도록 구현한다.
GetNode<HUD>("HUD").UpdateScore(Score);

 

  • _Ready 메소드를 지우거나 주석처리하여 시작버튼을 누르지 않으면 게임이 시작되지 않도록 수정한다.
/*public override void _Ready()
{
    NewGame();
}*/
  • res://Main.cs 파일들을 저장한 후 오론쪽 상단의 Build Project를 클릭하여 스크립트 내용을 적용한다.
  • res://Main.tscn 파일을 저장한다.

 

게임 씬 테스트

  • 오른쪽 상단의 Run Project를 클릭하여 게임을 테스트한다.

 

완성된 스크립트

HUD.cs
using Godot;
using System;

public partial class HUD : CanvasLayer
{
    [Signal]
    public delegate void StartGameEventHandler();

    public void ShowMessage(string text)
    {
        var messageLabel = GetNode<Label>("Message");
        messageLabel.Text = text;
        messageLabel.Show();
        GetNode<Timer>("MessageTimer").Start();
    }

    public async void ShowGameOver()
    {
        ShowMessage("Game Over");
        await ToSignal(GetNode<Timer>("MessageTimer"), Timer.SignalName.Timeout);

        var messageLabel = GetNode<Label>("Message");
        messageLabel.Text = "Dodge the Creeps!";
        messageLabel.Show();
        await ToSignal(GetTree().CreateTimer(1), Timer.SignalName.Timeout);

        GetNode<Button>("StartButton").Show();
    }

    public void UpdateScore(int score)
    {
        GetNode<Label>("ScoreLabel").Text = score.ToString();
    }

    public void OnStartButtonPressed()
    {
        GetNode<Button>("StartButton").Hide();
        EmitSignal(SignalName.StartGame);
    }

    public void OnMessageTimerTimeout()
    {
        GetNode<Label>("Message").Hide();
    }
}
Main.cs
using Godot;
using System;

public partial class Main : Node
{
    [Export]
    public PackedScene MobScene
    {
        get;
        private set;
    }

    public int Score
    {
        get;
        private set;
    }

    /*public override void _Ready()
    {
        NewGame();
    }*/

    void NewGame()
    {
        Score = 0;

        var player = GetNode<Player>("Player");
        var startPosition = GetNode<Marker2D>("StartPosition");
        var hud = GetNode<HUD>("HUD");

        player.Start(startPosition.Position);
        GetNode<Timer>("StartTimer").Start();
        hud.UpdateScore(Score);
        hud.ShowMessage("Get Ready");
    }

    void GameOver()
    {
        GetNode<Timer>("ScoreTimer").Stop();
        GetNode<Timer>("MobTimer").Stop();
        GetNode<HUD>("HUD").ShowGameOver();
    }

    void OnStartTimerTimeout()
    {
        GetNode<Timer>("ScoreTimer").Start();
        GetNode<Timer>("MobTimer").Start();
    }

    void OnScoreTimerTimeout()
    {
        ++Score;
        GetNode<HUD>("HUD").UpdateScore(Score);
    }

    void OnMobTimerTimeout()
    {
        var mob = MobScene.Instantiate<Mob>();

        var spawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
        spawnLocation.ProgressRatio = GD.Randf();

        var direction = spawnLocation.Rotation + Mathf.Pi / 2;
        direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);

        mob.Position = spawnLocation.Position;
        mob.Rotation = direction;

        var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0f);
        mob.LinearVelocity = velocity.Rotated(direction);

        AddChild(mob);
    }
}