Determine When SpriteNode Is Landed On A Rectangle SpriteNode - xcode

I am making a game where there are clouds moving, and I want it so the clouds will fade away when the character lands on it. However, when I put the code, it fades away if the character goes around it and hits the bottom or the side of the cloud while it is still falling. Here is the code I have for detecting when the character and cloud have hit.
Is there anyway to determine when the character has landed on top of the cloud so it does not fade the cloud away if it hits it from the bottom or side while it is falling?
Here is code for declaring the objects:
Person.physicsBody?.usesPreciseCollisionDetection = true
Person.size = CGSizeMake(self.frame.size.width / 25, self.frame.size.height / 16.25)
Person.physicsBody = SKPhysicsBody(rectangleOfSize: Person.size)
Person.physicsBody?.restitution = 0
Person.physicsBody?.friction = 0
Person.physicsBody?.allowsRotation = false
Person.physicsBody?.affectedByGravity = true
Person.physicsBody?.dynamic = true
Person.physicsBody?.linearDamping = 0
Person.zPosition = 5
Person.physicsBody?.categoryBitMask = BodyType.PersonCategory.rawValue
Person.physicsBody?.contactTestBitMask = BodyType.CloudCategory.rawValue
Person.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) * 1.7)
self.addChild(Person)
Cloud = SKSpriteNode(texture: NormalCloudTexture)
Cloud.zPosition = 7
Cloud.physicsBody?.usesPreciseCollisionDetection = true
Cloud.physicsBody?.affectedByGravity = false
Cloud.physicsBody?.allowsRotation = false
Cloud.size = CGSizeMake(self.frame.size.width / 8.05, self.frame.size.height / 40)
Cloud.physicsBody = SKPhysicsBody(rectangleOfSize: Cloud.size)
Cloud.physicsBody?.friction = 0
Cloud.physicsBody?.restitution = 0
Cloud.physicsBody?.dynamic = false
Cloud.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) / 7.60)
addChild(Cloud)
Here is code for when the objects have hit:
func didBeginContact(contact: SKPhysicsContact)
{
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(contactMask)
{
case BodyType.PersonCategory.rawValue | BodyType.CloudCategory.rawValue:
JumpContact = true
let CheckDelay = delay(0.055)
{
//cloud fades away here
}

I have not done it, but i have an idea. :)
I think you should do:
Track contact position with contactPoint property and then check if it is not touching x position of the cloud lower then few points from the top.
I hope it helps you. :)

Thank you for your recommendation! I decided to fool around, here is what you do guys!
//put like a few millisecond delay here, forgot it
if (Person.physicsBody?.velocity.dy == 0){//this means the person has stopped moving since it landed
//cloud fades away
}

Related

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.

line spacing in dynamically created swift 3/xcode labels

I'm having an issue where I am getting a list of skills back from an api and I want them to stack one on top of the other in two different sections, a left column and a right column. It works well but if the skill is longer than the width of the label it drops to a new line with the same spacing as the rest of the labels. The skill Adobe Creative Suite looks like Adobe Creative as one and Suite as another. I would like Suite to be underneath Adobe Creative but much closer so you can tell it's all one skill.
My code is here:
lblLeft.text = ""
lblRight.text = ""
if let expertiseCount = helper.expertise {
for i in 0..<expertiseCount.count {
if i % 2 == 0 {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
let attrString = NSMutableAttributedString(string: lblLeft.text! + "\(expertiseCount[i].name ?? "")\n")
attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range: NSMakeRange(0, attrString.length))
lblLeft.attributedText = attrString
} else {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
let attrString = NSMutableAttributedString(string: lblRight.text! + "\(expertiseCount[i].name ?? "")\n")
attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range: NSMakeRange(0, attrString.length))
lblRight.attributedText = attrString
}
}
}
I've already tried line spacing and that just changes the size between all lines so the space between Adobe Creative and Suite takes on that change as well.
Try:
lblLeft.numberOfLines = 0
lblLeft.lineBreakMode = .byWordWrapping
lblLeft.sizeToFit()
By setting number of lines to zero and turning word wrapping on, the label will grow to the required number of lines. sizeToFit() should size it properly.

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...

How would I get my background image to repeat forever in Swift?

