Trying to make a 2D destructable box in godot - godot

i suck at coding. I am currently trying to make a 2D box that can be destroyed when the player attacks, however (like i said before) i suck at coding. I managed to get it working somewhat (and by somewhat i mean not at all) the box has an animation that plays when the player attacks when in range, but the animation almost never plays (sometimes it does but idk why)
code for box
extends Area2D
var inside = false
var attacking = false
func _physics_process(delta):
pass
func _on_Area2D_body_entered(body):
if Input.is_action_just_pressed("Attack"):
$AnimationPlayer.play("box_hit")
$boxdeathtimer.set_wait_time(0.5)
$boxdeathtimer.start()
func _on_boxdeathtimer_timeout():
queue_free()
code for weapon (if needed)
extends RigidBody2D
var picked = false
func _ready():
Global.weapon = self
func _exit_tree():
Global.weapon = null
var attacking = false
func _physics_process(delta):
if picked == true:
self.position = get_node("../player/Position2D").global_position
func _input(event):
if Input.is_action_just_pressed("e"): #picks up weapon when in range
var bodies = $detector.get_overlapping_bodies()
for b in bodies:
if (b.get_name() == "player"):
picked = true
sleeping = true
rotation_degrees = 90
if Input.is_action_just_pressed("Attack"):
if picked == true && Global.player.facing_right == true:
$AnimationPlayer.play("attack")
attacking = true
if picked == true && Global.player.facing_right == false:
$AnimationPlayer.play("attack2")
attacking = true

The body_entered signal notifies you when a physics body enters the area. The method is_action_just_pressed tells you if the (key associated with the) action were pressed the same (graphics, by default) frame.
So, in your code, everything inside here (Presuming the signal is properly connected):
func _on_Area2D_body_entered(body):
if Input.is_action_just_pressed("Attack"):
# CODE HERE
Can only run if the player pressed (the keys associated with the) action the same (graphics, by default) frame that a body entered the area, which is very hard to manage.
My suggestion is to give a "range of attack" area to the weapon. Then when the player attacks, you can use the signals of that area as it moves.
By the way, Avoid using is_action_just_pressed in _input. It is not intended for that. See Godot - Input.is_action_just_pressed() runs twice. In fact, I would argue to just use Input in _physics_process, unless you really need _input. See the link for what to replace is_action_just_pressed with, if you are working in _input.
So, it can look like this:
On the player side:
func _physics_process(delta):
# … some other code …
if Input.is_action_just_pressed("Attack"):
if picked == true && Global.player.facing_right == true:
$AnimationPlayer.play("attack")
attacking = true
if picked == true && Global.player.facing_right == false:
$AnimationPlayer.play("attack2")
attacking = true
if attacking:
$RangeOfAttack.monitoring = true
yield($AnimationPlayer, "animation_finished")
$RangeOfAttack.monitoring = false
# attacking = false # ?
func _on_RangeOfAttack_area_entered(area):
if area.has_method("attacked"):
area.attacked()
Where:
yield($AnimationPlayer, "animation_finished") is telling Godot to continue the execution after the $AnimationPlayer emits the "animation_finished"signal.
$RangeOfAttack refers to te range of attack area. Child node of the weapon.
_on_RangeOfAttack_area_entered is connected to the "area_entered" signal of the range of attack area. The reason I'm using "area_entered" instead of "body_entered" is that you made the box an area. If box weren't an area but some physics body, you could use the "body_entered" signal of the weapon (which is a RigidBody2D), and you would have no need for $RangeOfAttack.
On the target side:
func attacked():
$AnimationPlayer.play("box_hit")
yield($AnimationPlayer, "animation_finished")
queue_free()

Related

Meele attack animation freezing alternate solution

