Using block for group of statement in swift - swift

I was reading a blog and there was a function like below. I want to know what could be the purpose of writing it in this way?
public func moveNinjaToBottomRight() {
ninja.center = {
let x = (frame.maxX - ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
return CGPoint(x: x, y: y)
}()
}

This is just a matter of style.
Writing the function this way:
public func moveNinjaToBottomRight() {
ninja.center = {
let x = (frame.maxX - ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
return CGPoint(x: x, y: y)
}()
}
adds clarity to the purpose of the function. At a glance you can see that it just updates ninja.center and you know that the code inside the closure is computing that new center. This would especially be true if computing the new center took many more lines.
The other way to write it would be:
public func moveNinjaToBottomRight() {
let x = (frame.maxX - ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
ninja.center = CGPoint(x: x, y: y)
}
In this case, you have to read through all of the lines before you get to what the function really does. Again, for such a short function it makes little difference, but it it were longer it would take the reader longer to access what was going on.
I would recommend only using this functional style if the code inside the closure has a specific purpose. In this case it is creating a CGPoint. If the code in the closure was causing side effects like playing sounds or updating the user interface, then that I believe the imperative style used by the second example makes more sense.

The parens after the braced expression are called an Immediately Invoked Closure and have the effect of executing the closure inside the braces.
The style is commonly used for initialisation where it serves a valuable purpose of allowing you to create a property with complex initialisation:
public let ninja: UIImageView = {
let image = UIImage(named: "ninja")
let view = UIImageView(image: image)
view.frame = CGRect(x: 0, y: 0, width: 45, height: 39)
return view
}()
Using it inside a function as you have shown has no technical purpose. It is likely to be less efficient unless the compiler is capable of optimising it to simpler logic.
It maintains a consistency with the way they are initialising variables elsewhere in the sample (assuming you are looking at the same code I found).
In C programming, it's common to just use a pair of braces to provide local scoping of variables. That is a robust programming practice to ensure they are not re-used further down a method.
The Swift idiom is to use do { ...} if you want to achieve such scoping.
So, to be clear, the function you are looking at could have been written plainly
public func moveNinjaToBottomRight() {
let x = (frame.maxX - ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
ninja.center = CGPoint(x: x, y: y)
}
Or even
public func moveNinjaToBottomRight() {
ninja.center = CGPoint(
x: (frame.maxX - ninja.frame.width / 2),
y: (frame.maxY - ninja.frame.height / 2))
}
Or, if there was more logic and they wanted local scoping.
public func moveNinjaToBottomRight() {
do {
let x = (frame.maxX - ninja.frame.width / 2)
let y = (frame.maxY - ninja.frame.height / 2)
ninja.center = CGPoint(x: x, y: y)
}
// more code here which wants to use x and y names
// but is not reusing the variables above
}

Related

Problem with game area on the new form factor

For my studies, I have to create a game on iOS (like a space invaders). Actually, I'm stucked for the game area : I need that the bullet can be pulled from the maximum value of the x position no matter the form factor.
Indeed, my code works perfectly on the old form factor (as iPhone 8). However, on the new form factor (iPhone X, XS..), the planet can disappear of all the screen.
(I'm a beginner about Swift, so I know there is a lot of mistake in my code). But I think that the problem is from the fact that I divide by an int.
First picture : what I want (which works on iPhone 8)
Second one : My problem.
Thanks for you help (and sorry for my bad english, french people ahaha).
translate :
zone_de_jeu = game_area
largeur_jouable = width_playable
marge_largeur = width_margin
hauteur_jouable = height_playable
marge_hauteur = height_margin
enter code here
var zone_de_jeu: CGRect
override init(size: CGSize) {
let max_ratio_aspect: CGFloat = 16.0 / 9.0
let largeur_jouable = size.height / max_ratio_aspect
let marge_largeur = (size.width - largeur_jouable) / 2
let hauteur_jouable = size.width / max_ratio_aspect
let marge_hauteur = (size.height - hauteur_jouable) / 2
zone_de_jeu = CGRect(x: marge_largeur, y: marge_hauteur, width: largeur_jouable, height: hauteur_jouable)
super.init(size: size)
let initialisation_mouvement_planete = touch.location(in: self)
let mouvement_planete = touch.previousLocation(in: self)
let arrive_final_planete_x = initialisation_mouvement_planete.x - mouvement_planete.x
planete.position.x += arrive_final_planete_x
if planete.position.x > zone_de_jeu.maxX - planete.size.width / 6.5 {
planete.position.x = zone_de_jeu.maxX - planete.size.width / 6.5
}
if planete.position.x < zone_de_jeu.minX + planete.size.width / 6.5 {
planete.position.x = zone_de_jeu.minX + planete.size.width / 6.5
}
Should I modify, my divider by something more general ?

SpriteKit Issue With SKShader Animation In Swift

For the life of me, I cannot understand why this shader animation is not running. Any help is appreciated. Please see the code below... the shader displays correctly, but does not animate. Thank you.
NOTE: Xcode 9.4.1, Swift 4.1, iOS 11.4
import SpriteKit
class GameScene: SKScene {
static let marquee: SKShader = {
let shader = SKShader(
source: "void main() {" +
"float np = mod(a_path_phase + v_path_distance / u_path_length, 1.0);" +
"vec3 hsb = vec3(np, 1.0, 1.0);" +
"vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);" +
"vec3 p = abs(fract(hsb.xxx + K.xyz) * 6.0 - K.www);" +
"vec3 rgb = hsb.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsb.y);" +
"vec4 df = SKDefaultShading();" +
"gl_FragColor = vec4(rgb, df.z);" +
"}"
)
shader.attributes = [SKAttribute(name: "a_path_phase", type: .float)]
return shader
} ()
private let beat: TimeInterval = 0.05
private var phase: Float = 0
override func didMove(to view: SKView) {
let node = SKShapeNode(rectOf: CGSize(width: 256, height: 256), cornerRadius: 16)
node.fillColor = .clear
node.strokeColor = .white
node.strokeShader = GameScene.marquee
node.lineWidth = 8
node.glowWidth = 4
node.run(
SKAction.repeatForever(
SKAction.sequence(
[
SKAction.wait(forDuration: beat),
SKAction.run() { [weak self] in
if let weak = self {
weak.phase = fmodf(weak.phase + Float(weak.beat), 1)
node.setValue(SKAttributeValue(float: weak.phase), forAttribute: "a_path_phase")
}
}
]
)
)
)
addChild(node)
}
}
I was also having trouble with .setValue(forAttribute:). It did not seem to work. Upon investigation, my conclusion is that...
I believe SKShapeNode.setValue(forAttribute:) is not working.
The fact that SKSpriteNode.setValue(forAttribute:) did work correctly under the same set of conditions led me to conclude that it was almost surely some bug in iOS in SKShapeNode.
The fact was that any SKAttributes I might set in
SKShapeNode.fillShader or SKShapeNode.strokeShader kept their
values set to 0.0 no matter what I did.
However, the same did not happen with SKSpriteNode.shader.
So I had to find a way to circumvent this bug.
Some sort of hack.
Fortunately, in my case, I did find a way.
And fortunately, it seems to be enough to solve your problem too.
What you can do, in this case, is to pass the value of your phase as one of the components of the strokeColor, for example, the red component.
To do this you will have to normalize the phase to values between 0.0 to 1.0.
You can pass a phase multiplier using a shader.uniform to help denormalize it.
Then read the phase value inside the shader from SKDefaultShading().x, (the red component).
Make sure to set the alpha component of the
strokeColor to 1.0, for SKDefaultShading() will have its color
component values already premultiplied by its alpha.
Now, because your strokeColor is already being used to pass the phase, you can pass the actual color of the stroked line as a vec4 shader.uniform.
In my case, I also made use of u_time to help in performing my
animation. Stuff like fract(u_time) can be very useful.
Here is how the corrected code might look like:
> NOTE: There is a much simpler solution. Look by the end of this post.
import SpriteKit
class GameScene: SKScene {
private typealias Me = GameScene
static let phaseMultiplier: Float = 1000
static let marquee: SKShader = {
let shader = SKShader(
source: "void main() {" +
"float red = SKDefaultShading().x;" +
"float phase = red * u_phase_multiplier;" +
"float np = mod(phase + v_path_distance / u_path_length, 1.0);" +
"vec3 hsb = vec3(np, 1.0, 1.0);" +
"vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);" +
"vec3 p = abs(fract(hsb.xxx + K.xyz) * 6.0 - K.www);" +
"vec3 rgb = hsb.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsb.y);" +
"vec4 df = u_stroke_color;" +
"gl_FragColor = vec4(rgb, df.z);" +
"}"
)
let white = vector_float4.init(1, 1, 1, 1)
shader.uniforms = [
SKUniform(name: "u_phase_multiplier", float: Me.phaseMultiplier),
SKUniform(name: "u_stroke_color", vectorFloat4: white)]
return shader
} ()
private let beat: TimeInterval = 0.05
private var phase: Float = 0
override func didMove(to view: SKView) {
let node = SKShapeNode(rectOf: CGSize(width: 256, height: 256), cornerRadius: 16)
node.fillColor = .clear
node.strokeColor = .white
node.strokeShader = Me.marquee
node.lineWidth = 8
node.glowWidth = 4
node.run(
SKAction.repeatForever(
SKAction.sequence(
[
SKAction.wait(forDuration: beat),
SKAction.run() { [weak self] in
if let weak = self {
weak.phase = fmodf(weak.phase + Float(weak.beat), 1)
let phase = weak.phase / Me.phaseMultiplier
node.strokeColor = SKColor.init(red: phase, green: 0, blue: 0, alpha: 1)
}
}
]
)
)
)
addChild(node)
}
}
NOTE: I did not compile the code above posted. I should serve merely as a sketch.
The day after this post, I found out that attribute parameters are not supposed to be used in fragment shaders, only in vertex shaders.
Take a look at StackOverflow question:
In WebGL what are the differences between an attribute, a uniform, and a varying variable?
The SDK documentation of SKAttribute and SKAttributeValue, however, seem to suggest that there may be no direct relation between "SKAttributes" and actual shader attribute parameters. They just seem to be named the same.
Simpler Solution:
In the end, however, there is never any need to make use of any hack like the one I show here.
All that needs to be done is to set the value of a uniform through SKShader.uniformNamed("u_phase")?.floatValue = phase, instead of using .setValue(forAttribute:) just as jmdecombe, correctly, later pointed out.
I will keep my answer here for reference, as it presents an unconventional solution to a problem.

Understanding Spritekit run block, variable not as expected

I'm looking for some help understanding why my speedToPoint variable is always 1 when it gets to the wait action.
This is part of an SKSpriteNode extension i wrote for making a bird fly to a random point in a predefined rectangle. The speedToPoint is also randomized between 1 and 4 and used as the duration for the moveTo action. However, i also need to use that TimeInterval for my wait block in the action sequence.
speedToPoint is indeed being randomized in the run block (i've confirmed). How can i use that same randomized number in the wait block in the next part of the sequence?
var speedToPoint:TimeInterval = 1
self.run(SKAction.repeatForever(
SKAction.sequence([
SKAction.run{
speedToPoint = TimeInterval(Globals.sharedInstance.randomF(min: 1, max: 4))
var pointX = Globals.sharedInstance.randomF(min: left,max: right)
let pointY = Globals.sharedInstance.randomF(min: top,max:bottom)
while abs(self.position.x.distance(to: pointX)) < 200 {
pointX = Globals.sharedInstance.randomF(min: left,max: right)
}
self.xScale = pointX < self.position.x ? -1 : 1
self.run(SKAction.move(to: CGPoint(x:pointX,y:pointY),duration: speedToPoint))
},
SKAction.wait(forDuration: speedToPoint)])
),withKey: "inFrame")
To clarify, what i'm really tring to do is have the bird fly to a point, once it's arrived at that point, fly to another point. I'm still wrapping my heard around action sequences and whether or not they actually wait for completion to move on. Which from what i've read, they do, but not for any move actions. That's why the wait is in there. Perhaps there is another way?
An SKAction, once created, can't be modified, and it is meant to be reused eg. you can't modify the duration parameter, or change other passed parameters. This means that you have to re-create it if you need it changed. Of course you can change the speed property of an existing action, or you can pause the action but that's pretty much it when it comes to modifying the existing action.
To solve your issue, you could do next:
1) Create an action which moves a sprite to a specific location
2) Once the action is completed, you create a new one which does the same
you can do this using recursion, like this (just copy & paste the code to see how it works):
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
private var bird = SKSpriteNode(color: .purple, size: CGSize(width: 100, height: 100))
override func didMove(to view: SKView) {
addChild(bird)
recursive()
}
func recursive(){
let sequence = SKAction.sequence([
SKAction.move(to: self.randomPoint(inRect: self.frame), duration: TimeInterval(self.random(between: 1, and: 3))),
SKAction.run({[unowned self] in NSLog("Block executed"); self.recursive()})
])
self.bird.run(sequence, withKey: "aKey")
}
func random(between minimum: CGFloat, and maximum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(minimum - maximum) + min(minimum, maximum)
}
func randomPoint(inRect rect:CGRect)->CGPoint{
let x = random( between: -rect.size.width / 2.0 , and: rect.origin.x + rect.size.width/2.0)
let y = random(between: -rect.size.height / 2.0 , and: rect.origin.y + rect.size.height/2.0)
return CGPoint(x: x, y: y)
}
}
To stop this action, remove the key associated with it.

Choosing to spawn different SKSpriteNodes

I am making a game and I have objects which fall from the top of the screen to the bottom. I want to spawn choose between the objects and drop one of them. I currently the drop all at the same time.
func ShapePicker() -> SKSpriteNode{
let shapeArray = [purpleOctagon, coin, greenTriangle, orangeHexagon]
let MaxValue = self.size.width / 2 - 200
let MinValue = self.size.width / 3 * 0.95
let rangeMax = UInt32(MaxValue)
let rangeMin = UInt32(MinValue)
purpleOctagon.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(purpleOctagon)
greenTriangle.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(greenTriangle)
coin.position = CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax), y: self.size.height)
self.addChild(coin)
return shapeArray[Int(arc4random_uniform(UInt32(shapeArray.count)))]
}
I would like the program to randomly .addChild because right now it just puts them on the screen.
Your code implies that you want all of them to be on the screen, and then one randomly drops... So you do want to continue to .addChild. What you want, is for them to NOT drop all at once.
So, you need to change the .physicsBody.pinned to true to keep them at the top of the screen.
Then, in your update() you can check for how much time has passed, etc, and after a certain # of seconds you can do an arc4random_uniform and use that result to change the .pinned property of one of the nodes (thus causing that one and that one only to fall).
So, if the coin is 0, triangle is 1, and octagon is 2, then, in your .update keep track of the time elapsed and after say 3 seconds, do a random check 0-2, and that check will perform:
switch result {
case 0: // coin
coin.physicsBody?.pinned = false // makes it drop
case 1: // triangle
...
Just make sure that your nodes are in the proper scope so that you can do the logic in update()
If I read your Q wrong, and you only want to spawn and then drop just one, then you would still need the above switch statement, but instead of changing physicsBody you would do .addChild instead.
so inside of your func would be more like:
// This can be global or inside of your GameScene:
var myGlobalCurrentTime: CFTimeInterval
override func update(currentTime: CFTimeInterval) {
myGlobalCurrentTime = myTimerUpdateTime(currentTime)
func myDropFunc() {
... // Initialize your nodes
let result = myRandomNumber(3)
switch result {
case 0:
coin.position
= CGPoint(x: CGFloat(arc4random_uniform(rangeMin) + rangeMax),
y: self.size.height)
self.addChild(coin)
case 1:
...
}
}
// Execute the func:
myDropFunc()
}
I'm still a bit confused by your code and question, so please clarify in the comments so I can update this answer if needed.
You can move the addChild call out of this function, it returns a SKSpriteNode, which you can then add.
let sprite = ShapePicker()
addChild(sprite)
Or, just add the one randomly chosen and return it. You don't need to addChild nodes that you're not planning on using

Create random CGPoint with Swift

So, I'm trying to develop a simple game written in Swift, but I'm having trouble doing a pretty simple thing. I can't manage to create a random CGPoint... When using arc4random, a compiler error shows up telling me that I can't use Int32 in a CGPoint. So, Is there any way to do this? Any workaround? Thanks!
can also maybe make use of Swift's extensions of base types to create a reusable set of overloaded functions of CGPoint. Maybe something like:
extension CGPoint {
func random()->CGPoint { return CGPoint(x:Int(arc4random()%1000),y:Int(arc4random()%1000))}
func random(range:Int)->CGPoint {
return CGPoint(x:Int(arc4random()%range),y:Int(arc4random()%range))}
func random(rangeX:Int, rangeY:Int)->CGPoint {
return CGPoint(x:Int(arc4random()%rangeX),y:Int(arc4random()%rangeY))}
}
You can then write random CGPoints like this:
var p = CGPoint.random()
//random x and y with a range of 1000
or
var p = CGPoint.random(range:100)
//random x and y with a range of 100
or
var p = CGPoint.random(rangeX:200, rangeY:400)
//random x up to 200 and random y with a range of up to 400
Granted, I'm not in the Xcode IDE at the moment to check syntax / if it compiles correctly but hope that could be of help :-)
...
//////////////////
Swift 1.2 Update
//////////////////
Seems these type-level function calls are not allowed anymore with extensions...at least for CGPoint; probably because CGPoint is actually a struct and not a class based on the current IOS documentation.
Here's a more in-depth version of my extension that allows for Range types.
This is confirmed working as of XCode 6.4 Beta
(Github repository with Playground file found here:
https://github.com/princetrunks/Random-CGPoint-Extension)
//creates random CGPoints in Swift as of XCode Beta 6.4 (6E7)
extension CGPoint {
/*private functions that help alleviate the ambiguity of the modulo bias
and nested typecasting as well as recycle similar functionality
for either Int or Range type parameter inputs */
private func randomInt(num:Int) ->Int{
return Int(arc4random_uniform(UInt32(num)))
}
private func randomIntFromRange(numRange:Range<Int>) ->Int{
return Int(arc4random_uniform(UInt32((numRange.endIndex - numRange.startIndex) + numRange.startIndex)))
}
//private variable for the default range
private var defaultRange : Int{
get{return 1000}
}
//(a) public variable that creates a default random CGPoint
static var randomPoint = CGPoint.zeroPoint.random()
//(b) default random point creation
func random()->CGPoint { return CGPoint(x:randomInt(defaultRange),y:randomInt(defaultRange))}
//(c) using an Int parameter for both the random x and y range
func random(range:Int)->CGPoint {
return CGPoint(x:randomInt(range),y:randomInt(range))
}
//(d) allows for the specification of the x and y random range
func random(#rangeX:Int, rangeY:Int)->CGPoint {
return CGPoint(x:randomInt(rangeX),y:randomInt(rangeY))
}
//(e) allows the same functionality as (c) but with a Range<Int> type parameter
func random(range:Range<Int>)->CGPoint {
return CGPoint(x:randomIntFromRange(range), y:randomIntFromRange(range))
}
//(f) allows the same functionality as (d) but with a Range<Int> type parameter
func random(#rangeX:Range<Int>, rangeY:Range<Int> )->CGPoint {
return CGPoint(x:randomIntFromRange(rangeX), y:randomIntFromRange(rangeY))
}
}
Here's how we can test this extension:
//(a)
let r = CGPoint.randomPoint
//(b)
var anotherRandomPoint = r.random()
//(c)
anotherRandomPoint = r.random(1000)
//(d)
anotherRandomPoint = r.random(0...1000)
//(e)
anotherRandomPoint = r.random(rangeX:90, rangeY: 2000)
//(f)
anotherRandomPoint = r.random(rangeX:0...90, rangeY: 0...2000)
// generates 100 random CGPoints between -1000 and 999
for _ in 0...100 {
anotherRandomPoint.random(-1000...1000)
}
hi what about constructing an Int? Int(arc4random())
e.g.
var p = CGPoint(x:Int(arc4random()%1000),y:Int(arc4random()%1000))
Swift 4,5
// Add some range
let minX = 0
let maxX = 100
let minY = 0
let maxY = 100
let randomX = CGFloat.random(in: minX..<maxX)
let randomY = CGFloat.random(in: minY..<maxY)
let random = CGPoint(x: randomX, y: randomY)
Here is an extension on CGPoint to generate random point based on your x,y closed range.
extension CGPoint {
static func randPoint(xRange: ClosedRange<CGFloat>, yRange: ClosedRange<CGFloat>) -> Self {
let x = CGFloat.random(in: xRange)
let y = CGFloat.random(in: yRange)
return .init(x: x, y: y)
}
}