Swift - Trouble with SKAction sequence - ios

I'm trying to move my background image down and then after a 3 second delay, move it back up the screen. And I want this to occur forever.
It slides down the screen perfectly fine, but then nothing else happens. Can anyone see what I'm doing wrong here?
override func didMoveToView(view: SKView) {
background.anchorPoint = CGPoint(x: 0, y: 0)
background.size = CGSize(width: frame.size.width, height: frame.size.height)
background.name = "bg"
addChild(background)
let moveBG1 = SKAction.moveToY(-(size.height), duration: 3.0)
let moveBGback = SKAction.moveToY(size.height, duration: 3.0)
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runAction(moveBG1, onChildWithName: "bg"),
SKAction.waitForDuration(3.0),
SKAction.runAction(moveBGback, onChildWithName: "bg")
])
))

According with your anchorPoint, the first action is correct. As nickfalk write to you, the second action must to be:
let moveBGback = SKAction.moveToY(0, duration: 3.0)
if you want to see your BG come back, so you have wrong the Y position, otherwise your BG is putted at the TOP of the screen (so not visible)

Related

SpriteKit scrolling background image showing line between images

I'm learning how to make games in iOS, so I decided to replicate the first level of Mario Bros using my own assets, just to learn how to make them as well.
The issue I'm having right now is that, when creating the scrolling background, using this formula I found on Hacking with Swift, I keep getting a line in between my images.
I've checked that my images have no border in the AI file. (The images are the ones above)
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var player = SKSpriteNode()
private var bg = SKSpriteNode()
override func didMove(to view: SKView) {
let playerTexture = SKTexture(imageNamed: "player")
player = SKSpriteNode(texture: playerTexture)
player.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
addBackground()
self.addChild(player)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
// MARK: UTILITY FUNCTIONS
func addBackground() {
let bgTexture = SKTexture(imageNamed: "bg")
for i in 0 ... 1 {
bg = SKSpriteNode(texture: bgTexture)
//bg.position = CGPoint(x: (i == 0 ? bgTexture.size().width : bgTexture.size().width - 1) * CGFloat(i), y: self.frame.midY)
bg.position = CGPoint(x: (bgTexture.size().width * CGFloat(i)) - CGFloat(1 * i), y: 0)
bg.size.height = self.frame.height
//bg.anchorPoint = CGPoint.zero
bg.zPosition = -10
self.addChild(bg)
let moveLeft = SKAction.moveBy(x: -bgTexture.size().width, y: 0, duration: 5)
let moveReset = SKAction.moveBy(x: bgTexture.size().width, y: 0, duration: 0)
let moveLoop = SKAction.sequence([moveLeft, moveReset])
let moveForever = SKAction.repeatForever(moveLoop)
bg.run(moveForever)
}
}
}
Just create a new project, copy-paste it and you should see it running
I also changed my GameScene.sks size to: 2688 x 1242
And one last change I made to make the background appear in full screen was to set the LaunchScreen as stated in this answer which seems to be an issue in Xcode 12
I understand that the formula from Hacking with Swift post is making the images overlap by 1px, I've tried removing the 1 * i part from it, yet results are not different.
Other thing I did was to verify the images were "joining" perfectly together, and they do, the lines you can see in the image below are from the AI canvas, both images join in between "cactuses".
I also tried running it into a device in case it might be a bug in the simulator:
Your image has a thin black border on the left handside and along the top, 1 pixel wide. It's black. Remove that and try again.

How to move a character back and forth across horizontally in swift

