You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Godot 4.1报错:为Nil类型设置texture传入AtlasTexture问题排查

Godot Inventory系统报错:Invalid set index 'texture' (on base: 'Nil')

问题场景

在Godot开发Inventory系统,架构包含:

  • Slot场景:单个物品槽的UI与逻辑
  • Slot Block场景:承载Slot网格的容器
  • Inventory Screen场景:带Tab容器的库存主界面

单独运行Slot Block场景无异常,但运行Inventory Screen场景时,Slot脚本触发错误:Invalid set index 'texture' (on base: 'Nil') with value of type 'AtlasTexture'。测试库存含20个SlotData槽位,其中两个配置物品数据,报错发生在第一个有数据的槽位(口粮)。

相关代码

Slot场景脚本

extends PanelContainer

@onready var item_icon: TextureRect = $MarginContainer/ItemIcon
@onready var quantity_label: Label = $QuantityLabel

func set_slot_data(slot_data: SlotData) -> void:
    var item_data = slot_data.item_data
    item_icon.texture = item_data.item_icon
    tooltip_text = "%s\n%s" % [item_data.name, item_data.description]
    
    if slot_data.quantity > 1:
        quantity_label.text = "x%s" % slot_data.quantity
        quantity_label.show()

Slot Block场景脚本

extends MarginContainer

const SLOT = preload("res://inventory/slot.tscn")

func _ready() -> void:
    pass

func set_slots_block_data(slot_datas: Array[SlotData], item_type_filter: ItemData.ITEMTYPE) -> void:
    
    var passed_inv_data: Array[SlotData] = slot_datas
    #optional item type filter
    if item_type_filter and item_type_filter != ItemData.ITEMTYPE.NULL:
        match item_type_filter:
            ItemData.ITEMTYPE.EQUIPPABLE:
                passed_inv_data = passed_inv_data.filter(func(x): return x.item_data.item_type == ItemData.ITEMTYPE.EQUIPPABLE)
            ItemData.ITEMTYPE.FOOD:
                passed_inv_data = passed_inv_data.filter(func(x): return x.item_data.item_type == ItemData.ITEMTYPE.FOOD)
            ItemData.ITEMTYPE.USABLE:
                passed_inv_data = passed_inv_data.filter(func(x): return x.item_data.item_type == ItemData.ITEMTYPE.USABLE)
            ItemData.ITEMTYPE.QUEST:
                passed_inv_data = passed_inv_data.filter(func(x): return x.item_data.item_type == ItemData.ITEMTYPE.QUEST)
            ItemData.ITEMTYPE.MISC:
                passed_inv_data = passed_inv_data.filter(func(x): return x.item_data.item_type == ItemData.ITEMTYPE.MISC)
            
    populate_item_grid(passed_inv_data)

func populate_item_grid(slot_datas: Array[SlotData]) -> void:
    var item_grid = $"ScrollContainer/MarginContainer/ItemGrid"
    var children = item_grid.get_children()
    
    if children:
        for child in children:
            child.queue_free()
        
        for slot_data in slot_datas:
            var slot = SLOT.instantiate()
            slot.set_slot_data(slot_data)
            item_grid.add_child(slot)

Inventory Screen场景脚本

extends PanelContainer

@onready var inventory_tab_container: TabContainer = $HBoxContainer/MarginContainer/VBoxContainer/InventoryTabContainer

const SLOTS_BLOCK = preload("res://inventory/inventory_slots_block.tscn")

var test_inv = preload("res://test_objects/test_inventory.tres")
var slotdatas = test_inv.slot_datas

# Called when the node enters the scene tree for the first time.
func _ready() -> void:      
    set_inventory_panel(slotdatas)

func set_inventory_panel(inv: Array[SlotData]) -> void:
    var children = inventory_tab_container.get_children()
    for c in children:
        self.remove_child(c)
        c.queue_free()
        
    if inv:
        var inv_panel_all = SLOTS_BLOCK.instantiate()
        inv_panel_all.set_slots_block_data(inv, ItemData.ITEMTYPE.NULL)
        inventory_tab_container.add_child(inv_panel_all)

