I'm constructing a Swift Playground with UIKit that contains an SKScene. After creating the code I'm planning to use within an Xcode project, I tried to adapt it into my Playground.
I encountered an issue while running the Playground - when a new SKSpriteNode is added to the Scene, the new and existing nodes jitter and lag for a split-second, then return to smooth 60fps. You can see this issue in effect here. No errors or warnings are spat, and the console is completely empty.
The scene in the project didn't have this issue, and I can't recreate it on another project - even when I directly copy-paste the code from the Playground to the project.
I searched the internet for answers to this issue and all I found was this StackOverflow question with no answers and the link to OP's supposed solution broken.
The code that is in effect for this SKScene is below.
class SimulatorController: UIViewController {
override func loadView() {
let view = SKView()
let scene = GameScene(size: view.bounds.size)
view.showsFPS = true
view.showsNodeCount = true
view.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
view.presentScene(scene)
self.view = view
}
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
run(SKAction.repeatForever(SKAction.sequence([
SKAction.run(addNeutron),
SKAction.wait(forDuration: 1)
])
))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func addNeutron() {
let neutron = SKSpriteNode(imageNamed: "neutron.heic")
neutron.size = CGSize(width: 20, height: 20)
neutron.physicsBody = SKPhysicsBody(rectangleOf: neutron.size)
neutron.physicsBody?.isDynamic = true
let actualX = random(min: 0, max: size.width)
let actualY = random(min: 0, max: size.height)
neutron.position = CGPoint(x: actualX, y: actualY)
addChild(neutron)
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
let actualX2 = random(min: 0, max: size.width)
let actualY2 = random(min: 0, max: size.height)
let actionMove = SKAction.move(to: CGPoint(x: actualX2, y: actualY2), duration: TimeInterval(actualDuration))
let actionMoveDone = SKAction.removeFromParent()
neutron.run(SKAction.sequence([actionMove, actionMoveDone]))
}
}
Thanks so much for your help!
Related
My code works perfectly fine for one common background but I am unable to figure out the required logic have it working perfectly fine if the backgrounds were unique.
override func didMove(to view: SKView)
{
createBackground()
}
func createBackground ()
{
var texture1 = SKTexture(imageNamed: "card1")
let texture2 = SKTexture(imageNamed: "card2")
for i in 0...1
{
let background = SKSpriteNode(texture: texture1)
background.anchorPoint = CGPoint.zero
background.position = CGPoint(x: 0, y: texture1.size().height * CGFloat(i))
addChild(background)
let moveDown = SKAction.moveBy(x: 0, y: -background.size.height, duration: 5)
let reset = SKAction.moveBy(x: 0, y: background.size.height, duration: 0)
let sequence = SKAction.sequence([moveDown, reset])
let forever = SKAction.repeatForever(sequence)
background.run(forever)
texture1 = texture2
}
}
}
Here is my code:
var red: SKSpriteNode?
var redHolding = false
/////
// MARK: didMove
/////
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
if let r = self.childNode(withName: "red") as? SKSpriteNode { red = r }
createRope(red!, 10)
}//
func createRope(_ anchorSprite: SKSpriteNode, _ numOfLinks: Int) {
var links: [SKSpriteNode] = []
anchorSprite.position = CGPoint(x: 0, y: 0)
for i in 0...numOfLinks {
let link = SKSpriteNode(color: UIColor.black, size: CGSize(width: 20, height: 30))
link.position = CGPoint(x: anchorSprite.frame.midX, y: anchorSprite.frame.minY - link.frame.height/2)
link.physicsBody? = SKPhysicsBody(rectangleOf: link.size)
link.physicsBody?.isDynamic = true
link.physicsBody?.allowsRotation = true
link.physicsBody?.affectedByGravity = true
link.physicsBody?.categoryBitMask = 4
self.addChild(link)
var joint = SKPhysicsJointPin()
links.append(link)
if i == 0 {
joint = SKPhysicsJointPin.joint(withBodyA: anchorSprite.physicsBody!, bodyB: link.physicsBody!, anchor: CGPoint(x: anchorSprite.frame.midX, y: anchorSprite.frame.minY))
} else {
link.position = CGPoint(x: anchorSprite.frame.midX, y: (links[i-1].frame.minY - links[i-1].frame.height/2) - link.frame.height/2)
joint = SKPhysicsJointPin.joint(withBodyA: links[i-1].physicsBody!, bodyB: links[i].physicsBody!, anchor: CGPoint(x: links[i-1].frame.minX, y: links[i-1].frame.minY))
}
physicsWorld.add(joint)
}
}
I'm trying to build a function that programmatically creates a rope and I could be way off so far, but I'm having an issue and I don't understand why.
I get an error "unexpectedly found nil while unwrapping optional"
It happens for the "Link" variable with its physics body when I try to make the Joint.
I don't understand why and I keep moving things around to try to fix it with no success.
Does anyone see what is wrong with the code?
Thanks for any help.
You need to remove the ? after the physicsBody property of the link.
Old:
link.physicsBody? = SKPhysicsBody(rectangleOf: link.size)
New:
link.physicsBody = SKPhysicsBody(rectangleOf: link.size)
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)
}
}
I am new is swift programming. I want to spawn an enemies into a random positions using a Class node. I tried to search a code for a random spawning of an enemies but it seems it is irrelevant for my code.
Here is the code I searched for the random spawning.
import SpriteKit
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed:"spacemonkey_fly02")
override func didMoveToView(view: SKView) {
player.position = CGPoint(x:frame.size.width * 0.1, y: frame.size.height * 0.5)
addChild(player)
backgroundColor = SKColor.blackColor()
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(spawnEnemy),
SKAction.waitForDuration(1.0)])))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func spawnEnemy() {
let enemy = SKSpriteNode(imageNamed: "boss_ship")
enemy.name = "enemy"
enemy.position = CGPoint(x: frame.size.width, y: frame.size.height * random(min: 0, max: 1))
addChild(enemy)
}
}
Here is the Class I made that I want to spawn randomly in my gamescene
import SpriteKit
class Meteor: SKSpriteNode, GameSprite {
var textureAtlas:SKTextureAtlas = SKTextureAtlas(named:"meteor.atlas")
var meteorAnimation = SKAction()
func spawn(parentNode: SKNode, position: CGPoint, size: CGSize = CGSize(width: 30, height: 30)) {
parentNode.addChild(self)
meteorRotation()
self.size = size
self.position = position
self.texture = textureAtlas.textureNamed("meteor-1.png")
self.physicsBody = SKPhysicsBody(texture: textureAtlas.textureNamed("meteor-1.png"), size: size)
self.physicsBody = SKPhysicsBody(circleOfRadius: size.width / 2)
self.physicsBody?.affectedByGravity = true
self.runAction(meteorAnimation)
}
func meteorRotation() {
let meteorCycle = SKAction.rotateByAngle(4, duration: 2);
meteorAnimation = SKAction.repeatActionForever(meteorCycle)
}
func onTap() {
//self.physicsBody?.affectedByGravity = false
self.physicsBody?.dynamic = false
self.physicsBody?.categoryBitMask = 0
let crashAnimation = SKAction.group([
SKAction.fadeAlphaTo(0, duration: 0.2),
SKAction.scaleTo(1.5, duration: 0.2),
SKAction.moveBy(CGVector(dx: 0, dy: 25), duration: 0.2)
])
let resetAfterCrashed = SKAction.runBlock {
self.position.y = 5000
self.alpha = 1
self.xScale = 1
self.yScale = 1
}
let crashSequence = SKAction.sequence([
crashAnimation,
resetAfterCrashed
])
self.runAction(crashSequence)
}
}
Is it possible to use the code I searched for a class node?
I tried to modify the random spawning code you found to use your meteor class. I took out any lines that didn't involve the meteors, so you'll need to add your world, earth, and field code as you sent me. Give this a shot and let me know if it works out:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.blackColor()
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(spawnEnemy),
SKAction.waitForDuration(1.0)])))
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func spawnEnemy() {
let newMeteor = Meteor()
let meteorPosition = CGPoint(x: frame.size.width, y: frame.size.height * random(min: 0, max: 1))
newMeteor.spawn(world, meteorPosition)
}
}
Right now, I am creating a new game that will involve a ball that moves up and down the screen by itself without touching a button or the screen. I have been looking everywhere for a solution, but I haven't found one. I know how to make the ball move up and down in objective-c, but not in swift or spritekit. I don't know if the following code will help or not. Any solution is accepted.
class GamePlayScene: SKScene, SKPhysicsContactDelegate {
var ball = SKSpriteNode(imageNamed: "green ball.png")
override func didMoveToView(view: SKView) {
//setup scene
physicsWorld.gravity = CGVector.zeroVector
self.scene?.backgroundColor = UIColor.blackColor()
self.scene?.size = CGSize(width: 640, height: 1136)
ball.position = CGPoint (x: self.size.width * 0.5, y: self.size.width * 0.9)
ball.size = CGSize (width: 80, height: 82)
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
self.addChild(ball)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
if ball.position.y == 600 {
self.ball.runAction(moveBallUp)
} else {
self.ball.runAction(moveBallDown)
}
}
}
I suggest you use repeatActionForever. If you start your ball position from the bottom of the screen this sequence should work. If you want to start the ball from the top then switch the positions of the actions in moveUpAndDown with each other.
func moveSprite() {
let moveBallUp = SKAction.moveToY(600, duration: 0.4)
let moveBallDown = SKAction.moveToY(293, duration: 0.4)
let moveUpAndDown = SKAction.sequence([moveBallUp, moveBallDown])
let moveUpAndDownForever = SKAction.repeatActionForever(moveUpAndDown)
ball.runAction(moveUpAndDownForever)
}