I have these two images that are the same image and I want them to repeat so it looks like the background is moving. I have this code but only one of the background images goes through then it goes back to the grey screen. The other background image doesn't follow the first one. How do I fix that? Thanks! (This is in swift xcode)
func spawnBackground() {
let cloud = SKSpriteNode(imageNamed: "CloudBg")
let cloud2 = SKSpriteNode(imageNamed: "CloudBg2")
cloud.position = CGPointMake(frame.size.width + cloud.frame.size.width / 2,cloud.frame.height / 2)
cloud.setScale(1.35)
cloud.zPosition = -15
cloud2.position = CGPointMake(self.size.width/2, self.size.height/2)
cloud2.setScale(1.35)
cloud.zPosition = -15
let moveTop = SKAction.moveByX(0, y: cloud.size.height, duration: NSTimeInterval(CGFloat(gameSpeed) * cloud.size.height))
cloud.runAction(SKAction.repeatActionForever(moveTop))
cloud2.runAction(SKAction.repeatActionForever(moveTop))
addChild(cloud)
addChild(cloud2)
}
I think you are looking for an action sequence like this:
let cloud = SKSpriteNode(imageNamed: "CloudBg")
let cloud2 = SKSpriteNode(imageNamed: "CloudBg2")
cloud.position = CGPointMake(frame.size.width + cloud.frame.size.width / 2,cloud.frame.height / 2)
cloud.setScale(1.35)
cloud.zPosition = -15
cloud2.position = CGPointMake(self.size.width/2, self.size.height/2)
cloud2.setScale(1.35)
cloud.zPosition = -15
var actionone = SKAction.moveToX(0, y: cloud.size.height, duration: NSTimeInterval(CGFloat(gameSpeed) * cloud.size.height))
var actiontwo = SKAction.runBlock({
cloud.position = CGPointMake(frame.size.width + cloud.frame.size.width / 2,cloud.frame.height / 2)
})
cloud.runAction(SKAction.repeatForever(SKAction.sequence([actionone,actiontwo])))
Of course you will have to edit this code to work where when one cloud reaches a certain point the other one begins, but that is the general idea.

Sprite Kit Swift Jumping Character

Right now, I am trying to use impulses/forces to make my character jump. I was using gravity so it looks like they are jumping, but the sizes of the jumps were different overtime. Here is my code for attempting to make the character jump, but when I do it, it does not work.
Here is the jumping code I have right now:
func Bigpersonjump () {
BigPerson.physicsBody?.velocity = CGVectorMake(0.0, 0.0)
BigPerson.physicsBody?.applyImpulse(CGVectorMake(0.0, 10.0))
println("Detected")
}
Here is the code for spawning the character:
self.BigPerson = SKSpriteNode(imageNamed: "1")
//self.BigPerson = SKSpriteNode(texture: BigPersonTexture)
self.BigPerson.size.height = self.size.height / 8
self.BigPerson.size.width = self.size.width / 13
self.BigPerson.position = CGPointMake(CGRectGetMidX(self.frame) / 2 , self.frame.size.height / 2.2)
self.BigPerson.zPosition = 9
BigPerson.physicsBody = SKPhysicsBody(rectangleOfSize: BigPerson.size)
BigPerson.physicsBody?.usesPreciseCollisionDetection = true
BigPerson.physicsBody?.dynamic = false
BigPerson.physicsBody?.categoryBitMask = BodyType.BigPersonCategory.rawValue
BigPerson.physicsBody?.allowsRotation = false
BigPerson.physicsBody?.collisionBitMask = BodyType.ScoreCategory.rawValue
BigPerson.physicsBody?.contactTestBitMask = BodyType.ScoreCategory.rawValue
var distanceToMove = CGFloat(self.frame.size.width + 2.0 * BigPerson.size.width)
var moveBigPerson = SKAction.moveByX(distanceToMove * 5.3, y: 0.0, duration: NSTimeInterval (0.02 * distanceToMove))
BigPerson.runAction(moveBigPerson)
BigPerson.runAction(LeftSideRunning)
self.addChild(self.BigPerson)
For realistic jump you need to use only applyImpulse, not SKActions (e.g. on user's tap).
Physics body should be dynamic (in your code it is false).
Check different values for impulse value, because you body may be too heavy.
Also please check friction value for both body and surface (as I see in your code you are trying to move it horizontally).
Also try different directions, may be you are trying to jump in the wrong direction.

Resources