RayCast Weapon Collision is not quite working properly - collision

I'm currently attempting to make an FPS in Godot Engine (Using GDscript), and I decided to use a raycast weapon because it seemed easier to track. However, when I check the collision and tell the enemy to queue_free(), it won't work. Hence why I cam to stack overflow, because it always has the answers.
PS, I'm using the print function to test firing because I don't have proper models yet, just a cylinder a box and some ground.
extends Node
class_name Weapon
export var FireRate = 0.5
export var ClipSize = 6
export var Reload = 2
onready var raycast = $"../Head/Camera/RayCast"
var reloading = true
var currentammo = ClipSize
var can_fire = true
func _process(delta):
if (Input.is_action_just_pressed("LMB") and can_fire):
if (currentammo > 0):
reloading = false
print ("fired weapon")
can_fire = false
currentammo -= 1
check_collision()
yield(get_tree().create_timer(FireRate), "timeout")
can_fire = true
elif not reloading:
reloading = true
print ("Out of Ammo!")
print ("Reloading...")
yield(get_tree().create_timer(Reload), "timeout")
currentammo = ClipSize
print ("Reload Complete!")
func check_collision():
if raycast.is_colliding():
var collider = raycast.get_collider()
if collider.is_in_group("Enemies"):
collider.queue_free()
print("Killed " + collider.name)

It is kind of hard to tell from the information you have given, but my best guess is that the root node for your enemy scene is not the KinematicBody or RigidBody that is being returned by get_collider(). When using the get_collider() method it will return the physics body that it is colliding with. If you are checking the "Enemies" group for a physics body instead of the instance of the enemy, then it would return false. However, if my guess is wrong and the physics body is the root node, then I would suggest you add more context to your question.

Related

How do I skip to a position in the animation with an animationplayer in Godot?

I'm making a game in godot and have to store the animation position because the animation gets suspended by another one. I want to continue the animation where it was before and that's why I have to store the animation position and then set it to the stored value.
I've tried setting it (didn't work) and in the documentation and other places in the internet I haven't found anything helpful.
This is the script on it:
extends KinematicBody2D
onready var animation_player = $AnimationPlayer
var hurt_anim_playing: bool = false
var hurt_anim_progress: float = 0
func _ready():
animation_player.play("idle")
pass
func _physics_process(delta):
# for each touching heart, get hurt
for body in hitbox.get_overlapping_bodies():
if body.has_method("heart"):
G.health -= 1
hurt_anim_playing = true
hurt_anim_progress = animation_player.current_animation_position
animation_player.play("hurt")
update_sprite()
body.queue_free()
func die():
dieLayer.visible = true
get_tree().paused = true
func update_sprite():
sprite.frame = G.max_health - G.health
if G.health == 0:
die()
func _on_AnimationPlayer_animation_finished(anim_name):
if anim_name == "hurt":
hurt_anim_playing = false
animation_player.play("idle")
animation_player.current_animation_position = hurt_anim_progress
Actually I wanted to set the animation position and let the animation continue where it stopped, but instead I got an error
The problem is that current_animation_position has only a getter, not a setter:
https://docs.godotengine.org/en/3.1/classes/class_animationplayer.html#class-animationplayer-property-current-animation-position
To set the animation to a specific point, you can use advance(float delta), if you want to handle everything between start and the resuming point or seek(float seconds, bool update=false), if you just want to jump to the new position. You may test out, if you need to set update to true. The documentation says "If update is true, the animation updates too, otherwise it updates at process time."
Your code would simply look like this:
func _physics_process(delta):
# for each touching heart, get hurt
for body in hitbox.get_overlapping_bodies():
if body.has_method("heart"):
G.health -= 1
hurt_anim_playing = true
hurt_anim_progress = animation_player.current_animation_position
animation_player.play("hurt")
update_sprite()
body.queue_free()
func _on_AnimationPlayer_animation_finished(anim_name):
if anim_name == "hurt":
hurt_anim_playing = false
animation_player.play("idle")
animation_player.seek(hurt_anim_progress) #maybe (hurt_anim_progress, true)

Godot jumping animation just plays the first frame

