feat(inventory): hotbar + inventory UI + 5 crafting recipes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
279
scripts/inventory/InventoryUI.gd
Normal file
279
scripts/inventory/InventoryUI.gd
Normal file
@@ -0,0 +1,279 @@
|
||||
extends CanvasLayer
|
||||
|
||||
const SLOT_SIZE: int = 48
|
||||
const SLOT_MARGIN: int = 4
|
||||
|
||||
var inventory: Inventory = null
|
||||
|
||||
var _hotbar_slots: Array = []
|
||||
var _inv_slots: Array = []
|
||||
var _inv_window: Control = null
|
||||
var _recipe_list: VBoxContainer = null
|
||||
var _is_open: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_build_hotbar()
|
||||
_build_inventory_window()
|
||||
|
||||
|
||||
func setup(inv: Inventory) -> void:
|
||||
inventory = inv
|
||||
inventory.inventory_changed.connect(_refresh)
|
||||
_refresh()
|
||||
|
||||
|
||||
func _build_hotbar() -> void:
|
||||
var hotbar_root := HBoxContainer.new()
|
||||
hotbar_root.name = "HotbarRoot"
|
||||
hotbar_root.add_theme_constant_override("separation", SLOT_MARGIN)
|
||||
|
||||
var total_width: int = SLOT_SIZE * 9 + SLOT_MARGIN * 8
|
||||
var anchor_node := Control.new()
|
||||
anchor_node.name = "HotbarAnchor"
|
||||
anchor_node.set_anchors_preset(Control.PRESET_BOTTOM_WIDE)
|
||||
anchor_node.custom_minimum_size = Vector2(total_width, SLOT_SIZE + 8)
|
||||
anchor_node.set_offset(SIDE_BOTTOM, -8)
|
||||
anchor_node.set_offset(SIDE_TOP, -(SLOT_SIZE + 16))
|
||||
add_child(anchor_node)
|
||||
|
||||
hotbar_root.set_anchors_preset(Control.PRESET_CENTER)
|
||||
hotbar_root.position = Vector2(-total_width / 2.0, 0)
|
||||
anchor_node.add_child(hotbar_root)
|
||||
|
||||
for i: int in range(9):
|
||||
var slot := _make_slot_panel(i)
|
||||
hotbar_root.add_child(slot)
|
||||
_hotbar_slots.append(slot)
|
||||
|
||||
|
||||
func _make_slot_panel(index: int) -> PanelContainer:
|
||||
var panel := PanelContainer.new()
|
||||
panel.custom_minimum_size = Vector2(SLOT_SIZE, SLOT_SIZE)
|
||||
panel.set_meta("slot_index", index)
|
||||
|
||||
var style_normal := StyleBoxFlat.new()
|
||||
style_normal.bg_color = Color(0.05, 0.1, 0.2, 0.85)
|
||||
style_normal.border_color = Color(0.0, 0.8, 0.8)
|
||||
style_normal.set_border_width_all(2)
|
||||
style_normal.set_corner_radius_all(3)
|
||||
panel.add_theme_stylebox_override("panel", style_normal)
|
||||
panel.set_meta("style_normal", style_normal)
|
||||
|
||||
var style_selected := StyleBoxFlat.new()
|
||||
style_selected.bg_color = Color(0.05, 0.1, 0.2, 0.85)
|
||||
style_selected.border_color = Color(1.0, 0.9, 0.1)
|
||||
style_selected.set_border_width_all(3)
|
||||
style_selected.set_corner_radius_all(3)
|
||||
panel.set_meta("style_selected", style_selected)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
panel.add_child(vbox)
|
||||
|
||||
var color_rect := ColorRect.new()
|
||||
color_rect.name = "ColorRect"
|
||||
color_rect.custom_minimum_size = Vector2(SLOT_SIZE - 8, SLOT_SIZE - 18)
|
||||
color_rect.color = Color(0, 0, 0, 0)
|
||||
vbox.add_child(color_rect)
|
||||
|
||||
var count_label := Label.new()
|
||||
count_label.name = "CountLabel"
|
||||
count_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
count_label.add_theme_font_size_override("font_size", 10)
|
||||
count_label.text = ""
|
||||
vbox.add_child(count_label)
|
||||
|
||||
return panel
|
||||
|
||||
|
||||
func _build_inventory_window() -> void:
|
||||
_inv_window = Control.new()
|
||||
_inv_window.name = "InventoryWindow"
|
||||
_inv_window.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
_inv_window.visible = false
|
||||
add_child(_inv_window)
|
||||
|
||||
# Dim background
|
||||
var bg := ColorRect.new()
|
||||
bg.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
bg.color = Color(0, 0, 0, 0.55)
|
||||
_inv_window.add_child(bg)
|
||||
|
||||
# Main HBox: left = slots, right = craft panel
|
||||
var hbox := HBoxContainer.new()
|
||||
hbox.set_anchors_preset(Control.PRESET_CENTER)
|
||||
hbox.add_theme_constant_override("separation", 16)
|
||||
_inv_window.add_child(hbox)
|
||||
# Center it
|
||||
hbox.set_offset(SIDE_LEFT, -360)
|
||||
hbox.set_offset(SIDE_RIGHT, 360)
|
||||
hbox.set_offset(SIDE_TOP, -240)
|
||||
hbox.set_offset(SIDE_BOTTOM, 240)
|
||||
|
||||
# Left panel: inventory grid + hotbar row
|
||||
var left_vbox := VBoxContainer.new()
|
||||
left_vbox.add_theme_constant_override("separation", 8)
|
||||
hbox.add_child(left_vbox)
|
||||
|
||||
var inv_label := Label.new()
|
||||
inv_label.text = "Inventaire"
|
||||
inv_label.add_theme_font_size_override("font_size", 14)
|
||||
left_vbox.add_child(inv_label)
|
||||
|
||||
# 3 rows × 9 cols = 27 slots (indices 9..35)
|
||||
var inv_grid := GridContainer.new()
|
||||
inv_grid.columns = 9
|
||||
inv_grid.add_theme_constant_override("h_separation", SLOT_MARGIN)
|
||||
inv_grid.add_theme_constant_override("v_separation", SLOT_MARGIN)
|
||||
left_vbox.add_child(inv_grid)
|
||||
|
||||
for i: int in range(9, 36):
|
||||
var slot := _make_slot_panel(i)
|
||||
inv_grid.add_child(slot)
|
||||
_inv_slots.append(slot)
|
||||
|
||||
# Separator
|
||||
var sep := HSeparator.new()
|
||||
left_vbox.add_child(sep)
|
||||
|
||||
var hbar_label := Label.new()
|
||||
hbar_label.text = "Hotbar"
|
||||
hbar_label.add_theme_font_size_override("font_size", 12)
|
||||
left_vbox.add_child(hbar_label)
|
||||
|
||||
# Hotbar row inside inventory (mirror of bottom hotbar)
|
||||
var hotbar_grid := HBoxContainer.new()
|
||||
hotbar_grid.add_theme_constant_override("separation", SLOT_MARGIN)
|
||||
left_vbox.add_child(hotbar_grid)
|
||||
|
||||
for i: int in range(9):
|
||||
var slot := _make_slot_panel(i)
|
||||
hotbar_grid.add_child(slot)
|
||||
_inv_slots.append(slot) # also tracked here for refresh
|
||||
|
||||
# Right panel: craft
|
||||
var right_vbox := VBoxContainer.new()
|
||||
right_vbox.custom_minimum_size = Vector2(200, 0)
|
||||
right_vbox.add_theme_constant_override("separation", 6)
|
||||
hbox.add_child(right_vbox)
|
||||
|
||||
var craft_label := Label.new()
|
||||
craft_label.text = "Craft"
|
||||
craft_label.add_theme_font_size_override("font_size", 14)
|
||||
right_vbox.add_child(craft_label)
|
||||
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.custom_minimum_size = Vector2(200, 380)
|
||||
right_vbox.add_child(scroll)
|
||||
|
||||
_recipe_list = VBoxContainer.new()
|
||||
_recipe_list.add_theme_constant_override("separation", 6)
|
||||
scroll.add_child(_recipe_list)
|
||||
|
||||
|
||||
func _refresh() -> void:
|
||||
if inventory == null:
|
||||
return
|
||||
_refresh_hotbar_slots()
|
||||
if _is_open:
|
||||
_refresh_inv_slots()
|
||||
_refresh_recipes()
|
||||
|
||||
|
||||
func _refresh_hotbar_slots() -> void:
|
||||
for i: int in range(_hotbar_slots.size()):
|
||||
_update_slot_visual(_hotbar_slots[i], i)
|
||||
|
||||
|
||||
func _refresh_inv_slots() -> void:
|
||||
# _inv_slots contains: 27 inv slots (9..35) + 9 hotbar mirror (0..8)
|
||||
for i: int in range(27):
|
||||
_update_slot_visual(_inv_slots[i], i + 9)
|
||||
for i: int in range(9):
|
||||
_update_slot_visual(_inv_slots[27 + i], i)
|
||||
|
||||
|
||||
func _update_slot_visual(panel: PanelContainer, slot_index: int) -> void:
|
||||
var is_selected: bool = (slot_index == inventory.selected_hotbar and slot_index < 9)
|
||||
if is_selected:
|
||||
panel.add_theme_stylebox_override("panel", panel.get_meta("style_selected"))
|
||||
panel.scale = Vector2(1.1, 1.1)
|
||||
else:
|
||||
panel.add_theme_stylebox_override("panel", panel.get_meta("style_normal"))
|
||||
panel.scale = Vector2(1.0, 1.0)
|
||||
|
||||
var slot_data: Variant = inventory.slots[slot_index]
|
||||
var color_rect: ColorRect = panel.get_node("VBoxContainer/ColorRect")
|
||||
var count_label: Label = panel.get_node("VBoxContainer/CountLabel")
|
||||
|
||||
if slot_data == null:
|
||||
color_rect.color = Color(0, 0, 0, 0)
|
||||
count_label.text = ""
|
||||
else:
|
||||
color_rect.color = ItemDatabase.get_item_color(slot_data["item_id"])
|
||||
if slot_data["count"] > 1:
|
||||
count_label.text = str(slot_data["count"])
|
||||
else:
|
||||
count_label.text = ""
|
||||
|
||||
|
||||
func _refresh_recipes() -> void:
|
||||
for child in _recipe_list.get_children():
|
||||
child.queue_free()
|
||||
|
||||
for i: int in range(CraftingRecipes.RECIPES.size()):
|
||||
var recipe: Dictionary = CraftingRecipes.RECIPES[i]
|
||||
var can_craft: bool = inventory.has_items(recipe["inputs"])
|
||||
|
||||
var recipe_panel := VBoxContainer.new()
|
||||
recipe_panel.add_theme_constant_override("separation", 2)
|
||||
_recipe_list.add_child(recipe_panel)
|
||||
|
||||
var name_label := Label.new()
|
||||
name_label.text = recipe["name"]
|
||||
name_label.add_theme_font_size_override("font_size", 11)
|
||||
recipe_panel.add_child(name_label)
|
||||
|
||||
# Inputs summary
|
||||
var inputs_text: String = ""
|
||||
for inp: Dictionary in recipe["inputs"]:
|
||||
inputs_text += "%s x%d " % [ItemDatabase.get_item_name(inp["item_id"]), inp["count"]]
|
||||
var inp_label := Label.new()
|
||||
inp_label.text = inputs_text.strip_edges()
|
||||
inp_label.add_theme_font_size_override("font_size", 9)
|
||||
inp_label.modulate = Color(0.7, 0.7, 0.7)
|
||||
recipe_panel.add_child(inp_label)
|
||||
|
||||
var craft_btn := Button.new()
|
||||
craft_btn.text = "Creer"
|
||||
craft_btn.disabled = not can_craft
|
||||
var idx: int = i
|
||||
craft_btn.pressed.connect(func() -> void: _on_craft_pressed(idx))
|
||||
recipe_panel.add_child(craft_btn)
|
||||
|
||||
var sep := HSeparator.new()
|
||||
_recipe_list.add_child(sep)
|
||||
|
||||
|
||||
func _on_craft_pressed(recipe_index: int) -> void:
|
||||
CraftingRecipes.craft(inventory, recipe_index)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("toggle_inventory"):
|
||||
_toggle_inventory()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _toggle_inventory() -> void:
|
||||
_is_open = not _is_open
|
||||
_inv_window.visible = _is_open
|
||||
if _is_open:
|
||||
get_tree().paused = true
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_refresh_inv_slots()
|
||||
_refresh_recipes()
|
||||
else:
|
||||
get_tree().paused = false
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
Reference in New Issue
Block a user