0 votes

Hi everyone! I'm working on my first game (ever!) and am running into a problem.

I followed along with the Gamefromscratch's pong tutorial and wanted to modify it so that the ball is served when the space bar is pressed. If no points have been scored yet it chooses between serving left or right by randomly generating an integer 0-1 (left for 0, else: right). If the last point went to the left player it will serve right and vice versa.

The problem I'm having is that when I press the space bar it makes more than one instance of the ball (which looks kinda cool but doesn't work for the game lol). If anyone can point out what I'm doing wrong in my script I'd really appreciate it!

#serve ball
if not self.has_node("Ball") and Input.is_action_pressed('serve_ball'):
    var ball = ball_scene.instance()
    ball.set_pos(Vector2(320,200))
    if last_point == null:
        if randi()%2 == 0:
            ball.set_linear_velocity(-INIT_VELOCITY)
        else:
            ball.set_linear_velocity(INIT_VELOCITY)
    elif last_point == 'left':
        ball.set_linear_velocity(INIT_VELOCITY)
    else:
        ball.set_linear_velocity(-INIT_VELOCITY)
    get_tree().get_root().add_child(ball)
in Engine by (17 points)

2 Answers

+2 votes
Best answer
  1. You are adding the ball to the viewport (look at the debugger remote inspector), you may want to add it to get_tree().get_root().get_node("mysceneroot").add_child(ball) or just get_node("/root/mysceneroot"), I prefer to have a node in a group to store important spawning things, so every other node can find it (with get_tree().get_nodes_in_group) and add a child independent of the scene tree.

  2. Is the ball node named "Ball" on the tree? (check the remote inspector after fixing 1), again, I prefer groups instead of names even if is a one node group.

  3. The good old boolean flag could be better than has_node, like a is_ball_playing toggle when adding and removing the ball, it wont depend on the ball name or where you add it.

by (7,834 points)
selected by

Thank you for pointing that out! I changed the script so that the ball node is made the child of Node2D and it works perfectly now. I also switched the has_node conditional like you suggested in point 3.

I understand how to put nodes in groups but I'm not sure I'm understanding what you're talking about in the second half of point 1 and in point 2. If you don't mind could you explain the concept a little more for me?

A couple more questions if you have the time: I saw in Heartbeast's Breakout tutorial that he made his ball node its own scene that was instanced in the main game scene when needed. I copied that idea while making this Pong. Was that a good way of organizing the elements of the game? The way I have it now, when the ball goes off screen the script in the Ball scene modifies the isballplaying (true or false) and last_point ('left' or 'right') variable in the main scene's script. Not sure if this is a good way of going about it.

Thank you again for your help, this community is great!

The name is case sensitive and you need to be sure of your naming convention, also, the engine can change the name if is in conflict with a sibling, if you queue free a ball and add another, the second may change the name if is added before the other is completely removed.

More, if you add a new type of ball and the node is called, Ball2, you will have to rename it or change the detection or other messy stuff.

Adding all the balls to the "ball" group, you only need to check if get_nodes_in_group("ball") size is >0 and work with the 0 index, all that independent of the node name, if it is in group ball then it is a ball (even if the node is a brick).

That is independent of the scene too, you can change the root name, create a different scene, and the code will keep working for the group "ball"


The scene instancing is the common way, depends much on the design and your style how to use it, if you prefer to delete nodes and re-instance them or just remove from tree and re-add.

But making scenes for every object or turning branches into scenes is a strong point of Godot.


The way to detect the borders is not bad, I prefer to have local game data on the scene root too than to use of globals/autoloads.

For the detection method, later you can try using Area2D (there are many shapes for different uses), VisibilityNotifier is useful too to detect things that go off screen.


In the end, everything is mostly preference, style and design and Godot allows many good approaches for the same thing.

