How to change color of SKShapeNode randomly each time spawned? - sprite-kit

Currently, when running this code, a colored ball runs from the top of the screen to the bottom. When it reaches the bottom it respawns at the top and drops again. I need the ball to change to a random color each time it spawns again. I have a colorChange action but it still doesn't change the color of the ball when it runs.
import SpriteKit
class GameScene: SKScene {
let ball = SKShapeNode(circleOfRadius: 20)
let label = SKLabelNode(fontNamed: "Futura")
let colorChange = SKAction.colorizeWithColor(.blueColor(), colorBlendFactor: 1, duration: 1)
let randomRed = Int(arc4random_uniform(255))
let randomGreen = Int(arc4random_uniform(255))
let randomBlue = Int(arc4random_uniform(255))
override func didMoveToView(view: SKView) {
let sceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
self.physicsBody = sceneBody
//Ball Transition
let ballTransition = SKAction.sequence([SKAction.fadeInWithDuration(1)])
ball.runAction(ballTransition)
ball.fillColor = UIColor(red: 100, green: 100, blue: 0, alpha: 1)
ball.physicsBody = SKPhysicsBody(circleOfRadius: 25)
ball.physicsBody?.affectedByGravity = false
ball.position = CGPoint(x: self.frame.size.width/2, y: CGFloat(self.frame.size.height*1))
ballMovement()
self.addChild(ball)
self.addChild(label)
}
func ballMovement() {
let moveBall = SKAction.moveToY(0, duration: 3)
let goBackUp = SKAction.moveToY(self.frame.size.height, duration:0)
let keepFalling = SKAction.sequence([moveBall, goBackUp, colorChange])
ball.runAction(SKAction.repeatActionForever(keepFalling))
//Label Sprite
label.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
label.fontColor = SKColor.redColor()
label.fontSize = 30
}
override func update(currentTime: CFTimeInterval) {
label.text = "\(ball.position.y)"
if ball.position.y < 26 {
ball.position = CGPoint(x: self.frame.size.width/2, y: CGFloat(self.frame.size.height*1))
}
}
}

Instead of using an SKAction to change the color, just do
ball.color = UIColor...
To generate a random color check this out: How to make a random background color with Swift
and use that to generate a random color
ball.color = .randomColor()

I suggest you don't use an SKShape for an important piece that moves because I find that they can slow down the app and cause problems. I would use an SKSpritenode instead for the ball. If you then make the ball default as all white, you can change the colour by doing this:
ball.colorBlendFactor = 1
ball.color = SKColor(hue: randomNumberFunctionBetweenZeroAndOne(), saturation: 0.75, contrast: 0.5)
The hue should be a random value between 0 and 1.
If you put this inside a function, you could call the function to change the colour of the ball inside and SKAction or anywhere else.

Related

Trail of nodes along a path as in hello world

