场景切换时角色传送至出生点触发意外碰撞的问题排查
房间切换时误触发碰撞的问题解决
场景设置
- 创建了
RoomExit场景(蓝色“门路径”)和SpawnPosition场景(门旁十字准星标识) - Level1右侧有1个RoomExit;Level2左右各1个RoomExit;Level3左侧有1个RoomExit(注意:其坐标与Level2左侧RoomExit完全相同)
问题现象
从Level3左侧RoomExit退出后,玩家会按预期出现在Level2右侧,但随即触发Level2左侧RoomExit的碰撞,导致直接跳转至Level1。
现有逻辑
RoomExit工作机制
碰撞发生时,通过SignalBus单例发送OnGotoRoom信号,附带两个参数:
- 目标场景路径(如
res://Rooms/TestLevel2.tscn) - 目标场景内出生点节点名称(如
Spawn002)
Game节点处理逻辑
监听OnGotoRoom信号后执行以下步骤:
- 实例化新房间场景
- 根据名称获取出生点的
GlobalPosition - 将玩家的
GlobalPosition设置为该出生点坐标 - 将新房间添加至
CurrentLevel节点 - 销毁前一个场景
对应的C#代码:
public void gotoRoom(string roomPath, string spawnPoint){ // 实例化新房间 var newRoomScene = (PackedScene)ResourceLoader.Load(roomPath); Room newRoom = (Room)newRoomScene.Instance(); // 设置玩家全局位置为出生点位置 PlayableCharacter player = GetNode<Node2D>("Player").GetChild<PlayableCharacter>(0); player.GlobalPosition = newRoom.GetNode<Node2D>("PlayerSpawnPoints").GetNode<Node2D>(spawnPoint).GlobalPosition; if (this.playerStats.character == "Robot"){ player.GlobalPosition = new Vector2(player.GlobalPosition.x, player.GlobalPosition.y-32); } // 将新房间添加到CurrentLevel节点 GetNode<Node2D>("CurrentLevel").AddChild(newRoom); // 如果已有旧房间则销毁 if (GetNode<Node2D>("CurrentLevel").GetChildCount() != 1){ var current_room = GetNode<Node2D>("CurrentLevel").GetChild<Room>(0); current_room.QueueFree(); } // 将新房间移至第一个子节点位置 GetNode<Node2D>("CurrentLevel").MoveChild(newRoom, 0); }
推测原因
切换Level3到Level2时,玩家位置更新后,仍会触发Level2左侧RoomExit的碰撞(该出口与Level3出口坐标重合),尝试在发送信号前将玩家位置设为new Vector2(-10000, -10000)但无效。
可行解决方案
方案1:临时禁用玩家碰撞体
在房间切换过程中先禁用玩家碰撞体,完成位置设置后再延迟启用,避免位置更新前触发新房间的碰撞:
public void gotoRoom(string roomPath, string spawnPoint){ var player = GetNode<Node2D>("Player").GetChild<PlayableCharacter>(0); // 禁用玩家碰撞体(根据实际组件类型调整,比如CollisionShape2D/CollisionPolygon2D) var playerCollider = player.GetNode<CollisionShape2D>(); playerCollider.SetDeferred("disabled", true); // 原有房间实例化逻辑 var newRoomScene = (PackedScene)ResourceLoader.Load(roomPath); Room newRoom = (Room)newRoomScene.Instance(); player.GlobalPosition = newRoom.GetNode<Node2D>("PlayerSpawnPoints").GetNode<Node2D>(spawnPoint).GlobalPosition; if (this.playerStats.character == "Robot"){ player.GlobalPosition = new Vector2(player.GlobalPosition.x, player.GlobalPosition.y-32); } GetNode<Node2D>("CurrentLevel").AddChild(newRoom); if (GetNode<Node2D>("CurrentLevel").GetChildCount() != 1){ var current_room = GetNode<Node2D>("CurrentLevel").GetChild<Room>(0); current_room.QueueFree(); } GetNode<Node2D>("CurrentLevel").MoveChild(newRoom, 0); // 下一帧再启用碰撞体,确保位置更新完成 CallDeferred(nameof(EnablePlayerCollider), playerCollider); } private void EnablePlayerCollider(CollisionShape2D collider){ collider.disabled = false; }
方案2:给RoomExit添加触发冷却
在RoomExit脚本中添加标志位,避免短时间内重复触发碰撞:
// RoomExit脚本 private bool isTriggering = false; private void OnBodyEntered(Node2D body){ if (isTriggering || body.Name != "Player") return; isTriggering = true; // 发送切换信号 SignalBus.Instance.EmitSignal("OnGotoRoom", targetRoomPath, spawnPointName); // 0.2秒冷却,避免误触发(可根据实际调整时长) GetTree().CreateTimer(0.2f).Connect("timeout", this, nameof(ResetTrigger)); } private void ResetTrigger(){ isTriggering = false; }
方案3:调整房间切换顺序
先销毁旧房间,等待一帧确保销毁完成后,再添加新房间并设置玩家位置,避免新旧房间碰撞体同时存在:
public async void gotoRoom(string roomPath, string spawnPoint){ var currentLevel = GetNode<Node2D>("CurrentLevel"); // 先销毁旧房间 if (currentLevel.GetChildCount() > 0){ var current_room = currentLevel.GetChild<Room>(0); current_room.QueueFree(); // 等待一帧确保旧房间销毁完成 await ToSignal(GetTree(), "idle_frame"); } // 实例化新房间并设置玩家位置 var newRoomScene = (PackedScene)ResourceLoader.Load(roomPath); Room newRoom = (Room)newRoomScene.Instance(); currentLevel.AddChild(newRoom); var player = GetNode<Node2D>("Player").GetChild<PlayableCharacter>(0); player.GlobalPosition = newRoom.GetNode<Node2D>("PlayerSpawnPoints").GetNode<Node2D>(spawnPoint).GlobalPosition; if (this.playerStats.character == "Robot"){ player.GlobalPosition = new Vector2(player.GlobalPosition.x, player.GlobalPosition.y-32); } }
内容的提问来源于stack exchange,提问作者DanielRead
相关产品推荐
相关产品推荐