I had a script somewhat similar like in this video:
extends KinematicBody2D
var movement=Vector2();
var up= Vector2(0, -1);
var speed=200;
var isAttacking=false;
func _process(delta):
if Input.is_action_pressed("ui_right") && isAttacking == false:
movement.x = speed;
$AnimatedSprite.play("walk");
elif Input.is_action_pressed("ui_left") && isAttacking == false:
movement.x= -speed;
$AnimatedSprite.play("Walk");
else:
movement.x = 0;
if isAttacking == false:
$AnimatedSprite.play("Idle");
if Input.is_action_just_pressed("Attack"):
$AnimatedSprite.play("Slash");
isAttacking=true;
movement = move_and_slide(movement, up * delta);
func _on_AnimatedSprite_animation_finished():
if $AnimatedSprite.animation == "Slash":
isAttacking= false;
but the problem was when I was rapidly pressing attack & movement on my keyboard
sometimes the isAttacking did not get set back to false after the animation was completed and hence froze my character animation
Maybe it was a bug in invoking the connected signal function when pressed rapidly? but anyhow it gave me a nightmare
so I came up with this workaround for rapid key pressing attack and movements (check the solutions) so no one else has to go through what I did :)
Instead of checking for attack in _process() I used _unhandled_key_input() and this seemed to get rid of the problem
Hope this helps! :D
...
func _process(delta):
if Input.is_action_pressed("ui_right") && isAttacking == false:
movement.x = speed;
$AnimatedSprite.play("walk");
elif Input.is_action_pressed("ui_left") && isAttacking == false:
movement.x= -speed;
$AnimatedSprite.play("Walk");
else:
movement.x = 0;
if isAttacking == false:
$AnimatedSprite.play("Idle");
if Input.is_action_just_pressed("Attack"):
$AnimatedSprite.play("Slash");
isAttacking=true;
movement = move_and_slide(movement, up * delta);
func attack_animation_finished(var _extra):
isAttacking=false
func _unhandled_key_input(event):
if(event.echo):
return
if(!event.pressed):
return
if(event.scancode==KEY_X and !isAttacking): # x key to attack (you can use whatever you like)
isAttacking=true
if!($AnimatedSprite.is_connected("animation_finished",self,"attack_animation_finished")):
$AnimatedSprite.connect("animation_finished", self, "attack_animation_finished", [], CONNECT_ONESHOT) # warning-ignore:return_value_discarded
$AnimatedSprite.play("Slash");
Note: I haven't ran this particular code segment but I have used this logic/approach in a working larger script which would be too long to share here

Kinematic body 2d won't do anything when entering area 2d - godot

I made a kinematic body2d for my boss battle in godot and made it so that when the bullet which is an area 2d has a body entered it checks for the name then does something. This is my code:
This is my boss code:
extends KinematicBody2D
const ATTACK_THRESHOLD = 1
const ATTACKS = ["movingL", "movingR", "plus", "cross", "horizon"]
var _idle_count = 0
var _attack_set = ATTACKS
signal startin
var health = 1
onready var _anim_tree = $boostree
func increase_idle_count():
_idle_count += 1
if health == 0:
death()
if _idle_count > ATTACK_THRESHOLD:
_idle_count = 0
attack()
func _on_bosstrigger_start():
_anim_tree.set_condition("starting", true)
emit_signal("startin")
$boossoo.play()
func _ready():
randomize()
func attack():
var attack = _attack_set[randi()%_attack_set.size()]
_anim_tree.set_condition(attack, true)
func death():
_anim_tree.set_condition("die", true)
func _on_area2DE_area_entered(area):
if "bullet2" in area.name:
health -= 1
I used "area2DE" which was an area2d that was a child of the boss.
the "increase_idle_count()" function was put at the end of my idle animation.
.
I would think that the boss would do the death animation after getting shot but absolutely nothing happens. all the other animations work normally and will happen except for the death animation. I had spent the entire day trying different solutions and nothing had worked. Please help.

How do I make the player spawn next to car here

extends KinematicBody2D
var active = false
var car_zone = false
#car driving
func get_car_input():
var velocity = Vector2.ZERO
var speed = 200
if Input.is_action_pressed("forward"):
velocity.y = -1
if Input.is_action_pressed("backward"):
velocity.y = 1
if Input.is_action_pressed("left"):
velocity.x = -1
if Input.is_action_pressed("right"):
velocity.x = 1
move_and_slide(velocity*speed)
func _physics_process(_delta):
if active:
get_car_input()
leaving_car()
if !active:
entering_car()
pass
#entering/exiting car
func _on_player_detect_body_entered(body):
if body.name == "player":
car_zone = true
func _on_player_detect_body_exited(body):
if body.name == "player":
car_zone = false
func entering_car():
if Input.is_action_just_pressed("interact") && car_zone == true:
var hidden_player = get_parent().get_node("player")
hidden_player.active = false
#$Camera.make_current()
active = true
print("car entered")
func leaving_car():
var vehicle = $"."
var hidden_player = get_parent().get_node("player")
#spawn player to car HERE
if car_zone == false && Input.is_action_just_pressed("interact"):
hidden_player.active = true
active = false
#hidden_player.global_transform.origin = newLoc
I followed this tutorial: https://www.youtube.com/watch?v=7VzBHbG8sqo, and at 14:41, it shows how to do it in godot 3d, but I need it in godot 2d. He used "var newLoc = vehicle.global_transform.origin - 2*vehicle.transform.basis.x" to do that, but it doesn't work in 2d
There is a pretty simple solution for your problem, which does not even contain any math to work.
There is a Node called Position2D, which you can add to your Car Scene. Place it, where you want your character should stand after leaving the vehicle (so as a driver on the left side of your car)
Because the node is in your car scene it will move along with the car and rotate as well, so its always right next to your car.
All we need to do now is getting the global_position of the Position2D and setting the global_position of our player to it.
To make it easier to receive the global_position of the Position2D Node, we can add a function to the car which returns exactly that. Saying your Car Scene looks like this:
Vehicle
Sprite
ExitPosition (Our Position2D node. Renamed for clearity)
The function in our vehicle.gd could be like this:
func get_exit_location() -> Vector2:
return $ExitPosition.global_position
As I see it you have a variable named vehicle in your player code, which points to your car. So now, when you want to leave the car you can set the player position like this:
## Calling inside player code
global_position = vehicle.get_exit_location()
Keep in mind, that both ways (the one in the video and this one here) will make problems if there is something at the point your trying to place your player. So always check if your player can be at that position.