Currently trying to get a monster character to move across the screen horizontal back and forth. I'm extremely new to Swift as I just started learning last week and also haven't exactly master OOP. So how do I properly call the enemyStaysWithinPlayableArea func while it calls the addEnemy func. Heres what I have so far:
import SpriteKit
class GameScene: SKScene {
var monster = SKSpriteNode()
var monsterMove = SKAction()
var background = SKSpriteNode()
var gameArea: CGRect
// MARK: - Set area where user can play
override init(size: CGSize) {
// iphones screens have an aspect ratio of 16:9
let maxAspectRatio: CGFloat = 16.0/9.0
let playableWidth = size.height/maxAspectRatio
let margin = (size.width - playableWidth)/2
gameArea = CGRect(x: margin, y: 0, width: playableWidth, height: size.height)
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMove(to view: SKView) {
// I want to run addEnemy() while it checks for enemyStayWithinPlayableArea()
// run(SKAction.repeatForever(...)) ???
enemyStayWithinPlayableArea()
// MARK: - Setting the background
background = SKSpriteNode(imageNamed: "background")
background.size = self.size
// Set background at center of screen
background.position = CGPoint(x:self.size.width/2 ,y:self.size.height/2)
// Layering so everything in the game will be in front of the background
background.zPosition = 0
self.addChild(background)
}
func addEnemy() {
// MARK: - Set up enemy and its movements
monster = SKSpriteNode(imageNamed: "monster")
monster.zPosition = 5
// this is where the enemy starts
monster.position = CGPoint(x: 0, y: 300)
self.addChild(monster)
// I want it to move to the end of the screen within 1 sec
monsterMove = SKAction.moveTo(x: gameArea.maxX, duration: 1.0)
monster.run(monsterMove)
}
func enemyStayWithinPlayableArea(){
addEnemy()
// when the monster reaches the right most corner of the screen
// turn it back around and make it go the other way
if monster.position.x == gameArea.maxX{
monsterMove = SKAction.moveTo(x: gameArea.minX, duration: 1.0)
monster.run(monsterMove)
}
// when the monster reaches the left most corner of the screen
// same idea as above
if monster.position.x == gameArea.minX{
monsterMove = SKAction.moveTo(x: gameArea.maxX, duration: 1.0)
monster.run(monsterMove)
}
}
}
So far the enemy moves across the screen to the right edge of the screen, so that part works perfectly. I just need help making sure it continues in a loop (perhaps using the SKAction.repeatForever method but not sure how).
To understand movement of image in spritekit check this example.
let moveLeft = SKAction.moveTo(CGPoint(x: xpos, y: ypos), duration: duration)
let moveRight = SKAction.moveTo(CGPoint(x: xpos, y: ypos), duration: duration)
sprite.runAction(SKAction.repeatActionForever(SKAction.sequence([moveLeft, moveRight])))
for horizontal movement y position never been changed it should be same as in move left and move right.to stop repeatforever action use this.
sprite.removeAllActions()
Make two functions moveToMax and moveToMin with respective SKActions.And run action with completion handler.
func moveToMin(){
monsterMove = SKAction.moveTo(x: gameArea.minX, duration: 1.0)
monster.run(monsterMove, completion: {
moveToMax()
})
}

UIView.animate problems with view frame

I'm struggling trying to make an animation. This is what's happening: video
The back card was supposed to do the same animation as in the beginning of the video, but it's doing a completely different thing. I'm checking the UIView.frame at the beginning of the animation and it's the same as the first time the card enters, but obviously something is wrong... Here is the code:
func cardIn() {
let xPosition = (self.darkCardView?.frame.origin.x)! - 300
let yPosition = (self.darkCardView?.frame.origin.y)!
self.initialPos = self.darkCardView.frame
let height = self.darkCardView?.frame.size.height
let width = self.darkCardView?.frame.size.width
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(Double.pi/4)))!
UIView.animate(withDuration: 1.1, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
})
}
func cardOut() {
let xPosition = (self.darkCardView?.frame.origin.x)! - 600
let yPosition = (self.darkCardView?.frame.origin.y)!
let height = self.darkCardView?.frame.size.height
let width = self.darkCardView?.frame.size.width
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
UIView.animate(withDuration: 1.0, delay: 0, options: .allowAnimatedContent, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(-Double.pi/4)))!
}) { (true) in
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
self.darkCardView?.frame = self.initialPos
self.cardIn()
}
}
Does somebody know how can I repeat the same animation that's in the beginning of the video after cardOut function is called?
Given that we do not know where/when you are calling the above two functions I can only take a guess at what is happening.
So you have an animate in and an animate out, but what about a reset function?
Timeline:
Animate the card in.
...
Eventually animate the card out
...
Reset the cards location to off screen to the right (location before animating in for the first time)
...
animate the card in
...
Repeat, ect....
I managed to solve the animation problem by calling viewDidLoad() instead of calling self.cardIn() in the end o cardOut completion block. But I don't want to depend on calling viewDidLoad every time a new card has to enter the screen...
Also, I didn't set any properties of the back card in viewDidLoad. I only have it in the .xib file. Any ideas?
Note that if you set a view's transform to anything other than the identity transform then the view's frame rect becomes "undefined". You can neither set it to a new value nor read it's value reliably.
You should change your code to use the view's center property if you're changing the transform.
As Jerland points out in their post, you also probably need to reset your back card to it's starting position before beginning the next animation cycle. (Set it to hidden, put it's center back to the starting position and set it's transform back to the identity transform.
EDIT:
This code:
UIView.animate(withDuration: 1.0, delay: 0, options: .allowAnimatedContent, animations: {
self.darkCardView?.frame = CGRect(x: xPosition, y: yPosition, width: width!, height: height!)
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(-Double.pi/4)))!
}) { (true) in
self.darkCardView?.transform = (self.darkCardImageView?.transform.rotated(by: CGFloat(0)))!
self.darkCardView?.frame = self.initialPos
self.cardIn()
}
Does not set the transform to identity. Try changing that line to
self.darkCardView?.transform = .identity

