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

Godot入门2D游戏教程中GetNode<Timer>抛出类型转换异常求助

Godot C# 教程中GetNode抛出InvalidCastException的问题

我是Godot新手,正在跟随官方《你的第一个2D游戏》教程用C#开发。调用GetNode<Timer>("/root/Main/StartTimer")时抛出System.InvalidCastException: Specified cast is not valid异常,但已确认StartTimer确实是Timer类型。当前处于教程的主游戏场景章节。

报错相关代码片段

public void New_Game()
{
    Score = 0;

    var player = GetNode<Player>("/root/Main/Player");
    var startPosition = GetNode<Position2D>("/root/Main/StartPosition");
    player.Start(startPosition.Position);

    GetNode<Timer>("/root/Main/StartTimer").Start();
}

完整脚本

Main.cs

using Godot;
using System;

public class Main : Node
{
#pragma warning disable 649
[Export]
public PackedScene MobScene;
#pragma warning restore 649

public int Score;

public override void _Ready()
{ 
    GD.Randomize();
    New_Game();
}

public void Game_Over()
{
    GetNode<Timer>("MobTimer").Stop();
    GetNode<Timer>("ScoreTimer").Stop();
}

public void New_Game()
{
    Score = 0;

    var player = GetNode<Player>("/root/Main/Player");
    var startPosition = GetNode<Position2D>("/root/Main/StartPosition");
    player.Start(startPosition.Position);

    GetNode<Timer>("/root/Main/StartTimer").Start();
}

public void OnScoreTimerTimeout()
{
    Score++;
}

public void OnStartTimerTimeout()
{
    GetNode<Timer>("MobTimer").Start();
    GetNode<Timer>("ScoreTimer").Start();
}

public void OnMobTimerTimeout()
{
    var mob = (Mob)MobScene.Instance();

    var mobSpawnLocation = GetNode<PathFollow2D>("MobPath/MobSpawnLocation");
    mobSpawnLocation.Offset = GD.Randi();

    float direction = mobSpawnLocation.Rotation + Mathf.Pi / 2;
    mob.Position = mobSpawnLocation.Position;

    direction += (float)GD.RandRange(-Mathf.Pi / 4, Mathf.Pi / 4);
    mob.Rotation = direction;

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

    AddChild(mob);
}
}

Mob.cs

using Godot;
using System;

public class Mob : RigidBody2D
{
public override void _Ready()
{
    var animSprite = GetNode<AnimatedSprite>("AnimatedSprite");
    animSprite.Playing = true;
    string[] mobTypes = animSprite.Frames.GetAnimationNames();
    animSprite.Animation = mobTypes[GD.Randi() % mobTypes.Length];
}

public void OnVisibilityNotifier2DScreenExited()
{
    QueueFree();
}   
}

Player.cs

using Godot;
using System;

public class Player : Area2D
{
[Export]
public int Speed = 400;
[Signal]
public delegate void Hit();
public Vector2 ScreenSize;

public override void _Ready()
{
    ScreenSize = GetViewportRect().Size;
    Hide();
}

public override void _Process(float delta)
{
    var velocity = Vector2.Zero;

    if (Input.IsActionPressed("move_right"))
    {
        velocity.x += 1;
    }
    if (Input.IsActionPressed("move_left"))
    {
        velocity.x -= 1;
    }
    if (Input.IsActionPressed("move_down"))
    {
        velocity.y += 1;
    }
    if (Input.IsActionPressed("move_up"))
    {
        velocity.y -= 1;
    }

    var animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");

    if (velocity.Length() > 0)
    {
        velocity = velocity.Normalized() * Speed;
        animatedSprite.Play();
    }
    else
    {
        animatedSprite.Stop();
    }

    Position += velocity * delta;
    Position = new Vector2(
        x: Mathf.Clamp(Position.x, 0, ScreenSize.x),
        y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
        );

    if (velocity.x != 0)
    {
        animatedSprite.Animation = "walk";
        animatedSprite.FlipV = false;
        animatedSprite.FlipH = velocity.x < 0;
    }
    else if (velocity.y != 0)
    {
        animatedSprite.Animation = "up";
        animatedSprite.FlipV = velocity.y > 0;
    }
  }

public void On_Player_Body_Entered(PhysicsBody2D body)
{
    Hide();
    EmitSignal(nameof(Hit));
    GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
} 

public void Start(Vector2 pos)
{
    Position = pos;
    Show();
    GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
}

解决步骤

  1. 校验节点路径与命名

    • 确认场景树中StartTimer的路径确实是/root/Main/StartTimer,注意Godot节点名区分大小写,避免拼写错误。
    • 尝试用相对路径替代绝对路径,比如从Main节点出发调用GetNode<Timer>("StartTimer"),减少层级定位错误概率。
  2. 检查节点脚本绑定

    • 确认StartTimer节点未错误附加自定义脚本,若有附加脚本,必须确保该脚本继承自Timer类,否则会导致类型转换失败。
  3. 延迟节点初始化

    • 在_Ready()中直接调用New_Game()时,部分节点可能未完成初始化。将调用改为延迟执行:
      public override void _Ready()
      { 
          GD.Randomize();
          CallDeferred(nameof(New_Game));
      }
      
  4. 调试节点实际类型

    • 先获取无类型节点,打印其实际类型定位问题:
      var node = GetNode("/root/Main/StartTimer");
      GD.Print("Node type: ", node.GetType().FullName);
      var startTimer = node as Timer;
      if (startTimer != null)
      {
          startTimer.Start();
      }
      else
      {
          GD.Print("Failed to cast node to Timer");
      }
      

内容的提问来源于stack exchange,提问作者Alex Cunha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 19:03:38