I am trying to call a function via an SKAction that takes 1 input parameter.
When I declare the SKAction, I get an error: "Cannot convert value of type '()' to expected argument type '() -> Void'
self.run(SKAction.repeat(SKAction.sequence([SKAction.wait(forDuration: 1), SKAction.run(self.decreaseHealth(by: 5.0))]), count: 10))
func decreaseHealth(by amount: Double){
print(health)
health -= amount
print(health)
let percentageToDecrease = amount / totalHealth
let resizeAction = SKAction.resize(toHeight: CGFloat(600*(1-percentageToDecrease)), duration: 1)
let redAmount: CGFloat = CGFloat(1.0 - health / totalHealth)
let greenAmount: CGFloat = CGFloat(health / totalHealth)
let recolorAction = SKAction.colorize(with: UIColor(red: redAmount, green: greenAmount, blue: CGFloat(0.0), alpha: CGFloat(1.0)), colorBlendFactor: 0, duration: 1)
healthBar.run(SKAction.group([resizeAction, recolorAction]))
}
usually when you get that error, it just means you need to enclose the function inside of curly brackets. so:
self.run(SKAction.repeat(SKAction.sequence([SKAction.wait(forDuration: 1), SKAction.run( { self.decreaseHealth(by: 5.0) } )]), count: 10))
If you had split this into multiple lines, it would have been easier to spot.
this is because decreaseHealth is of type () but { decreasHealth } if of type () -> () or () -> Void
(Closures are function type in Swift, thus have parameter / arguments and returns)
I haven't used SpriteKit for 3 months. But something like the following should work.
func repeatMe() {
let waitAction = SKAction.wait(forDuration: 1.0)
let completionAction = SKAction.run {
self.decreaseHealth(by: 5.0)
}
let seqAction = SKAction.sequence([waitAction, completionAction])
let repeatAction = SKAction.repeat(seqAction, count: 10)
}
Related
I have an app that that aims to record the camera's current X,Y,Z coordinates and print them out. It does this properly with the code down below
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else { return }
let transform = pointOfView.transform
let CamPosition = SCNVector3(transform.m41, transform.m42, transform.m43)
print(CamPosition)
I want to truncate the printed output since it's very long. I found this extension to truncate the values.
extension Double {
func truncate(places : Int)-> Double {
return Double(floor(pow(10.0, Double(places)) * self)/pow(10.0, Double(places)))
}
}
This works if I print something like this:
x = 1.123456789
print(x.truncate(places: 2))
but will not work if I print it out like this:
print(camRotation.truncate(palces:2))
The error it gives me says "Value of type 'SCNVector4' has no member 'truncate'"
is this a formatting issue on my end or do SCNVectors just not allow you to use extensions?
"Value of type 'SCNVector4' has no member 'truncate'"
camRotation is a SCNVector4.
Your extension is on Double, not SCNVector4.
/// Double, not SCNVector4
extension Double {
func truncate(places : Int)-> Double {
return Double(floor(pow(10.0, Double(places)) * self)/pow(10.0, Double(places)))
}
}
Extensions work on any type, but camRotation.truncate(palces:2) doesn't work, because the type doesn't match.
Ok, now on to the actual answer. Currently, your code says
let CamPosition = SCNVector3(transform.m41, transform.m42, transform.m43)
Just apply truncate to each of the components:
let CamPosition = SCNVector3(
transform.m41.truncate(places: 2),
transform.m42.truncate(places: 2),
transform.m43.truncate(places: 2)
)
print(CamPosition)
Because these components take in Float values, you should also change your extension to this:
extension Float {
func truncate(places: Int) -> Float {
return Float(floor(pow(10.0, Float(places)) * self)/pow(10.0, Float(places)))
}
}
I have the problem that I want to use a variable in an action, which has not the matching type. I therefore get the following error: Cannot convert value of type 'SKSpriteNode' to expected argument type 'CGPoint'.
func enemyProofNextFields() {
for i in 0...41 {
if getDistance(knightRed.position, grassTileArray[i].position) > 120 && getDistance(knightRed.position, grassTileArray[i].position) < 121 {
possibleEnemeyFields.append(grassTileArray[i])
}
}
var lenght = possibleEnemeyFields.count
let randomNumber = Int.random(in: 0 ... lenght)
print("Random Enemy Number: \(randomNumber)")
var position = possibleEnemeyFields[randomNumber]
let move = SKAction.move(to: position, duration: 0.3) //here is the error
knightRed.run(SKAction.sequence([move]))
}
For any answer I would be very grateful.
The error is pretty clear: possibleEnemeyFields contains obviously SKSpriteNode instances, but the first parameter of move expects a CGPoint, assumedly the position
You probably mean
let field = possibleEnemeyFields[randomNumber]
let move = SKAction.move(to: field.position, duration: 0.3)
I have this code:
var triangles: [[[CAShapeLayer]]] = Array(repeating: Array(repeating: Array(repeating: 0, count: 2), count: 15), count: 15);
But it generates an "Cannot convert value of type..." compilation error.
How can I solve that? I want to access my CAShapeLayers like this:
triangles[1][2][1].fillColor = UIColor(red: 40/255, green: 73/255, blue: 80/255, alpha: 1).cgColor;
Use optionals.
var triangles: [[[CAShapeLayer?]]] = Array(repeating: Array(repeating: Array(repeating: nil, count: 2), count: 15), count: 15)
Now there's a nil instead of a 0, which is what I think you were hinting at. But every triangles[x][y][z] is now an optional type you'll have to safely unwrap.
So now you have to do something like triangles[x][y][z] = CAShapeLayer() before you do anything to that object.
Edit for correction. Thanks #OOPer
I thought about it some more, and realized I didn't really answer your question.
So you may use for loops to initialize everything (which would be a pain), or you could do something like this every time you access an index:
if triangles[x][y][z] == nil
{
triangles[x][y][z] = CAShapeLayer()
}
let bloop = triangles[x][y][z]!
bloop.fillColor = UIColor(...
Then you could pull it out into an outside method so it becomes a 1 liner. Like:
func tri(at x: Int, _ y: Int, _ z: Int) -> CAShapeLayer
{
if triangles[x][y][z] == nil
{
triangles[x][y][z] = CAShapeLayer()
}
return triangles[x][y][z]!
}
Then when using it:
tri(at: 1, 2, 1).fillColor = ...
Of course, you should pull triangles out and make it a property of the class you're in, or you can include it in the parameter list of that 1 liner method.
All that nesting makes your code hard to understand, and Array(repeating:count:) can't do what you want anyway.
func newGrid() -> [[[CAShapeLayer]]] {
func newStack() -> [CAShapeLayer] {
return (0 ..< 2).map({ _ in CAShapeLayer() })
}
func newRow() -> [[CAShapeLayer]] {
return (0 ..< 15).map({ _ in newStack() })
}
return (0 ..< 15).map({ _ in newRow() })
}
var triangles = newGrid()
You cannot use "0" as the repeating value, it will be inferred to be type [[[Int]]]. Just replace "0" with "CAShapeLayer()"
func colorBall() {
let colorize1 = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 1.0, duration: 0.1)
let colorize2 = SKAction.colorizeWithColor(UIColor.greenColor(), colorBlendFactor: 1.0, duration: 0.1)
let colorize3 = SKAction.colorizeWithColor(UIColor.blueColor(), colorBlendFactor: 1.0, duration: 0.1)
let actions = [colorize1, colorize2, colorize3]
let randomIndex = Int(arc4random_uniform(3))
self.Ball.runAction(actions[randomIndex])
}
var colorBucket = [UIColor]()
func randomColor() -> UIColor {
if colorBucket.isEmpty {
fillBucket()
}
let randomIndex = Int(arc4random_uniform(UInt32(colorBucket.count)))
let randomColor = colorBucket[randomIndex]
colorBucket.removeAtIndex(randomIndex)
return randomColor
}
func fillBucket() {
colorBucket = [UIColor.redColor(), UIColor.greenColor(), UIColor.blueColor()]
}
When I run this code in my game, and print out the color value of my ball, it sometimes prints out numbers like this:
UIDeviceRGBColorSpace 1 2.98023e-08 2.98023e-08 1
Why does it do this? I just want it to say: UIDeviceRGBColorSpace 0 0 1 1 if it's blue, IDeviceRGBColorSpace 1 0 0 1 if it's red, etc.
How can I keep those numbers from going higher than one, or much lower than one? What makes them do that in my code?
Based partially on zneak's answer I've made this (no thrills or frills) extension to UIColor which could come in handy:
extension UIColor {
func isVisuallyEqual(color: UIColor) -> Bool {
let compareValues = CGColorGetComponents(color.CGColor)
let values = CGColorGetComponents(self.CGColor)
let count = CGColorGetNumberOfComponents(self.CGColor)
if count != CGColorGetNumberOfComponents(color.CGColor) {
debugPrint("color-parameter has mismatching colorSpace")
return false
}
for index in 0..<count {
if !fuzzyFloatCompares(values[index], float2: compareValues[index]) {
return false
}
}
return true
}
private func fuzzyFloatCompares(float1: CGFloat, float2: CGFloat) -> Bool {
let difference = float1 - float2
return difference >= -1/256 && difference <= 1/256
}
}
2.98023e-08 is 0.0000000298023. If you look up the value 2.98023e-08 on Google or another search engine, you can find several examples of people getting that value because of rounding errors. Rounding errors occur because of how computers treat floating-point numbers.
It's probably a rounding error from the interpolation code that colorizeWithColor uses, and you get it instead of zero. For practical purposes, when talking about color components about to be displayed to an end user, I'd say that anything smaller than 1/256 can be considered to be zero.
You can test if two floating point numbers are "about equal" like this (typed on my phone, not really guaranteed to work):
func areAboutTheSame(a: Double, b: Double) -> Bool {
let difference = a-b
return difference < 1/256 && difference > -1/256
}
I got error: '(Int, Int)' is not identical to 'CGPoint'
How to convert a (Int, Int) to CGPoint
let zigzag = [(100,100),
(100,150),(150,150),
(150,200)]
override func drawRect(rect: CGRect)
{
// Get the drawing context.
let context = UIGraphicsGetCurrentContext()
// Create the shape (a vertical line) in the context.
CGContextBeginPath(context)
//Error is here
CGContextAddLines(context, zigzag, zigzag.count)
// Configure the drawing environment.
CGContextSetStrokeColorWithColor(context,UIColor.redColor().CGColor)
// Request the system to draw.
CGContextStrokePath(context)
}
CGContextAddLines() expects an array of CGPoint. If you already have an
array of (Int, Int) tuples then you can convert it with
let points = zigzag.map { CGPoint(x: $0.0, y: $0.1) }
An alternate way to avoid the boilerplate code required to create instances of the same type is to make CGPoint implement the ArrayLiteralConvertible, making it initializable by assigning an array of CGFloat:
extension CGPoint : ArrayLiteralConvertible {
public init(arrayLiteral elements: CGFloat...) {
self.x = elements.count > 0 ? elements[0] : 0.0
self.y = elements.count > 1 ? elements[1] : 0.0
}
}
and then use it as follows:
let zigzag:[CGPoint] = [
[100,100],
[100,150],
[150,150],
[150,200]
]
A few notes:
stylistically, it doesn't look good - it would be good if literals could be used for tuples, but I am not aware of any way to do that
if an empty array is used, the CGPoint is initialized with x = 0 and y = 0
if an array with one element is used, it is initialized with y = 0
if more than 2 values are used, all the ones after the 2nd are ignored
If it tells you to use CGPoint, use it! Just (number,number) is a pair of ints.
let zigzag = [CGPointMake(100,100),
CGPointMake(100,150),CGPointMake(150,150),
CGPointMake(150,200)]
Yet another:
func CGPoints(points:(x:CGFloat, y:CGFloat)...) -> [CGPoint] {
return map(points) { CGPoint($0) }
}
let zigzag = CGPoints(
(100,100),(100,150),(150,150),(150,200)
)