hi I have a node traveling back and forth along a path. I am trying to integrate the moving shapeNode as in the hello world example to trail my moving node. I'm using a trigger from frame update to trigger the copying of the shape node. And using the position of the travelling node.
The problem is that the trail has some sort of offset and i don't know where its coming from. I have tried to compensate but to no avail. I'm wondering if it might have anything to do with the actions. Thanks for reading.
I have tried looking at this link but i cannot translate
Here is my code so far
spriteKit xcode 9 swift 4 iPad Air
// GameScene.swift
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var borderBody = SKPhysicsBody()
var myMovingNode = SKSpriteNode()
var trailNode : SKShapeNode?
override func didMove(to view: SKView) {
//Define Border
borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
physicsWorld.contactDelegate = self
//Define myMovingNode
myMovingNode = SKSpriteNode(imageNamed: "greenball")
myMovingNode.size.width = 50
myMovingNode.size.height = 50
let myMovingNodeBody:CGSize = CGSize(width: 50, height: 50)
myMovingNode.physicsBody = SKPhysicsBody.init(rectangleOf: myMovingNodeBody)
myMovingNode.zPosition = 2
addChild(myMovingNode)
//Make Path for myMovingNode to travel
let myPath = CGMutablePath()
myPath.move(to: CGPoint(x: 30, y: 30))
myPath.addLine(to: CGPoint(x: 500, y: 500))
/*This is another path to try*/
// myPath.addCurve(to: CGPoint(x: 800, y: 700), control1: CGPoint(x: 500, y: 30), control2: CGPoint(x: 50, y: 500))
/*This draws a line along myPath*/
let myLine = SKShapeNode(path: myPath)
myLine.lineWidth = 10
myLine.strokeColor = .green
myLine.glowWidth = 0.5
myLine.zPosition = 2
addChild(myLine)
/*This sets myMovingNode running along myPath*/
let actionForward = SKAction.follow(myPath, asOffset: false, orientToPath: true, duration: 10)
let actionReverse = actionForward.reversed()
let wait = SKAction.wait(forDuration: 1)
let actionSequence = SKAction.sequence([actionForward, wait, actionReverse, wait])
myMovingNode.run(SKAction.repeatForever(actionSequence))
/*This defines TrailNode and its actions*/
trailNode = SKShapeNode.init(rectOf: CGSize.init(width: 20, height: 20), cornerRadius: 10)
if let trailNode = trailNode {
trailNode.lineWidth = 1
trailNode.fillColor = .cyan
trailNode.run(SKAction.sequence([SKAction.wait(forDuration: 5),
SKAction.fadeOut(withDuration: 3),
SKAction.removeFromParent()]))
}//Eo if Let
}//eo overdrive
func timeFunction (){/*This is controlled by func update*/
let n = trailNode?.copy() as! SKShapeNode?
/*this is where i'm trying to compensate*/
let movingNodeX = myMovingNode.position.x
let movingNodeY = myMovingNode.position.y
let movingNodeOffSet = CGPoint(x: movingNodeX - 0, y: movingNodeY - 0)
n?.position = movingNodeOffSet
myMovingNode.addChild(n!)
}
var frameCounter = 0
override func update(_ currentTime: TimeInterval) {
// print( "Called before each frame is rendered")
if frameCounter == 10{frameCounter = 0; timeFunction()}
frameCounter = frameCounter + 1
}
}//class GameScene
hi in answer to my own question. It was the simplest. On the last line change from
myMovingNode.addChild(n!) to addChild(n!)

Swift 3 Expect Argument Error

