+2 votes

Hello everyone, hope all is well! So I'm having an issue understanding how to spawn a player character at specific points on a map. So what I would like to do is have the player enter and exit a house that has two entrances and two exits. I would like the player to be able to spawn in the appropriate spawn points on both the outside of the house and inside! In other words, OutsidepointA spawns the Player to InsidepointB and OutsidepointC spawns player to InsidepointD and vice-versa. The scene will change to a new scene for inside the house and the over world, just thought I'd be specific since I'm sure the changing the scene will effect the coding method in some way.

here's a picture of what I'd like to happen:!
Thank you in advance!

in Engine by (55 points)

https://sta.sh/01o244du9tp2

my picture wasn't saved in the question

I think you can make that happen by assigning the position of the starting point of that node when they enter the Area2D (or whatever collision node you're using) to the player's character sprite. This has to be done after the scene switch, but before the whole thing loads.

1 Answer

+3 votes
Best answer

Create an AutoLoad holding a variable player_position defaulting to null. I'll simply call this AutoLoad Global in the following. Use an Area2D with a CollisionShape2D to create a door-scene and then add the following script to it:

extends Area2D

export (PackedScene) var change_to_scene
export (Vector2) var move_to_position

func _on_Area2D_body_entered(body):
    if change_to_scene and move_to_position:
        Global.player_position = move_to_position
        get_tree().change_scene_to(change_to_scene)

Make sure the function _on_Area2D_body_entered is properly connected to the body_entered-signal of the Area2D and that your player is a body, not an area.

Finally add the following bit to the scripts of all your level scenes:

func _ready():
    if Global.player_position:
        get_node("Player").global_position = Global.player_position

That's assuming that your player-node is called "Player" and is a direct child of each level scene. If not, you need to adapt the node-path used here accordingly.

Now you can add a new instance of the door-scene to any of your levels and once both export variables are set, the door will start working as intended. This can be easily adapted to create "teleporting" areas inside the same map as well:

func _on_Area2D_body_entered(body):
    if change_to_scene:
        Global.player_position = move_to_position
        get_tree().change_scene_to(change_to_scene)
    else:
        var player_node = get_parent().get_node("Player")
        player_node.global_position = move_to_position
by (10,551 points)
selected by

Thank you for answering :) But I still can't seem to get it to work quite right. I either get an error ( previous node freed) or my player keeps spawning in the middle of the map when leaving the house instead of spawning in front of the door like he should. Maybe I'm missing something obvious or maybe a little more information is needed ¯_(ツ)_/¯

my player is already in both scenes, so there's no need to instance him over though I know how to do it both ways. I'm using an area 2D for tile base movement so there's no kinematic body to use. Here's some code :)

my door code:

extends Area2D

export(String, FILE, "*.tscn") var world_exit

func _process(_delta):
    var areas = get_overlapping_areas()
    for area in areas:
        if area.name == "Player" && Input.is_action_just_pressed("ui_accept"):
            get_tree().change_scene(world_exit)

func _on_Door_area_entered(_area: Area2D) -> void:
# Store value

I'm thinking I can store/give a value once the player overlaps with the door area. And I can use that variable to set a new spawn location. Just have no idea how to do that.

code attached to the main level node:

extends Node2D

func _ready():
$Player.global_position = $Player_spawner.global_position

the Player spawner will be a position 2D so I can set them freely throughout the world and not have to worry about a door or whatever being put in the middle of a forest. Woulds just seem out of place.

I also have a singleton as well, just have no idea what to do with it.

code is literally just var playerpos = null

maybe I can use this to store the values I need but I'm not really sure.

If you have any other ways to make this work I'm all ears :) I'll just fiddle around with your code and mines until it works like I want it to. Hope what I've posted isn't to confusing. Thank you again :)

I got it to work! Thank you brother! So for anyone else trying to get this to work here's my code.

code for the door/portal:

extends Area2D

export(String, FILE, "*.tscn") var world_exit
export (Vector2) var spawn_pos

func _process(_delta):
var areas = get_overlapping_areas()
for area in areas:
    if area.name == "Player" && Input.is_action_just_pressed("ui_accept"):
            get_tree().change_scene(world_exit)
            Global.player_pos = spawn_pos

