所以,我正在制作一款自上而下的射击游戏,我想无限地实例化敌人来追逐玩家。当我实例化敌人时,它只是出现在屏幕上,并且不执行我在敌人脚本中放入的任何代码。这是主要的lvl脚本:敌人是它的孩子。
extends Node2D
var enemy_scene = preload("res://level/enemies/zombie.tscn")
func _ready():
pass
func _on_timer_timeout():
spawn_enemy()
func spawn_enemy():
var enemy_instance = enemy_scene.instantiate() as CharacterBody2D
enemy_instance.position = get_enemy_position()
enemy_instance.visible = true
add_child(enemy_instance)
func get_enemy_position():
var spawn_x = randf_range($player.position.x + 100.0, $player.position.x - 100.0)
var spawn_y = randf_range($player.position.y + 100.0, $player.position.y - 100.0)
return Vector2(spawn_x, spawn_y)
有一个 3 秒计时器可以实例化敌人,但它没有做任何我尝试编码的事情。我可以使用变量“敌人实例”来做基本的事情,比如将可见性设置为 true,但如果我想每帧都做一些事情,那就是一个问题。
我尝试观看 youtube 教程,出于某种原因,他们做了与我相同的事情,并且实例化仍然有效。我什至问过GPT,也没有用。
这要么是一个好主意,要么有必要为
zombie.tscn
类的根提供一个脚本。那么让我们从这里开始...
创建一个脚本,我将其称为
zombie.gd
,将其附加到 zombie.tscn
场景的根目录。
zombie.gd
必须扩展CharacterBody2D
,因为这是节点类型,zombie.tscn
场景的根是:
zombie.gd
extends CharacterBody2D
您还可以给它一个类名,以便我们可以将其用作全局类型:
zombie.gd
class_name Zombie
extends CharacterBody2D
在该脚本中,您可以定义
_physics_process
它将运行每个物理帧,以及 _process
它将运行每个图形帧(delta
参数将具有自同类最后一帧以来的时间(以秒为单位):
zombie.gd
class_name Zombie
extends CharacterBody2D
func _process(delta: float) -> void:
print(delta)
func _physics_process(delta: float) -> void:
print(delta)
所以你可以使用这些让你的
Zombie
每帧都做一些事情。
值得注意的是,作为
CharacterBody2D
,您可能想在 move_and_slide
上致电 _physics_process
。
我还会假设你希望他们追逐玩家角色。我想到的是这样的:
您可以将
Area2D
作为子级添加到 Zombie
,其碰撞形状定义了 Zombie
将检测玩家角色的区域。
对于该检测,您可以将
body_entered
信号连接到 zombie.tscn
场景的根,在 zombie.gd
脚本中添加一个新方法,如下所示:
zombie.gd
func _on_body_entered(body:Node) -> void:
pass
这样您就可以获得它检测到的身体的参考:
zombie.gd
var maybe_player_character:Node2D
func _on_body_entered(body:Node) -> void:
maybe_player_character = body as Node2D
您可能想确认它是玩家角色。我们可以通过为玩家角色场景提供一个带有类名的脚本来实现这一点(类似于我们为
Zombie
场景提供 zombie.tscn
的方式)。例如,如果该类是 PlayerCharacter
,我们可以这样做:
zombie.gd
var player_character:PlayerCharacter
func _on_body_entered(body:Node) -> void:
var maybe_player_character := body as PlayerCharacter
if is_instance_valid(maybe_player_character):
player_character = maybe_player_character
我将推迟您到如何检测 Godot 中的碰撞?了解详细信息
然后使用
global_position
的 Zombie
和玩家角色,您可以选择移动方向,并导出速度变量(您在检查器面板中设置):
zombie.gd
@export var speed:float
func _physics_process(delta: float) -> void:
if not is_instance_valid(player_character):
return
var direction := global_positition.direction_to(
player_character.global_positition
)
velocity = direction * speed
move_and_slide()
当然,僵尸的行为可能会更复杂,但有了这个,僵尸就会做一些事情。
想必您还想处理僵尸与玩家角色碰撞的情况。再次请参阅如何检测 Godot 中的碰撞?。想必您还想要某种损害/健康/生命......虽然这超出了这个问题的范围,但这里有一个提示:您可以向
health
类添加 PlayerCharacter
属性。
您似乎希望您的生成器脚本不仅生成敌人,而且还控制它们。为了这个答案,我将继续称其为“spawner”,但要知道你要做的是导演。
让我们从在您的生成器中使用
Zombie
类开始:
产卵者
var enemy_instance := enemy_scene.instantiate() as Zombie
因此,如果您在
zombie.gd
中定义了其他属性或方法,您可以从您的生成器中访问它们。例如:
zombie.gd
class_name Zombie
extends CharacterBody2D
var another_property:int
产卵者
var enemy_instance := enemy_scene.instantiate() as Zombie
enemy_instance.another_property = 42
或者:
zombie.gd
class_name Zombie
extends CharacterBody2D
func my_method() -> void:
print("my_method called")
产卵者
var enemy_instance := enemy_scene.instantiate() as Zombie
enemy_instance.my_method()
您可以在方法中添加参数,例如:
zombie.gd
class_name Zombie
extends CharacterBody2D
func my_method(something:String) -> void:
print(something)
产卵者
var enemy_instance := enemy_scene.instantiate() as Zombie
enemy_instance.my_method("Director says hello")
这可以是向僵尸提供信息的一种方式,例如玩家的位置,或者告诉他们停止追逐等。
在您的生成器中,您将实例添加为子项:
add_child(enemy_instance)
因此,如果您想对所有这些子项调用某些操作,您可以迭代所有子项,例如:
产卵者
func _process(_delta:float) -> void:
for child in get_children():
var zombie := child as Zombie
if not is_instance_valid(zombie):
continue
zombie.my_method("Director says hello")
另一种选择可能是将
zombie.tscn
场景的根放在节点组上(您可以在节点停靠栏的“组”选项卡中找到它),例如,假设它们位于 "zombie"
组中,那么您可以这样做:
产卵者
func _process(_delta:float) -> void:
get_tree().call_group(&"zombie", &"my_method", "Director says hello")
节点通信是一座冰山。还有其他方法可以告诉节点做某事,特别是使用信号。但如果没有更多关于你想要做什么的背景,以上就是我愿意建议的。