I am creating some functions in order to make a game similar to Flappy Bird. I am a complete beginner and am trying to understand everything fully as I go before moving on. I have been able to get my obstacle to move but when I attempt to put it into a function to allow me more flexibility later on with multiple obstacles I receive an error.
'Cannot convert type '()' to expected argument type 'SKAction'
class GameScene : SKScene, SKPhysicsContactDelegate {
var Player = SKSpriteNode()
var Ground = SKSpriteNode()
var Roof = SKSpriteNode()
var Background = SKSpriteNode()
let Obstacle1 = SKSpriteNode(imageNamed: "Fire Barrel 1")
override func didMove(to view: SKView) {
// Create Background Color
backgroundColor = bgColor
// Set World Gravity
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -4.0)
// Create Player
Player = SKSpriteNode(imageNamed: "Player")
Player.setScale(0.5)
Player.position = CGPoint(x: -self.frame.width / 2 + 100, y: -Player.frame.height / 2)
self.addChild(Player)
// Create Ground
Ground = SKSpriteNode(imageNamed: "BGTileBtm")
Ground.anchorPoint = CGPoint(x: 0,y: 0.5)
Ground.position = CGPoint(x: -self.frame.width / 2, y: -self.frame.height / 2)
self.addChild(Ground)
// Create Roof
Roof = SKSpriteNode(imageNamed: "BGTileTop")
Roof.anchorPoint = CGPoint(x: 1,y: 1)
Roof.position = CGPoint(x: -self.frame.width / 2, y: self.frame.height / 2 - Roof.frame.height)
Roof.zRotation = CGFloat(M_PI)
self.addChild(Roof)
// Set Physics Rules
Player.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: Player.size)
Player.physicsBody!.affectedByGravity = true
Player.physicsBody!.allowsRotation = false
Ground.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ground"), size: Ground.size)
Ground.physicsBody!.affectedByGravity = false
Ground.physicsBody!.isDynamic = false
// Obstacle
func addObstacle1(){
Obstacle1.position = CGPoint(x: self.frame.width / 2, y: -self.frame.height / 2 + Obstacle1.frame.height)
Obstacle1.zPosition = 1
addChild(Obstacle1)
}
func moveObstacle1(){
let distance = CGVector(dx: -self.frame.width, dy: 0)
let moveDistance = SKAction.move(by: distance, duration: 5)
run(moveDistance)
}
addObstacle1()
Obstacle1.run(moveObstacle1())
}
Change the declaration of moveObstacle1 to this:
func moveObstacle1() -> SKAction{
let distance = CGVector(dx: -self.frame.width, dy: 0)
let moveDistance = SKAction.move(by: distance, duration: 5)
return moveDistance
}
EDIT:
Regarding your comment,
run is a method. When you call it, it runs the SKAction you passed in. That's it! What you are trying to do is run(moveObstacle1()). What does that mean exactly? How can you pass a method call as a parameter? At runtime, the return value of moveObstacle1() is passed to run. In other words, for run(moveObstacle1()) to compile, moveObstacle1() must return a value using the return statement. And that value must be of type SKAction, since that's the thing you're passing to run.
return is used to return a value from a moveObstacle1(), so that you can call run(moveObstacle1()).
run is just a regular old method.
Change:
Obstacle1.run()
To:
Obstacle1.run(SKAction(moveObstacle1()))
The error message is pretty clear, you need to pass a SKAction to your SKSpriteNode.
Edit
import SpriteKit
import GameplayKit
class GameScene : SKScene, SKPhysicsContactDelegate {
var Player = SKSpriteNode()
var Ground = SKSpriteNode()
var Roof = SKSpriteNode()
var Background = SKSpriteNode()
let Obstacle1 = SKSpriteNode(imageNamed: "Spaceship")
let sprite = SKSpriteNode(imageNamed:"Spaceship")
override func didMove(to view: SKView) {
// Create Background Color
backgroundColor = UIColor.green
// Set World Gravity
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -4.0)
// Create Player
Player = SKSpriteNode(imageNamed: "Player")
Player.setScale(0.5)
Player.position = CGPoint(x: -self.frame.width / 2 + 100, y: -Player.frame.height / 2)
self.addChild(Player)
// Create Ground
Ground = SKSpriteNode(imageNamed: "BGTileBtm")
Ground.anchorPoint = CGPoint(x: 0,y: 0.5)
Ground.position = CGPoint(x: -self.frame.width / 2, y: -self.frame.height / 2)
self.addChild(Ground)
// Create Roof
Roof = SKSpriteNode(imageNamed: "BGTileTop")
Roof.anchorPoint = CGPoint(x: 1,y: 1)
Roof.position = CGPoint(x: -self.frame.width / 2, y: self.frame.height / 2 - Roof.frame.height)
Roof.zRotation = CGFloat(M_PI)
self.addChild(Roof)
// Set Physics Rules
Player.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: Player.size)
Player.physicsBody!.affectedByGravity = true
Player.physicsBody!.allowsRotation = false
Ground.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Ground"), size: Ground.size)
Ground.physicsBody!.affectedByGravity = false
Ground.physicsBody!.isDynamic = false
sprite.size = CGSize(width:50, height: 50)
sprite.position = CGPoint(x:self.frame.midX, y:self.frame.midY);
self.addChild(sprite)
Obstacle1.run(SKAction(moveObstacle1()))
}
func moveObstacle1(){
let action = SKAction.moveTo(x: self.frame.size.width * 2, duration: 20)
sprite.run(action)
}
}

Playable Game Area in Swift 3.0

