I want two different types of nodes to fade there alpha to 0 one after the other (with a sequence). However to find the nodes to fade, I'm using EnumerateChildNodeWithName and there is two of them, so I'm unable (I think) to use a sequence because I would have to use the sequence outside the EnumerateChildNodeWithName (because there is two of them) and at that point, I lose control of the nodes.
Not sure if this makes sense, but here is my code (this fades both types of nodes are the same time):
nodeMovingPlatform1.enumerateChildNodesWithName("*") {
node, stop in
if node.position.x + nodeMovingPlatform1.position.x > self.frame.size.width/2 + node.frame.size.width/2 {
node.removeFromParent()
} else {
if node.name == "landscapeTrigger" {
node.name = "landscape"
node.runAction(actionFadeAlphaTo0_3)
}
}
}
nodeMovingPlatform2.enumerateChildNodesWithName("*") {
node, stop in
if node.position.x + nodeMovingPlatform2.position.x > self.frame.size.width/2 + node.frame.size.width/2 {
node.removeFromParent()
} else {
if node.name == "landscapeTrigger" {
node.name = "landscape"
node.runAction(actionFadeAlphaTo0_3)
}
}
}
The solution to this would be to make use of SKAction Sequences, it only runs the second action once the first action is completed.
From the Apple Documentation
A sequence is a set of actions that run consecutively. When a node
runs a sequence, the actions are triggered in consecutive order. When
one action completes, the next action starts immediately. When the
last action in the sequence completes, the sequence action also
completes.
Related
I'm creating a game in xcode. The winner will have a negative score or 0. Everything went well but now I want those negative points to be added to his/hers opponent. I used this code:
while (activePlayer.score < 0) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.activePlayer.score += 1
self.activePlayer.scoreLabel.text = String (self.activePlayer.score)
self.notActivePlayer.score += 1
self.notActivePlayer.scoreLabel.text = String (self.notActivePlayer.score)
}
}
When I now run the Simulator it freezes when it comes to this part. The worst thing is my whole iMac freezes. It's becoming so extremely slow I have to wait like 10 minutes to close the simulator and getting some speed back.
My simple conclusion is this code is wrong. But why?
I want to player to see the score change that's why the label text will be updated after every point added to the score.
Your while loop is a very bad idea there. What do you expect it to do? On first iteration you schedule an asnyc task, then the iteration is complete and the next task is scheduled, etc.
You will have a couple of thousand async task scheduled in the first split second the loop is running.
If you want to animate the change you should do it by scheduling the next task after the first finished. The following is a general way of doing that, I have not run it in Playground:
func schedule() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.activePlayer.score += 1
self.activePlayer.scoreLabel.text = String (self.activePlayer.score)
self.notActivePlayer.score += 1
self.notActivePlayer.scoreLabel.text = String (self.notActivePlayer.score)
if (activePlayer.score < 0) {
self.schedule()
}
}
}
As Whirlwind correctly pointed out using dispatch in sprite-kit makes you break out of the present game-loop. You can do the same thing via SKActions and repeatedly create new actions after one completed.
I'm doing some death animations for a game, and wanted to ask for some help. I want my monster to disappear in a puff of smoke, but not before it animates a slash effect going across his body.
I have 3 animations that I want to use:
weaponSlash - a line that draws across the monster. Looks like you slashed him with a sword.
smoke - a puff of smoke that slowly expands out
monsterFalling - the monster falls back, startled
What I want to do is play it in this order:
Simultaneously, the slash appears & the monster starts to fall back
About 0.25s into the above animation, I want the cloud to start to appear
When the cloud is about to end (so maybe after 1s) I want the monster to disappear
Remove the smoke, the monster, the sword, etc, and drop some coins on the ground
I started like this, as a test that works somewhat: (ignore the above times for now)
//Cancel any current actions, like a monster attacking
monster.removeAllActions()
//since you can't play 3 animations on one node at the same time, you have to create 3 separate nodes for everything
let slash = SKSpriteNode()
let cloud = SKSpriteNode()
cloud.size = monster.size
slash.size = monster.size
monster.addChild(cloud)
monster.addChild(slash)
//Start the slash animation
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
//Start the cloud animation (how I get it is elsewhere and not relevant)
cloud.run(cloudAnimation)
//Run the monster death animation, followed by the cleanup/coin dropping
monster.run(SKAction.sequence([monster.deathAnimation(), SKAction.wait(forDuration: 1), postDeathActions]))
The variable PostDeathActions above simply removes the monster node and animates some coins falling.
WHERE I NEED SOME HELP
So the above code doesn't work so great in that the animations all run independently of each other. Based on this, you can see how regardless of whether the slash/cloud finish, the monster will run two actions: him falling back, followed by cleanup, which just removes the monster and spawns the coins. As you can see I tried to delay this by adding a 1s delay but this is all somewhat of a hack since I may have different monsters or attacks, etc, that are faster/slower. I'd rather guarantee that everything finishes before I despawn the monster.
I tried to group this into an SKAction.Run like so:
let preDeath = SKAction.run {
[unowned self] in
monster.run(monster.deathAnimation()
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
cloud.run(cloudAnimation)
}
but this runs everything at the same time again.
What I want to do is sequence it like this (pseudo code):
let preDeathAnimations = SKAction.Group([slash, cloud, monsterDeathAnimation])
])
SKAction.sequence([preDeathAnimations, postDeathActions])
So this way it'll run all 3 before running cleanup.
Is there a way to do something like this? I know Sequnce/Group need to be run against an SKNode, but I don't have 3 separate ones.
Thanks for your time reading this and any advice you can offer!
This is one idea that I had, but you could use threading + state + onCompletion blocks to take the math out of it. I didn't test it out fully but this general concept should work:
let slash = SKAction.fadeIn(withDuration: 0.5)
let fall = SKAction.fadeOut(withDuration: 0.25)
let puff = SKAction.fadeIn(withDuration: 0.1)
// Put in ALL of the actions from ALL parties that you want to happen prior to puff:
func findLongestTime(from actions: [SKAction]) -> TimeInterval {
var longestTime = TimeInterval(0)
for action in actions {
if action.duration > longestTime { longestTime = action.duration }
}
// Note, if you put a sequence into this function I don't know if it will work right..
// Might need another func like `findDurationOfSequence(_ sequence: SKAction) -> TimeInterval
return longestTime
}
// Note, if you have the monster doing more than falling prior to puff, then you will
// need to subtract those as well:
let monsterActionsPriorToPuff = [fall]
// Add the duration of all monster animations prior to puff:
var MAPTP_duration = TimeInterval(0)
for action in monsterActionsPriorToPuff {
MAPTP_duration += action.duration
}
// Calculate our final wait time, with no negative numbers:
var waitTime = findLongestTime(from: [slash, fall]) - MAPTP_duration
if waitTime < 0 { waitTime = 0 }
let wait = SKAction.wait(forDuration: waitTime)
// Our monster sequence (I forgot to add the disappear, just add after puff)
let monsterSequence = SKAction.sequence([fall, wait, puff])
// Player slashes:
SKSpriteNode().run(slash)
// Monster will wait 0.25 seconds after falling,
// for slash to finish before puffing:
SKSpriteNode().run(monsterSequence)
et me know if this idea isn't working I can try updating it.
Context
I am making a mobile game in which the player is required to touch objects in a specified order. The correct order is determined in a List called clickOrder. To determine the current object the player is supposed to click, currClickIndex is used.
Problem
When touching a correct object, the debug text will display "Correct" for a split second, and will then immediately change to "Wrong." What I am unsure about is why both the if and else blocks are executed when only touching a single object.
Code
void Update()
{
if (Input.touchCount == 1)
{
if (this.enabled)
{
Vector2 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero);
if (hit != null && hit.collider != null)
{
// check if the touched object is the correct one
if (hit.collider.gameObject == clickOrder[MyData.currClickIndex])
{
debug.text = "Correct";
MyData.currClickIndex++;
}
else
{
debug.text = "Wrong";
}
}
}
}
}
As soon as the correct object is being touched, you do this:
MyData.currClickIndex++;
which moves you forward in the ordered sequence, and from then on, the previously correct object is not correct anymore. But you're still touching it.
If you want to avoid this, you need to move forward in the sequence after you've touched the correct object.
if (there are touches and the correct object is being touched)
{
set a flag;
}
else if (a flag has been set)
{
MyData.currClickIndex++;
reset the flag;
}
Once a node is removed, I'm needing to respawn it back to it's original position.
I've tried a few things:
1) In the didBeginContact where it gets deleted, I've just added an addChild() after the delete, but that just added it back where it was, making it look like it just paused in place
2) When it dies, I'm setting a variable to 0, and I created a function that adds the node back where it starts out, and then I set up an if statement that looks like :
if playerLife == 0 {
spawnPlayer()
}
and the player life gets set to 0 down in my didBeginContact. Don't know if that even makes sense
3) I tried slapping all of this down in the didBeginContact
The ball is either freezing when I try to addchild directly back right after I delete it in the DidBegin, otherwise it gets deleted and never is respawned.
Try moving your code inside of the update function. also in your spawnPlayer function make sure to add your node if node.parent == nil
override func update(currentTime: NSTimeInterval) {
if playerLife == 0 {
spawnPlayer()
}
}
I'm trying to remove some nodes from a parent node once they reach a certain x position. The problem I have is that the parent node is changing x position, but the children are not changing x position inside the parent (but are obviously moving with the parent), so when I put in if node.position.x < 300 . . . (remove node), nothing happens. I tried the below code, but this only work one time and then doesn't remove nodes again, which I'm not 100% sure why it stops working.
func cleanUp() {
let positionX = nodeBase.position.x
nodeBase.enumerateChildNodesWithName("segment", usingBlock: {
node, stop in
if node.position.x - positionX < 300 {
node.removeFromParent()
}
})
}
Can anybody see where I am going wrong with my code, or can you point me in the right direction?
Try the following:
nodeBase.enumerateChildNodesWithName("segment") { node, _ in
if !self.intersectsNode(node) {
node.removeFromParent()
}
}
intersectsNode returns true whilst the node is inside the bounds of SKScene. Therefore, when intersectsNode returns false you know the node is offscreen and you can remove the node.