So, I have an area 2D that gets input and creates a line using it that works in it's scene:
extends Area2D
signal vector_created(vector)
export var maximum_length := 200
var touch_down := false
var position_start := Vector2.ZERO
var position_end := Vector2.ZERO
var vector := Vector2.ZERO
func _ready() -> void:
connect("input_event", self, "_on_input_event")
func _draw() -> void:
draw_line(position_start - global_position,
position_end - global_position,
Color.blue,
8)
draw_line(position_start - global_position,
position_start - global_position + vector,
Color.red,
16)
func _reset() -> void:
position_start = Vector2.ZERO
position_end = Vector2.ZERO
vector = Vector2.ZERO
update()
func _input(event) -> void:
if event.is_action_released("ui_touch"):
touch_down = false
emit_signal("vector_created", vector)
_reset()
if not touch_down:
return
if event is InputEventMouseMotion:
position_end = event.position
vector = -(position_end - position_start).clamped(maximum_length)
update()
func _on_input_event(_viewport, event, _shape_idx) -> void:
if event.is_action_pressed("ui_touch"):
touch_down = true
position_start = event.position
But when I put it in the main scene it doesn't work. Why ?
I tried to change a an TextureRect, which was set to passing but still it doesn't work, I haven't figured it out since like a week!
The issue I see is that you are mixing coordinate systems. The mouse position is in global space, but the points of the line are in local space. Which is fine is everything is at the origin and there is no scaling or rotation… But presumably that isn't the case.
For your Line2D it would be like this:
$Line2D.set_point_position(0,$Line2D.to_local(first_touch))
$Line2D.set_point_position(1,$Line2D.to_local(release))
And for draw_line it would be like this:
draw_line(to_local(first_touch),to_local(release),Color.blue,5)
Related
The Player Script giving me the error.
extends KinematicBody2D
const FRICTION = 5000
const ACCELERATION = 5000
const MOVE_SPEED = 300
onready var animationPlayer = $AnimationPlayer
var velocity = Vector2.ZERO
var aim_direction: Vector2
var max_recoil: float
var current_recoil: float
# Declare the signal that will be used to add recoil to the player's aim
signal add_recoil(recoil: float)
func _ready():
aim_direction = Vector2(1, 0)
max_recoil = 20
current_recoil = 0
connect("add_recoil", self, "_on_add_recoil")
# Add recoil to the player's aim
func _on_add_recoil(recoil: float) -> void:
current_recoil += recoil
# Clamp the current recoil to the maximum recoil
current_recoil = clamp(current_recoil, 0, max_recoil)
func _physics_process(delta):
#--Local Variables--
var lerp_rate = FRICTION
#--Input Logic--
var input_vector = Vector2(Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left"),
Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up"))
if input_vector.y != 0: lerp_rate = ACCELERATION
#--Animation Logic--
if input_vector.y == -1:
animationPlayer.play("WalkUp")
elif input_vector.y == 1:
animationPlayer.play("WalkDown")
elif input_vector == Vector2.ZERO:
animationPlayer.stop()
#--Movement--
velocity = velocity.move_toward(MOVE_SPEED*input_vector, lerp_rate*delta)
velocity = move_and_slide(velocity)
# Slowly reduce the current recoil over time
current_recoil = lerp(current_recoil, 0, delta * 5)
# Rotate the aim direction by the current recoil
aim_direction = aim_direction.rotated(current_recoil)
The error is coming up on signal add_recoil(recoil: float)
It'll be great if any coding wises for the Godot engine could explain how to fix this.
It's supposed to receive the signal from the WeaponBaseClass script to the players aim.
The script in question:
extends KinematicBody2D
export (PackedScene) var Bullet
# Declare variables to store the properties of the weapon
export var weapon_name: String
export var recoil: float
export var damage: int
export var shooting_speed: float
export var magazine_capacity: int
export var reload_speed: float
export var attachments: Array
# Get a reference to the player kinematic node in the main scene
onready var player_node = get_node("/root/Main/Player")
# Declare a variable to store the speed of the bullets fired by the weapon
export var bullet_speed: float
onready var end_of_gun = $EndOfGun
# Declare variables to store the current state of the weapon
var current_magazine: int
var is_reloading: bool
# Initialize the weapon
func _ready():
current_magazine = magazine_capacity
is_reloading = false
func _process(delta: float) -> void:
look_at(get_global_mouse_position())
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_released ("shoot"):
shoot()
if event.is_action_released ("reload"):
reload()
# Shoot the weapon
func shoot():
# Check if the weapon is reloading or out of ammo
if is_reloading or current_magazine <= 0:
return
# Decrement the magazine size
current_magazine -= 1
# Emit the "add_recoil" signal to tell the player to add recoil to their aim
EventBus.emit_signal("add_recoil", recoil)
# Send the add_recoil signal to the player
player_node.add_recoil(recoil)
# Play a shooting sound effect
#audio_play("shoot.wav")
# Spawn a bullet and apply damage to any enemies it hits
var bullet = preload("Bullet.tscn").instance()
# Set the bullet speed
#bullet.set_linear_velocity(Vector2(BULLET_SPEED, 0))
var target = get_global_mouse_position()
var direction_to_mouse = end_of_gun.global_position.direction_to(target).normalized()
bullet.rotation = get_global_transform().get_rotation()
EventBus.emit_signal("fired_bullet", bullet, end_of_gun.global_position, direction_to_mouse)
bullet.damage = damage
add_child(bullet)
# Reload the weapon
func reload():
# Check if the weapon is already reloading
if is_reloading:
return
# Set the reloading flag
is_reloading = true
# Play a reloading sound effect
#audio_play("reload.wav")
# Call the reload animation
$AnimationPlayer.play("reload")
# Wait for the reload time to elapse
yield(get_tree().create_timer(reload_speed), "timeout")
# Reset the reloading flag and refill the magazine
is_reloading = false
current_magazine = magazine_capacity
I also have an EventBus script if that helps:
extends Node
#Only holds signals
signal fired_bullet(bullet, position, direction)
GDScript in Godot 3 does not support specifying types for signal parameters. It would work in Godot 4.
So Godot 3 won't parse this:
signal add_recoil(recoil: float)
Instead you would have to drop the type:
signal add_recoil(recoil)
For some reason, the enemy, when following the player, can change the path for a second to some corner or somewhere else. Because of this, he sometimes twitches and goes back. What could be the problem? Here is my code
extends CharacterBody2D
class_name Ghosts
#onready var region_id: RID = NavigationServer2D.region_create()
var path:Array = []
var nav:NavigationRegion2D = null
var player = null
func _ready():
await get_tree()
var tree = get_tree()
if tree.has_group("LevelNavigation"):
nav = tree.get_nodes_in_group("LevelNavigation")[0]
NavigationServer2D.region_set_map(region_id, get_world_2d().navigation_map)
NavigationServer2D.region_ser_navpoly(region_id, nav.navpoly)
if tree.has_group("Player"):
player = tree.get_nodes_in_group("Player")[0]
func attack(speed:int):
if path.size() > 0:
velocity = global_position.direction_to(path[1]) * speed
if global_position == path[0]:
path.pop_front()
move_and_slide()
func generate_path(line2D: Line2D):
if nav != null and player != null:
path = NavigationServer2D.map_get_path(get_world_2d().navigation_map, position
line2D.points = path
I would like to get the correct path. Maybe it's the features of Godot 4
Noop
First this is nothing:
await get_tree()
The method get_tree is not asynchronous, and you are discarding the result anyway. Perhaps you wanted to await for the Node to enter the scene tree? That would be await self.tree_entered… Except you have the line inside _ready and when Godot calls _ready the Node is inside the tree (which also means it will not enter, so waiting for it to enter won't work).
Just remove that line.
Following the path
Second this won't work well:
if path.size() > 0:
velocity = global_position.direction_to(path[1]) * speed
if global_position == path[0]:
path.pop_front()
move_and_slide()
You are moving towards path[1] and not path[0]
Even if you were, nothing is preventing you from overshooting.
Even if you were preventing overshooting, the equality comparison won't work well due to floating point errors.
So the plan:
If there are no points in the path, we are done.
If there are points in the path, pick the first point.
If the current position is close enough to the point, remove it.
Otherwise move towards the point.
Let us do it:
if path.empty():
return
var point := path[0]
if global_position.distance_to(point) <= threshold:
path.pop_front()
return
velocity = global_position.direction_to(point) * speed
move_and_slide()
Ok, caveats:
We need to prevent overshooting.
How much is the threshold?
When we remove we don't move.
Let us fix that. We need to figure out, beforehand, much we have to move:
var distance_to_move := speed * delta
But we should not advance more than the distance to the point!
if path.empty():
return
var point:Vector2 = path[0]
var distance_to_point := global_position.distance_to(point)
var distance_to_move := min(speed * delta, distance_to_point)
And the velocity will be that to move that in this frame:
if path.empty():
return
var point:Vector2 = path[0]
var distance_to_point := global_position.distance_to(point)
var direction_to_point := global_position.direction_to(point)
var distance_to_move := minf(speed * delta, distance_to_point)
if distance_to_point <= distance_to_move:
path.pop_front()
velocity = direction_to_point * (distance_to_move / delta)
move_and_slide()
This way we don't overshoot, we move even when we remove, and we have a clear threshold for removing.
Caveats:
We don't always move the full distance.
For that we need a loop:
var total_distance_to_move := speed * delta
while total_distance_to_move > 0.0 and not path.empty():
var point:Vector2 = path[0]
var distance_to_point := global_position.distance_to(point)
var direction_to_point := global_position.direction_to(point)
var distance_to_move := minf(total_distance_to_move, distance_to_point)
if distance_to_point <= distance_to_move:
path.pop_front()
velocity = direction_to_point * (distance_to_move / delta)
move_and_slide()
total_distance_to_move -= distance_to_move
Generating the path
The code is cut off where you use map_get_path, however I can see you are using position.
Work with the global_position instead:
path = Navigation2DServer.map_get_path(
get_world_2d().get_navigation_map(),
global_position,
target_pos,
true
)
I don't know how you were getting the target position, but I'll trust you can figure out how to make it with global coordinates.
And you are setting this to a Line2D. That wants its own local coordinantes. So you do this:
line2d.points = line2d.global_transform.affine_inverse() * path
In my game I want it so when either the player or the enemy gets damaged they bounce but i do not know how to do so.
code for player:
extends KinematicBody2D
var speed = 200 # speed in pixels/sec
var velocity = Vector2.ZERO
var hasDagger = false
func get_input():
velocity = Vector2.ZERO
if Input.is_action_pressed('right'):
velocity.x += 1
if Input.is_action_pressed('left'):
velocity.x -= 1
if Input.is_action_pressed('down'):
velocity.y += 1
if Input.is_action_pressed('up'):
velocity.y -= 1
if Input.is_action_just_pressed("sprint"):
speed += 100
if Input.is_action_just_released("sprint"):
speed -= 100
# Make sure diagonal movement isn't faster
velocity = velocity.normalized() * speed
func _physics_process(delta):
get_input()
velocity = move_and_slide(velocity)
enemy code:
extends KinematicBody2D
var run_speed = 201
var velocity = Vector2.ZERO
var collider = null
var health = 3
func _physics_process(delta):
velocity = Vector2.ZERO
if collider:
#print(collider)
velocity = global_position.direction_to(collider.global_position) * run_speed
velocity = move_and_slide(velocity)
func _on_DetectRadius_body_entered(body):
collider = body
func _on_DetectRadius_body_exited(body):
collider = null
func _on_hit_box_body_entered(body):
health -= 1
if health == 0:
get_tree().change_scene("res://scenes/game over.tscn")
the health you see in the enemy script is for the player
the player is a kinematicbody2D with a colilistionshape2D and the enemy is the same as that
What I'm going to show is not the only way to go about this - In fact, we should be talking about animations and states - instead it is just one way to tackle this.
I'll define a variable target. I'll leave it without specifying a type, so it is variant. However, I'll have it be Vector2. The reason I leave it variant, is so it can be null. If I could, I'd type it as a nullable Vector2, but Godot does not support that.
The idea is to have the KinematicBody2D go to the target position if it is a Vector2. But if it isn't, let the regular movement continue. Something like this:
export var speed:float
var velocity := Vector2.ZERO
var target = null
func _physics_process(_delta:float) -> void:
if target != null:
velocity = global_position.direction_to(target) * speed
else:
# call get_input or whatever
pass
velocity = move_and_slide(velocity)
I'll add a method for this, it will make things easier:
export var speed:float
var velocity := Vector2.ZERO
var target = null
func _physics_process(_delta:float) -> void:
if not advance_to_target():
# call get_input or whatever
pass
velocity = move_and_slide(velocity)
func advance_to_target() -> bool:
if target == null:
return false
velocity = global_position.direction_to(target) * speed
return true
Well, we should clear target when we reach it, and we should not overshoot. Let us fix that:
export var speed:float
var velocity := Vector2.ZERO
var target = null
func _physics_process(_delta:float) -> void:
if not advance_to_target():
target = null
# call get_input or whatever
velocity = move_and_slide(velocity)
func advance_to_target() -> bool:
if target == null:
return false
var displacement := target - global_position
var distance := displacement.length()
if is_zero_approx(distance):
return false
var time := get_physics_process_delta_time()
var max_speed := distance / time
var direction := displacement / distance
velocity = direction * min(speed, max_speed)
return true
Here we are computing max_speed, which is the speed at which the KinematicBody2D would have to move to reach the target in the current physics frame. Any faster than that, and it would overshoot. So we use min(speed, max_speed) to make sure the actual speed is capped at that.
And when the target is reached we would have found that the distance is zero. Well, almost zero. Because of rounding errors we cannot rely on it hitting zero exactly. That is why I use is_zero_approx(distance). This also avoids a division by zero at displacement / distance.
For the enemy we can co-opt this system to chase the collider… Let us refactor to make that easier:
export var speed:float
var velocity := Vector2.ZERO
var target = null
var collider = null
func _physics_process(_delta:float) -> void:
var target_position = null
if target != null:
target_position = target
elif collider != null:
target_position = collider.global_position
if not advance_to(target_position):
target = null
velocity = move_and_slide(velocity)
func advance_to(target_position) -> bool:
if target_position == null:
return false
var displacement := position - global_position
var distance := displacement.length()
if is_zero_approx(distance):
return false
var time := get_physics_process_delta_time()
var max_speed := distance / time
var direction := displacement / distance
velocity = direction * min(speed, max_speed)
return true
Or we can add get_input into it:
export var speed:float
var velocity := Vector2.ZERO
var target = null
func _physics_process(_delta:float) -> void:
if not advance_to(target):
target = null
get_input()
velocity = move_and_slide(velocity)
func advance_to(target_position) -> bool:
if target_position == null:
return false
var displacement := position - global_position
var distance := displacement.length()
if is_zero_approx(distance):
return false
var time := get_physics_process_delta_time()
var max_speed := distance / time
var direction := displacement / distance
velocity = direction * min(speed, max_speed)
return true
func get_input():
velocity = Vector2.ZERO
if Input.is_action_pressed('right'):
velocity.x += 1
if Input.is_action_pressed('left'):
velocity.x -= 1
if Input.is_action_pressed('down'):
velocity.y += 1
if Input.is_action_pressed('up'):
velocity.y -= 1
if Input.is_action_just_pressed("sprint"):
speed += 100
if Input.is_action_just_released("sprint"):
speed -= 100
# Make sure diagonal movement isn't faster
velocity = velocity.normalized() * speed
Ah, yes, the damage. We are going to set a target when we register the collision. Something like this:
export var push_back_distance:float
func _on_hit_box_body_entered(body):
var direction := global_position.direction_to(body.global_position)
target = global_position - direction * push_back_distance
# the rest of the code here
That should make the KinematicBody2D move away from whatever it collided with.
I'm trying to create a "Block" input that summons a wall at the position the camera is looking at and have it face the camera's direction. It seems to be using the global coordinates which doesn't make sense to me, because I use the same code to spawn a bullet with no problem. Here's my code:
if Input.is_action_just_pressed("light_attack"):
var b = bullet.instance()
muzzle.add_child(b)
b.look_at(aimcast.get_collision_point(), Vector3.UP)
b.shoot = true
print(aimcast.get_collision_point())
if Input.is_action_just_pressed("block"):
var w = wall.instance()
w.look_at(aimcast.get_collision_point(),Vector3.UP)
muzzle.add_child(w)
w.summon = true
The light attack input is the code used to summon and position the bullet. muzzle is the spawn location (just a spatial node at the end of the gun) and aimcast is a raycast from the center of the camera. All of this is run in a get_input() function. The wall spawns fine, I just can't orient it. I also need to prevent any rotation on the y-axis. This question is kinda hard to ask, so I couldn't google it. If you need any clarification please let me know.
New Answer
The asked comment made me realize there is a simpler way. In the old answer I was defining a xz_aim_transform, which could be done like this:
func xz_aim_transform(pos:Vector3, target:Vector3) -> Transform:
var alt_target := Vector3(target.x, pos.y, target.z)
return Transform.IDENTITY.translated(pos).looking_at(alt_target, Vector3.UP)
That is: make a fake target at the same y value, so that the rotation is always on the same plane.
It accomplishes the same thing as the approach in the old answer. However, it is shorter and easier to grasp. Regardless, I generalized the approach in the old answer, and the explanation still has value, so I'm keeping it.
Old Answer
If I understand correctly, you want something like look_at except it only works on the xz plane.
Before we do that, let us establish that look_at is equivalent to this:
func my_look_at(target:Vector3, up:Vector3):
global_transform = global_transform.looking_at(target, up)
The take away of that is that it sets the global_transform. We don't need to delve any deeper in how look_at works. Instead, let us work on our new version.
We know that we want the xz plane. Sticking to that will make it simpler. And it also means we don't need/it makes no sense to keep the up vector. So, let us get rid of that.
func my_look_at(target:Vector3):
# global_transform = ?
pass
The plan is to create a new global transform, except it is rotated by the correct angle around the y axis. We will figure out the rotation later. For now, let us focus on the angle.
Figuring out the angle will be easy in 2D. Let us build some Vector2:
func my_look_at(target:Vector3):
var position := global_transform.origin
var position_2D := Vector2(position.x, position.z)
var target_2D := Vector2(target.x, target.z)
var angle:float # = ?
# global_transform = ?
That part would not have been as easy with an arbitrary up vector.
Notice that we are using the 2D y for the 3D z values.
Now, we compute the angle:
func my_look_at(target:Vector3):
var position := global_transform.origin
var position_2D := Vector2(position.x, position.z)
var target_2D := Vector2(target.x, target.z)
var angle := (target_2D - position_2D).angle_to(Vector2(0.0, -1.0))
# global_transform = ?
Since we are using the 2D y for the 3D z values, Vector2(0.0, -1.0) (which is the same as Vector2.UP, by the way) is representing Vector3(0.0, 0.0, -1.0) (Which is Vector3.FORWARD). So, we are computing the angle to the 3D forward vector, on the xz plane.
Now, to create the new global transform, we will first create a new basis from that rotation, and use it to create the transform:
func my_look_at(target:Vector3):
var position := global_transform.origin
var position_2D := Vector2(position.x, position.z)
var target_2D := Vector2(target.x, target.z)
var angle := (target_2D - position_2D).angle_to(Vector2.UP)
var basis := Basis(Vector3.UP, angle)
global_transform = Transform(basis, position)
You might wonder why we don't use global_transform.rotated, the reason is that using that multiple times would accumulate the rotation. It might be ok if you only call this once per object, but I rather do it right.
There is one caveat to the method above. We are losing any scaling. This is how we fix that:
func my_look_at(target:Vector3):
var position := global_transform.origin
var position_2D := Vector2(position.x, position.z)
var target_2D := Vector2(target.x, target.z)
var angle := (target_2D - position_2D).angle_to(Vector2.UP)
var basis := Basis(Vector3.UP, angle).scaled(global_transform.basis.get_scale())
global_transform = Transform(basis, position)
And there you go. That is a custom "look at" function that works on the xz plane.
Oh, and yes, as you have seen, your code works with global coordinates. In fact, get_collision_point is in global coordinates.
Thus, I advice not adding your projectiles as children. Remember that when the parent moves, the children move with it, because they are placed relative to it.
Instead give them the same global_transform, and then add them to the scene tree. If you add them to the scene before giving them their position, they might trigger a collision.
You could, for example, add them directly as children to the root (or have a node dedicated to holding projectiles, another common option is to add them to owner).
That way you are doing everything on global coordinates, and there should be no trouble.
Well, since you are going to set the global_transform anyway, how about this:
func xz_aim_transform(position:Vector3, target:Vector3) -> Transform:
var position_2D := Vector2(position.x, position.z)
var target_2D := Vector2(target.x, target.z)
var angle := (target_2D - position_2D).angle_to(Vector2.UP)
var basis := Basis(Vector3.UP, angle)
return Transform(basis, position)
Then you can do this:
var x = whatever.instance()
var position := muzzle.global_transform.origin
var target := aimcast.get_collision_point()
x.global_transform = xz_aim_transform(position, target)
get_tree().get_root().add_child(x)
x.something = true
print(target)
By the way, this would be the counterpart of xz_aim_transform not constrained to the xz plane:
func aim_transform(position:Vector3, target:Vector3, up:Vector3) -> Transform:
return Transform.IDENTITY.translated(position).looking_at(target, up)
It took me some ingenuity, but here is the version constrained to an arbitrary plane (kind of, as you can see it does not handle all cases):
func plane_aim_transform(position:Vector3, target:Vector3, normal:Vector3) -> Transform:
normal = normal.normalized()
var forward_on_plane := Vector3.FORWARD - Vector3.FORWARD.project(normal)
if forward_on_plane.length() == 0:
return Transform.IDENTITY
var position_on_plane := position - position.project(normal)
var target_on_plane := target - target.project(normal)
var v := forward_on_plane.normalized()
var u := v.rotated(normal, TAU/4.0)
var forward_2D := Vector2(0.0, forward_on_plane.length())
var position_2D := Vector2(position_on_plane.project(u).dot(u), position_on_plane.project(v).dot(v))
var target_2D := Vector2(target_on_plane.project(u).dot(u), target_on_plane.project(v).dot(v))
var angle := (target_2D - position_2D).angle_to(forward_2D)
var basis := Basis(normal, angle)
return Transform(basis, position)
Here w - w.project(normal) gives you a vector perpendicular to the normal. And w.project(u).dot(u) gives you how many times u fit in w, signed. So we use that build our 2D vectors.
This is a follow up question to this one: Are accessors of class properties useful?
In this complete answer it is stated that:
"When you use the property, you are using the getter and setter methdods. The property is a convenience for language binding. Thus, we could also say that you don't need the property, only the getter and setter."
But the following experiment seems to contradict that point:
file src/PositiveInteger.gd
With a setter which prevents negative integer to be assigned to the property n
class_name PositiveInteger
var n: int
func _init() -> void:
self.n = 0
func set_n(_n: int) -> void:
if _n < 0:
n = 0
else:
n = _n
func get_n() -> int:
return n
file main.gd
Now let us consider this test, with c1 using the setter and c2 using the dot construct:
tool
extends EditorScript
tool
extends EditorScript
func _run() -> void:
var PositiveInteger = load("res://src/PositiveInteger.gd")
var c1 = PositiveInteger.new()
c1.set_n(-3)
print("c1: " + str(c1.n))
var c2 = PositiveInteger.new()
c2.n = -3
print("c2: " + str(c2.n))
The output is:
c1: 0
c2: -3
So the property assignement seems to bypass the setter, is this behaviour different with the core class of the language only?
My prior answer applies to core classes (and should apply to modules classes), which are written in C++. They do not expose anything by default. Instead they must bind their C++ methods as methods and properties to expose them.
Now, if you want something like that for a class created in GDScript, you can do it with setget:
class_name PositiveInteger
var n: int setget set_n, get_n
func _init() -> void:
self.n = 0
func set_n(_n: int) -> void:
if _n < 0:
n = 0
else:
n = _n
func get_n() -> int:
return n
With that your output should be:
0
0