Godot smooth transition between players

I have a "parent" player scene, and I inherit scenes for each player. The parent player scene has a camera. When the game switches between players, one player turns off its camera, and the other player turns its camera on:
if state != State.ACTIVE:
# If this player is becoming active, also
# set camera current
state = State.ACTIVE
camera.current = true
else:
# If player is not becoming active,
# disable this players camera
camera.current = false
But players can be in different positions, so the camera "jumps" from one to the other. Can we do something more sophisticated, like set the new camera to the current position so the smooth setting can be used to handle the transition?
One idea is to do get_viewport().get_camera() to find the current position of the camera to try and sync the position of the current camera with the new camera that is about to turn on, but appears to not work for 2D scenes. CF: https://github.com/godotengine/godot/pull/38317
Sadly, as you found out, there is no way to get the current Camera2D in Godot 3.x. And you found the pull request that adds the feature to Godot 4.0.
What I'm going to suggest is to have one sole Camera2D, so that one is always the current one. And you can define Position2D inside your scenes that can serve as interpolation targets to move the Camera2D.
I have an script that I think will be useful for you (I made it to be RemoteTransform2D but backwards, it does push a transform, it pulls it), I call it anchor_transform_2d.gd:
tool
class_name AnchorTransform2D
extends Node2D
export var anchor_path:NodePath setget set_anchor_path
export var reference_path:NodePath setget set_reference_path
export var set_local_transform:bool
export(int, FLAGS, "x", "y") var translation_mode:int
export(int, FLAGS, "x", "y") var scale_mode:int
export var rotation_mode:bool
var _anchor:Node2D
var _reference:Node2D
func _physics_process(_delta: float) -> void:
if not is_instance_valid(_anchor) or Engine.editor_hint:
set_physics_process(false)
return
#INPUT
var input := _anchor.global_transform
if is_instance_valid(_reference):
input = _reference.global_transform.affine_inverse() * input
#TRANSLATION
var origin := Vector2 (
input.origin.x if translation_mode & 1 else 0.0,
input.origin.y if translation_mode & 2 else 0.0
)
#ROTATION
var angle := 0.0
if rotation_mode:
angle = input.get_rotation()
#SCALE
var source_scale = input.get_scale()
var scaling := Vector2 (
source_scale.x if scale_mode & 16 else 1.0,
source_scale.y if scale_mode & 32 else 1.0
)
#RESULT
_set_target_transform(
Transform2D(angle, origin) * Transform2D.IDENTITY.scaled(scaling)
)
func set_anchor_path(new_value:NodePath) -> void:
anchor_path = new_value
if not is_inside_tree():
yield(self, "tree_entered")
_anchor = get_node_or_null(anchor_path) as Node2D
set_physics_process(is_instance_valid(_anchor) and not Engine.editor_hint)
if Engine.editor_hint:
update_configuration_warning()
func set_reference_path(new_value:NodePath) -> void:
reference_path = new_value
if not is_inside_tree():
yield(self, "tree_entered")
_reference = get_node_or_null(reference_path) as Node2D
func _set_target_transform(new_value:Transform2D) -> void:
if set_local_transform:
transform = new_value
return
global_transform = new_value
func _get_configuration_warning() -> String:
if _anchor == null:
return "Anchor not found"
return ""
Add this attached to a Node2D in anchor_path set the target from which you want to pull the transform (anchor_path is a NodePath, you can set to it something like $Position2D.get_path()). And set what do you want to copy (you can choose any combination of position x, position y, scaling x, scaling y, and rotation). Then put the Camera2D as a child of the AnchorTransform2D, and set smoothing_enabled to true.
Rundown of the properties:
anchor_path: A NodePath pointing to the Node2D you want to pull the transform from.
reference_path: A NodePath pointing to a Node2D used to make the transform relative (you will be taking the transform of what you put in anchor_path relative to what you put in reference_path).
set_local_transform: Set to true if you want to pull the transform as local (relative to the parent of AnchorTransform2D), leave to false to set the global transform instead.
translation_mode: Specifies if you are going to copy the x position, y position, both or neither.
scale_mode: Specifies if you are going to copy the x scale, y scale, both or neither.
rotation_mode: Specifies if you are going to copy the rotation or not.
The only reason the script is a tool script is to give you a warning in the editor if you forgot to set the anchor_path.

