Looking to make an SKSprite (road in this case) move back and forth across the screen. I'd like for the road to move faster and faster every second. I implemented the following but it only sets the oscillation once and never gets updated. Is there any way I can make it oscillate faster and faster? Or is there a better way of doing this using something else?
var wallSpeedMultiplier = 1.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if !gameStarted {
titleView.removeFromParent()
tapToStart.removeFromParent()
gameStarted = true
let wait = SKAction.wait(forDuration: 1.0)
let block = SKAction.run({
[unowned self] in
wallSpeedMultiplier = wallSpeedMultiplier - 0.01
})
let sequence = SKAction.sequence([wait, block])
run(SKAction.repeatForever(sequence), withKey: "timer")
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: (30*wallSpeedMultiplier), midPoint: road!.position)
road!.run(SKAction.repeatForever(oscillate))
road!.run(SKAction.moveBy(x: size.width, y: 0, duration: 5))
}
}
extension SKAction {
static func oscillation(amplitude a: CGFloat, timePeriod t: CGFloat, midPoint: CGPoint) -> SKAction {
let action = SKAction.customAction(withDuration: Double(t)) { node, currentTime in
let displacement = a * sin(2 * π * currentTime / t)
node.position.x = midPoint.x + displacement
}
return action
}
}
Related
I am attempting to set the duration of SKAction.moveBy to reduce by 1.0 every time it repeats. For some reason when SKAction.repeatForever is ran, variables such as duration (or wallSpeedMultiplier) do not get changed on the repeat. Does anyone know a work around to this?
I tried putting it in a sequence with an action to change the duration, to move the object, then wait for the movement to be done to repeat.
var wallSpeedMultiplier = 10.0
...
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: 15.0, midPoint: road!.position)
road!.run(SKAction.resize(toWidth: 100, duration: 2.0))
let multiplyIt = SKAction.run { [self] in
wallSpeedMultiplier = wallSpeedMultiplier - 1.0
}
let moveIt = SKAction.moveBy(x: size.width, y: 0, duration: wallSpeedMultiplier)
let waitIt = SKAction.wait(forDuration: wallSpeedMultiplier)
let sequenceIt = SKAction.sequence([moveIt,waitIt, multiplyIt])
road!.run(SKAction.repeatForever(oscillate))
road!.run(SKAction.repeatForever(sequenceIt))
Tried solution from https://www.stackoverflow.com/a/34633329/3402095
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if !gameStarted {
gameStarted = true
road!.run(SKAction.resize(toWidth: 100, duration: 2.0))
recursive()
}
}
func recursive() {
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: wallSpeedMultiplier, midPoint: road!.position)
let multiplyIt = SKAction.run { [self] in
wallSpeedMultiplier = wallSpeedMultiplier - 5.0
}
let recursive = SKAction.sequence([oscillate, SKAction.wait(forDuration: wallSpeedMultiplier),multiplyIt])
road!.run(recursive, withKey: "aKey")
}
FIXED:
func recursive() {
let oscillate = SKAction.oscillation(amplitude: 200, timePeriod: wallSpeedMultiplier, midPoint: road!.position)
let multiplyIt = SKAction.run { [self] in
wallSpeedMultiplier = wallSpeedMultiplier - 5.0
print(wallSpeedMultiplier)
print(oscillate)
}
let recursive = SKAction.sequence([oscillate, multiplyIt, SKAction.run({[unowned self] in NSLog("Block executed"); self.recursive()})])
road!.run(recursive, withKey: "aKey")
}
I'm working on a game project with Xcode.
I've written the code that makes the ship shoot the projectiles, but I don't know what function to use to make the ship shoot automatically.
Could you help me? Thank you in advance!
Here's my code, from the GameScene :
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let projectile = SKSpriteNode(imageNamed: "projectile")
projectile.zPosition = 1
projectile.position = CGPoint(x: player.position.x, y: player.position.y)
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.None
projectile.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(projectile)
let action = SKAction.moveTo(x: self.frame.width + projectile.size.width, duration: 0.5)
projectile.run(action, completion: {
projectile.removeAllActions()
projectile.removeFromParent()
})
}
Based on a comment by Jake I am assuming that you want the ship to fire "automatically" not repeating while holding the finger down.
You can use the update func to control the automatic shooting. in my example the update command fires every 1 second
private var updateTime: Double = 0
override func update(_ currentTime: TimeInterval) {
if updateTime == 0 {
updateTime = currentTime
}
if currentTime - updateTime > 1 {
self.shoot()
updateTime = currentTime
}
}
func shoot() {
let projectile = SKSpriteNode(imageNamed: "projectile")
projectile.zPosition = 1
projectile.position = CGPoint(x: player.position.x, y: player.position.y)
projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.isDynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.None
projectile.physicsBody?.usesPreciseCollisionDetection = true
self.addChild(projectile)
let action = SKAction.moveTo(x: self.frame.width + projectile.size.width, duration: 0.5)
projectile.run(action, completion: {
projectile.removeAllActions()
projectile.removeFromParent()
})
}
I'm having some problems with a SKAction when clicking retry after the player dies. It's the shooting SKAction that I'm having difficulty with. It's shooting well when I run the game but when I click "Retry" to play again only one bullet fires. After that, the SKAction stops. All the other SKActions work when i play again, but not that one.
I know what the problem is. I have all the relevant functions in a func called initialize() that I add in the didMove. But I'm having difficulty calling some of the functions there since the functions have a parameter.
These are the shooting functions I have:
func fireMissile() {
let missile = SKSpriteNode(color: .yellow, size: CGSize(width: 20,
height: 5))
missile.name = "Missile"
missile.position = CGPoint(x: player.position.x + 28, y:
player.position.y + 10)
missile.zPosition = 2
missile.physicsBody = SKPhysicsBody(rectangleOf: missile.size)
missile.physicsBody?.isDynamic = false
missile.physicsBody?.categoryBitMask = ColliderType.Bullet
missile.physicsBody?.collisionBitMask = ColliderType.Enemy |
ColliderType.Boat
missile.physicsBody?.contactTestBitMask = ColliderType.Enemy |
ColliderType.Boat
let missileFlightTime = travelTime(to: missileDestination, from:
player.position, atSpeed: missileSpeed)
missile.zRotation = direction(to: missileDestination, from:
missile.position)
self.addChild(missile)
let missileMove = SKAction.move(to: missileDestination,
duration:
TimeInterval(missileFlightTime))
let missileRemove = SKAction.removeFromParent()
missile.run(SKAction.sequence([missileMove, missileRemove]))
}
func travelTime(to target : CGPoint, from : CGPoint, atSpeed speed :
CGFloat) -> TimeInterval {
let distance = sqrt(pow(abs(target.x - from.x),2) +
pow(abs(target.y - from.y),2))
return TimeInterval(distance/speed)
}
func direction(to target : CGPoint, from: CGPoint) -> CGFloat {
let x = target.x - from.x
let y = target.y - from.y
var angle = atan(y / x)
if x < 0 {
angle = angle + CGFloat.pi
}
return angle
}
And then we have the didMove function:
func initialize() {
score = 0
physicsWorld.contactDelegate = self
createPlayer()
createBG()
createWall()
spawnEnemiesLeft()
spawnEnemiesRight()
spawnBoat()
fireMissile()
createlabel()
}
When i try adding the "func travelTime()" and func "direction()", it requires for me to also add the CGPoint for the "to target" and "from", and I don't know what to type in there, I don't know if thats even the way to go, but it should be.
I'm trying to add one simple level to my flappy bird styled game. I figure a way of doing it using 2 different scenes but would prefer it to be within the same one. I simply want the walls to appear more frequently once a score of 15 has been reached! Here is my code
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false{
gameStarted = true
body.physicsBody?.affectedByGravity = true
let spawn = SKAction.run({
() in
self.createWalls()
})
let delay = SKAction.wait(forDuration: 2.0)
let spawnDelay = SKAction.sequence([delay, spawn])
let spawnDelayForever = SKAction.repeatForever(spawnDelay)
self.run(spawnDelayForever)
let distance = CGFloat(self.frame.width + wallPair.frame.width)
let movePipes = SKAction.moveBy(x: -distance - 50, y: 0, duration: TimeInterval( 0.01 * distance))
let removePipes = SKAction.removeFromParent()
moveAndRemove = SKAction.sequence([movePipes,removePipes])
body.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
body.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 68))
run(flap)
}
Ive tried adding a if statement before which was an if statement like
if score = 20{
but that just delays applying impulse till the player has a score of 20.
Is there a way I can say
if score is between 0 and 20 then run my first spawning action
then another saying if score > 20 run my second spawning action which would just have a slower spawning time?
Hope this makes sense! Thanks
You could move the spawnDelayForever logic into a function and call it first with the 2 second delay as parameter, then when the game score gets to 20, call the spawnDelayForever function again passing 5 as input parameter. You would need to add the action using a key.
self.run(spawnDelayForever(withDelay: 2.0), withKey: "spawnDelayForever")
Then remove the running action before adding again.
self.removeAction(forKey: "spawnDelayForever")
self.run(spawnDelayForever(withDelay: 5.0), withKey: "spawnDelayForever")
Your code might look something like this.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
gameStarted = true
body.physicsBody?.affectedByGravity = true
self.run(spawnDelayForever(withDelay: 2.0), withKey: "spawnDelayForever")
// other code...
} else if score > 20 && spawnDelayApplied == false {
self.removeAction(forKey: "spawnDelayForever")
self.run(spawnDelayForever(withDelay: 5.0), withKey: "spawnDelayForever")
spawnDelayApplied = true
}
}
func spawnDelayForever(withDelay sec: TimeInterval) -> SKAction {
let spawn = SKAction.run({
() in
self.createWalls()
})
let delay = SKAction.wait(forDuration: sec)
let spawnDelay = SKAction.sequence([delay, spawn])
return SKAction.repeatForever(spawnDelay)
}
I need to know how to make a ball slow down after two or three seconds to apply an impulse
func setupPlayer(){
player = SKSpriteNode(imageNamed: "ball")
player.anchorPoint = CGPoint(x: 0.5, y: 0.5)
player.position = CGPoint(x: size.width/2, y: playableStart)
let scale: CGFloat = 0.07
player.xScale = scale
player.yScale = scale
player.physicsBody = SKPhysicsBody(circleOfRadius: player.size.width / 2)
worlNode.addChild(player)
}
func movePlayer(){
player.physicsBody?.applyImpulse((CGVectorMake( 50, 50)))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
movePlayer()
}
}
One way to decelerate a physics body is to reduce its velocity over time:
override func update(currentTime: CFTimeInterval) {
// This controls how fast/slow the body decelerates
let slowBy:CGFloat = 0.95
let dx = player.physicsBody!.velocity.dx
let dy = player.physicsBody!.velocity.dy
player.physicsBody?.velocity = CGVectorMake(dx * slowBy, dy * slowBy)
}
Are you looking for something like this?
let wait = SKAction.waitForDuration(2.0)
let slow = SKAction.runBlock({
node.physicsBody?.applyImpulse(CGVectorMake(-node.physicsBody?.velocity.dx, -node.physicsBody?.velocity.dy))
//you will probably want to mess with the vector
})
node.runAction(SKAction.sequence([wait, slow]))