+1 vote

I have created the player for a game in a script. the player is visible, but just falls through the platform. If I create everything in the gui the collision works, but it does not in the script. I need to re-use this on each level so it needs to be in a script. Does anyone have any ideas as to why it is not working?

Here is the function that creates the player:

func createPlayer():
var playerBody = KinematicBody2D.new()
var playerSprite = Sprite.new()
var playerCollision = CollisionShape2D.new()
var shape = CircleShape2D.new()
var gameCamera = Camera2D.new()

playerSprite.set_texture(load("res://Textures/Game/blue_circle.tex"))
playerBody.add_child(playerSprite)

shape.set_radius(31)
playerCollision.set_shape(shape)
playerBody.add_child(playerCollision)
print(playerCollision.get_pos())

playerBody.set_name("Player")
playerBody.set_script(load("res://Scripts/Game/player.gd"))
playerBody.set_pos(Vector2(96, 512))

playerBody.add_child(gameCamera)
self.add_child(playerBody)
gameCamera.make_current()
in Engine by (15 points)

1 Answer

0 votes

Hello, and welcome to Godot Q&A :)

CollisionShape2D (and children) are editor helper nodes, meaning that it does not actually exist at runtime (or didn't), therefore changing it via script does nothing after the Physics body has used it.

The one way I found to work around this was by defining a prototypical shape, saving it it as a .res, preloading it. Then you have to duplicate and scale it at runtime, and call set_shape on your PhysicsBody2D, as such:

var protoshape = preload("res://shape.res");
var shape = protoshape.duplicate();
shape.set_radius(shape.get_radius()*factor);
self.set_shape(0,shape);

There may be a simpler way, but I can confirm this works.

by (54 points)

Hi, thank you for the answer, but I can not get it to work. When I load the shape from a resource it still falls through the ground. Any ideas why this is happening?

In the code you wrote here you are adding the shape to the Collisions Hape, you should try adding it to the (kinematic) body, see if that works

I believe I am adding it to the KinematicBody2D. The edited script is below. The script is called by a ViewportSprite which is the root node of the scene.

extends ViewportSprite

var factor = 1

func _ready():
    createPlayer()

func createPlayer():
    var playerBody = KinematicBody2D.new()
    var playerSprite = Sprite.new()
    var gameCamera = Camera2D.new()
    var protoshape = preload("res://shape.res");

    playerSprite.set_texture(load("res://Textures/Game/blue_circle.tex"))
    playerBody.add_child(playerSprite)

    var shape = protoshape.duplicate();
    shape.set_radius(shape.get_radius()*factor);
    playerBody.set_shape(0,shape);

    playerBody.set_name("Player")
    playerBody.set_pos(Vector2(96, 512))

    playerBody.add_child(gameCamera)

    playerBody.set_script(load("res://Scripts/Game/player.gd"))
    self.add_child(playerBody)

    gameCamera.make_current()

It looks like it should work as it is, I would scrutinize the values of the variables both inside the shape.res and the ground you're trying to collide with.

Try to set cansleep to false (setcan_sleep(false)) on both bodies, if nothing (i.e. Gravity) is moving the bodies they may have slept.

The ground is a TileMap, and the player is a KinameticBody2D. The player falls, but it falls through the ground and that does not happen when I create the player in the GUI using the same script to move the player.

!!!UPDATE I didn't realize that you're using a KinematicBody2D, physics don't work on them
like other bodies, head over to http://docs.godotengine.org/en/latest/tutorials/2d/kinematic_character_2d.html?highlight=kinematic%20character and see if it helps you at all :)

Other than that, I would scrutinize the values of the variables both inside the shape.res and the ground you're trying to collide with. Try to set cansleep to false (setcan_sleep(false)) on both bodies, if nothing (i.e. Gravity) is moving the bodies they may have slept.

It could be that creating through the GUI sets some variables that we forget to set via code, as well.

I can not find cansleep in the kinematic body. The function 'setcansleep' does not appear to exist. The link above is where I built my player script from. I added it below. Thanks again for all of your help.

extends KinematicBody2D

const GRAVITY = 750.0
const FAST_SPEED = 400
const NORMAL_SPEED = 200
var WALK_SPEED = 200
const JUMP_SPEED = 325

var velocity = Vector2()
var onGround = true

var hasEnded = false

var running = false

var left
var right
var jump
var fast

var gcp
var gp

var killed = false
var killJumped = false

var levelsFilePath = "user://levels.config"

func _fixed_process(delta):
    if !hasEnded:
        if fast.get_toggle_pressed() or Input.is_key_pressed(KEY_CONTROL):
            WALK_SPEED = FAST_SPEED
        else:
            WALK_SPEED = NORMAL_SPEED
        velocity.y += delta * GRAVITY
        if (Input.is_key_pressed(KEY_LEFT) or left.is_pressed()):
            velocity.x = -WALK_SPEED
        elif (Input.is_key_pressed(KEY_RIGHT) or right.is_pressed()):
            velocity.x =  WALK_SPEED
        else:
            velocity.x = 0
        if onGround and (Input.is_key_pressed(KEY_SPACE) or jump.is_pressed()):
            velocity.y -= JUMP_SPEED
        var motion = velocity * delta
        motion = move(motion)
        onGround = false
        var collider = get_collider()
        if (is_colliding()):
            if collider == get_parent().get_node("House/StaticBody2D"):
                complete()
            var p = get_pos()
            var cp = get_collision_pos()
            gcp = cp
            gp = p
            var n = get_collision_normal()
            motion = n.slide(motion)
            velocity = n.slide(velocity)
            move(motion)
            if abs(cp.y - p.y) >= (abs(cp.x - p.x) - 5) and cp.y > p.y: #5 is buffer for how far off the middle it can be
                if !running:
                    print("Start timer")
                    get_node("Timer").start()
                    running = true
    elif killed:
        if killJumped:
            var collisionShape = get_node("PlayerShape")
            collisionShape.set_trigger(true)
            velocity.y += delta * GRAVITY
            var motion = velocity * delta
            motion = move(motion)
            onGround = false
            if (is_colliding()):
                var p = get_pos()
                var cp = get_collision_pos()
                var n = get_collision_normal()
                motion = n.slide(motion)
                velocity = n.slide(velocity)
                move(motion)
        else:
            velocity.y -= JUMP_SPEED
            velocity.y += delta * GRAVITY
            var motion = velocity * delta
            motion = move(motion)
            onGround = false
            if (is_colliding()):
                var p = get_pos()
                var cp = get_collision_pos()
                var n = get_collision_normal()
                motion = n.slide(motion)
                velocity = n.slide(velocity)
                move(motion)
            killJumped = true
    elif !killed and hasEnded:
        var flag = get_parent().get_node("House/Flag")
        if flag.get_pos().y > -128:
            flag.set_pos(Vector2(flag.get_pos().x, flag.get_pos().y - 4))

func complete():
    hasEnded = true
    get_parent().get_node("Player").hide()
    get_parent().get_node("House/Flag").show()
    get_node("Timer").start()

func kill():
    hasEnded = true
    killed = true
    velocity.x = 0
    get_node("Camera2D").clear_current()
    get_node("Timer").start()

func _ready():
    left = get_parent().get_node("gui/Left")
    right = get_parent().get_node("gui/Right")
    jump = get_parent().get_node("gui/Jump")
    fast = get_parent().get_node("gui/Fast")
    if Globals.has("TSButtons"):
        if !Globals.get("TSButtons"):
            left.hide()
            right.hide()
            jump.hide()
            fast.hide()
    set_fixed_process(true)


func _on_Timer_timeout():
    var levelsFile = ConfigFile.new()
    levelsFile.load(levelsFilePath)
    levelsFile.set_value(Globals.get("WorldNum"), Globals.get("LevelNum"), true)
    if levelsFile.has_section_key(Globals.get("WorldNum"), "Latest"):
        var num = int(levelsFile.get_value(Globals.get("WorldNum"), "Latest"))
        if num < int(Globals.get("LevelNum")):
            levelsFile.set_value(Globals.get("WorldNum"), "Latest", int(Globals.get("LevelNum")))
    else:
        levelsFile.set_value(Globals.get("WorldNum"), "Latest", int(Globals.get("LevelNum")))
    levelsFile.save(levelsFilePath)
    get_node("Camera2D").make_current() #Make camera switch to default when scene is switched
    get_node("/root/globals").setScene("res://Scenes/Game/menu/w1.scn")

func timer():
    running = false
    var p = gp
    var cp = gcp
    if abs(cp.y - p.y) >= (abs(cp.x - p.x) - 5) and cp.y > p.y: #5 is buffer for how far off the middle it can be
        onGround = true
    print(onGround)

I'll try recreating this, getting to something that works and will try to report back in a bit

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 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 webmaster@godotengine.org with your username.