햇빛 시스템 구현

Godot로 Plants vs. Zombies 클론을 만들고 있다. 오늘은 햇빛 시스템을 구현했다.


목표

  • 하늘에서 햇빛이 주기적으로 떨어짐
  • 클릭하면 수집되고 카운터에 반영
  • 나중에 Sunflower 연동까지 확장 가능한 구조

구조 설계

Sun (Area2D) ← sun.gd
├── AnimatedSprite2D
└── CollisionShape2D

SunManager (Node) ← Autoload 등록
Main
├── SunTimer
└── Label (햇빛 카운터)

SunManager는 Autoload로 등록해서 어디서든 접근 가능하게 했다.

햇빛 카운터처럼 게임 전체에서 공유하는 데이터는 Autoload가 적합하다.


Sun 씬

스프라이트는 Retro Diffusion으로 뽑은 64×64 픽셀아트 스프라이트시트 (4×4, 16프레임)를 사용했다. AnimatedSprite2D에 SpriteFrames 리소스로 등록하고 loop 설정.

충돌 모양은 CircleShape2D, 반지름 28 정도로 잡았다.

GDScript
# sun.gd
extends Area2D

signal sun_collected

var fall_speed = 50.0
var target_y = 0.0
var is_falling = true

func _ready():
    $AnimatedSprite2D.play("idle")

func _process(delta):
    if is_falling:
        position.y += fall_speed * delta
        if position.y >= target_y:
            position.y = target_y
            is_falling = false

func _on_input_event(_viewport, event, _shape_idx):
    if event is InputEventMouseButton and event.pressed:
        get_viewport().set_input_as_handled()
        emit_signal("sun_collected")
        queue_free()

_on_input_event는 Area2D의 시그널을 에디터에서 직접 연결해서 사용했다.

클릭하면 sun_collected 시그널을 발생시키고 자기 자신을 삭제한다.


SunManager

GDScript
# sun_manager.gd
extends Node

signal sun_changed(new_amount)

var sun_count = 50

func add_sun(amount):
    sun_count += amount
    emit_signal("sun_changed", sun_count)

func spend_sun(amount):
    if sun_count >= amount:
        sun_count -= amount
        emit_signal("sun_changed", sun_count)
        return true
    return false

sun_changed 시그널로 UI에 알린다. UI는 이 시그널을 구독해서 Label을 업데이트하는 방식.


Main - 타이머로 Sun 생성

GDScript
# main.gd
extends Node

@onready var debug_label = $Label
@onready var sun_timer = $SunTimer

var SunScene = preload("res://scenes/sun.tscn")

func _ready() -> void:
    SunManager.sun_changed.connect(_on_sun_changed)
    debug_label.text = "Sun: %d" % SunManager.sun_count
    sun_timer.timeout.connect(_on_sun_timer_timeout)

func _on_sun_changed(new_amount):
    debug_label.text = "Sun: %d" % new_amount

func _on_sun_timer_timeout():
    var sun = SunScene.instantiate()
    sun.position = Vector2(randf_range(100, 820), -32)
    sun.target_y = randf_range(200, 580)
    sun.sun_collected.connect(SunManager.add_sun.bind(25))
    add_child(sun)

SunTimer는 Wait Time 5초, Autostart 켜둔 상태. 나중에 시작 씬 만들면 Autostart 끄고 코드로 제어할 예정.

sun.position.y = -32는 스프라이트 크기 절반 기준으로 화면 위쪽 바깥에서 시작하게 한 것.


시행착오 - ColorRect가 클릭을 먹고 있었다

클릭해도 Sun이 아무 반응이 없었다. 원인을 찾는 데 시간이 좀 걸렸다.

의심한 것들:

  • _on_input_event 연결 문제
  • input_event 시그널 미연결
  • game_board.gd의 _input이 클릭을 가로채는 것
  • _input vs _unhandled_input 차이

실제 원인은 배경으로 깔아둔 ColorRect였다.

ColorRect의 Mouse Filter 기본값이 Stop이라 모든 마우스 입력을 ColorRect가 먼저 먹어버리고 있었다.

Ignore로 바꾸니까 바로 해결됐다.

Mouse Filter동작
Stop클릭을 여기서 멈춤 (기본값)
Pass처리하고 아래로도 전달
Ignore무시하고 아래로 전달

UI 노드 쪽은 항상 Mouse Filter 확인하는 습관을 들여야겠다.


다음

  • Sunflower가 주기적으로 Sun 생성
  • 식물 선택 UI
  • 좀비 기본 이동