Collision detection not working when objects are created from a script

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Eyeball Monkey Media
:warning: Old Version Published before Godot 3 was released.

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()

For more info on resources: http://docs.godotengine.org/en/latest/tutorials/step_by_step/resources.html

mafious | 2016-06-07 01:34

:bust_in_silhouette: Reply From: mafious

Hello, and welcome to Godot Q&A :slight_smile:

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.

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?

Eyeball Monkey Media | 2016-06-07 11:19

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

mafious | 2016-06-07 11:43

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()

Eyeball Monkey Media | 2016-06-07 18:40

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 can_sleep to false (set_can_sleep(false)) on both bodies, if nothing (i.e. Gravity) is moving the bodies they may have slept.

mafious | 2016-06-08 18:26

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.

Eyeball Monkey Media | 2016-06-08 18:33

!!!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 :slight_smile:

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 can_sleep to false (set_can_sleep(false)) on both bodies, if nothing (i.e. Gravity) is moving the bodies they may have slept.

mafious | 2016-06-08 18:35

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

mafious | 2016-06-08 18:37

I can not find cansleep in the kinematic body. The function ‘set_can_sleep’ 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)

Eyeball Monkey Media | 2016-06-08 21:58

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

mafious | 2016-06-09 21:45