I have a script in my simple platformer game which says that if my player is in the ground and "Z" is pressed, his movement in the Y axis is going to go up to 600, and if he's not in the ground, he's going to perform the jump animation.
So here's the thing, I know it plays only the first frame of the jumping animation because the code is constantly detecting that the player is in the air. I want a way to tell the code to trigger the animation only once.
I tried using a function called input_(event): but it seems that it doesn't have a is_action_just_pressed type of Input, just is_action_pressed.
I'm fairly new to Godot and don't know how to use signals. Signals might help via animation_finished(), although that function might have nothing to do with what I actually want to do in my code.
Here's my code:
extends KinematicBody2D
#Variables van aquĆ­
var movimiento = Vector2();
var gravedad = 20;
var arriba = Vector2(0, -1);
var velocidadMax = 600;
var fuerza_salto = -600;
var aceleracion = 5;
var saltando = false;
func _ready(): # Esto es void Start()
pass;
func _physics_process(delta): #Esto es void Update()
movimiento.y += gravedad;
if Input.is_action_pressed("ui_right"):
$SonicSprite.flip_h = true;
$SonicSprite.play("Walk");
movimiento.x = min(movimiento.x +aceleracion, velocidadMax);
elif Input.is_action_pressed("ui_left"):
$SonicSprite.flip_h = false;
$SonicSprite.play("Walk");
movimiento.x = max(movimiento.x-aceleracion, -velocidadMax);
else:
movimiento.x = lerp(movimiento.x, 0, 0.09);
$SonicSprite.play("Idle");
if is_on_floor():
if Input.is_action_just_pressed("z"):
movimiento.y = fuerza_salto;
else:
$SonicSprite.play("Jump");
movimiento = move_and_slide(movimiento, arriba)
I had the smae problem, and it was solved for me adding the next after move_and_slide():
if velocity.y == 0:
velocity.y = 10
Apparently, if velocity is 0 after move_and_slide() it does not detect is_on_floor() anymore (bug?) so i added small velocity in direction of gravity.
About usinginput_(event), you do not need just_pressed because it only process when there is an input.. you can do somethin like this:
func _input(event):
if event.is_action_pressed("ui_up") and is_on_floor():
velocity.y = jump_speed
What i called velocity, i think in your script is called movimiento. Also, i think you are mixing gravities and velocities in movimiento. I'm sharing you my character scrpit so you can compare and see if it works better:
extends KinematicBody2D
var Bullet = preload("res://Bullet.tscn")
var speed = 200
var jump_speed = -300
var shot_speed = 100
var velocity = Vector2()
var grav = 980
var shooting = false
func _ready():
$AnimatedSprite.play()
$AnimatedSprite.connect("animation_finished",self,"on_animation_finished")
func _input(event):
if event.is_action_pressed("ui_up") and is_on_floor():
velocity.y = jump_speed
if event.is_action_pressed("shoot") and !shooting:
shooting = true
shot_speed = 20
velocity.y = -200
fire_weapon()
func _physics_process(delta):
velocity.x = 0
if Input.is_action_pressed("ui_right"):
velocity.x += speed
if Input.is_action_pressed("ui_left"):
velocity.x -= speed
if velocity.length() > 0:
if velocity.x < 0:
$AnimatedSprite.flip_v = true
$AnimatedSprite.rotation_degrees = 180
elif velocity.x > 0:
$AnimatedSprite.flip_v = false
$AnimatedSprite.rotation_degrees = 0
if shooting:
$AnimatedSprite.animation = "shot"
velocity.x = -shot_speed * cos($AnimatedSprite.rotation_degrees)
shot_speed *= 0.98
else:
if is_on_floor():
if velocity.x == 0:
$AnimatedSprite.animation = "idle"
else:
$AnimatedSprite.animation = "moving"
else:
$AnimatedSprite.animation = "jumping"
velocity.y += grav * delta
velocity = move_and_slide(velocity, Vector2(0,-1))
if velocity.y == 0:
velocity.y = 10
func on_animation_finished():
if $AnimatedSprite.animation == "shot":
shooting = false
func fire_weapon():
var bullet = Bullet.instance()
get_parent().add_child(bullet)
if $AnimatedSprite.flip_v :
bullet.position = $ShotLeft.global_position
else:
bullet.position = $ShotRight.global_position
bullet.rotation_degrees = $AnimatedSprite.rotation_degrees
bullet.linear_velocity = Vector2(1500 * cos(bullet.rotation),1500*sin(bullet.rotation))
Notice that i do not use gravity as a velocity or moviemiento; instead y multiply it by delta for getting velocity, as acceleration x time gives velocity.
I hope this helps.
This kind of problem can be solved using a Finite State Machine to manage your character controls and behaviour. You would play the Jump animation on entering the Jumping state from the Walking state. Utilizing correct design patterns early prevents spaghetti code in the future.
This is not a Godot specific solution, but definitely one worth your attention.
Let's dissect what's happening here, you fire up the code and ask your player to play the animation in one frame and then again in the other and in the next as long as the correct key is pressed or you in the correct state.
What the problem, the problem is that at every call of the function play it fires the animation again and again so it just restarts or another call runs separately and this causes unexpected behaviour.
The better way will be to manage the state of the player and animation player simultaneously and using that to perform animation calls.
enum State { IDLE, WALK, RUN, JUMP, INAIR, GROUNDED } ## This is to manage the player state
var my_state = State.IDLE ## Start the player with the idle state
As for the animation player state use the signal that you were talking about using the GUI or with code like below.
get_node("Animation Player").connect("animation_finished", this, "method_name")
## Here I assume that you have the Animation Player as the Child of the Node you the script on
And also hold a boolean variable to tell if the animation is playing or not.
if ( animation_not_playing and (case)):
animation_player.play("animation")
Turn it true or false as per your liking. Based on the animation finished signal.
In future, you might want to consider even using a simple FSM to maintain all this state data and variables.