func _on_Door_area_entered(_area: Area2D) -> void:
    if world_exit:
        get_node("/root/Global").player_pos = spawn_pos

code attached to my main level node:

extends Node2D

func _ready():
    if Global.player_pos:
        get_node("Player").global_position = Global.player_pos

my autoload/singleton

extends Node
var player_pos = null

when setting the spawn pos variables be sure they match your new scene not your current one. IE portal in level one should have the spawn pos for level two and vice versa. You'll have to set each spawn point manually this way but, this does in fact work. I'll be experimenting to see if I can automate the process, so all you'd have to do is instance a new door/portal and the spawn coordinate will set automatically. Cheers!

Hey there! This post couldn't have had better timing. I'm also looking to do exactly what you did, and following this exchange helped me get there. I'm now facing a new problem that I'm wondering if you've dealt with yet.

Whenever my player loads into a new scene, it uses the left idle sprite, really I'd want them to continue their animation from the previous scene. Right now, if ui_up is active as my character travels between scenes, there will be one frame of the left idle sprite that appears as they spawn in the new scene, before continuing to walk up.

I'm wondering if there's a way to use the same process of getting global information to change the current character sprite animation upon loading a new scene. Another approach would be to keep my input vector consistent between scenes.

I figured you would be dealing with this too, or else you've already prevented this issue from happening. If you have any ideas, I'd appreciate the help!

I'm wondering if there's a way to use the same process of getting global information to change the current character sprite animation upon loading a new scene.

Yeah, the approach is pretty much the same: Store the properties you're interested in (judging from your description: animation and frame) in an AutoLoad and apply them when the new scene is getting ready. Something like this:

func _ready():
    var player = load("<PathToPlayerScene>").instance()
    player.get_node("AnimatedSprite").animation = Global.player_animation
    player.get_node("AnimatedSprite").frame = Global.player_animation_frame
    add_child(player)

Thanks for responding! I'm self-taught with coding and fairly new at Godot so I appreciate it!

I'm still having some issues though since I'm using an AnimationPlayer and an AnimationTree instead of an AnimatedSprite. The way my movement is coded in the Player script, the player takes an input vector from the ui. If input_vector is (0,-1) the player goes up, (-1,0) they go left, etc. This also tells the AnimationTree to play the correct directional animation.

I'm trying to make a Vector2 in an Autoload that tells the Player script what the input_vector is upon entering a new scene. This seems simpler than telling it to export properties of the AnimationPlayer or the AnimationTree. I've been having problems accessing this variable from other scenes though. Do you have any advice? Thanks again for responding. Here is my Player script for reference:

extends KinematicBody2D

const ACCELERATION = 1000
const MAX_SPEED = 70
const FRICTION = 1000

var velocity = Vector2.ZERO

onready var animationPlayer = $AnimationPlayer
onready var animationTree = $AnimationTree
onready var animationState = animationTree.get("parameters/playback")

func _process(delta):
    var input_vector = Vector2.ZERO
    input_vector.x = Input.get_action_strength("ui_right") - 
    Input.get_action_strength("ui_left")
    input_vector.y = Input.get_action_strength("ui_down") - 
Input.get_action_strength("ui_up")
    input_vector = input_vector.normalized()


if input_vector != Vector2.ZERO:
    animationTree.set("parameters/Idle/blend_position", input_vector)
    animationTree.set("parameters/Run/blend_position", input_vector)
    animationState.travel("Run")


    velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta)

else:
    animationState.travel("Idle")
    velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)


velocity = move_and_slide(velocity)

Well, then before you change the scene, get the player node and store the input_vector:

Global.last_input_vector = get_node("Player").input_vector
get_tree().change_scene("<PathToNextScene>")

And then have the player load that value from the AutoLoad:

func _ready():
    input_vector = Global.last_input_vector

However, looking at your code input_vector (a) isn't a global variable and only known in the scope of the _process-method and (b) redefined each frame based on the player input. If it can change each frame, there is no point in preserving it.

In case that doesn't solve your problem, I recommend you create a separate question for your problem. This way a lot more people will see it and might help you.

Will do! Thanks again for clearing this up!

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.