How do you make a sprite rock back and forth constantly like a boat?
Heres the code i wrote:
import UIKit
import SpriteKit
let boat = SKSpriteNode(imageNamed: "boat_floor")
var rotationVal = CGFloat(-1)
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
backgroundColor = UIColor.whiteColor()
boat.size = CGSize(width: self.frame.size.width, height: self.frame.size.width)
boat.position = CGPoint(x: self.frame.size.width/2, y: 0)
boat.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "boat_floor"), size: boat.size)
boat.physicsBody?.dynamic = false
boat.alpha = 1
self.addChild(boat)
ChangeAngle()
NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector("ChangeAngle"), userInfo: nil, repeats: true)
}
func ChangeAngle(){
let action = SKAction.rotateByAngle(rotationVal, duration:30)
if rotationVal < 0 {
rotationVal = CGFloat(M_PI)
boat.runAction(action)
}
else if rotationVal > 0 {
rotationVal = -CGFloat(M_PI)
boat.runAction(action)
}
print(rotationVal)
}
}
If I understand you correctly, you can do it like this (just copy&paste the code to see how it works):
import UIKit
import SpriteKit
let boat = SKSpriteNode(color: SKColor.greenColor(), size:CGSize(width: 200, height: 80))
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
backgroundColor = UIColor.whiteColor()
boat.position = CGPoint(x: self.frame.size.width/2, y: 100)
boat.alpha = 1
self.addChild(boat)
let rotate = SKAction.rotateByAngle(-0.6, duration:4)
let sequence = SKAction.repeatActionForever(SKAction.sequence([rotate, rotate.reversedAction()]))
let complete = SKAction.sequence([SKAction.rotateByAngle(0.3, duration:2), sequence])
boat.runAction(complete, withKey:"someKey")
}
}
Using NSTimer is generally bad idea in SpriteKit because it don't respect node's, scene's or view's paused state.
Related
Im getting 2 errors in line #objc func addAlien() (#objc can only be used with members of classes) and gameTimer = Timer.scheduledTimer(timeInterval: 0.75, target: self, selector: #selector(addAlien), userInfo: nil, repeats: true) (use of local variable 'addAlian' before its declaration)
I'm sorry, i'm new in Swift developming, any ideas how i can fix it?
Thanks.
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var starfield: SKEmitterNode!
var player: SKSpriteNode!
var scoreLabel: SKLabelNode!
var score: Int = 0{
didSet {
scoreLabel.text = "Счет \(score)"
}
}
var gameTimer: Timer!
var aliens = ["alien" , "alien2" , "alien3 "]
override func didMove(to view: SKView) {
starfield = SKEmitterNode (fileNamed: "Starfield") // Connect animation to starfield
starfield.position = CGPoint(x: 0, y: 1472) // Position starfield on screen
starfield.advanceSimulationTime(10)
self.addChild(starfield) // Add starfield on screen
starfield.zPosition = -1
player = SKSpriteNode(imageNamed: "damir2") // Подключаем картинку игрока
player.position = CGPoint (x: 0, y: -300) // Позиция игрока
self.addChild(player) // add player
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0) // Delete gravitation from the game
self.physicsWorld.contactDelegate = self
scoreLabel = SKLabelNode(text: "Score: 0") // Score table
scoreLabel.fontName = "AmericanTypewriter-Bold" // Шрифт для таблички
scoreLabel.fontSize = 56
scoreLabel.fontColor = UIColor.white
scoreLabel.position = CGPoint(x: -200, y: 500)
score = 0
self.addChild(scoreLabel)
gameTimer = Timer.scheduledTimer(timeInterval: 0.75, target: self, selector: #selector(addAlien), userInfo: nil, repeats: true)
#objc func addAlien(){
aliens = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: aliens) as! [String]
let alien = SKSpriteNode(imageNamed: aliens[0])
let randomPos = GKRandomDistribution(lowestValue: 20, highestValue: 350)
let pos = CGFloat(randomPos.nextInt())
alien.position = CGPoint(x: pos, y: -800)
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
According to code you shared - you declared addAlien inside of the didMove function. You need to move addAlien out of the didMove function and put it same level with didMove - in a class scope.
This is a game I have been working on. In summary there is a moving block named enemy and I want it to collide with an invisible static block called invisibleGround2. I have it printing hit when they supposedly collide but they are not colliding. Ive read every swift collision documentation by apply and others out there and I dont know whats wrong. Any help would be much appreciated!
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var orb = SKSpriteNode(imageNamed: "orb")
var scrollingG:scrollingGround?
var invisibleGround = SKSpriteNode(imageNamed: "invisible")
var invisibleGround2 = SKSpriteNode(imageNamed: "invisible")
let enemies = [SKSpriteNode(imageNamed: "blueE.png"), SKSpriteNode(imageNamed: "redE.png")]
let enemyCategory : UInt32 = 1
let jumperCategory : UInt32 = 1
let rotateDuration = 2
let enemyMoveSpeed = 5
let groundScrollingSpeed = 5
func createOrb(){
let orbConst = frame.size.width/2
let xConstraint = SKConstraint.positionX(SKRange(constantValue: orbConst))
orb.position = CGPoint(x: frame.size.width/2, y: 480)
orb.physicsBody = SKPhysicsBody(texture: orb.texture!, size: orb.texture!.size())
orb.constraints = [xConstraint]
self.addChild(orb)
}
func createScrollingGround () {
scrollingG = scrollingGround.scrollingNodeWithImage(imageName: "ground", containerWidth: self.size.width)
scrollingG?.scrollingSpeed = CGFloat(groundScrollingSpeed)
scrollingG?.anchorPoint = .zero
self.addChild(scrollingG!)
}
func createGround(){
invisibleGround2.size.width = 1
invisibleGround2.size.height = 1
invisibleGround2.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width:1, height: 100))
invisibleGround2.physicsBody?.isDynamic = false
invisibleGround2.position = CGPoint(x: 530, y: 191)
invisibleGround2.physicsBody?.categoryBitMask = jumperCategory
invisibleGround2.physicsBody?.collisionBitMask = enemyCategory
invisibleGround2.physicsBody?.contactTestBitMask = enemyCategory
invisibleGround2.name = "jumper"
invisibleGround.position = CGPoint(x: 0, y: 190)
invisibleGround.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: self.size.width * 3, height: 10))
invisibleGround.physicsBody?.isDynamic = false
self.addChild(invisibleGround2)
self.addChild(invisibleGround)
}
func getRandomEnemy(fromArray array:[SKSpriteNode])->SKSpriteNode{
return array[Int(arc4random_uniform(UInt32(array.count)))]
}
func spawnEnemy() {
if let enemy = getRandomEnemy(fromArray: enemies).copy() as? SKSpriteNode {
enemy.position = CGPoint(x: frame.size.width + frame.size.width/3, y: 440)
enemy.physicsBody = SKPhysicsBody(texture: enemy.texture!, size: enemy.texture!.size())
if enemy.size.width < 95 {
enemy.physicsBody?.categoryBitMask = enemyCategory
enemy.physicsBody?.collisionBitMask = jumperCategory
enemy.physicsBody?.contactTestBitMask = jumperCategory
}
enemy.name = "enemy"
self.addChild(enemy)
let moveLeft = SKAction.moveBy(x: -1500, y: 0, duration: TimeInterval(enemyMoveSpeed))
enemy.run(moveLeft)
}
}
func addEnemies () {
self.run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
self.spawnEnemy()
}, SKAction.wait(forDuration: 4)])))
}
func jump() {
orb.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 335))
}
func rotate() {
let rotate = SKAction.rotate(byAngle: CGFloat(M_PI * -2.55), duration: TimeInterval(rotateDuration))
let repeatAction = SKAction.repeatForever(rotate)
orb.run(repeatAction)
}
override func didMove(to view: SKView) {
createScrollingGround()
createOrb()
createGround()
rotate()
addEnemies()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
jump()
}
override func update(_ currentTime: TimeInterval) {
if self.scrollingG != nil {
scrollingG?.update(currentTime: currentTime)
func didBegin(_ contact: SKPhysicsContact) {
let bodyA = contact.bodyA.categoryBitMask
let bodyB = contact.bodyB.categoryBitMask
if bodyA == jumperCategory && bodyB == enemyCategory {
print("hit")
} else if bodyA == enemyCategory && bodyB == jumperCategory {
print("hit 2")
}
}
}
}
}
Not an swer, but some things to check:
Have you set the scene’s physicsworld delegate property set to
self?
Move didBegin outside of update
Is the 'enemy' sprite's width < 95?
Add a print("Contact detected") as the first line of your relocated didBegin so you at least know that some contact has been detected.
See my answer here https://stackoverflow.com/a/43605825/1430420 for a simple SK collision demo which might help - I think it needs updates to Swift 4 which I'll try and do.
I tried copying the template code from a SpriteKit project into a playground, but all I get is a grey screen when I present the Scene. Do I need to create an SKScene and if so how do I assign it to the scene class that I am using.
The following is my code to create and present the scene:
#objc func goToGameScene(){
print("GameView Loaded")
sceneView = SKView(frame: CGRect(x: 0, y: 0, width: 666, height: 500))
sceneView.backgroundColor = UIColor.black
PlaygroundPage.current.liveView = sceneView
if let view = self.sceneView as SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
}
And this is my SKScene class, which has a filename of GameScene.swift.
import Foundation
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var bg = SKSpriteNode()
func didBegin(_ contact: SKPhysicsContact) {
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
let bgTexture = SKTexture(image: UIImage(named: "MainScreenBackground.png")!)
let moveBGAnimation = SKAction.move(by: CGVector(dx:-bgTexture.size().width, dy:0), duration: 11)
let shiftBackground = SKAction.move(by: CGVector(dx: bgTexture.size().width, dy:0), duration: 0)
let repeatAnimationBg = SKAction.repeatForever(SKAction.sequence([moveBGAnimation, shiftBackground]))
var q = 0
while(q < 3){
bg = SKSpriteNode(texture: bgTexture)
bg.position = CGPoint(x: bgTexture.size().width * CGFloat(q), y: self.frame.midY)
bg.size.height = self.frame.height
bg.run(repeatAnimationBg)
self.addChild(bg)
q+=1
bg.zPosition = -1
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
}
}
Assuming you have dragged and dropped your MainScreenBackground.png image into you Assets.xcassets folder, you can use the code below to add the image to your scene as a SKSpriteNode.
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
let bgTexture = SKSpriteNode(imageNamed: "MainScreenBackground")
self.addChild(bgTexture)
...
I was trying to make a game where the dragon moves around randomly and the hero has to avoid it. I can make the dragon appear at a random location and move once or twice but to make it continuously move from point to point and then move some more has given me trouble. I think it might be because I'm not waiting for the action to complete before generating all of the random numbers. I tried the following code including labels to prove to myself that the random numbers are generating, at least up to 20...
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private let greenDragonNode = GreenDragonSprite.newInstance()
private var lastUpdateTime : TimeInterval = 0
private var i = 0
override func sceneDidLoad() {
self.lastUpdateTime = 0
let xOrigin = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yOrigin = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonOrigin = CGPoint(x: xOrigin, y: yOrigin)
greenDragonNode.position = dragonOrigin
greenDragonNode.setScale(0.1)
addChild(greenDragonNode)
}
override func didMove(to view: SKView) {
for i in 1...20 {
let xDestination = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yDestination = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonDestination = CGPoint(x: xDestination, y: yDestination)
let xDestination2 = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.width)
let yDestination2 = CGFloat(arc4random()).truncatingRemainder(dividingBy: size.height)
let dragonDestination2 = CGPoint(x: xDestination2, y: yDestination2)
let dragonNodeTravel = SKAction.move(to:dragonDestination, duration: 3.0)
let dragonNodeReturn = SKAction.move(to: dragonDestination2, duration: 3.0)
greenDragonNode.run(SKAction.sequence([dragonNodeTravel, dragonNodeReturn]))
// i += 1
let label = SKLabelNode(text: "\(i)")
label.position = dragonDestination2
addChild(label)
}
}
}
That is happening because only the last action from the for loop is executed. You have to make a queue of actions. So you should append them to an array, and run them in a sequence, like this:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private let greenDragonNode = SKSpriteNode(color: .green, size: CGSize(width: 20, height: 20))
func getRandomPoint(withinRect rect:CGRect)->CGPoint{
let x = CGFloat(arc4random()).truncatingRemainder(dividingBy: rect.size.width)
let y = CGFloat(arc4random()).truncatingRemainder(dividingBy: rect.size.height)
return CGPoint(x: x, y: y)
}
override func didMove(to view: SKView) {
greenDragonNode.position = self.getRandomPoint(withinRect: frame)
addChild(greenDragonNode)
var actions:[SKAction] = []
for i in 1...20 {
let destination = getRandomPoint(withinRect: frame)
let move = SKAction.move(to:destination, duration: 1.0)
actions.append(move)
let label = SKLabelNode(text: "\(i)")
label.position = destination
addChild(label)
}
let sequence = SKAction.sequence(actions)
greenDragonNode.run(sequence)
}
}
The code below when run on a device that’s flat on the table shows a red circle (the 'ball') and a line.
When I gently tip the device the circle moves towards the line. When it reaches the line, it stops which is the expected and desired behaviour.
BUT if I keep tipping, the circle moves through the line. I’ve been trying for 2 days to work out why grrr I just want the circle to hit the line and not go through it.
Any ideas? Anything to do with the line being an 'edge'?
Cheers,
Andy
import SpriteKit
import CoreMotion
class GameScene: SKScene, SKPhysicsContactDelegate {
var ball : SKShapeNode!
var line : SKShapeNode!
var motionManager = CMMotionManager()
var destX : CGFloat!
var destY : CGFloat!
let ballRadius = CGFloat(20.0)
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
physicsWorld.contactDelegate = self
ball = SKShapeNode(circleOfRadius: ballRadius)
ball.fillColor = UIColor.redColor()
ball.position = CGPoint(x: frame.size.width/4, y: frame.size.height / 2)
destX = frame.size.width/4
destY = frame.size.height / 2
ball.physicsBody = SKPhysicsBody(circleOfRadius: ballRadius)
addChild(ball)
line = SKShapeNode()
var lineStart = CGPoint(x: frame.size.width/2, y: 0)
var lineEnd = CGPoint(x: frame.size.width/2, y: frame.size.height)
var linePoints = [lineStart, lineEnd]
var linePos : CGMutablePathRef = CGPathCreateMutable()
CGPathAddLines(linePos, nil, linePoints, 2)
line.path = linePos
line.strokeColor = UIColor.blackColor()
line.physicsBody = SKPhysicsBody(edgeFromPoint: lineStart, toPoint: lineEnd)
addChild(line)
}
override func update(currentTime: NSTimeInterval) {
moveBall()
}
func moveBall() {
motionManager.startAccelerometerUpdatesToQueue(
NSOperationQueue.currentQueue(), withHandler: {
data, error in
var currentX = self.ball.position.x
var currentY = self.ball.position.y
self.destY = currentY - CGFloat(data.acceleration.x * 100)
self.destX = currentX + CGFloat(data.acceleration.y * 100)
}
)
ball.position = CGPoint(x: destX, y: destY)
}
}
import UIKit
import SpriteKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let skView = self.view as! SKView
let scene = GameScene(size: CGSizeMake(
skView.bounds.size.width, skView.bounds.size.height))
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}