I am creating a game (space shooter) using SpriteKit. Currently adding animations to the game. When explosions spawn, the animation loops at an X number of times (its really random). Sometimes it will loop 3 times and sometimes up to 10 times. The screen ends up being filled up with meaningless explosion animations.
I used to have a simple fade in/fade out animation which was working fine, but have finally upgraded to something smoother. I introduced a for loop and it has given me this issue. i also tried using a while loop with no avail. I have tried using the animation without a sequence but that doesn't fix anything either.
func spawnExplosion(spawnPosition: CGPoint) {
var explosion = SKSpriteNode()
textureAtlas = SKTextureAtlas(named: "explosion")
for i in 1...textureAtlas.textureNames.count {
let name = "explosion\(i).png"
textureArray.append(SKTexture(imageNamed: name))
print(i)
}
explosion = SKSpriteNode(imageNamed: "explosion1.png" )
explosion.setScale(0.6)
explosion.position = spawnPosition
explosion.zPosition = 3
self.addChild(explosion)
print(textureAtlas.textureNames.count)
//explosion animation-action
let explosionAnimation = SKAction.repeat(SKAction.animate(with: textureArray, timePerFrame: 0.05), count: 1)
let delete = SKAction.removeFromParent()
let explosionSequence = SKAction.sequence([explosionSound, explosionAnimation, delete])
explosion.run(explosionSequence)
}
The expected result is, when the function is called, the animation should run through ONCE and delete itself. Instead, it runs once up to 10 or so times.
Thanks to #Knight0fDragon, I was able to fix this issue by making the texture array local within the function. Now each explosion has it's own instance.
func spawnExplosion(spawnPosition: CGPoint) {
var explosion = SKSpriteNode()
var textureAtlas = SKTextureAtlas()
var textureArray = [SKTexture]()
textureAtlas = SKTextureAtlas(named: "explosion")
for i in 1...textureAtlas.textureNames.count {
let name = "explosion\(i).png"
textureArray.append(SKTexture(imageNamed: name))
}
explosion = SKSpriteNode(imageNamed: "explosion1.png" )
explosion.setScale(0.6)
explosion.position = spawnPosition
explosion.zPosition = 3
self.addChild(explosion)
//explosion animation-action
let explosionAnimation = SKAction.repeat(SKAction.animate(with: textureArray, timePerFrame: 0.05), count: 1)
let delete = SKAction.removeFromParent()
let explosionSequence = SKAction.sequence([explosionSound, explosionAnimation, delete])
explosion.run(explosionSequence)
}
Related
I'm kind of stuck on what I would assume is a simple solution and I just can't figure out what to do.
I'm basically creating an expenses application for iOS and I've gotten to the point where I need to add up all the total expenses from my UITableView list.
private var items:[Shoe] = []
private var appDelegate = UIApplication.shared.delegate as! AppDelegate
private var managedContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
// Start of calculating the gain or loss for the individual item
let buyDouble: Double? = Double(buyToSave)
let sellDouble: Double? = Double(sellToSave)
let feeDouble: Double? = Double(feeToSave)
let feeInt = 1-(feeDouble!/100)
let profit = (feeInt*sellDouble!) - buyDouble!
let profitNSNumb = profit as NSNumber
let profitString = currencyFormatter.string(from: profitNSNumb)
let shoe = Shoe(entity: Shoe.entity(), insertInto: self.managedContext)
shoe.name = nameToSave
shoe.buyPrice = buyToSave
shoe.sellPrice = sellToSave
shoe.size = sizeToSave
shoe.fee = feeToSave
shoe.profitLoss = profitString
shoe.quantity = Int16(shoeQuantity)
shoe.sum = shoe.sum + profit
// Save the data
self.appDelegate.saveContext()
// Reloads the UITableView
self.shoeList.reloadData()
self.viewDidLoad()
I believe my logic is correct right? Im not sure why I keep start at $0 every time I quit my application and open it back up again?
ex:
ITEM #.......EXPENSE
Item 1.......$100
Item 2.......$200
Item 3.......$50
Total Expense: $350
Then when I close the app and start it up again:
ITEM #.......EXPENSE
Item 1.......$100
Item 2.......$200
Item 3.......$50
Total Expense: $350 <---- I want it to start at $350 and not $0
You have to call .save() on context in order to save the changes you made (in this case created a new Shoe object).
Try saving it like this:
do {
try self.managedContext.save()
} catch {
print("Failed saving")
}
I'm having issues with getting ALL elements of an array to fall using the Gravity module. I have managed to get the LAST element in the array to fall and then the remaining elements just stay at the top of the screen during testing. Upon debugging
I am using UIKit and want to understand this language thoroughly before using other various engines such as SpriteKit and GameplayKit.
func mainGame()
{
let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
for i in 1...random2
{
let image = UIImage(named: cars[i - 1])
let carView = UIImageView(image: image!)
carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
view.addSubview(carView)
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
gravityBehavior = UIDynamicItemBehavior(items: [carView]) //cars falling
dynamicAnimator.addBehavior(gravityBehavior)
collisionBehavior = UICollisionBehavior(items: [carView, mainCar]) //collide
collisionBehavior.translatesReferenceBoundsIntoBoundary = false
gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
dynamicAnimator.addBehavior(collisionBehavior)
}
collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
collisionBehavior.removeAllBoundaries()
}
With the game so far the last car in the array falls and the main player car that I control has collision behaviour, which is a big step for me!
You are creating a new UIDynamicAnimator with each iteration of the loop and assigning it to dynamicAnimator. That is why only the last element is working, because it is the last one assigned to that variable.
To fix it, just move this line to somewhere that would only be called once.
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
viewDidLoad is a possible place that should work.
UIKitDynamics is backwards of most similar frameworks. You don't animate the object. You have an animator and attach objects to it. As Clever Error notes, you only want one animator in this case.
The key point is that you don't attach gravity to cars; you attach cars to behaviors (gravity), and then behaviors to the animator. Yes, that's bizarre and backwards.
I haven't tested this, but the correct code would be closer to this:
func mainGame()
{
let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
var carViews: [UIImageView] = []
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
// First create all the views
for i in 1...random2
{
let image = UIImage(named: cars[i - 1])
let carView = UIImageView(image: image!)
carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
view.addSubview(carView)
carViews.append(carView)
}
// and then attach those to behaviors:
gravityBehavior = UIGravityBehavior(items: carViews) //cars falling
dynamicAnimator.addBehavior(gravityBehavior)
collisionBehavior = UICollisionBehavior(items: carView + mainCar) //collide
collisionBehavior.translatesReferenceBoundsIntoBoundary = false
dynamicAnimator.addBehavior(collisionBehavior)
collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
collisionBehavior.removeAllBoundaries()
// You don't need this; it's built into Gravity
// gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
}
The main way that UIKitDynamics is different than most animation frameworks is that things that are animated don't know they're being animated. You can't ask a car what behaviors it has, because it doesn't have any. A UIDynamicAnimator basically is a timing loop that updates the center and transform of its targets. There's really not anything fancy about it (in contrast to something like Core Animation which has many fancy things going on). With a little iOS experience, you could probably implement all of UIKitDynamics by hand with a single GCD queue (it probably doesn't even need that, since it runs everything on main....)
Trying to use an SKLabelNode to display the elements one by one of the array below, on the label. I believe the problem is that it iterates over the array faster than it takes the sequence to run its course, therefore causing a crash because self.addChild() is trying to be displayed again for "Set" while "Ready" is still being displayed.
My question is, how do I slow down this iteration so that "Ready" can appear and disappear, before "Set" gets displayed?
let readySetGo = ["Ready", "Set", "GO!"]
for i in readySetGo {
newLevelLabel.text = i
newLevelLabel.fontSize = 60
let wait = SKAction.wait(forDuration: 2)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([wait, remove])
newLevelLabel.run(sequence)
self.addChild(newLevelLabel)
}
The reason why it is immediately going to GO is because you do not chain all your actions in the same sequence, so all of your stages are running at the same time:
Try this, I've not the compiler now:
let readySetGo = ["Ready", "Set", "GO!"]
newLevelLabel.fontSize = 60
let seq: [SKAction]=[]
let waitTime:TimeIneterval = 2
for i in readySetGo {
let block = SKAction.run{
self.newLevelLabel.text = i
}
let wait = SKAction.wait(forDuration: waitTime)
seq.append(wait)
seq.append(block)
}
let wait = SKAction.wait(forDuration: waitTime)
let remove = SKAction.removeFromParent()
seq.append(wait)
seq.append(remove)
let sequence = SKAction.sequence(seq)
newLevelLabel.run(sequence)
self.addChild(newLevelLabel)
I was hoping someone could offer a simple solution. I am trying to save a 'labeled' frame on the timeline by storing it as a SharedObject.
The user can flip between various different backgrounds on the stage by clicking a button - button one corresponds to background one, background 2 corresponds to btn two and so on... For your reference these backgrounds are stored in a sub timeline in a movieClip. Any tips on how to get this to store..?
I'm open to new theories as I'm not having a lot of success saving from the movieClip on the time line.
I have already posted a question similar to this but I was wandering if it was possible to store these frames in an array? Array1 = image1 Array2 = image2 and so making it easier to store. I'm guessing I would need to make a loadermodule to store these images on the stage as well.
Thanks
// SAVE FUNCTIONS ---------------------------------------
//---------------------------------------------------
//---------------------------------------------------
var mySO:SharedObject = SharedObject.getLocal("iDesign");
bones_mc.x = mySO.data.my_x;
bones_mc.y = mySO.data.my_y;
if (!mySO.data.my_y) {
bones_mc.x = 424;
bones_mc.y = 119;
}
//----
save_btn.addEventListener (MouseEvent.CLICK, clickersave);
function clickersave (e:MouseEvent):void {
mySO.data.my_x = bones_mc.x;
mySO.data.my_y = bones_mc.y;
mySO.data.mybut_x = btrfly_mc.x;
mySO.data.mybut_y = btrfly_mc.y;
mySO.data.mytig_x = tiger_mc.x;
mySO.data.mytig_y = tiger_mc.y;
mySO.data.mybow_x = pink_bow_mc.x;
mySO.data.mybow_y = pink_bow_mc.y;
mySO.data.myblkbow_y = pink_bow_mc.y;
mySO.data.myblkbow_x = pink_bow_mc.x;
// tears saved - - - - - - -
mySO.data.mytear_drop_mc_three_x = tear_drop_mc_three.x;
mySO.data.mytear_drop_mc_three_y = tear_drop_mc_three.y;
mySO.data.mytear_drop_mc_one_x = tear_drop_mc_one.x;
mySO.data.mytear_drop_mc_one_y = tear_drop_mc_one.y;
mySO.data.mytear_drop_mc_two_x = tear_drop_mc.x;
mySO.data.mytear_drop_mc_two_y = tear_drop_mc.y;
mySO.data.mytear_drop_mc_four_x = tear_drop_mc_four.x;
mySO.data.mytear_drop_mc_four_y = tear_drop_mc_four.y;
mySO.data.myframe = caseSwapper.currentFrame;
trace(caseSwapper.currentFrame)
mySO.flush ();
}
//caseSwapper.currentFrame = mySO.data.myframe;
tear_drop_mc_three.x = mySO.data.mytear_drop_mc_three_x;
tear_drop_mc_three.y = mySO.data.mytear_drop_mc_three_y;
CODE ADDED TO MAKE THE TIMELINE SAVE - - - - - - - - - -
// applied to the clickersave function
mySO.data.myBgFrame = 2;
mySO.flush ();
}
if (mySO.data.myBgFrame){
caseSwapper.gotoAndStop(mySO.data.myBgFrame);
}
Not sure I understood exactly what you mean, but if you use click on button1 for BG1, in the click function you could write:
mySO.myBgFrame = 1;
mySO.flush ();
and than, when you need to set the saved BG:
if (mySO.myBgFrame){
bgMovieClip.gotoAndStop(mySO.myBgFrame);
}
Is this what you need?
I have created an array which is being used to store a series of .gif images and I'm just trying to test everything out by using document.getElementById to change the .src value but when I change it and load the page the image stays the same as it was before.
function setImage()
{
var images = new Array();
images[0] = anemone.gif;
images[1] = ball.gif;
images[2] = crab.gif;
images[3] = fish2.gif;
images[4] = gull.gif;
images[5] = jellyfish.gif;
images[6] = moon.gif;
images[7] = sail.gif;
images[8] = shell.gif;
images[9] = snail.gif;
images[10] = sun.gif;
images[11] = sunnies.gif;
images[12] = whale.gif;
var slots = new Array();
slots[0] = document.getElementById("slot" + 0);
slots[1] = document.getElementById("slot" + 1);
slots[2] = document.getElementById("slot" + 2);
slots[0].src = "snail.gif";
document.getElementById('slot0').src = images[0];
alert(images.length);
}
I can't understand why the image wont change, but I know it has to be something very simple. I've been wasting hours trying to get this one thing to change but nothing works. can anyone please point out the error of my ways?
There are a couple of issues with your code:
Your filenames need to be Strings, so they'll have to be quoted (also you can simplify the Array creation):
var images = ['anemone.gif', 'ball.gif', 'crab.gif', 'fish2.gif', 'gull.gif', 'jellyfish.gif', 'moon.gif', 'sail.gif', 'shell.gif', 'snail.gif', 'sun.gif', 'sunnies.gif', 'whale.gif'];
Also make sure you are getting your slot-elements right, quote all the attributes like:
<img id="slot0" class="slot" src="crab.gif" width="120" height="80">
When you create the slots-Array you can do it like this (no need to concat the ID string):
var slots = [document.getElementById('slot0'), document.getElementById('slot1'), document.getElementById('slot2')];
Finally make sure you call your function when the document has loaded / the DOM is ready. If you don't want to use a framework like jQuery your easiest bet is probably still using window.onload:
window.onload = setImage; //note that the parens are missing as you want to refer to the function instead of executing it
Further reading on Arrays, window.onload and DOMReady:
https://developer.mozilla.org/de/docs/DOM/window.onload
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array
javascript domready?