自定义 Resource

带 class_name 的 Resource 被当作类型化数据使用。六个条目共用同一个脚本,每张卡片都靠 changed 信号重绘。

上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。

用到的 Godot 类

Resourceclass_nameResource.changedGDScript

代码

scripts/main.gd
extends Node

const ROSTER: Array = [
	{title = 'Copper coin', cost = 10, power = 1, tint = '#f3d48d'},
	{title = 'Oak shield', cost = 45, power = 3, tint = '#8eef97'},
	{title = 'Frost dagger', cost = 80, power = 4, tint = '#8da5f3'},
	{title = 'Ember ring', cost = 120, power = 2, tint = '#fc7f7f'},
	{title = 'Silk cloak', cost = 60, power = 5, tint = '#8eef97'},
	{title = 'Iron helm', cost = 95, power = 6, tint = '#8da5f3'},
]
const TICK: float = 0.08

# Variant and not PlaygroundItem: the playground loads the script at runtime, so the global class never registers
var items: Array = []
# index -> { cost: Label, bar: ProgressBar }
var cards: Array = []
var status: Label
var elapsed: float = 0.0
var next_index: int = 0


func _ready() -> void:
	var column: VBoxContainer = _root()

	var title: Label = Label.new()
	title.text = 'One Resource, six items'
	title.add_theme_font_size_override('font_size', 28)
	column.add_child(title)

	var hint: Label = Label.new()
	hint.text = 'item.gd extends Resource with class_name PlaygroundItem. Every card redraws from the changed signal.'
	hint.add_theme_color_override('font_color', Color('#8a90a0'))
	column.add_child(hint)

	var grid: GridContainer = GridContainer.new()
	grid.columns = 3
	grid.add_theme_constant_override('h_separation', 20)
	grid.add_theme_constant_override('v_separation', 20)
	column.add_child(grid)

	var item_script: GDScript = Get.script('item')

	for entry in ROSTER:
		var item: Variant = item_script.new()
		item.title = entry.title
		item.cost = entry.cost
		item.power = entry.power
		item.tint = Color(entry.tint)
		items.append(item)
		grid.add_child(_card(item))

	var button: Button = Button.new()
	button.text = 'level up every item'
	button.pressed.connect(_level_all)
	column.add_child(button)

	status = Label.new()
	status.add_theme_color_override('font_color', Color('#8eef97'))
	status.text = 'six instances of the same script, each one holding its own values'
	column.add_child(status)

	print('the roster levels itself up, click the button to bump all six at once')


func _process(delta: float) -> void:
	elapsed += delta

	if elapsed < TICK:
		return

	elapsed = 0.0
	items[next_index].level_up()
	status.text = '%s leveled up' % items[next_index].title
	next_index = (next_index + 1) % items.size()


func _level_all() -> void:
	for item in items:
		item.level_up()

	status.text = 'every item leveled up'


# binds the index and not the item: a Callable holding the Resource it is connected to leaks the pair
func _refresh(index: int) -> void:
	var item: Variant = items[index]
	var card: Dictionary = cards[index]
	card.cost.text = 'cost %d gold' % item.cost
	card.bar.value = item.power


func _card(item: Variant) -> Control:
	var style: StyleBoxFlat = StyleBoxFlat.new()
	style.bg_color = Color('#2a2f3d')
	style.set_corner_radius_all(12)
	style.set_content_margin_all(16)

	var panel: PanelContainer = PanelContainer.new()
	panel.custom_minimum_size = Vector2(300, 0)
	panel.add_theme_stylebox_override('panel', style)

	var box: VBoxContainer = VBoxContainer.new()
	box.add_theme_constant_override('separation', 10)
	panel.add_child(box)

	var head: HBoxContainer = HBoxContainer.new()
	head.add_theme_constant_override('separation', 12)
	box.add_child(head)

	var swatch: ColorRect = ColorRect.new()
	swatch.color = item.tint
	swatch.custom_minimum_size = Vector2(24, 24)
	head.add_child(swatch)

	var name_label: Label = Label.new()
	name_label.text = item.title
	name_label.add_theme_font_size_override('font_size', 20)
	head.add_child(name_label)

	var cost: Label = Label.new()
	cost.add_theme_color_override('font_color', Color('#8a90a0'))
	box.add_child(cost)

	var bar: ProgressBar = ProgressBar.new()
	bar.max_value = 10
	bar.show_percentage = false
	bar.custom_minimum_size = Vector2(0, 18)
	bar.add_theme_stylebox_override('background', _bar_style(Color('#12141a')))
	bar.add_theme_stylebox_override('fill', _bar_style(item.tint))
	box.add_child(bar)

	var index: int = cards.size()
	cards.append({cost = cost, bar = bar})
	item.changed.connect(_refresh.bind(index))
	_refresh(index)

	return panel


func _bar_style(color: Color) -> StyleBoxFlat:
	var style: StyleBoxFlat = StyleBoxFlat.new()
	style.bg_color = color
	style.set_corner_radius_all(6)

	return style


func _root() -> VBoxContainer:
	var background: ColorRect = ColorRect.new()
	background.color = Color('#12141a')
	add_child(background)
	background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	var margin: MarginContainer = MarginContainer.new()

	for side in ['margin_left', 'margin_right', 'margin_top', 'margin_bottom']:
		margin.add_theme_constant_override(side, 56)

	add_child(margin)
	margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

	var center: CenterContainer = CenterContainer.new()
	margin.add_child(center)

	var column: VBoxContainer = VBoxContainer.new()
	column.add_theme_constant_override('separation', 18)
	center.add_child(column)

	return column
scripts/item.gd
extends Resource
class_name PlaygroundItem

@export var title: String = 'item'
@export var cost: int = 10
@export var power: int = 1
@export var tint: Color = Color.WHITE


func level_up() -> void:
	power = mini(power + 1, 10)
	cost += 15
	emit_changed()

更多数据示例