ItemData类

extends Resource
class_name ItemData

const MAX_VALUE: int = 1000000

#name of the item
@export var name: String = ""
#description for the item
@export_multiline var description: String = ""
#value in currency for a single unit of the item
@export_range(0,MAX_VALUE) var value: int = 0
#determines max hit points of item
@export var max_hp: int = 1
#determines if item is indestructable
@export var indestructable: bool = false
#item type of item 
@export var item_type: ITEMTYPE = ITEMTYPE.MISC: set = set_item_type
#item icon
@export var item_icon: AtlasTexture
#determines if an item is stackable
@export var stackable: bool = false

enum ITEMTYPE {
  EQUIPPABLE,
  FOOD,
  USABLE,
  QUEST,
  MISC,
  NULL,
}

func set_item_type(new_value: ItemData.ITEMTYPE):
    item_type = new_value

func decrease_value(change: int = 0):
    var prev_value = value
    value = value - change
    
func increase_value(change: int = 0):
    var prev_value = value
    value = value + change

InventoryData类

extends Resource
class_name InventoryData

@export var slot_datas: Array[SlotData]

SlotData类

extends Resource
class_name SlotData

const MAX_STACK_SIZE:int = 99

@export var item_data: ItemData 
@export_range(1, MAX_STACK_SIZE) var quantity: int = 1: set = set_quantity

func set_quantity(value: int) -> void:
    quantity = value
    if quantity > 1 and not item_data.stackable:
        quantity = 1
        push_error("%s is not stackable, setting quantity to 1" % item_data.name)

问题原因与修复

核心原因

报错显示item_icon为Nil,因为Godot中@onready变量只有当节点加入场景树后才会完成赋值。但当前代码中,先调用slot.set_slot_data(slot_data),之后才将Slot节点添加到场景树——此时item_icon还未初始化,自然无法设置texture属性。

修复方案

1. 调整Slot节点的添加与数据设置顺序

修改Slot Block脚本中的populate_item_grid函数,先将Slot节点加入场景树,再调用set_slot_data:

func populate_item_grid(slot_datas: Array[SlotData]) -> void:
    var item_grid = $"ScrollContainer/MarginContainer/ItemGrid"
    var children = item_grid.get_children()
    
    if children:
        for child in children:
            child.queue_free()
        
    for slot_data in slot_datas:
        var slot = SLOT.instantiate()
        item_grid.add_child(slot)  # 先加入场景树,触发@onready初始化
        slot.set_slot_data(slot_data)  # 再设置数据

2. 修正Inventory Screen的子节点移除逻辑

原代码中用self.remove_child(c)移除TabContainer的子节点,这是错误的,应该从inventory_tab_container中移除:

func set_inventory_panel(inv: Array[SlotData]) -> void:
    var children = inventory_tab_container.get_children()
    for c in children:
        inventory_tab_container.remove_child(c)  # 从正确的父节点移除
        c.queue_free()
        
    if inv:
        var inv_panel_all = SLOTS_BLOCK.instantiate()
        inventory_tab_container.add_child(inv_panel_all)
        inv_panel_all.set_slots_block_data(inv, ItemData.ITEMTYPE.NULL)

3. 可选:添加空值防护(增强鲁棒性)

在Slot脚本的set_slot_data中加入空值判断,避免因节点未初始化或数据缺失导致报错:

func set_slot_data(slot_data: SlotData) -> void:
    # 如果节点未加入场景树,延迟执行数据设置
    if not is_inside_tree():
        call_deferred("set_slot_data", slot_data)
        return
    
    var item_data = slot_data.item_data
    # 确保item_data和item_icon都不为空
    if item_data and item_icon:
        item_icon.texture = item_data.item_icon
        tooltip_text = "%s\n%s" % [item_data.name, item_data.description]
    
    # 确保quantity_label不为空
    if slot_data.quantity > 1 and quantity_label:
        quantity_label.text = "x%s" % slot_data.quantity
        quantity_label.show()

内容的提问来源于stack exchange,提问作者Sarah Coulston

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 23:35:58