Does the group have to belong to the same scene where I am using get_nodes_in_group("ball)? I have the ball node in its own scene, and when I use get_nodes_in_group("ball") in my main scene where I want to instance the ball node, I'm getting an empty array.

Some actions make the groups in a scene file to be removed (can't remember which now), look again to the ball scene if it appears on a group.

If it keeps removing it from the ball scene, do add_to_group to the instance before adding to the tree (could be after too), and you will get a list of grouped objects that are on the tree (after they are ready), nodes outside the scene tree won't appear on the group list.

Wow, thanks for the fast reply! Unfortunately I still can't seem to grasp this concept (my fault, not yours). If you have time take a look at my scripts and let me know how I can implement the group functionality correctly.

Pong.tscn

extends Node2D

var last_point = null
var ball_playing = false

const INIT_VELOCITY = Vector2(100,0)
const PAD_SPEED = 150
const LEFT_INIT_POS = Vector2(48, 200)
const RIGHT_INIT_POS = Vector2(592, 200)
const ball_scene = preload("res://MiniScenes/Ball.tscn")

func _ready():
    var ball_group = get_tree().get_nodes_in_group('balls')
    print(str(ball_group))
    set_process(true)

func _process(delta):
    #move left pad
    var left_pos = get_node("Left Pad").get_pos()
    if left_pos.y > 25 and Input.is_action_pressed("left_pad_up"):
        left_pos.y += -PAD_SPEED * delta
    if left_pos.y < 375 and Input.is_action_pressed("left_pad_down"):
        left_pos.y += PAD_SPEED * delta
    get_node("Left Pad").set_pos(left_pos)

    #move right pad
    var right_pos = get_node("Right Pad").get_pos()
    if right_pos.y > 25 and Input.is_action_pressed("right_pad_up"):
        right_pos.y += -PAD_SPEED * delta
    if right_pos.y < 375 and Input.is_action_pressed("right_pad_down"):
        right_pos.y += PAD_SPEED * delta
    get_node("Right Pad").set_pos(right_pos)

    #serve ball
    if not ball_playing and Input.is_action_pressed('serve_ball'):
        var ball = ball_scene.instance()
        ball_playing = true
        ball.set_pos(Vector2(320,200))
        if last_point == null:
            if randi()%2 == 0:
                ball.set_linear_velocity(-INIT_VELOCITY)
            else:
                ball.set_linear_velocity(INIT_VELOCITY)
        elif last_point == 'left':
            ball.set_linear_velocity(INIT_VELOCITY)
        else:
            ball.set_linear_velocity(-INIT_VELOCITY)    
        get_tree().get_root().get_node('Game').add_child(ball)

Ball.tscn

extends RigidBody2D

const MAX_SPEED = 300

func _ready():
    add_to_group('balls')
    set_fixed_process(true)

func _fixed_process(delta):
    var bodies = get_colliding_bodies()

    for body in bodies:
        if body.get_name() == "Left Pad":
            var speed = get_linear_velocity().length()
            var direction = get_pos() - body.get_node("Anchor").get_global_pos()
            var velocity = direction.normalized() * min(speed * 1.1, MAX_SPEED)
            set_linear_velocity(velocity)
        elif body.get_name() == "Right Pad":
            var speed = get_linear_velocity().length()
            var direction = get_pos() - body.get_node("Anchor").get_global_pos()
            var velocity = direction.normalized() * min(speed * 1.1, MAX_SPEED)
            set_linear_velocity(velocity)

    #kill ball if it exits viewport
    if (get_pos().x < 0) or (get_pos().x > get_viewport_rect().end.x):
        queue_free()
        get_tree().get_root().get_node('Game').ball_playing = false
        if get_pos().x < 0:
            get_tree().get_root().get_node('Game').last_point = 'right'
        else:
            get_tree().get_root().get_node('Game').last_point = 'left'

I appreciate all the help you have given me so far!

It seems that you are controlling everything from the "root" (other option is to make a scene for the pads so they take the inputs, a single pad scene with many inputs according rotation could be a good excercise).

On ready, it won't find anything on a group because nothing was added, you can use the group check instead of the not ball_playing, check if size is > 0, it should say 1 if a "ball" is on scene (try adding a scene ball with the editor and root may see it when playing, not sure if when _ready or _enter_tree is when register groups).


What I see there too is a bit of redundancy, since you are using a rigid body for the ball you don't need to control the velocity when hits the pads, just override damp to 0 and put max bounce on the pads and/or ball, let the physics engine do the rest (may take pads velocity too, depending on what the pads are).

Also, use _fixed_process to move the pads too or you will get strange collisions sometimes (pads could fully overlap the ball).

And... randomize() at least once to not get always the same seed for the randi.

Thanks for pointing that out about the pads and the randomizer!

I'm not sure (I could be, and probably am wrong :-P ), that setting the velocity when the ball hits the pads is redundant. I use a Position2D behind the paddle (a design I got from Heartbeast's Breakout tutorial) to determine the direction the ball will bounce based on what area of the paddle the ball bounced off of. If the ball hits the exact center of the paddle it will go straight, if it hits towards the left of the paddle the ball bounces towards the left, etc. I have to set velocity to control the direction it bounces in. I'll have to test it, but I don't think that physics engine alone would give it that behavior.

Thanks for taking the time out to discuss these things with me, I really appreciate it!

Well, if the pads have a shape like \___/ or nearly curved it could give an extra effect.

Another more physics engine-friendly way is to use a custom integrator and modify the ball state according to your rules in _integrate_forces.

But if works fine your way, keep it :P

+2 votes

I'd change the way the ball is added altogether, maybe you're running into the event echoing. I'd do something more along the lines of the following.

i. Store a reference to the ball in the scene and populate it using the ready function.

var ball = null
var default_pos

func _ready():
    # this assumes the ball exists in your scene via the editor
    ball = get_node("<Name_of_Ball_In_Scene>") 
    default_pos = ball.get_pos()

ii. Then I'd check whether the ball has been served and serve it if not.

var served = false
func _input(event):
    # serve the ball when the input event is triggered
    if (not served and Input.is_action_pressed('serve_ball')) :
        served = true
        if (last_point == null):
            if (randi() % 2 == 0):
                ball.set_linear_velocity(-INIT_VELOCITY)
            else:
                ball.set_linear_velocity(INIT_VELOCITY)
        elif (last_point == 'left'):
            ball.set_linear_velocity(INIT_VELOCITY)
        else:
            ball.set_linear_velocity(-INIT_VELOCITY)

iii. Simply reset the ball whenever it needs to be i.e. whenever a point is scored.

func reset_nodes():
    served = false
    ball.set_pos(default_pos)
    # reset the paddles to their starting positions as well
by (122 points)

Thank you so much for responding to my question! I'm going to try to implement your way of adding the ball now and see how it goes. Just a quick question before I do:

Right now I have the if-statment for serving the ball under the "func _process(delta):" in the script of the main game node, and in the script you wrote you have it under "func _input(event):". What's the difference between where the if-statement is placed? I have a little bit of experience in Python (just the basics), but I'm still trying to wrap my head around how scripting works in GDScript.

Thanks again for the help!

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.