Easeljs keep object within bounds

I am working on a drawing application in HTML5 canvas, using Easeljs. So far I am able to drag and drop objects into the field, but I only want them within certain bounds.
To illustrate:
Objects 1, 2, 4 and 5 should get deleted, but object 3 should be kept.
I have tried using hitTest(), but that didn't work properly (I probably did something wrong). I would love to post the code I used, but my PC froze while working on it... Thought I'd better ask while unfreezing, haha.
Here is a quick drag and drop sample with bounds constraining:
http://jsfiddle.net/xrqatyLs/8/
The secret sauce is just in constraining the drag position to your own values.
clip.x = Math.max(bounds.x, Math.min(bounds.x + bounds.width - clipWidth, evt.stageX));
clip.y = Math.max(bounds.y, Math.min(bounds.y + bounds.height-clipHeight, evt.stageY));
Here is another more complex example that manually checks if one rectangle intersects another rectangle:
http://jsfiddle.net/lannymcnie/yvfLwdzn/
Hope that helps!
Solution:
var obj1 = obj.getBounds().clone(); // obj = a pylon
var e = obj1.getTransformedBounds();
var obj2 = bg.getBounds().clone(); // bg = the big green field
var f = obj2.getTransformedBounds();
if(e.x < f.x || e.x + e.width > f.x + f.width) return false;
if(e.y < f.y || e.y + e.height > f.y + f.height) return false;
return true;
After all it's so simple, but I guess I was working on it for so long that I started to think too hard...

GDScript: How to play an animation while key is preessed?

I am very new to coding and I'm still trying different languages out, I started off with GameMaker Studio and changed to Godot due to its compatibility with Mac I might as well learn something newer since GameMaker has been out for quite some time.
I want to create a RPG game and apply animation to each direction the character moves but the animation only plays after the key is pressed AND lifted. This means that while my key is pressed, the animation stops, and the animation only plays while my character is standing still, which is the complete opposite of what I want. The script looked really straight forward, but doesn't seem to be working.
I would tag this as the GDScript language instead of Python, but I guess I'm not reputable enough to make a new tag, so I tagged it under python because it is the most similar.
#variables
extends KinematicBody2D
const spd = 100
var direction = Vector2()
var anim_player = null
func _ready():
set_fixed_process(true)
anim_player = get_node("move/ani_move")
#movement and sprite change
func _fixed_process(delta):
if (Input.is_action_pressed("ui_left")) :
direction.x = -spd
anim_player.play("ani_player_left")
elif (Input.is_action_pressed("ui_right")):
direction.x = spd
anim_player.play("ani_player_right")
else:
direction.x = 0
if (Input.is_action_pressed("ui_up")) :
direction.y = -spd
anim_player.play("ani_player_up")
elif (Input.is_action_pressed("ui_down")):
direction.y = (spd)
anim_player.play("ani_player_down")
else:
direction.y = 0
if (Input.is_action_pressed("ui_right")) and (Input.is_action_pressed("ui_left")):
direction.x = 0
if (Input.is_action_pressed("ui_up")) and (Input.is_action_pressed("ui_down")) :
direction.y = 0
# move
var motion = direction * delta
move(motion)
As you check the input in _fixed_process, you call anim_player.play() several times a frame, which always seems to restart the animation, and thus, keeps the very first frame of the animation visible all the time.
As soon as you release the key, anim_player.play() stops resetting the animation back to start, and it can actually proceed to play the following frames.
A simple straight-forward solution would be to remember the last animation you played, and only call play() as soon as it changes.
You need to know if the animation has changed
First you need to put these variables in your code:
var currentAnim = ""
var newAnim = ""
And then you add this in your _fixed process:
if newAnim != anim:
anim = newAnim
anim_player.play(newAnim)
To change the animation you use:
newAnim = "new animation here"

SceneKit - Stop continuously looping Collada animation

