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
Related
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
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()
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.
Here I am trying to devolope a game.
Anyone please explain why boolean value of the variable win is not updating if Board value changes
Here is the code..
Board = {'1':'1','2':'6','3':'7',
'4':'2','5':'5','6':'8',
'7':'3','8':'4','9':'9'}
Win = Board['1'] == Board['2']== Board['3']=='X' or Board['4'] == Board['5'] == Board['6'] or Board['7'] == Board['8'] == Board['9'] or Board['1'] == Board['5'] == Board['9'] or Board['3'] == Board['5'] == Board['7'] or Board['1'] == Board['4'] == Board['7'] or Board['2'] == Board['5'] == Board['8'] or Board['3'] == Board['6'] == Board['9']
turn = 'X'
print(Win)
for i in range(9):
a = str(input('Enter choice {}: '.format(turn)))
Board[a] = turn
turn = 'Y' if turn == 'X' else 'X'
if Win: #Here I am expecting Win to be True but it is actually False
print('Win')
Please help me out with this.
You have one static boolean value that is unmodified as you "play the game"
Instead, define a function that inspects the state of the board
def isWin(board):
return board['1'] == board['2']== board['3']=='X' or ...
Then use that within the game loop
Board[a] = turn
turn = 'Y' if turn == 'X' else 'X'
if isWin(Board):
...
break # stop the game
so I have tried detecting the collision by making 2 shapes but there is some kind of mistake in it and I am wondering if there are easier ways. so this is what i tried:
cursorSurface = pygame.Surface((0,0))
cursor = pygame.draw.rect(cursorSurface, (0,0,0),(mouseX,mouseY,2,2))
mouse_mask = pygame.mask.from_surface(cursorSurface,255)
mouse_rect = cursor
if mouseAction[0] == 'move':
if mouseX > x and mouseX < xEnd and mouseY > y and mouseY < yEnd:
topTriangle = selectedSquare.subsurface(x+4,y+4,xEnd-(x+5),int((yEnd-(y+5))*0.25))
bottomTriangle = selectedSquare.subsurface(x+4,y+4+int((yEnd-(y+5))*0.75),xEnd-(x+5),int((yEnd-(y+5))*0.25))
leftTriangle = selectedSquare.subsurface(x+4,y+4,int((xEnd-(x+5))*0.25),yEnd-(y+5))
rightTriangle = selectedSquare.subsurface(x+4+int((xEnd-(x+5))*0.75),y+4,int((xEnd-(x+5))*0.25),yEnd-(y+5))
collisionTop_mask = pygame.mask.from_surface(topTriangle,255)
collisionTop_rect = topTriangle.get_rect()
collisionTop_rect.topleft = (0,0)
pygame.draw.rect(selectedSquare, colorDark,(x+5+int((xEnd-(x+5))*0.25),y+5+int((yEnd-(y+5))*0.25),int((xEnd-(x+5))*0.75)-int((xEnd-(x+5))*0.25)-2,int((yEnd-(y+5))*0.75)-int((yEnd-(y+5))*0.25)-2))
pygame.draw.polygon(topTriangle, colorDark, ((1,0), (topTriangle.get_width()-2,0), (int((xEnd-(x+7))/2),(int((yEnd-(y+7))/2)-1))))
pygame.draw.polygon(leftTriangle, colorDark, ((0,1), (0,leftTriangle.get_height()-2), (int((xEnd-(x+7))/2)-1,(int((yEnd-(y+7))/2)))))
pygame.draw.polygon(bottomTriangle, colorDark, ((1,yEnd-(y+6)-int((yEnd-(y+5))*0.75)), (bottomTriangle.get_width()-2,yEnd-(y+6)-int((yEnd-(y+5))*0.75)), (int((xEnd-(x+7))/2),(int((yEnd-(y+7))/2)+1-(yEnd-(y+5))*0.75))))
pygame.draw.polygon(rightTriangle, colorDark, ((xEnd-(x+6)-int((xEnd-(x+5))*0.75),1), (xEnd-(x+6)-int((xEnd-(x+5))*0.75),rightTriangle.get_height()-2), (int((xEnd-(x+7))/2)+1-int((xEnd-(x+5))*0.75),(int((yEnd-(y+7))/2)))))
screen.blit(selectedSquare, (0,0))
if collisionTop_mask.overlap(mouse_mask,(mouse_rect.left-collisionTop_rect.left,mouse_rect.top-collisionTop_rect.top)) != None:
print('detect')
but i have also seen this methode:
if topTriangle.get_rect().collidepoint(pygame.mouse.get_pos()):
the problem is that this only detects squares and I need to detect a triangle. could someone pleas help me with this?
You could do something like this I think (didn't test):
posx, posy = pygame.mouse.get_pos()
posx -= XCoordinateOfTopTriangleOnScreen
posy -= YCoordinateOfTopTriangleOnScreen
if topTriangle.get_at((posx,posy)) == colorDark:
print('detect')