I am running a for loop in my game that would generate SKSpriteNodes.
I want my first block to start at (20, 40) and add 20 to the x position and 40 to the y position for every new block.
First block (20, 40)
Second block (40, 80)
Third Block (60, 120)
And so on.
The y position is behaving the way I want to, however the x position would randomly start anywhere on my screen then add 20 for every new block generated.
Below is my code.
func generateBlocks() {
for _ in 1..<5 {
xP += 20
yP += 40
let xx = CGFloat (xP)
let yy = CGFloat (yP)
print("x position: \(xx)")
print("y position: \(yy)")
let resourcePath = NSBundle.mainBundle().pathForResource("Blocks", ofType: "sks")
let blocks = MSReferenceNode(URL: NSURL (fileURLWithPath: resourcePath!))
blocks.goals.position = CGPoint (x: xx,y: yy)
addChild(blocks)
}
}
I also added the print statement of how it would look like when I run the code.
Below is the print statement of the x and y positions of the blocks.
I tried switching the order of the xP and yP initializing. That didn't work.
Any suggestions?
I would prefer to use while loops for this kind of operations where you must impose a condition from start, for example limits:
class GameScene: SKScene {
let deltaX:CGFloat = 20.0
let deltaY:CGFloat = 40.0
override func didMoveToView(view: SKView) {
let widthLimit = self.frame.width-40
let heightLimit = self.frame.height-140
let startX:CGFloat = 80.0
drawStair(startX,limits:CGSizeMake(widthLimit,heightLimit))
}
func drawStair(startFromX:CGFloat,limits:CGSize) {
var currentX:CGFloat = startFromX
var currentY:CGFloat = startFromX+deltaY
while currentX<limits.width && currentY<limits.height {
drawBlock(CGPointMake(currentX,currentY))
currentX += deltaX
currentY += deltaY
}
}
func drawBlock(position: CGPoint,size:CGSize = CGSizeMake(40,40)) {
let block = SKSpriteNode(color: UIColor.redColor(), size: size)
block.position = position
block.name = "block"
addChild(block)
}
}
Output:
Related
I've been asked to simplify this question, so that's what I'm doing.
I'm struggling in SpriteKit's physic joints (and possibly physic body properties). I tried every single subclass and many configurations but seams like nothing works or I'm doing something wrong.
I'm developing Snake game. User controls head of snake which should move at constant speed ahead and user can turn it clockwise or anticlockwise. All the remaining snake's pieces should follow the head - they should travel exactly the same path that head was some time ago.
I think for this game the Pin joint should be the answer, which anchor point is exactly in the centre between elements.
Unfortunately the result is not perfect. The structure should make the perfect circle, but it doesn't. I'm attaching the code, and gif showing the current effect. Is anyone experience enough to give me any suggestion what properties of physic body and or joints should are apply here for desired effect?
My code:
class GameScene: SKScene {
private var elements = [SKNode]()
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
let dummyTurnNode = SKNode()
dummyTurnNode.position = CGPoint(x: size.width / 2 - 50, y: size.height / 2)
let dummyTurnBody = SKPhysicsBody(circleOfRadius: 1)
dummyTurnBody.isDynamic = false
dummyTurnNode.physicsBody = dummyTurnBody
addChild(dummyTurnNode)
for index in 0..<5 {
let element = SKShapeNode(circleOfRadius: 10)
let body = SKPhysicsBody(circleOfRadius: 10)
body.linearDamping = 0
// body.mass = 0
element.physicsBody = body
element.position = CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index))
elements.append(element)
addChild(element)
let label = SKLabelNode(text: "A")
label.fontSize = 10
label.fontName = "Helvetica-Bold"
element.addChild(label)
if index == 0 {
element.fillColor = UIColor.blue()
body.velocity = CGVector(dx: 0, dy: 30)
let dummyTurnJoint = SKPhysicsJointPin.joint(withBodyA: dummyTurnBody, bodyB: body, anchor: dummyTurnNode.position)
physicsWorld.add(dummyTurnJoint)
} else {
body.linearDamping = 1
element.fillColor = UIColor.red()
let previousElement = elements[index - 1]
let connectingJoint = SKPhysicsJointPin.joint(withBodyA: previousElement.physicsBody!, bodyB: body, anchor: CGPoint(x: size.width / 2, y: size.height / 2 - 30 * CGFloat(index) + CGFloat(15)))
physicsWorld.add(connectingJoint)
}
}
}
override func update(_ currentTime: TimeInterval) {
let head = elements.first!.physicsBody!
var velocity = head.velocity
velocity.normalize()
velocity.multiply(30)
head.velocity = velocity
}
}
extension CGVector {
var rwLength: CGFloat {
let xSq = pow(dx, 2)
let ySq = pow(dy, 2)
return sqrt(xSq + ySq)
}
mutating func normalize() {
dx /= rwLength
dy /= rwLength
}
mutating func multiply(_ factor: CGFloat) {
dx *= factor
dy *= factor
}
}
"All the remaining snake's pieces should follow the head - they should travel exactly the same path that head was some time ago."
You should note that with Physics joints you are likely going to have variance no matter what you do. Even if you have it close to perfect you'll have rounding errors under the hood making the path not exact.
If all the tail parts are equal you can also use a different approach, this is something I've done for a comet tail. Basically the idea is that you have an array of tail objects and per-frame move move the last tail-object always to the same position as the head-object. If the head-object has a higher z-position the tail is drawn below it.
If you need to keep your tail in order you could vary the approach by storing an array of head-positions (per-frame path) and then place the tail objects along that path in your per-frame update call to the snake.
See my code below for example:
These are you head-object variables:
var tails = [SKEmitterNode]()
var tailIndex = 0
In your head init function instantiate the tail objects:
for _ in 0...MAX_TAIL_INDEX
{
if let remnant = SKEmitterNode(fileNamed: "FireTail.sks")
{
p.tails.append(remnant)
}
}
Call the below per-frame:
func drawTail()
{
if tails.count > tailIndex
{
tails[tailIndex].resetSimulation()
tails[tailIndex].particleSpeed = velocity() / 4
tails[tailIndex].emissionAngle = zRotation - CGFloat(M_PI_2) // opposite direction
tails[tailIndex].position = position
tailIndex = tailIndex < MAX_TAIL_INDEX ? tailIndex + 1 : 0
}
}
The resulting effect is actually really smooth when you call it from the scene update() function.
i want to be able move the ball with pause every 20 pixels moved i tried this one but didn't do nothing, the ball stays at the point where it started it doesn't move to the end of the screen,i know i didn't put the waitForDuration , because i wanted to check if it will move or not
func spwan()
{
let ball:SKSpriteNode = SKScene(fileNamed: "Ball")?.childNodeWithName("ball") as! SKSpriteNode
ball.removeFromParent()
self.addChild(ball)
ball.name = "spriteToTrack"
ball.zPosition = 0
ball.position = CGPointMake(1950, 1000)
var num:CGFloat = ball.position.x
let a1 = SKAction.moveToX(num - 20, duration: 10)
// i want to get to -50 let a1 = SKAction.moveToX(-50 , duration: 10)
let minus = SKAction.runBlock{
num -= 20
}
let sq1 = SKAction.sequence([a1,minus])
ball.runAction(SKAction.repeatAction(sq1, count: 10)
}
The ball should move 20 pixels at least in the above code, but 20 pixels in 10 seconds might seen like a standstill. Anyways I think you've overcomplicated things quite a bit by using moveToX rather than moveBy:, so (with a bit of rejigging) you're probably better of with something like this:
func spawn() {
let x: CGFloat = 1950
let xDelta: CGFloat = -20
let xDestination: CGFloat = -50
let repeats = Int((x - xDestination)/fabs(xDelta))
let move = SKAction.moveBy(CGVectorMake(xDelta, 0), duration: 2) // made it a lot quicker to show that stuff is happening
move.timingMode = .EaseInEaseOut
let ball:SKSpriteNode = SKScene(fileNamed: "Ball")?.childNodeWithName("ball") as! SKSpriteNode
ball.removeFromParent()
ball.name = "spriteToTrack"
ball.position = CGPointMake(x, 1000)
addChild(ball)
ball.runAction(SKAction.repeatAction(move, count: repeats))
}
I am generating Y position for SKSpriteNode. I need to generate y position, but it should not be in the position previously generated image, should not overlap. How would I be able to do this in Swift in SpriteKit?
for _ in 1...3 {
let lower : UInt32 = 100
let upper : UInt32 = UInt32(screenSize.height) - 100
let randomY = arc4random_uniform(upper - lower) + lower
obstaclesTexture = SKTexture(imageNamed: "levaPrekazka")
obstacle = SKSpriteNode(texture: obstaclesTexture)
prekazka = obstacle.copy() as! SKSpriteNode
prekazka.name = "prekazka"
prekazka.position = CGPoint(x: 0, y: Int(randomY))
prekazka.zPosition = 10;
prekazka.size = CGSize(width: screenSize.width / 7, height: screenSize.height / 7)
prekazka.physicsBody = SKPhysicsBody(texture: obstaclesTexture, size: obstacle.size)
prekazka.physicsBody?.dynamic = false
prekazka.physicsBody?.categoryBitMask = PhysicsCatagory.obstacle
prekazka.physicsBody?.contactTestBitMask = PhysicsCatagory.ball
prekazka.physicsBody?.contactTestBitMask = PhysicsCatagory.ball
self.addChild(prekazka)
}
To avoid both overlapping sprites and different positions you need to take the height of the sprite into consideration. Something like this perhaps:
let spriteHeight = 60 // or whatever
let lower = 100
let upper = Int(screenSize.height) - 100
let area = upper - lower
let ySpacing = area/spriteHeight
var allowedYs = Set<CGFloat>()
let numberOfSprites = 3
while allowedYs.count < numberOfSprites {
let random = arc4random_uniform(UInt32(spriteHeight))
let y = CGFloat(random) * CGFloat(ySpacing)
allowedYs.insert(y)
}
for y in allowedYs {
obstaclesTexture = SKTexture(imageNamed: "levaPrekazka")
obstacle = SKSpriteNode(texture: obstaclesTexture)
prekazka = obstacle.copy() as! SKSpriteNode
prekazka.name = "prekazka"
prekazka.position = CGPoint(x: 0, y: y)
// etc...
}
I would be tempted to generate the sprite's position and then loop over all existing sprites in the scene and see if their frame overlaps with the new sprites frame:
let lower : UInt32 = 100
let upper : UInt32 = UInt32(screenSize.height) - 100
obstaclesTexture = SKTexture(imageNamed: "levaPrekazka")
obstacle = SKSpriteNode(texture: obstaclesTexture)
// Generate a position for our new sprite. Repeat until the generated position cause the new sprite’s frame not to overlap with any existing sprite’s frame.
repeat {
let randomY = arc4random_uniform(upper - lower) + lower
prekazka.position = CGPoint(x: 0, y: Int(randomY))
} while checkForOverlap(prekazka) = true // If it overlaps, generate a new position and check again
//Function to see if an SkSpriteNode (passed as parameter node) overlaps with any existing node. Return True if it does, else False.
Func checkForOverlap(SKSpriteNode: node) ->> bool {
for existingNode in children { // for every existing node
if CGRectIntersectsRect(existingNode.frame, node.frame) {return True} // return True if frames overlap
}
return false //No overlap
}
(Writing this from memory, so may need a bit of fiddling to work).
Another approach would be to generate and position all the nodes with physics bodies, and set the scene up so that all nodes collide with all others. Then allow the physics engine to handle any collisions which would cause it to move the nodes around until none are overlapping. At this point, your could remove all the physics bodies.
I'm trying to stop my nodes from falling just for a second or two at the start of my game. So my problem is when I push start the nodes are already halfway down the screen. I also tried changing how high the nodes start but it seems like a costly solution since I want to be careful not to let my FPS get too low. In my code I am trying to do this in the didMoveToView and I am using waitForDuration but it doesn't work.
Example Image of Nodes Falling Down
Any SpriteKit masters know what I should do? I'm using Swift.
Here is my code:
override func didMoveToView(view: SKView) {
let wait = SKAction.waitForDuration(2.5)
let run = SKAction.runBlock {
self.spawnNumbers()
}
numContainer.runAction(SKAction.sequence([wait, run]))
}
func spawnNumbers() {
let minValue = self.size.width / 8
let maxValue = self.size.width - 36
let spawnPoint = CGFloat(arc4random_uniform(UInt32(maxValue - minValue)))
let action = SKAction.moveToY(-300, duration: 2)
numContainer = SKSpriteNode(imageNamed: "Circle")
numContainer.name = "Circle"
numContainer.size = CGSize(width: 72, height: 72)
numContainer.anchorPoint = CGPointMake(0, 0)
numContainer.position = CGPoint(x: spawnPoint, y: self.size.height)
numContainer.runAction(SKAction.repeatActionForever(action))
numContainer.zPosition = 2
let numberLabel = SKLabelNode(fontNamed: "AvenirNext-Bold")
numberLabel.text = "\(numToTouch)"
numberLabel.name = "Label"
numberLabel.zPosition = -1
numberLabel.position = CGPointMake(CGRectGetMidX(numContainer.centerRect) + 36, CGRectGetMidY(numContainer.centerRect) + 36)
numberLabel.horizontalAlignmentMode = .Center
numberLabel.verticalAlignmentMode = .Center
numberLabel.fontColor = UIColor.whiteColor()
numberLabel.fontSize = 28
addChild(numContainer)
numContainer.addChild(numberLabel)
numContainerArray.append(numContainer)
numToTouch += 1
}
I think you have to cast the 2.5 seconds to an NSTimeInterval:
let wait = SKAction.waitForDuration(NSTimeInterval(2.5))
You could also try putting the actions in the init function for your scene:
override init (size: CGSize) {
super.init(size: size)
//your code from before
}
Also, not sure if it matters, but this is what I normally do and it works,
let wait = SKAction.waitForDuration(NSTimeInterval(2.5))
let action = SKAction.runBlock({() in self.spawnNumbers()})
let actionThenWait = SKAction.sequence([wait, action])
self.runAction(actionThenWait)
I've been racking my brain and searching here and all over to try to find out how to generate a random position on screen to spawn a circle. I'm hoping someone here can help me because I'm completely stumped. Basically, I'm trying to create a shape that always spawns in a random spot on screen when the user touches.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenHeight = screenSize.height
let screenWidth = screenSize.width
let currentBall = SKShapeNode(circleOfRadius: 100)
currentBall.position = CGPointMake(CGFloat(arc4random_uniform(UInt32(Float(screenWidth)))), CGFloat(arc4random_uniform(UInt32(Float(screenHeight)))))
self.removeAllChildren()
self.addChild(currentBall)
}
If you all need more of my code, there really isn't any more. But thank you for whatever help you can give! (Just to reiterate, this code kind of works... But a majority of the spawned balls seem to spawn offscreen)
The problem there is that you scene is bigger than your screen bounds
let viewMidX = view!.bounds.midX
let viewMidY = view!.bounds.midY
print(viewMidX)
print(viewMidY)
let sceneHeight = view!.scene!.frame.height
let sceneWidth = view!.scene!.frame.width
print(sceneWidth)
print(sceneHeight)
let currentBall = SKShapeNode(circleOfRadius: 100)
currentBall.fillColor = .green
let x = view!.scene!.frame.midX - viewMidX + CGFloat(arc4random_uniform(UInt32(viewMidX*2)))
let y = view!.scene!.frame.midY - viewMidY + CGFloat(arc4random_uniform(UInt32(viewMidY*2)))
print(x)
print(y)
currentBall.position = CGPoint(x: x, y: y)
view?.scene?.addChild(currentBall)
self.removeAllChildren()
self.addChild(currentBall)
First: Determine the area that will be valid. It might not be the frame of the superview because perhaps the ball (let's call it ballView) might be cut off. The area will likely be (in pseudocode):
CGSize( Width of the superview - width of ballView , Height of the superview - height of ballView)
Once you have a view of that size, just place it on screen with the origin 0, 0.
Secondly: Now you have a range of valid coordinates. Just use a random function (like the one you are using) to select one of them.
Create a swift file with the following:
extension Int
{
static func random(range: Range<Int>) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
And now you can specify a random number as follows:
Int.random(1...1000) //generate a random number integer somewhere from 1 to 1000.
You can generate the values for the x and y coordinates now using this function.
Given the following random generators:
public extension CGFloat {
public static var random: CGFloat { return CGFloat(arc4random()) / CGFloat(UInt32.max) }
public static func random(between x: CGFloat, and y: CGFloat) -> CGFloat {
let (start, end) = x < y ? (x, y) : (y, x)
return start + CGFloat.random * (end - start)
}
}
public extension CGRect {
public var randomPoint: CGPoint {
var point = CGPoint()
point.x = CGFloat.random(between: origin.x, and: origin.x + width)
point.y = CGFloat.random(between: origin.y, and: origin.y + height)
return point
}
}
You can paste the following into a playground:
import XCPlayground
import SpriteKit
let view = SKView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
XCPShowView("game", view)
let scene = SKScene(size: view.frame.size)
view.presentScene(scene)
let wait = SKAction.waitForDuration(0.5)
let popIn = SKAction.scaleTo(1, duration: 0.25)
let popOut = SKAction.scaleTo(0, duration: 0.25)
let remove = SKAction.removeFromParent()
let popInAndOut = SKAction.sequence([popIn, wait, popOut, remove])
let addBall = SKAction.runBlock { [unowned scene] in
let ballRadius: CGFloat = 25
let ball = SKShapeNode(circleOfRadius: ballRadius)
var popInArea = scene.frame
popInArea.inset(dx: ballRadius, dy: ballRadius)
ball.position = popInArea.randomPoint
ball.xScale = 0
ball.yScale = 0
ball.runAction(popInAndOut)
scene.addChild(ball)
}
scene.runAction(SKAction.repeatActionForever(SKAction.sequence([addBall, wait])))
(Just make sure to also paste in the random generators, too, or to copy them to the playground's Sources, as well as to open the assistant editor so you can see the animation.)