How do I change the animation of all objects of the same type that are running in the game scene? C#

Godot Version

V4.2.2.mono

Question

I have completed this tutorial, I want to add some features to it for more practice. One of the possibilities is the explosion of mobs. For this, I made a bomb object, when the player hits it, all the mobs in the scene must be destroyed, but before the destruction, the explosion animation must occur.

I wrote the following code, but this animation will be added to the mobs that are added to the scene later!

Player.cs

	private void OnBodyEntered(Node2D body)
	{
		if (body.Name == "Mob")
		{
			Hide(); // Player disappears after being hit.
			EmitSignal(SignalName.Hit);
			// Must be deferred as we can't change physics properties on a physics callback.
			GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
		}
		else if (body.Name == "Bomb")
		{
			EmitSignal(SignalName.ActiveBomb);
		}
	}

Main.cs

	public async void Explosion()
	{
		var bomb = GetNode<Bomb>("Bomb");
		var animatedSprite2D = bomb?.GetNode<AnimatedSprite2D>(nameof(AnimatedSprite2D));
		animatedSprite2D?.Play("explosion");
		GetTree().CallGroup("mobs", AnimatedSprite2D.MethodName.Play, "explosion");
		await ToSignal(GetTree().CreateTimer(0.5), SceneTreeTimer.SignalName.Timeout);
		GetTree().CallGroup("mobs", Node.MethodName.QueueFree);
		bomb?.QueueFree();
	}

It seems that the code is correct in theory, but it does not work correctly in terms of performance.

And a side note, when it uses Task instead of Void, the code does not work properly.

You need to provide some more information:

  • What is your current result?
  • What is your expected result?
  • Which method(s) are connected to what signals?
  • Why do you not have a Bomb-script?

Without context, we are unable to help you.

P.S. when you say it “does not work correctly in terms of performance” do you mean that it does not do what you expect; or is there a performance issue?

1 Like

The current result is that the explosion animation does not work for mobs.

GetTree().CallGroup("mobs", AnimatedSprite2D.MethodName.Play, "explosion");

What I expect is that when the player hits the bomb, before the mobs in the scene are removed, their animation will change to explosion.

GetTree().CallGroup(“mobs”, AnimatedSprite2D.MethodName.Play, “explosion”);


		await ToSignal(GetTree().CreateTimer(0.5), SceneTreeTimer.SignalName.Timeout);
		GetTree().CallGroup("mobs", Node.MethodName.QueueFree);
		bomb?.QueueFree();

The player’s ActiveBomb signal is connected to the Explosion method in the script from Main.cs.

I also fixed the problem that when mobs are added to the scene, they are accompanied by the explosion animation. The problem was in the mob script, which was fixed as follows:

Mob.cs

	public override void _Ready()
	{
		var animatedSprite2D = GetNode<AnimatedSprite2D>(nameof(AnimatedSprite2D));
		string[] mobType = animatedSprite2D.SpriteFrames.GetAnimationNames();
		var animationName = mobType[GD.Randi() % mobType.Length];
		animatedSprite2D.Play(animationName == "explosion" ? "walk" : animationName);
	}

I didn’t write a script for the bomb because I wanted to keep the structure of the training and act like it

I also get this error in the debugger window:

E 0:00:22:0336 NativeCalls.cs:6183 @ Godot.GodotObject Godot.NativeCalls.godot_icall_1_693(nint, nint, Godot.NativeInterop.godot_node_path): Node not found: “animatedSprite2D” (relative to “/root/Main/Mob”).
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1651 @ get_node()
NativeCalls.cs:6183 @ Godot.GodotObject Godot.NativeCalls.godot_icall_1_693(nint, nint, Godot.NativeInterop.godot_node_path)
Node.cs:802 @ Godot.Node Godot.Node.GetNode(Godot.NodePath)
NodeExtensions.cs:47 @ T Godot.Node.GetNode(Godot.NodePath)
Main.cs:26 @ void Main+d__12.MoveNext()
:0 @ void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(TStateMachine&)
:0 @ void Main.Explosion()
Main_ScriptMethods.generated.cs:79 @ bool Main.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)
Godot.NativeInterop.NativeFuncs.generated.cs:361 @ Godot.NativeInterop.godot_variant Godot.NativeInterop.NativeFuncs.godotsharp_method_bind_call(nint, nint, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error&)
NativeCalls.cs:6254 @ int Godot.NativeCalls.godot_icall_2_699(nint, nint, Godot.NativeInterop.godot_string_name, Godot.Variant, Godot.NativeInterop.godot_string_name)
GodotObject.cs:606 @ Godot.Error Godot.GodotObject.EmitSignal(Godot.StringName, Godot.Variant)
Player.cs:87 @ void Player.OnBodyEntered(Godot.Node2D)
Player_ScriptMethods.generated.cs:59 @ bool Player.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)

Well, from your error log you may notice that:

Node not found: “animatedSprite2D” (relative to “/root/Main/Mob”).

I’m assuming it’s because your AnimatedSprite2D is named something different from “animatedSprite2D”.

Its name is correct. This problem is not because mobs are instantiated through code?

	private void OnMobTimerTimeout()
	{
		// Note: Normally it is best to use explicit types rather than the `var`
		// keyword. However, var is acceptable to use here because the types are
		// obviously Mob and PathFollow2D, since they appear later on the line.

		// Create a new instance of the Mob scene.
		Mob mob = MobScene.Instantiate<Mob>();

		// Choose a random location on Path2D.
		var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
		mobSpawnLocation.ProgressRatio = GD.Randf();

		// Set the mob's direction perpendicular to the path direction.
		float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2;

		// Set the mob's position to a random location.
		mob.Position = mobSpawnLocation.Position;

		// Add some randomness to the direction.
		direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
		mob.Rotation = direction;

		// Choose the velocity.
		var velocity = new Vector2((float)GD.RandRange(150.0, 250.0), 0);
		mob.LinearVelocity = velocity.Rotated(direction);

		// Spawn the mob by adding it to the Main scene.
		AddChild(mob);
	}

Okay, well instead of just telling me that the name is correct, how about you show me that it is? Your error log confirms what I said.

If you think there’s a different reason it’s not working, explain your reasoning.
Why would instantiating mobs through code be an issue?

download game here !

Exactly as I expected.
The name for your AnimatedSprite2D node is “AnimatedSprite2D” with a capital A. Your code is attempting to find an AnimatedSprite2D with a regular a.

  • Your scene node: AnimatedSprite2D
  • Your code request: animatedSprite2D

Don’t use nameof() to retrieve the default name for a node. Compare the node type or use your own string constant when searching for a node.

1 Like

The error problem has been fixed, but the explosion animation still does not play.

I fixed it. Just replace the following code:

	public async void Explosion()
	{
		var bomb = GetNode<Bomb>("Bomb");
		var animatedSprite2D = bomb?.GetNode<AnimatedSprite2D>("AnimatedSprite2D");
		animatedSprite2D?.Play("explosion");

		foreach (var mob in GetTree().GetNodesInGroup("mobs"))
		{
			var rigidBody = mob as RigidBody2D;
			if (rigidBody != null)
			{
				rigidBody.ContinuousCd = RigidBody2D.CcdMode.Disabled;
				rigidBody.Sleeping = true;
				rigidBody.SetDeferred("Freeze", true);
			}

			var collisionShape2D = mob.GetNode<CollisionShape2D>("CollisionShape2D");
			collisionShape2D.SetDeferred("Disabled", true);
			var animatedSprite = mob.GetNode<AnimatedSprite2D>("AnimatedSprite2D");
			animatedSprite?.Play("explosion");
		}

		await ToSignal(GetTree().CreateTimer(0.5), SceneTreeTimer.SignalName.Timeout);
		GetTree().CallGroup("mobs", Node.MethodName.QueueFree);
		if (bomb != null && GodotObject.IsInstanceValid(bomb))
		{
			bomb.QueueFree();
		}
	}