How to make objects move?

This is my code I wrote in Xcode using Sprite Kit, Swift. I'm trying to make a game like ComboQuest. The game consists of a moving bar that moves left and right trying to hit objects. As I'm trying to recreate that left and right movement here is my code:
func rightSprite(){
let actionR = SKAction.moveByX(0.001, y: 0, duration: 0.01)
Sprite.runAction(SKAction.repeatActionForever(actionR))
}
func leftSprite(){
let actionR = SKAction.moveByX(-0.001, y: 0, duration: 0.01)
Sprite.runAction(SKAction.repeatActionForever(actionR))
}
These two functions are then activate in the TouchesBegan override func, but there is a small flaw. When you touch the screen to change the direction there is not a complete reaction.
How would you guys code this?
First off, make sure you have a valid instance of a SKSpriteNode. It would be helpful to see more of your code to see where you are creating the bar sprite you would like to move. For example, below I created a red rectangle sprite.
let bar = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 50, height: 10))
As far as your actions, you need to call them on your instance of a sprite. I used the same bar sprite from the example above. Also, since you are setting an action to repeat forever, you will need to remove that action to get it to stop. It appears you want the bar to move left and right. You can either remove all actions as I have done, or you could name your actions, and just remove a certain one. For simplicity, I just removed all actions.
func rightSprite(){
let actionR = SKAction.moveByX(0.001, y: 0, duration: 0.01)
bar.removeAllActions()
bar.runAction(SKAction.repeatActionForever(actionR))
}
func leftSprite(){
let actionL = SKAction.moveByX(-0.001, y: 0, duration: 0.01)
bar.removeAllActions()
bar.runAction(SKAction.repeatActionForever(actionL))
}
Take a look a this :
override func didMoveToView(view: SKView) {
let sprite = SKSpriteNode(color: UIColor.purpleColor(), size: CGSize(width: 20, height:50))
var startPoint:CGPoint = CGPoint(x: 100, y: CGRectGetMidY(frame))
var endPoint:CGPoint = CGPoint(x: 300, y: CGRectGetMidY(frame))
let moveLeft = SKAction.moveTo(endPoint, duration:3)
let moveRight = SKAction.moveTo(startPoint, duration:3)
let sequence = SKAction.sequence([moveLeft,moveRight])
sprite.position = startPoint
addChild(sprite)
sprite.runAction(SKAction.repeatActionForever(sequence), withKey:"moving")
}
I assume that your scene and view are initialized and sized correctly (eg that you done something like this scene.size = skView.bounds.size ) so when you try this example, sprite will show up on screen (instead of possible off-screen because you probably loading a scene from a .sks file).
IMO this is the natural way when using SKAction to move a sprite from point A to point B continuously (like from a Combo Quest) and that is:
Create starting and ending point
Make separate actions for moving sprite left and right
Make a sequence to run those two actions one after another
Repeat that action forever
Run that action with key to give your self a way to stop an action
When you want to stop action, you should do something like this:
if(sprite.actionForKey("moving") != nil){
sprite.removeActionForKey("moving")
}

Vertical endless scrolling background in sprite kit

I'm making a game and I'd like to have my background scroll vertically endlessly but I have no clue how to start. I'm pretty new to coding and sprite kit especially so any help would be greatly appreciated.
Anyone know how I could go about doing this?
You want to have two nodes, one to start with and one that follows it. Lastly, loop forever.
func createGroundForward() {
let groundTexture = SKTexture(imageNamed: "Track")
for i in 0 ... 1 {
let ground = SKSpriteNode(texture: groundTexture)
ground.zPosition = -4
ground.position = CGPoint(x: 0, y: -groundTexture.size().height + (groundTexture.size().height + (groundTexture.size().height * CGFloat(i))))
addChild(ground)
let moveLeft = SKAction.moveBy(x:0 , y: -groundTexture.size().height, duration: 5)
let moveReset = SKAction.moveBy(x:0 , y: groundTexture.size().height, duration: 0)
let moveLoop = SKAction.sequence([moveLeft, moveReset])
let moveForever = SKAction.repeatForever(moveLoop)
ground.run(moveForever)
}
}
Then add the function to your didMove function (which is like viewDidLoad for Sprite).
override func didMove(to view: SKView) {
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
createGroundForward()
}
I will give you an answer conceptually to give you a place to start since you have not provided any code.
You will need multiple images that are the size of the screen and stack them on top of each other vertically.
You then want to move these images down point by point. When the top of one of the images reaches the bottom of the screen, put it back on top of your stack of images. This will cause a never ending scrolling of your background.
Best of luck!

Resources