GODOT - how to disable player movement when dialog box is active and reenable it when its inactive

Ok, i'm really bad at coding. I'm especially new to GODOT and am trying to make a 2d game. I've been able to set cant_move to false when dialog is playing, the problem I'm having is making cant_move true again. I don't even know where to put done = true on the dialog script (I kinda just put it in a random place and hoped it would work). Here is my dog-shit code. (the solution is probably easy im just really dumb)
npc script
`extends Area2D
var done = true
func _ready():
Global.npc1 = self
func _exit_tree():
Global.npc1 = null
var can_interact = false
const DIALOG = preload("res://dialoguebox.tscn")
func _physics_process(delta):
$AnimatedSprite.play()
func diaplay():
if done == false:
Global.player.can_move = false
print("test")
if done == true:
print("test2")
can_interact = false
Global.player.can_move = true
func _on_Area2D_body_entered(body):
if body.name == "player":
$Label.visible = true
can_interact = true
func _on_Area2D_body_exited(body):
if body.name == "player":
$Label.visible = false
can_interact = false
func _input(event):
if Input.is_key_pressed(KEY_E) and can_interact == true:
done = false
diaplay()
$Label.visible = false
var dialog = DIALOG.instance()
get_parent().add_child(dialog)
dialog.position = $Position2D.global_position
dialog script
extends Control
var dialog = [
'sampletext',
'sampletext2',
]
var done = false
var dialog_index = 0
var finished = false
func _ready():
load_dialog()
Global.DialogBox = self
func _exit_tree():
Global.DialogBox = null
func _physics_process(delta):
$"Ind".visible = finished
if Input.is_action_just_pressed("ui_accept"):
load_dialog()
func load_dialog():
if dialog_index < dialog.size():
finished = false
$RichTextLabel.bbcode_text = dialog[dialog_index]
$RichTextLabel.percent_visible = 0
$Tween.interpolate_property(
$RichTextLabel, "percent_visible", 0, 1, 1,
Tween.TRANS_LINEAR, Tween.EASE_IN_OUT
)
$Tween.start()
if dialog_index >= 0:
Global.npc1.done = true
else:
queue_free()
done = true
dialog_index += 1
func _on_Tween_tween_completed(object, key):
finished = true
If I understand correctly, you are opening the UI with this code:
var dialog = DIALOG.instance()
get_parent().add_child(dialog)
dialog.position = $Position2D.global_position
So add the instruction to make can_move false there (Global.player.can_move = false).
And apparently it is all done here:
queue_free()
That is, the UI is removing it self. When the UI removes itself, it exits the scene tree, we are going to take advantage of that. Connect the tree_exited of the UI to a func that sets can_move true again:
Global.player.can_move = false
var dialog = DIALOG.instance()
dialog.position = $Position2D.global_position
dialog.connect("tree_exited", self, "dialog_exited")
get_parent().add_child(dialog)
func dialog_exited() -> void:
Global.player.can_move = true
That should do.
Alternatively you could create and emit a signal to notify when the player can move again. Refer to the documentation about signals.
Addendum
I think I got what else is not working. Look at load_dialog:
func load_dialog():
if dialog_index < dialog.size():
# ... some code ...
if dialog_index >= 0:
Global.npc1.done = true
else:
queue_free()
done = true
dialog_index += 1
The first check (dialog_index < dialog.size()) is if there is more dialog text. If there isn't then it is done. So change it to this:
func load_dialog():
if dialog_index < dialog.size():
# … some code …
else:
Global.npc1.done = true
queue_free()
done = true
dialog_index += 1
I hope that makes sense.
You also mention you got an error in Global.player.can_move = true, I suppose that happened with closing the game, that code ran, and Global.player was no longer valid.
Regardless of the situation, you can check if the player is valid:
var player = Global.player
if is_instance_valid(player):
player.can_move = true
I don't have enough rep to comment but I just want to add that you have two different done variables. One in NPC and another in Dialog. You set them true here:
if dialog_index >= 0:
Global.npc1.done = true
else:
queue_free()
done = true
I think the problem might also be that dialog's done is never set true again. But I'm a noob too and Theraot always give good advice so check his answer again.
In fact he already solved this problem:
if dialog_index < dialog.size():
# … some code …
else:
Global.npc1.done = true
queue_free()
done = true
dialog_index += 1
For clarity i would try to use only one done variable, probably connecting the scrips with signals also as Theraot said.

Resources