I was working on adding an upgrade-like system to my game, but was having some trouble with the get_tree().get_root().get_node() thing.
In an attempt to retrieve a different node multiple layers up, I go the error
ERROR: Attempt to call function 'get_root' in base 'null instance'\ on a null instance. If I am correct, this means that it cannot find the node in the tree.
I have also tried using get_parent().get_parent()..., as well as get_node("../../../.. ...") with no luck.
Variables to get (points and pointsAdder)
extends Area2D
var points = 0.000000000001
var pointAdder = 1
var pointMultiplier = 1
var mouseover = false
var unit
var defaultArrow = load("defaultCursor_16x16.png")
var clickableCursor = load("clickableCursor.png")
func _ready():
get_parent().get_node("scoreLabel").text = "Logs: 0"
func _input(event):
if mouseover and event is InputEventMouseButton and event.pressed and event.button_index == BUTTON_LEFT:
points = (pointAdder* pointMultiplier) +points
if points < 1000:
set_label_text(points)
elif points >= 1000 and points < 1000000:
set_label_text(str(points / 1000) + " K")
unit = "thousand"
set_unit_text()
elif points >= 1000000 and points < 1000000000:
set_label_text(str(points / 1000000) + " M")
unit = "million"
set_unit_text()
elif points >= 1000000000 and points < 1000000000000:
set_label_text(str(points / 1000000000) + " B")
unit = "billion"
set_unit_text()
elif points >= 1000000000000:
set_label_text(str(points / 1000000000000) + " T")
unit = "trillion"
set_unit_text()
elif Input.is_key_pressed(KEY_SPACE) and not event.is_echo():
points = (pointAdder* pointMultiplier) +points
if points < 1000:
set_label_text(points)
elif points >= 1000 and points < 1000000:
set_label_text(str(points / 1000) + " K")
unit = "thousand"
set_unit_text()
elif points >= 1000000 and points < 1000000000:
set_label_text(str(points / 1000000) + " M")
unit = "million"
set_unit_text()
elif points >= 1000000000 and points < 1000000000000:
set_label_text(str(points / 1000000000) + " B")
unit = "billion"
set_unit_text()
elif points >= 1000000000000:
set_label_text(str(points / 1000000000000) + " T")
unit = "trillion"
set_unit_text()
func _on_Area2D_mouse_entered():
Input.set_custom_mouse_cursor(clickableCursor)
mouseover = true
func _on_Area2D_mouse_exited():
Input.set_custom_mouse_cursor(defaultArrow)
mouseover = false
func set_label_text(text_to_send):
get_parent().get_node("scoreLabel").text = ("Logs: "+str(text_to_send))
func set_unit_text():
get_parent().get_node("scoreUnitLabel").text = unit
And the code I am attempting to retrieve them in:
Extends Button
var points_cost = 10
func _on_Button_down():
if get_tree().get_root().get_node("treeClickableArea").points >= points_cost:
points_cost = (points_cost/2) + points_cost
get_tree().get_root().get_node("treeClickableArea").pointAdder = get_tree().get_root().get_node("").pointAdder + 1
get_tree().get_root().get_node("treeClickableArea").points = get_tree().get_root().get_node("treeClickableArea").points - points_cost
\Node tree:
┖╴Spatial
┠╴○ backgroundMap2
┠╴▦ backgroundMap
┠╴☺ scoreBackground
┠╴Tᵀ scoreLabel
┠╴Tᵀ scoreUnitLabel
┠╴☺ logSymbol
┠╴▭ treeClickableArea
┃ ┠╴☺ treeSprite
┃ ┖╴treeCollider
┠╴☺ upgradeBackground
┖╴▣ tabContainer
┠╴▯ perClick
┃ ┠╴perUpgradeScroll
┃ ┃ ┖╴▬ Button [scriptHavingIssuesHere]
┃ ┖╴Tᵀ Label
┖╴▯ perClickMultiplier
Could anybody clarify why I am getting this error? What exactly does it mean? Thank you for you time, I greatly appreciate any help.
Ok, another edit. I'm leaving the code from the edit I made before in case you need it for reference. I'm calling it EDIT#1.
The new example I'm posting is from a very simple test program I made. I'll call it EDIT#2.
EDIT#2 - TEST PROGRAM
Ok, so I made a simple 2D scene consisting of only a root node and 2 sprites that I named "Test1" and "Test2". I attached a script to both of the sprite nodes, each containing a variable called "points". In the first script I made points = 60,000,000, and in the second, I made points = 16. In the first script (Test1), I used onready var testNode to load the Test2 node and store it as the variable testNode. I then made a simple input event that prints the points value of the testNode variable when I press the key I assigned to "move forward". The expected result is to have the value 16 print to the console, despite both scripts containing a variable named points which each have different values (to prove i'm getting the number from the second script). For me, it works just fine. Here is the code I used for the scripts:
Node setup
-Node2d
--Test1(sprite) - script 1
--Test2(sprite) - script 2
Script 1 -
extends Sprite
var points = 60000000
onready var testNode = get_parent().get_node("Test2")
func _input(event):
if Input.is_action_just_pressed("move_forward"):
print(testNode.points)
Script 2 -
extends Sprite
var points = 16
Hopefully this stripped down example can help provide some insight into what is going on. Hope it helps.
EDIT#1 - (PREVIOUS)NEW CODE EXAMPLE - for reference
I changed the code to be more reflective of our discussion. This is how it should look in final form. Notice I changed the name of the variable treePoints to treeClickNode for clarity.
extends Button
var points_cost = 10
onready var treeClickNode = get_tree().get_root().get_node("treeClickableArea")
func _on_Button_down():
if treeClickNode.points >= points_cost:
points_cost = (points_cost/2) + points_cost
treeClickNode.pointAdder += 1
treeClickNode.points = treeClickNode.points - points_cost
Related
I have a game I'm making in Godot, 2D
I have a progres sbar called "Healthbar"
I'm trying to set it's value to the players HP value
the full code is as follows
extends KinematicBody2D
var health: float = 100
func ready():
pass
export var movespeed : int
export var batteryspeed: int
var battery = preload("res://Bullet.tscn")
func _physics_process(delta):
get_tree().get_root().find_node("HealthBar").value = health
var motion = Vector2()
if (health <= 0):
gameover()
if(Input.is_action_pressed("MoveUp")):
motion.y -= 1
if(Input.is_action_pressed("MoveLeft")):
motion.x -= 1
if(Input.is_action_pressed("MoveDown")):
motion.y += 1
if(Input.is_action_pressed("MoveRight")):
motion.x += 1
motion = motion.normalized()
motion = move_and_slide(motion * movespeed)
if(Input.is_action_just_pressed("Fire")):
fire()
look_at(get_global_mouse_position())
func fire():
var batteryInstance = battery.instance()
batteryInstance.position = position
batteryInstance.rotation_degrees = rotation_degrees
batteryInstance.apply_impulse(Vector2(), Vector2(batteryspeed, 0).rotated(rotation))
get_tree().get_root().call_deferred("add_child", batteryInstance)
func gameover():
get_tree().reload_current_scene()
func _on_Area2D_body_entered(body):
if "Enemy" in body.name:
health -= 10
and the part I'm having issues with, I suspect is
get_tree().get_root().find_node("HealthBar").value = health
What do I do to set the progressbar value to the health variable?
It turns out you must use the function
{Progress Bar}.set_value({value})
I'm trying to make a bird that follows a Position2D node attached to the character.(The Position2D node is in group called birdpos) When I run the game as soon as the bird is on screen (screendetector) it goes to the Position2D node. However once it reaches its destination it gives me the error "Invalid get index '1' (on base: 'Array')." (im also getting jittering when it reaches position) Im sure this is an easy fix, here is my code
extends KinematicBody2D
export(int) var SPEED: int = 100
var velocity: Vector2 = Vector2.ZERO
var path: Array = []
var levelNavigation: Navigation2D = null
var birdpos = null
onready var line2d = $Line2D #shows pathing
func _ready():
var tree = get_tree()
$flyingsprite1/AnimationPlayer.play("flying")
if tree.has_group("LevelNavigation"):
levelNavigation = tree.get_nodes_in_group("LevelNavigation")[0]
func move():
velocity = move_and_slide(velocity)
func _on_screenchecker_area_entered(area):
$Timer.start()
print("ligma")
yield(get_tree(), "idle_frame")
var tree = get_tree()
if tree.has_group("LevelNavigation"): #navigation node
levelNavigation = tree.get_nodes_in_group("LevelNavigation")[0]
if tree.has_group("birdpos"): #Position2D that is attached to player
birdpos = tree.get_nodes_in_group("birdpos")[0]
func _on_screenchecker_area_exited(area):
print("liga")
$Timer.stop()
var birdpos = null
var levelNavigation: Navigation2D = null
func _on_Timer_timeout():
line2d.global_position = Vector2.ZERO
if birdpos and levelNavigation:
generate_path()
func _physics_process(delta):
if Global.player.facing_right == true:
$flyingsprite1.scale.x = -1
else:
$flyingsprite1.scale.x = 1
if birdpos and levelNavigation:
navigate()
move()
func generate_path():
if levelNavigation != null and birdpos != null:
path = levelNavigation.get_simple_path(global_position, birdpos.global_position, false)
line2d.points = path
func navigate():
if path.size() > 0:
velocity = global_position.direction_to(path[1]) * SPEED
if global_position == path[0]:
path.pop_front()
edit: Updated Code
extends KinematicBody2D
export(int) var SPEED: int = 200
var velocity: Vector2 = Vector2.ZERO
var path: Array = []
var levelNavigation: Navigation2D = null
var birdpos = null
onready var line2d = $Line2D
func _ready():
# speed is distance over time
var tree = get_tree()
$flyingsprite1/AnimationPlayer.play("flying")
#if tree.has_group("Player"):
#player = tree.get_nodes_in_group("Player")[0]
func _on_screenchecker_area_exited(area):
$Timer.stop()
var birdpos = null
var levelNavigation: Navigation2D = null
func _on_Timer_timeout():
line2d.global_position = Vector2.ZERO
if birdpos and levelNavigation:
generate_path()
func _physics_process(delta):
if path.size() == 0:
return
var levelNavigation: Navigation2D = null
var birdpos = null
var next := global_position.move_toward(path[0], SPEED * delta)
var displacement := next - global_position
# And divide by it delta to get velocity:
move_and_slide(displacement/delta)
if Global.player.facing_right == true:
$flyingsprite1.scale.x = -1
else:
$flyingsprite1.scale.x = 1
if birdpos and levelNavigation:
navigate()
move()
func _input(event):
if Input.is_key_pressed(KEY_Q):
var tree = get_tree()
func _on_screenchecker_area_entered(area):
$Timer.start()
yield(get_tree(), "idle_frame")
var tree = get_tree()
if tree.has_group("LevelNavigation"):
levelNavigation = tree.get_nodes_in_group("LevelNavigation")[0]
if tree.has_group("birdpos"):
birdpos = tree.get_nodes_in_group("birdpos")[0]
func generate_path():
if levelNavigation != null and birdpos != null:
if is_instance_valid(birdpos):
path = levelNavigation.get_simple_path(global_position, birdpos.global_position, false)
line2d.points = path
func navigate():
if path.size() > 1:
velocity = global_position.direction_to(path[1]) * SPEED
if path.size() == 0:
path.pop_front()
func move():
if path.size() == 0:
return
velocity = move_and_slide(velocity)
The error
In this code:
if path.size() > 0:
velocity = global_position.direction_to(path[1]) * SPEED
if global_position == path[0]:
path.pop_front()
You check if the path has more than 0 points with path.size() > 0. That is, you are checking if the path has at least 1 point.
But to access path[1], the path must have at least 2 points.
Thus, if the path has exactly 1 point, it will pass the check path.size() > 0 and fail when reading path[1].
I don't know when the path would have exactly one point. It is not documented how this could happen, and it could be a problem with the navigation, it could even be a bug in Godot. But, as far as I can tell it is happening for you.
Presumably you want to reach path[0] instead of path[1] since that is what you are checking against to remove points.
If you do in fact want path[1], then check if the path has at least 2 points with path.size() > 1 (or path.size() >= 2 if you prefer).
The jitter
I'm assuming here that path[0] is the target.
I believe it is three things:
You cannot trust vector equality
Vector equality boils down to equality of the components. Which is float equality. And thus Vector equality has all the problems of float equality.
So, to compare to your current target use is_equal_approx. For example global_position.is_equal_approx(path[0]).
You don't want to move if you reached the target
This is easy enough: If there are no more points in path, don't move. That is, you can add this at the start of move:
if path.size() == 0:
return
If you will have the code in _physics_process instead of move, remember to check there.
You don't want to overshoot
So, move_and_slide will move the object as much as it should given the time between physics frames (delta). But that might be more than necessary to reach the target. As consequence, it is very likely to overshoot the target. As a result, the next physics frame it will have to move in the opposite direction, and overshoot again, and so on… Jitter!
I'll give you three solutions:
Don't use move_and_slide (with the caveat that you would be forgoing physics collisions):
# you can use move_toward
global_position = global_position.move_toward(path[0], SPEED * delta)
Let us keep move_and_slide, but compute the displacement we want.
# you can use move_toward
var next := global_position.move_toward(path[0], SPEED * delta)
# Then compute the displacement
var displacement := next - global_position
# And divide by it delta to get velocity:
move_and_slide(displacement/delta)
Again, using move_and_slide, but this time we figure out the maximum speed to not overshoot:
# speed is distance over time
var max_speed := global_position.distance_to(path[0])/delta
# And we clamp!
move_and_slide(velocity.clamped(max_speed))
For the versions of the code that use delta you can either put the code in _physics_process, or pass delta to move as a parameter. Also don't forget the check for path.size() mentioned in the prior point.
Addendum If you use path[0] as target, but it was equal to the current position, you would get about no velocity, and have to waste a physics frame. Consider this rewrite:
if path.size() > 0 and global_position.is_equal_approx(path[0]):
path.pop_front()
if path.size() > 0:
velocity = global_position.direction_to(path[0]) * SPEED
Rewritten and Edited for clarity. Assume that I have a 2d platformer like the following example:
https://github.com/godotengine/godot-demo-projects/blob/master/2d/kinematic_character/player.gd
Now... Say I have the player location (vector2) and the enemy location (vector2). The enemy movement works just like the player in the above example. I use get_simple_path to build an array of pre-existing points that lead from the enemy location to the player location. How do I use that array with move_and_slide to get the enemy from point A to point B?
You could try the following:
const GRAVITY = 1000
const SLOPE_SLIDE_STOP = false
var speed = 250
var velocity = Vector2()
var points = [Vector2(100, 200), Vector2(200, 400), Vector2(400, 800)]
var current_point = null
func _physics_process(delta):
if points and current_point is null:
current_point = points.pop_front()
if current_point:
if current_point.distance_to(position) > 0:
target_direction = (position - current_point).normalized()
velocity.y += delta * GRAVITY
velocity = lerp(velocity, target_speed, 0.1)
velocity = move_and_slide(velocity, target_direction, SLOPE_SLIDE_STOP)
else:
current_point = null
Here I give the speed and the direction to the ball
if collision_ball_pad_l == True:
print("ball hit the left pad")
ball_movement = -ball_movement
if first_collision == 0:
ball_movement_y = random.choice([1,-1])*speed
first_collision +=1
If ball hits the edge it must change the direction but it prints me that the speed is 0
if collision_ball_shield_u == True:
print("ball hit the left pad")
ball_movement_y -= ball_movement_y
print (ball_movement_y)
You have to change sign of variable - from + to - or from - to +
You can do it in one line - see - in code
ball_movement_y = -ball_movement_y
I'm new to graphics and was wondering how I would go about creating an object boundary for my game.
Right now I have an object following my mouse. This works fine for my boundary when my ship has a rotation.z of 0. However, when I rotate the ship, the boundary has odd behavior.
I tried getting the world position of the ship and simply checking the x and y boundaries. However, the world position seems to give me a different x and y when I rotate. How can I
go about creating a boundary that works for all the rotations of my ship.
Here's my game(collisions turned off for purpose of help): http://www.cis.gvsu.edu/~chaua/CS371/WebGLProject/home.html
The behavior I'd like occurs when you don't rotate the ship.
Rotate the ship with left/right mouse.
Relevant code snippets(in render loop):
if(mouseLeftDown)
ship.rotation.z += leanSpeed;
if(mouseRightDown)
ship.rotation.z += -leanSpeed;
....
....
targetX = ((lastMouseX / window.innerWidth) * 2 - 1) * 90;
targetY = -((lastMouseY / window.innerHeight) * 2 - 1) * 60;
var worldPosition = (new THREE.Vector3()).getPositionFromMatrix(ship.matrixWorld);
if(worldPosition.x + targetX <= (randSpawnWidth/2)-500 && worldPosition.x + targetX >= -(randSpawnWidth/2)+500)
ship.translateX(targetX);
if(worldPosition.y + targetY <= (randSpawnHeight/2)-500 && worldPosition.y + targetY >= -(randSpawnHeight/2)+500)
ship.translateY(targetY);
I would appreciate any help. Thanks!