I'm creating a mario clone for mac to help me learn swift programming. An issue I have come across is setting a playable game area. As of now, the 2 backgrounds I have ("background" and "level") will move when the left or right keys are pressed but grey areas at the sides will become visible. I want to be able to make the backgrounds move, but set an area at which they will stop moving.
import SpriteKit
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: "mario")
let background = SKSpriteNode(imageNamed: "background")
let level = SKSpriteNode(imageNamed: "level")
override func didMove(to view: SKView) {
/* Setup your scene here */
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
level.size = self.size
level.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
level.zPosition = 1
self.addChild(level)
background.size = self.size
background.position = CGPoint(x: level.size.width + 511, y: self.size.height/2)
background.zPosition = 0
self.addChild(background)
player.setScale(0.23)
player.position = CGPoint(x: self.size.width/8, y: 120)
player.zPosition = 2
self.addChild(player)
}
override func keyDown(with theEvent: NSEvent) {
let keyCode = theEvent.keyCode
//Moving Right
if keyCode == 124 {
level.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
background.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
player.xScale = 0.23
}
//Moving Left
if keyCode == 123 {
background.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
level.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
player.xScale = -0.23
}
//Jump
if keyCode == 126{
player.run(SKAction.sequence([SKAction.moveTo(y: 250, duration: 0.15),SKAction.moveTo(y: 120, duration: 0.2)]))
}
}
}
I'm sure there is a much better, more clever solution, but off the top of my head you could add:
let LEFTBOUND: Int = //Insert the left limit here.
let RIGHTBOUND: Int = //Insert the right limit here.
Inside func keyDown(), in the moving right/left if-statements, nest these:
if level.position.x < RIGHTBOUND:
//Move level so long as it is less than the RIGHTBOUND value.
//TODO: set the level.position to RIGHTBOUND so it doesn't keep updating with new values while keyDown is true.
if level.position.x > LEFTBOUND:
//Move level so long as it is greater than the LEFTBOUND value.
//TODO: set the level.position to LEFTBOUND so it doesn't keep updating with new values while the keyDown is true.
//Repeat theses steps for background.position.x
You should also restrict the player movement so the player doesn't run out of the level unrecoverably.

How to code the accelerometer to have a fixed x position

I have a ship that moves based on the direction of gravity which is changed by the accelerometer, but I want it to only move along the width of the screen (I already know how to do that), but I do not know how to keep it on a fixed horizontal line. Here's my code
class GameScene: SKScene {
var manager = CMMotionManager()
var ship = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let shipTexture = SKTexture(imageNamed: "EvadersShipVert2.png")
ship = SKSpriteNode(texture: shipTexture)
ship.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) - 250)
ship.size = CGSize(width: 90, height: 115)
shipTexture.filteringMode = SKTextureFilteringMode.Nearest
ship.zPosition = 2
ship.physicsBody = SKPhysicsBody(texture: shipTexture, size: CGSize(width: 90, height: 115))
ship.physicsBody?.dynamic = true
self.addChild(ship)
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = 0.1
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) {
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!), CGFloat((data?.acceleration.y)!))
}
}
thanks in advance!
Replace
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!), CGFloat((data?.acceleration.y)!))
With
self.physicsWorld.gravity = CGVectorMake(0.0, CGFloat((data?.acceleration.y)!)
So you're not affecting the x position

Making SKLabelNode as a crop node of SKShapeNode

Can't find in web, how to make SKLabelNode cropping SKShapeNode. When i colorize the background, my goal is colorize the label too with the same method, so both of them have to colorize simultaneously. But can't imagine how to crop SKShapeNode with this label. Help me please!
But can't imagine how to crop SKShapeNode with this label.
If I understand you correctly, you can set SKLabelNode as a mask of a SKCropNode, like this:
override func didMoveToView(view: SKView) {
backgroundColor = .blackColor()
let cropNode = SKCropNode()
cropNode.position = CGPoint(x: frame.midX, y: frame.midY)
cropNode.zPosition = 1
let mask = SKLabelNode(fontNamed: "ArialMT")
mask.text = "MASK"
mask.fontColor = .greenColor()
mask.fontSize = 28
cropNode.maskNode = mask
let nodeToMask = SKSpriteNode(color: .purpleColor(), size: CGSize(width: 200, height: 200))
nodeToMask.position = CGPoint(x: 0, y: 0)
nodeToMask.name = "character"
cropNode.addChild(nodeToMask)
//Now colorize the sprite which acts like background
let colorize = SKAction.sequence([
SKAction.colorizeWithColor(.orangeColor(), colorBlendFactor: 0, duration: 1),
SKAction.colorizeWithColor(.purpleColor(), colorBlendFactor: 0, duration: 1)
])
nodeToMask.runAction(SKAction.repeatActionForever(colorize), withKey: "colorizing")
addChild(cropNode)
}
The result: