Type 'Range<CGFloat> does not conform to protocol Sequence' (Swift 3) [duplicate] - swift

This question already has an answer here:
Swift 3: replace c style for-loop with float increment
(1 answer)
Closed 6 years ago.
I am trying to do a for loop here using CGFloat but I am getting an error saying that
Type 'Range does not conform to protocol Sequence'
The code that I am trying to run is as below. The error happens at the "for" loop at the end of the code.
func setupBackgroundSea(){
//putting the background//
let texture = SKTexture(imageNamed: "background")
texture.filteringMode = .nearest
//calculating the number of background images needed//
let needNumber = 2.0 + (self.frame.size.width / texture.size().width)
//creating animation//
let moveAnim = SKAction.moveBy(x: -(texture.size().width), y: 0.0, duration: TimeInterval(texture.size().width/10.0))
let resetAnim = SKAction.moveBy(x: texture.size().width, y: 0.0, duration: 0.0)
let repeatForeverAnim = SKAction.repeatForever(SKAction.sequence([moveAnim,resetAnim]))
//setting the position of the image and the animation//
for var i:CGFloat in CGFloat(0)..<needNumber{
let sprite = SKSpriteNode(texture: texture)
sprite.zPosition = -100.0
sprite.position = CGPoint(x: i*sprite.size.width, y: self.frame.size.height/2.0)
}
I am quite new at Swift, so this might be a pretty noob question but I would appreciate it if anyone could help :)

Range<T>, unlike CountableRange<T> isn't a sequence, because it's unclear how to iterate it. Add 1 every time? 0.1? 0.001?
You can use stride instead:
for i in stride(from: 0 as CGFloat, to: needNumber, by: +1 as CGFloat) { //...

Related

My SKSpriteNode speed values are not updating properly

What I'm trying to do is update my SKSpriteNodes so I can change their scrolling speeds dynamically, however they aren't really working consistently. I didn't include the code, but I have another method with a switch case that sets the value of platformSpeed whenever the state is changed (in this case, the switch case is changed with UIButtons). In my code I have an SKSpriteNode array and a platformSpeed property that includes didSet so my value is updated properly.
In my method to create the platforms, I grouped my SpriteNodes into platformGroup then looped through them with addChild(). Not sure why it's acting this way but here's a quick video of what it looks like in action:
demonstration clip
So with the buttons I'm changing the switch case, and as you can see, not all of the nodes speeds are updating properly and some get faster than others and eventually pass them. I need them to stay equal distance between each other.
Now here's my code:
class GameScene: SKScene, SKPhysicsContactDelegate {
var platformGroup = [SKSpriteNode]()
var platformSpeed: CGFloat = 1.0 {
didSet {
for platforms in platformGroup {
platforms.speed = platformSpeed
}
}
}
let platformTexture = SKTexture(imageNamed: "platform")
var platformPhysics: SKPhysicsBody!
func createPlatforms() {
let platformLeft = SKSpriteNode(texture: platformTexture)
platformLeft.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformLeft.physicsBody?.isDynamic = false
platformLeft.scale(to: CGSize(width: platformLeft.size.width * 4, height: platformLeft.size.height * 4))
platformLeft.zPosition = 20
let platformRight = SKSpriteNode(texture: platformTexture)
platformRight.physicsBody = platformPhysics.copy() as? SKPhysicsBody
platformRight.physicsBody?.isDynamic = false
platformRight.scale(to: CGSize(width: platformRight.size.width * 4, height: platformRight.size.height * 4))
platformRight.zPosition = 20
let scoreNode = SKSpriteNode(color: UIColor.clear, size: CGSize(width: frame.width, height: 32))
scoreNode.physicsBody = SKPhysicsBody(rectangleOf: scoreNode.size)
scoreNode.physicsBody?.isDynamic = false
scoreNode.name = "scoreDetect"
scoreNode.zPosition = 40
platformGroup = [platformLeft, platformRight, scoreNode]
let yPosition = frame.width - platformRight.frame.width
let max = CGFloat(frame.width / 4)
let xPosition = CGFloat.random(in: -80...max)
let gapSize: CGFloat = -50
platformLeft.position = CGPoint(x: xPosition + platformLeft.size.width - gapSize, y: -yPosition)
platformRight.position = CGPoint(x: xPosition + gapSize, y: -yPosition)
scoreNode.position = CGPoint(x: frame.midX, y: yPosition - (scoreNode.size.width / 1.5))
let endPosition = frame.maxY + (platformLeft.frame.height * 3)
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
for platforms in platformGroup {
addChild(platforms)
platforms.run(moveSequence)
}
platformCount += 1
}
func loopPlatforms() {
let create = SKAction.run { [unowned self] in
self.createPlatforms()
platformCount += 1
}
let wait = SKAction.wait(forDuration: 1.1)
let sequence = SKAction.sequence([create, wait])
let repeatForever = SKAction.repeatForever(sequence)
run(repeatForever)
}
I think I can see what's going wrong. When you change platformSpeed, it changes the speed of all the platforms in platformGroup. And createPlatforms() is being called multiple times. Now, each time it's called you create a pair of platforms and assign these to platformGroup. Since you call the function multiple times, it's overwriting any existing values in the array. That's why changing platformSpeed only updates the speed of the latest platforms you've created---the older platforms stay the same speed because they're not in platformGroup anymore.
To fix this, my advice would be to have platformGroup store all the platforms currently on the screen. You could do this by changing
platformGroup = [platformLeft, platformRight, scoreNode]
to something like
let newNodes = [platformLeft, platformRight, scoreNode]
platformGroup += newNodes
// Alternatively, platformGroup.append(contentsOf: newNodes)
Now you need to make sure you're 1) only adding the new nodes to the scene, and 2) removing the old nodes from platformGroup when they're removed from the parent. You could do this by changing
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
let moveSequence = SKAction.sequence([moveAction, SKAction.removeFromParent()])
for platforms in platformGroup {
addChild(platforms)
platforms.run(moveSequence)
}
to something like
let moveAction = SKAction.moveBy(x: 0, y: endPosition, duration: 7)
for node in newNodes {
let moveSequence = SKAction.sequence([
moveAction,
SKAction.removeFromParent(),
SKAction.run {
self.platformGroup.remove(node)
}
])
addChild(node)
node.run(moveSequence)
}
Now that you're keeping a track of all platforms ever made, your speed changes should be applied consistently to every platform on the screen. Hope this works!

Cast from AnyObject to UIEdgeInsets [duplicate]

As my question title says how can I convert CGPoint into NSValues so I can store then Into array In swift.
In objective-C we can do it like this:
// CGPoint converted to NSValue
CGPoint point = CGPointMake(9, 9);
NSValue *pointObj = [NSValue valueWithCGPoint:point];
But can anybody tell me that how can I do this in swift?
Thanks In Advance.
Try something like that:
let point = CGPointMake(9, 9)
var pointObj = NSValue(CGPoint: point)
Exactly as you'd imagine:
let point = CGPoint(x: 9.0, y: 9.0)
let pointObj = NSValue(CGPoint: point)
let newPoint = pointObj.CGPointValue()
If you aren't planning on using the array in Objective-C, and can keep it as a Swift Array, then you don't need to turn the point into an NSValue, you can just add the point to the array directly:
let point1 = CGPoint(x: 9.0, y: 9.0)
let point2 = CGPoint(x: 10.0, y: 10.0)
let pointArray = [point1, point2] // pointArray is inferred to be of type [CGPoint]
let valuesArray = pointsArray as [NSValue]
swift 3
let pointObj = NSValue(cgPoint: CGPoint(x:9, y:9))
https://developer.apple.com/reference/foundation/nsvalue/1624531-init
swift 4
let point = NSValue.init(cgPoint: mask.position)

Randomizing node movement duration

I am making a game in SpriteKit and I have a node that is moving back and forth across the screen and repeating using the code:
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: 1.5)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: 1.5)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake2"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1"))
let moveBackAndForth = SKAction.repeatActionForever(SKAction.sequence([texRight, moveRight, texLeft, moveLeft,]))
Drake1.runAction(moveBackAndForth)
I am trying to figure out what method I can use to randomize the duration. Every time moveBackandForth runs, I want it to rerun using a different duration, (within games, not between). If someone could give me some example code to try I would really appreciate it.
Also arc4Random works fine, but it doesn't randomize within the game, only between games.
When you run actions like from your example and randomize duration parameter with something like arc4Random this is actually happening:
Random duration is set and stored in action.
Then action is reused in a sequence with a given duration.
Because the action is reused as it is, duration parameter remains the same over time and moving speed is not randomized.
One way to solve this (which I prefer personally) would be to create a "recursive action", or it is better to say, to create a method to run desired sequence and to call it recursively like this :
import SpriteKit
class GameScene: SKScene {
let shape = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: 20, height: 20))
override func didMoveToView(view: SKView) {
shape.position = CGPointMake(CGRectGetMidX(self.frame) , CGRectGetMidY(self.frame)+60 )
self.addChild(shape)
move()
}
func randomNumber() ->UInt32{
var time = arc4random_uniform(3) + 1
println(time)
return time
}
func move(){
let recursive = SKAction.sequence([
SKAction.moveByX(frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: NSTimeInterval(randomNumber())),
SKAction.runBlock({self.move()})])
shape.runAction(recursive, withKey: "move")
}
}
To stop the action, you remove its key ("move").
I don't have any project where I can try right now.
But you might want to try this :
let action = [SKAction runBlock:^{
double randTime = 1.5; // do your arc4random here instead of fixed value
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: randTime)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: randTime)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake2"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1"))
let sequence = SKAction.sequence([texRight, moveRight, texLeft, moveLeft])
Drake1.runAction(sequence)
}]; 
let repeatAction = SKAction.repeatActionForever(action)
Drake1.runAction(repeatAction)
Let me know if it helped.

How to make a spritekit node change from one image to another

I have an SKSpriteNode image with the code:
let Drake1 = SKSpriteNode(imageNamed: "Drake1")
Drake1.position = CGPoint(x:self.frame.size.width/3, y:self.frame.size.height - 230)
Drake1.zPosition = 2
addChild(Drake1)
//drake movement
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: 2)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: 2)
let moveBackAndForth = SKAction.repeatActionForever(SKAction.sequence([moveRight, moveLeft]))
Drake1.runAction(moveBackAndForth)
What I want to do is, when the image is moving to the right, I want to replace the image with a different SKSpriteNode image, and when it moves back left, I want to use the original image, and repeat this forever. I am struggling with what the code should be for this.
SpriteKit comes with a SKAction, setTexture, to instantaneously change a sprite's texture with relative ease. You can create an inline SKTexture object of each images, use them in SKActions, and add them to your sequence loop, like this:
let moveRight = SKAction.moveByX(frame.size.width/2.8, y: 0, duration: 2)
let moveLeft = SKAction.moveByX(-frame.size.width/2.8, y: 0, duration: 2)
let texRight = SKAction.setTexture(SKTexture(imageNamed: "Drake1r"))
let texLeft = SKAction.setTexture(SKTexture(imageNamed: "Drake1l"))
let moveBackAndForth = SKAction.repeatActionForever(SKAction.sequence([texRight, moveRight, texLeft, moveLeft]))
Drake1.runAction(moveBackAndForth)
Hopefully this works for you! Please note that if the textures are different sizes, you must add resize: Bool to setTexture's arguments.

Float is not convertible to CGFloat and CGFloat is not convertible to Float

I have a problem with swift on XCode 6 beta 4, is driving me nuts I'm in IOS 8 developing a game and I have follow a tutorial but I get this: Float is not convertible to CGFloat and then when I recast as CGFloat I get the other. Here is the code:
override func didMoveToView(view: SKView) {
/* Setup your scene here */
//physics
self.physicsWorld.gravity = CGVectorMake(0.0, -5.0)
// Bird
var birdTexture = SKTexture(imageNamed: "kirby")
birdTexture.filteringMode = SKTextureFilteringMode.Nearest
bird.setScale(0.5)
bird.position = CGPoint(x: self.frame.size.width * 0.35, y: self.frame.size.height * 0.6)
bird.physicsBody = SKPhysicsBody(circleOfRadius:bird.size.height/2)
bird.physicsBody.dynamic = true
bird.physicsBody.allowsRotation = false
self.addChild(bird)
//ground
ground.setScale(2.0)
ground.position = CGPointMake(self.size.width/2, ground.size.height/2)
ground.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(self.frame.size.width , ground.size.height))
ground.physicsBody.dynamic = false
self.addChild(ground)
//pipes
//create pipes
//^
//movement of the pipes
let distanceToMove = CGFloat(self.frame.size.width + 2.0 * pipeUp.texture.size().width)
let movePipe = SKAction.moveByX(-distanceToMove, y: CGFloat(0.0), duration: NSTimeInterval(0.01) * distanceToMove) <----- this line is the problem
}
What is going on, I move the line:
CGFloat(self.frame.size.width + 2.0 * pipeUp.texture.size().width)
I get the second error, then a recast:
Float(CGFloat(self.frame.size.width + 2.0 * pipeUp.texture.size().width))
or
Float(self.frame.size.width + 2.0 * pipeUp.texture.size().width)
gives me the first, so what do swift wants here, that is insane. Catch 22? Any help?
EDIT: I'm using a 13-inch mac late 2009, Intel Core 2 Duo, on mavericks OS X 10.9.4 (13E28) if any help. I saw something about the building options may affect the float types, but I do not know where are they.
This will be interpreted as a CGFloat and not as a NSTimeInterval as you cast and then multiply:
let movePipe = SKAction.moveByX(-distanceToMove, y: 0.0, duration: NSTimeInterval(0.01) * distanceToMove)
Try to multiply then cast to NSTimeInterval:
let movePipe = SKAction.moveByX(-distanceToMove, y: 0.0, duration: NSTimeInterval(0.01 * distanceToMove))
Also, as suggested already, you don't need to cast your distance. The following will work just fine:
let distanceToMove = self.frame.size.width + 2.0 * pipeUp.texture.size().width
Just make sure every operand has the same type. Literals (2.0) have always their type inferred. In your case, I see no problem because
let distanceToMove: CGFloat = self.frame.size.width + 2.0 * pipeUp.texture.size().width
has all the operands of type CGFloat so there is no need to cast at all.
On the next line, again make sure to use the correct types. You don't need to cast the literals:
SKAction.moveByX(-distanceToMove, y: 0.0, duration: 0.01 * NSTimeInterval(distanceToMove))