如何修复 "invalid get index 'position' (on base: Nil)." 错误
how do I fix an "invalid get index 'position' (on base: Nil)." error
在 Godot 中,我正在尝试制作一个简单的 space 射击游戏,玩家必须消灭一定数量的敌人才能进入游戏的下一关。我试图让敌人射击玩家以使游戏更有趣,但我收到一个错误,我不知道如何修复
无效的获取索引 'position'(基于:无)。
我认为这里的问题是变量位置显示为 null 但我不知道如何解决它
脚本:
extends StaticBody2D
var dir = Vector2()
var player
var bullet = preload("res://scebes/EnemyBullet.tscn")
func _ready():
_get_dir(player)
func _get_dir(target):
dir = (target.position - position).normalized()
func _on_Timer_timeout():
_get_dir(player)
var b = bullet.instance()
get_parent().add_child(b)
b.position = position + dir * offset
b.dir = dir
在线出现错误:
dir = (target.position - position).normalized()
场景树:
Node2D
├ Player
│ └ (...)
├ simple
│ ├ Sprite
│ └ CollisionShape2D
└ enemy
├ Sprite
├ CollisionShape2D
└ Timer
随着Timer信号的超时连接到_on_Timer_timeout
。
表面诊断
错误告诉您您正在尝试访问 position
上的空值。
行中:
dir = (target.position - position).normalized()
您尝试访问 target
(target.position
) 的 position
和 self
(position
) 的位置。 其中 self
是附加了脚本的对象。 显然 self
不为空。所以,让它必须是 target
.
代码获取target
作为参数:
func _get_dir(target):
dir = (target.position - position).normalized()
对症治疗
如果你空检查 target
:
,你将摆脱错误
代码获取它作为参数:
func _get_dir(target):
if target == null:
return
dir = (target.position - position).normalized()
或者更好的是,检查 is_instance_valid
:
func _get_dir(target):
if not is_instance_valid(target):
return
dir = (target.position - position).normalized()
这样它也将处理目标不为空但它引用已删除节点的情况。 您可以执行更多检查,例如 is_inside_tree
或 is_queued_for_deletion
,但通常您不需要这些。
但是,这并没有解决真正的问题。真正的问题是为什么 target
一开始是空的。
更深入的诊断
那么,_get_dir
在哪里调用呢?这里:
func _ready():
_get_dir(player)
稍后,这里:
func _on_Timer_timeout():
_get_dir(player)
# ...
player
从哪里获得它的价值?无处。这才是真正的问题。变量 player
未初始化。
解决方案 1(简单、脆弱)
想必你希望player
引用场景树上的Player
节点,对吧?
我们需要在 _ready
之前及时完成。所以我们需要在 _ready
的开头插入行,或者在 onready var
.
中插入行
这是可能的,尽管它是脆弱的代码:
onready var player := get_node("../Player")
问题在于它假定具有此脚本的节点和 Player
节点是兄弟节点。这在未来可能不是真的。也就是说,重新组织场景树会破坏代码。 因此,我们说代码是脆弱的(容易被破解)。
解决方案 2(复杂、稳健)
我们处于这种情况是因为敌人(我假设敌人中有这段代码)总是知道玩家在哪里。这些是无所不知的敌人。因此,另一种选择是接受它。
您可以创建一个 autoload (singleton)。为此,首先创建一个新脚本(二次单击“文件系统”面板和 select“新脚本...”),如下所示:
var player
也许输入它(Node2D
、KinematicBody2D
或其他):
var player:Node2D
将其注册为项目设置的自动加载,并使用一些名称,比如 PlayerInfo
。
现在,玩家需要自己注册:
func _ready() -> void:
PlayerInfo.player = self
敌人可以这样找到它:
func _ready() -> void:
player = PlayerInfo.player
情节扭曲:这段代码仍然很脆弱,因为 _ready
执行的顺序取决于场景树。
为避免这种情况,您可以在阅读 PlayerInfo.player
:
之前跳过 _ready
中的一帧
func _ready() -> void:
yield(get_tree(), "idle_frame")
player = PlayerInfo.player
这样,你确定 Player
有机会设置 PlayerInfo.player
。
您也可以在_enter_tree
中注册播放器:
func _enter_tree() -> void:
PlayerInfo.player = self
对于给定节点,_enter_tree
方法会在 _ready
之前 运行。但不一定在另一个节点的_ready
之前。
您可以做的另一件事是摆脱 player
变量并直接使用 PlayerInfo.player
:
_get_dir(PlayerInfo.player)
所以即使代码第一次得到null,它也不会卡在那个null上。
顺便说一下,您可以从任何节点使用 PlayerInfo.player
。
对了,你还记得额外的支票吗?你可以把它们放在自动加载中:
var player:Node2D setget , get_player
func get_player() -> Node2D:
if not is_instance_valid(player):
# player is null, or deleted
return null
if not player.is_inside_tree():
# player has not been added or has been removed form the scene
return null
if player.is_queued_for_deletion():
# player is queued for deletion (e.g. queue_free)
return null
return player
每次 PlayerInfo.player
被读取时 运行。
注意:还有其他原因需要自动加载以某种方式引用播放器。例如,您可以将保存和加载游戏功能放在那里。或者它可以帮助您将玩家从一个场景移动到另一个场景。因此,这种额外的复杂性可能在大型游戏的长 运行 中被证明是有用的。
在 Godot 中,我正在尝试制作一个简单的 space 射击游戏,玩家必须消灭一定数量的敌人才能进入游戏的下一关。我试图让敌人射击玩家以使游戏更有趣,但我收到一个错误,我不知道如何修复
无效的获取索引 'position'(基于:无)。
我认为这里的问题是变量位置显示为 null 但我不知道如何解决它
脚本:
extends StaticBody2D
var dir = Vector2()
var player
var bullet = preload("res://scebes/EnemyBullet.tscn")
func _ready():
_get_dir(player)
func _get_dir(target):
dir = (target.position - position).normalized()
func _on_Timer_timeout():
_get_dir(player)
var b = bullet.instance()
get_parent().add_child(b)
b.position = position + dir * offset
b.dir = dir
在线出现错误:
dir = (target.position - position).normalized()
场景树:
Node2D
├ Player
│ └ (...)
├ simple
│ ├ Sprite
│ └ CollisionShape2D
└ enemy
├ Sprite
├ CollisionShape2D
└ Timer
随着Timer信号的超时连接到_on_Timer_timeout
。
表面诊断
错误告诉您您正在尝试访问 position
上的空值。
行中:
dir = (target.position - position).normalized()
您尝试访问 target
(target.position
) 的 position
和 self
(position
) 的位置。 其中 self
是附加了脚本的对象。 显然 self
不为空。所以,让它必须是 target
.
代码获取target
作为参数:
func _get_dir(target):
dir = (target.position - position).normalized()
对症治疗
如果你空检查 target
:
代码获取它作为参数:
func _get_dir(target):
if target == null:
return
dir = (target.position - position).normalized()
或者更好的是,检查 is_instance_valid
:
func _get_dir(target):
if not is_instance_valid(target):
return
dir = (target.position - position).normalized()
这样它也将处理目标不为空但它引用已删除节点的情况。 您可以执行更多检查,例如 is_inside_tree
或 is_queued_for_deletion
,但通常您不需要这些。
但是,这并没有解决真正的问题。真正的问题是为什么 target
一开始是空的。
更深入的诊断
那么,_get_dir
在哪里调用呢?这里:
func _ready():
_get_dir(player)
稍后,这里:
func _on_Timer_timeout():
_get_dir(player)
# ...
player
从哪里获得它的价值?无处。这才是真正的问题。变量 player
未初始化。
解决方案 1(简单、脆弱)
想必你希望player
引用场景树上的Player
节点,对吧?
我们需要在 _ready
之前及时完成。所以我们需要在 _ready
的开头插入行,或者在 onready var
.
这是可能的,尽管它是脆弱的代码:
onready var player := get_node("../Player")
问题在于它假定具有此脚本的节点和 Player
节点是兄弟节点。这在未来可能不是真的。也就是说,重新组织场景树会破坏代码。 因此,我们说代码是脆弱的(容易被破解)。
解决方案 2(复杂、稳健)
我们处于这种情况是因为敌人(我假设敌人中有这段代码)总是知道玩家在哪里。这些是无所不知的敌人。因此,另一种选择是接受它。
您可以创建一个 autoload (singleton)。为此,首先创建一个新脚本(二次单击“文件系统”面板和 select“新脚本...”),如下所示:
var player
也许输入它(Node2D
、KinematicBody2D
或其他):
var player:Node2D
将其注册为项目设置的自动加载,并使用一些名称,比如 PlayerInfo
。
现在,玩家需要自己注册:
func _ready() -> void:
PlayerInfo.player = self
敌人可以这样找到它:
func _ready() -> void:
player = PlayerInfo.player
情节扭曲:这段代码仍然很脆弱,因为 _ready
执行的顺序取决于场景树。
为避免这种情况,您可以在阅读 PlayerInfo.player
:
_ready
中的一帧
func _ready() -> void:
yield(get_tree(), "idle_frame")
player = PlayerInfo.player
这样,你确定 Player
有机会设置 PlayerInfo.player
。
您也可以在_enter_tree
中注册播放器:
func _enter_tree() -> void:
PlayerInfo.player = self
对于给定节点,_enter_tree
方法会在 _ready
之前 运行。但不一定在另一个节点的_ready
之前。
您可以做的另一件事是摆脱 player
变量并直接使用 PlayerInfo.player
:
_get_dir(PlayerInfo.player)
所以即使代码第一次得到null,它也不会卡在那个null上。
顺便说一下,您可以从任何节点使用 PlayerInfo.player
。
对了,你还记得额外的支票吗?你可以把它们放在自动加载中:
var player:Node2D setget , get_player
func get_player() -> Node2D:
if not is_instance_valid(player):
# player is null, or deleted
return null
if not player.is_inside_tree():
# player has not been added or has been removed form the scene
return null
if player.is_queued_for_deletion():
# player is queued for deletion (e.g. queue_free)
return null
return player
每次 PlayerInfo.player
被读取时 运行。
注意:还有其他原因需要自动加载以某种方式引用播放器。例如,您可以将保存和加载游戏功能放在那里。或者它可以帮助您将玩家从一个场景移动到另一个场景。因此,这种额外的复杂性可能在大型游戏的长 运行 中被证明是有用的。