그록 ai와 Godot엔진으로 2D 자원 배치하기
이전 포스팅에서 플레이어 캐릭터를 생성했는데, 이어서 이번에는 플레이어가 채굴할 수 있는 자원을 배치해보려고 한다.
![]() |
Godot 엔진 게임 테스트 플레이 화면 |
플레이어에게 초기 자원을 할당하고 조작키를 통해 자원을 배치할 수 있게 했다. 초기에 랜덤하게 배치된 자원 덩어리를 향해 나아가 새로운 자원을 채굴할 수 있다.
그록3에 요청한 내용은 다음과 같다.
"이제 다음 단계로 넘어가보자. 이제 다음의 요구사항을 해결해줘.
- 플레이어에게 기본 자원 제공
- 플레이어가 소유 중인 자원을 UI로 표시
- 맵의 랜덤 위치에 자원을 배치
- 플레이어가 Shift를 누르면 앞으로 돌진하면서 자원 채굴. 자원에 한 번 부딪힐 때마다 튕겨져 나오고 세 번 부딪히면 자원 습득."
그록3가 단계적으로 잘 알려주긴 했으나, 중간 중간 추가하고 수정해야 할 사항이 계속 생겨서 채팅이 상당히 길어졌기 때문에 마지막에 정리된 내용을 기록하려고 한다.
플레이어 스크립트 수정
player.gdextends CharacterBody2D
@export var move_speed = 200.0
@export var acceleration = 1500.0
@export var friction = 1000.0
@export var jump_strength = 250.0
@export var gravity = 980.0
@export var dash_speed = 450.0
@export var dash_duration = 0.1
@export var dash_cooldown = 0.5
var player_color: Color
var resources = 10 # 초기 자원 10개로 증가
var is_dashing = false
var dash_timer = 0.0
var dash_cooldown_timer = 0.0
var dash_direction = 0.0
var is_placing_block = false
var current_preview_block = null
var block_offset = Vector2(32, 0)
func _ready():
player_color = Color(randf(), randf(), randf(), 1.0)
$Sprite.material.set_shader_parameter("color", player_color)
func _physics_process(delta):
if not is_dashing:
velocity.y += gravity * delta
if dash_cooldown_timer > 0:
dash_cooldown_timer -= delta
if Input.is_action_just_pressed("mine_resource") and not is_dashing and dash_cooldown_timer <= 0:
var direction = 0
if Input.is_action_pressed("move_left"):
direction = -1
elif Input.is_action_pressed("move_right"):
direction = 1
if direction != 0:
is_dashing = true
dash_timer = dash_duration
dash_cooldown_timer = dash_cooldown
dash_direction = direction
velocity.x = dash_direction * dash_speed
velocity.y = 0
if is_dashing:
dash_timer -= delta
if dash_timer <= 0:
is_dashing = false
velocity.x *= 0.5
if not is_dashing:
var direction = 0
if Input.is_action_pressed("move_left"):
direction -= 1
if Input.is_action_pressed("move_right"):
direction += 1
if direction != 0:
velocity.x = move_toward(velocity.x, direction * move_speed, acceleration * delta)
else:
if is_on_floor():
velocity.x = move_toward(velocity.x, 0, friction * delta)
else:
velocity.x = move_toward(velocity.x, 0, friction * delta * 0.2)
if Input.is_action_just_pressed("jump") and is_on_floor() and not is_dashing:
velocity.y = -jump_strength
if Input.is_action_just_pressed("place_block") and resources > 0:
if not is_placing_block:
is_placing_block = true
var block_scene = preload("res://block.tscn")
current_preview_block = block_scene.instantiate()
current_preview_block.get_node("Sprite").modulate = player_color
current_preview_block.get_node("Sprite").modulate.a = 0.5
current_preview_block.get_node("Collision").disabled = true
get_tree().current_scene.add_child(current_preview_block)
current_preview_block.position = position + block_offset
else:
is_placing_block = false
current_preview_block.get_node("Sprite").modulate.a = 1.0
current_preview_block.get_node("Collision").disabled = false
current_preview_block.add_to_group("blocks")
resources -= 1
current_preview_block = null
if is_placing_block and current_preview_block:
if Input.is_action_just_pressed("move_left"):
block_offset = Vector2(-32, 0)
elif Input.is_action_just_pressed("move_right"):
block_offset = Vector2(32, 0)
elif Input.is_action_just_pressed("move_up"):
block_offset = Vector2(0, -32)
elif Input.is_action_just_pressed("move_down"):
block_offset = Vector2(0, 32)
current_preview_block.position = position + block_offset
move_and_slide()
if is_dashing:
for i in get_slide_collision_count():
var collision = get_slide_collision(i)
var body = collision.get_collider()
if body.is_in_group("resources") or body.is_in_group("blocks"):
var hit_success = body.hit()
if hit_success:
resources += 1
velocity.x = -dash_direction * dash_speed * 0.5
velocity.y = -jump_strength * 0.5
is_dashing = false
dash_timer = 0
dash_cooldown_timer = dash_cooldown
break
@export var move_speed = 200.0
@export var acceleration = 1500.0
@export var friction = 1000.0
@export var jump_strength = 250.0
@export var gravity = 980.0
@export var dash_speed = 450.0
@export var dash_duration = 0.1
@export var dash_cooldown = 0.5
var player_color: Color
var resources = 10 # 초기 자원 10개로 증가
var is_dashing = false
var dash_timer = 0.0
var dash_cooldown_timer = 0.0
var dash_direction = 0.0
var is_placing_block = false
var current_preview_block = null
var block_offset = Vector2(32, 0)
func _ready():
player_color = Color(randf(), randf(), randf(), 1.0)
$Sprite.material.set_shader_parameter("color", player_color)
func _physics_process(delta):
if not is_dashing:
velocity.y += gravity * delta
if dash_cooldown_timer > 0:
dash_cooldown_timer -= delta
if Input.is_action_just_pressed("mine_resource") and not is_dashing and dash_cooldown_timer <= 0:
var direction = 0
if Input.is_action_pressed("move_left"):
direction = -1
elif Input.is_action_pressed("move_right"):
direction = 1
if direction != 0:
is_dashing = true
dash_timer = dash_duration
dash_cooldown_timer = dash_cooldown
dash_direction = direction
velocity.x = dash_direction * dash_speed
velocity.y = 0
if is_dashing:
dash_timer -= delta
if dash_timer <= 0:
is_dashing = false
velocity.x *= 0.5
if not is_dashing:
var direction = 0
if Input.is_action_pressed("move_left"):
direction -= 1
if Input.is_action_pressed("move_right"):
direction += 1
if direction != 0:
velocity.x = move_toward(velocity.x, direction * move_speed, acceleration * delta)
else:
if is_on_floor():
velocity.x = move_toward(velocity.x, 0, friction * delta)
else:
velocity.x = move_toward(velocity.x, 0, friction * delta * 0.2)
if Input.is_action_just_pressed("jump") and is_on_floor() and not is_dashing:
velocity.y = -jump_strength
if Input.is_action_just_pressed("place_block") and resources > 0:
if not is_placing_block:
is_placing_block = true
var block_scene = preload("res://block.tscn")
current_preview_block = block_scene.instantiate()
current_preview_block.get_node("Sprite").modulate = player_color
current_preview_block.get_node("Sprite").modulate.a = 0.5
current_preview_block.get_node("Collision").disabled = true
get_tree().current_scene.add_child(current_preview_block)
current_preview_block.position = position + block_offset
else:
is_placing_block = false
current_preview_block.get_node("Sprite").modulate.a = 1.0
current_preview_block.get_node("Collision").disabled = false
current_preview_block.add_to_group("blocks")
resources -= 1
current_preview_block = null
if is_placing_block and current_preview_block:
if Input.is_action_just_pressed("move_left"):
block_offset = Vector2(-32, 0)
elif Input.is_action_just_pressed("move_right"):
block_offset = Vector2(32, 0)
elif Input.is_action_just_pressed("move_up"):
block_offset = Vector2(0, -32)
elif Input.is_action_just_pressed("move_down"):
block_offset = Vector2(0, 32)
current_preview_block.position = position + block_offset
move_and_slide()
if is_dashing:
for i in get_slide_collision_count():
var collision = get_slide_collision(i)
var body = collision.get_collider()
if body.is_in_group("resources") or body.is_in_group("blocks"):
var hit_success = body.hit()
if hit_success:
resources += 1
velocity.x = -dash_direction * dash_speed * 0.5
velocity.y = -jump_strength * 0.5
is_dashing = false
dash_timer = 0
dash_cooldown_timer = dash_cooldown
break
자원을 UI로 표시
- UI 추가:
- main.tscn을 열고, CanvasLayer 아래에 새 Label 노드를 추가:
- 이름: ResourceLabel.
- 속성:
- Text: Resources: 5 (임시).
- Position: (10, 40) (기존 DebugLabel 아래).
- Theme Overrides > Fonts > Font Size: 24.
- Theme Overrides > Colors > Font Color: 흰색(#FFFFFF).
- 기존 DebugLabel의 위치를 (10, 10)으로 재확인.
- main.tscn을 열고, CanvasLayer 아래에 새 Label 노드를 추가:
extends Node2D
@onready var debug_label = $CanvasLayer/DebugLabel
@onready var resource_label = $CanvasLayer/ResourceLabel
@onready var player = $Player
var player_start_pos = Vector2(640, 600)
var initial_cluster_distance = 300.0 # 초기 거리 400 → 300
var dynamic_cluster_distance = 400.0 # 동적 배치 거리 유지
var active_clusters = []
var spawn_threshold = 300.0
var last_spawn_pos = Vector2(640, 600)
func _ready():
player.position = player_start_pos
last_spawn_pos = player_start_pos
var cluster_scene = preload("res://resource_cluster.tscn")
for i in 3:
var cluster = cluster_scene.instantiate()
var angle = randf_range(-PI/3, PI/3) - PI/2 # 각도 범위 확장 (-45° → -60°)
var pos = player_start_pos + Vector2(cos(angle), sin(angle)) * initial_cluster_distance
cluster.position = pos
add_child(cluster)
active_clusters.append(cluster)
func _process(delta):
debug_label.text = "Player Pos: " + str(player.position.round()) + "\nVelocity: " + str(player.velocity.round())
resource_label.text = "Resources: " + str(player.resources)
var distance = player.position.distance_to(last_spawn_pos)
if distance > spawn_threshold:
spawn_new_cluster()
last_spawn_pos = player.position
func spawn_new_cluster():
var cluster_scene = preload("res://resource_cluster.tscn")
var cluster = cluster_scene.instantiate()
var angle = randf_range(-PI/4, PI/4) - PI/2 # 동적 배치는 기존 각도 유지
var pos = player.position + Vector2(cos(angle), sin(angle)) * dynamic_cluster_distance
cluster.position = pos
add_child(cluster)
active_clusters.append(cluster)
@onready var debug_label = $CanvasLayer/DebugLabel
@onready var resource_label = $CanvasLayer/ResourceLabel
@onready var player = $Player
var player_start_pos = Vector2(640, 600)
var initial_cluster_distance = 300.0 # 초기 거리 400 → 300
var dynamic_cluster_distance = 400.0 # 동적 배치 거리 유지
var active_clusters = []
var spawn_threshold = 300.0
var last_spawn_pos = Vector2(640, 600)
func _ready():
player.position = player_start_pos
last_spawn_pos = player_start_pos
var cluster_scene = preload("res://resource_cluster.tscn")
for i in 3:
var cluster = cluster_scene.instantiate()
var angle = randf_range(-PI/3, PI/3) - PI/2 # 각도 범위 확장 (-45° → -60°)
var pos = player_start_pos + Vector2(cos(angle), sin(angle)) * initial_cluster_distance
cluster.position = pos
add_child(cluster)
active_clusters.append(cluster)
func _process(delta):
debug_label.text = "Player Pos: " + str(player.position.round()) + "\nVelocity: " + str(player.velocity.round())
resource_label.text = "Resources: " + str(player.resources)
var distance = player.position.distance_to(last_spawn_pos)
if distance > spawn_threshold:
spawn_new_cluster()
last_spawn_pos = player.position
func spawn_new_cluster():
var cluster_scene = preload("res://resource_cluster.tscn")
var cluster = cluster_scene.instantiate()
var angle = randf_range(-PI/4, PI/4) - PI/2 # 동적 배치는 기존 각도 유지
var pos = player.position + Vector2(cos(angle), sin(angle)) * dynamic_cluster_distance
cluster.position = pos
add_child(cluster)
active_clusters.append(cluster)
![]() |
main 장면 노드 구조 |
맵에 자원 랜덤 배치
- 자원 장면 생성:
- 새 장면 생성: File > New Scene, 루트 노드를 StaticBody2D, 이름: resource.tscn.
- 노드 추가:
- CollisionShape2D:
- 이름: Collision.
- Shape: New RectangleShape2D, Extents: (16, 16) (32x32 정사각형).
- Sprite2D:
- 이름: Sprite.
- Texture: New CanvasTexture, 크기: (32, 32), 색상: 검정(#000000).
- CollisionShape2D:
- StaticBody2D에 그룹 추가:
- 인스펙터에서 Node > Groups > resources 그룹 추가.
extends StaticBody2D
var hit_count = 0
func _ready():
rotation = randf_range(0, 2 * PI) # 랜덤 고정 회전
func hit():
hit_count += 1
if hit_count >= 3:
queue_free()
return true
return false
var hit_count = 0
func _ready():
rotation = randf_range(0, 2 * PI) # 랜덤 고정 회전
func hit():
hit_count += 1
if hit_count >= 3:
queue_free()
return true
return false
![]() |
Resource 장면 노드 구조 |
자원 덩어리 장면 생성
- 새 장면 생성: File > New Scene, 루트 노드 Node2D, 이름: resource_cluster.tscn.
extends Node2D
func _ready():
var resource_scene = preload("res://resource.tscn")
var count = randi_range(20, 50) # 20~50개로 증가
for i in count:
var resource = resource_scene.instantiate()
var angle = randf() * 2 * PI
var radius = randf_range(15, 40) # 반경 축소 (20~60 → 15~40)
resource.position = Vector2(cos(angle), sin(angle)) * radius
add_child(resource)
블록 생성
block.gdextends StaticBody2D
var hit_count = 0
func hit():
hit_count += 1
if hit_count >= 3:
queue_free()
return true
return false
var hit_count = 0
func hit():
hit_count += 1
if hit_count >= 3:
queue_free()
return true
return false
댓글
댓글 쓰기