I am using SceneKit to load a Collada (.dae) file.
Whatever I have tried, the animation repeats in a continuous loop whereas I only want it play once and then stop.
Here's the loading code where sceneView is my SCNView:
//Load the scene and play
let scene = SCNScene(named: "Scenes.scnassets/test4.dae")
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
The scene loads correctly and the animation plays on loading as required, but in a continuous loop.
I have tried removing all animations as follows:
sceneView.scene?.rootNode.removeAllAnimations()
but this seems to have no effect.
I have also tried retrieving all animations and setting the repeatCount = 1 or even to 0
let url = NSBundle.mainBundle().URLForResource("Scenes.scnassets/test4", withExtension: "dae")
let sceneSource = SCNSceneSource(URL:url!, options: nil)
let animationIndentifiers = sceneSource?.identifiersOfEntriesWithClass(CAAnimation)
if let ids = animationIndentifiers
{
for id in ids {
let animation: CAAnimation = sceneSource!.entryWithIdentifier(id as! String, withClass: CAAnimation.self)! as! CAAnimation
animation.repeatCount = 1
}
}
I have also tried this another way:
let keys = sceneView.scene!.rootNode.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = sceneView.scene?.rootNode.animationForKey(key as! String)
animation?.repeatCount = 1
}
}
but again no luck.
I can find nothing in the Collada file itself that is obviously causing the animation to repeat. However, I notice that the .dae file icon on my Mac disk has a play button and clicking this also plays the animation within the icon on a continuous loop.
Update:
I now notice that in the code above I am setting the constant 'animation' attributes and this is not copied back to the actual scene nodes. Also, the only animation in the .dae file is in a child node. So here's my next attempt:
//Get the child nodes
let children = scene?.rootNode.childNodes
//Initialise a childNode counter
var childCount = 0
//Iterate through the child nodes
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
//Iterate through the animations
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
println("Initial Repeat count: \(animation.repeatCount)")
animation.repeatCount = 1
//Remove existing animation
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
//Add amended animation
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
There is actually only one animation attached to one child node. (I have also tried setting this using the the actual animation id string in place of 'key' above.)
The above shows Initial Repeat count: inf
and on checking afterwards it is indeed set to 1.
However, the animation still runs in an infinite loop :-(
Any help to resolve this would be much appreciated.
Further update
I have now created a new Collada file with simple animation using Maya and for some reason one of the trials attempted above actually works:
func sceneSetup() {
let scene = SCNScene(named: "Scenes.scnassets/test10.dae")
let children = scene?.rootNode.childNodes
var childCount = 0
if let theChildren = children {
for child in theChildren {
let keys = child.animationKeys() //get all the animation keys
if let theKeys = keys {
for key in theKeys {
let animation = child.animationForKey(key as! String)
animation.repeatCount = 1
scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
}
}
childCount++
}
}
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
}
if anybody can explain that would be great!
Ah, but there's another problem!
Here's the start frame of the animation:
and here's the end frame where we want to end up:
But at the end of the animation the scene jumps back to the start view.
I have 'fixed' this by amending the animation so that frame 1 is copy of the final frame. This works and isn't noticeable but doesn't seem a very elegant solution.
For the animation jumping back to the first frame, the isAppliedOnCompletion value is what you are looking for.
animation.repeatCount = 1
animation.isAppliedOnCompletion = true
This will make sure that the animation pauses on the final frame.
I know this is an old question, but I came across this same problem and found a solution for the jumping back to the beginning issue. If you set the animation to not be removed on completion, the object should stay at the end location:
if let animation = child.animationForKey(child.animationKeys.first!) {
animation.repeatCount = 1
animation.removedOnCompletion = false
...
This works for me. It's based off your answer but will go through the node's entire tree and limit all of their animation counts to one.
Personally I found that if I missed any of the descendant nodes when manually traversing and setting the node animation counts to one, I would have a problem. This ensures that the node passed in itself, and all child nodes will only animate once, and then hold the model in place after finishing.
Use like this animateEntireNodeTreeOnce(mostRootNode: nodeYouImportedFromCollada).
func animateEntireNodeTreeOnce(mostRootNode node: SCNNode){
onlyAnimateThisNodeOnce(node)
for childNode in node.childNodes {
animateEntireNodeTreeOnce(mostRootNode: childNode)
}
}
func onlyAnimateThisNodeOnce(_ node: SCNNode) {
if node.animationKeys.count > 0 {
for key in node.animationKeys {
let animation = node.animation(forKey: key)!
animation.repeatCount = 1
animation.isRemovedOnCompletion = false
node.removeAllAnimations()
node.addAnimation(animation, forKey: key)
}
}
}
I Agree with #Phil Dudas and I like to add also this workaround to Prevent The Animation from Start By Default By Reducing Speed to Zero, When you set Speed to Zero it will remain at First state,
nodeAnimation.speed = 0
animation.repeatCount = 1
nodeAnimation.animation.isRemovedOnCompletion = false

Resources