Accessing the number of elements in an array and applying gravity behaviour - arrays

I'm having issues with getting ALL elements of an array to fall using the Gravity module. I have managed to get the LAST element in the array to fall and then the remaining elements just stay at the top of the screen during testing. Upon debugging
I am using UIKit and want to understand this language thoroughly before using other various engines such as SpriteKit and GameplayKit.
func mainGame()
{
let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
for i in 1...random2
{
let image = UIImage(named: cars[i - 1])
let carView = UIImageView(image: image!)
carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
view.addSubview(carView)
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
gravityBehavior = UIDynamicItemBehavior(items: [carView]) //cars falling
dynamicAnimator.addBehavior(gravityBehavior)
collisionBehavior = UICollisionBehavior(items: [carView, mainCar]) //collide
collisionBehavior.translatesReferenceBoundsIntoBoundary = false
gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
dynamicAnimator.addBehavior(collisionBehavior)
}
collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
collisionBehavior.removeAllBoundaries()
}
With the game so far the last car in the array falls and the main player car that I control has collision behaviour, which is a big step for me!

You are creating a new UIDynamicAnimator with each iteration of the loop and assigning it to dynamicAnimator. That is why only the last element is working, because it is the last one assigned to that variable.
To fix it, just move this line to somewhere that would only be called once.
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
viewDidLoad is a possible place that should work.

UIKitDynamics is backwards of most similar frameworks. You don't animate the object. You have an animator and attach objects to it. As Clever Error notes, you only want one animator in this case.
The key point is that you don't attach gravity to cars; you attach cars to behaviors (gravity), and then behaviors to the animator. Yes, that's bizarre and backwards.
I haven't tested this, but the correct code would be closer to this:
func mainGame()
{
let cars = ["car5", "car1", "car6", "car3", "car2", "car4"]
var random2 = Int(arc4random_uniform(UInt32(cars.count))) + 1
var carViews: [UIImageView] = []
dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
// First create all the views
for i in 1...random2
{
let image = UIImage(named: cars[i - 1])
let carView = UIImageView(image: image!)
carView.frame = CGRect(x:i * 52, y:0 , width: 40, height: 50)
view.addSubview(carView)
carViews.append(carView)
}
// and then attach those to behaviors:
gravityBehavior = UIGravityBehavior(items: carViews) //cars falling
dynamicAnimator.addBehavior(gravityBehavior)
collisionBehavior = UICollisionBehavior(items: carView + mainCar) //collide
collisionBehavior.translatesReferenceBoundsIntoBoundary = false
dynamicAnimator.addBehavior(collisionBehavior)
collisionBehavior.addBoundary(withIdentifier: "Barrier" as NSCopying, for: UIBezierPath(rect: mainCar.frame))
collisionBehavior.removeAllBoundaries()
// You don't need this; it's built into Gravity
// gravityBehavior.addLinearVelocity(CGPoint(x: 0, y: 200), for: carView)
}
The main way that UIKitDynamics is different than most animation frameworks is that things that are animated don't know they're being animated. You can't ask a car what behaviors it has, because it doesn't have any. A UIDynamicAnimator basically is a timing loop that updates the center and transform of its targets. There's really not anything fancy about it (in contrast to something like Core Animation which has many fancy things going on). With a little iOS experience, you could probably implement all of UIKitDynamics by hand with a single GCD queue (it probably doesn't even need that, since it runs everything on main....)

Related

ARSCNView prepare crash when SCNMaterialProperty is SpriteKit scene

I'm experiencing an ARKit/SceneKit crash.
It happens if I do the following:-
Create SCNPlane and set the SCNMaterialProperty to a SpriteKit
scene
Create SCNNode with SCNPlane
Call prepare on ARSCNView and add SCNNode.
It crashes when I call prepare. However, it does not crash if I add the SCNNode without calling prepare or if the SCNMaterialProperty is not a SpriteKit scene.
See code below:-
var exhibitScene = SKScene(fileNamed: spriteName.lowercased())
exhibitScene?.view?.preferredFramesPerSecond = 60
exhibitScene?.view?.ignoresSiblingOrder = true
exhibitScene?.scaleMode = .aspectFit
exhibitScene?.isPaused = false
let exhibitSize = CGSize.init(width: (exhibitScene?.frame.size.width)! / PIXEL_TO_CENTIMETRE, height: (exhibitScene?.frame.size.height)! / PIXEL_TO_CENTIMETRE)
let exhibitPlane = SCNPlane(width: exhibitSize.width/100, height: exhibitSize.height/100)
exhibitPlane.firstMaterial?.diffuse.contents = exhibitScene
exhibitPlane.firstMaterial?.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)
let exhibitNode = SCNNode(geometry: exhibitPlane)
exhibitNode.name = name
exhibitNode.geometry?.firstMaterial?.isDoubleSided = false
exhibitNode.position = SCNVector3(x/100, (y + Float(exhibitSize.height * 0.5))/100, z/100)
self.sceneView.prepare([exhibitNode], completionHandler: { (Bool) in
self.sceneView.scene.rootNode.addChildNode(exhibitNode)
})
Not sure how to fix this.
Do I have to set a parameter on the ARSCNView for it to prepare a SCNNode when the SCNMaterialProperty is set to use a SpriteKit scene?
Not sure about your case, but I had a similar crash when the size of SKScene (and consequently SCNPlane) was more than some limit. The actual reason for the crash was that SCNMaterialProperty.contents may not be bigger than some limit (I believe it was about 4000px or something).

SceneKit ARKit glowing effect

Hi I'm trying to have a glowing effect around a node.
I used the SCNNode filters property and set to an array of CIFilter.
It works and renders only when the node has no node behind it which I don't understand. I tried to set the rendering order and the readDepth options without success. I'm really stuck at this point and would appreciate your input!
Please see the screenshot for an example and the code sample.
func addBloom() -> [CIFilter]? {
let bloomFilter = CIFilter(name:"CIBloom")!
bloomFilter.setValue(10.0, forKey: "inputIntensity")
bloomFilter.setValue(30.0, forKey: "inputRadius")
return [bloomFilter]
}
Calling this using:
myNode.filters = addBloom()
A final note, I noticed that for CIFilter to work with Metal the antiAliasing needs to be set to .none
arSceneView.antialiasingMode = .none
Thanks a lot!
Adrien
Have you tried setting the writesToDepthBuffer to false for those nodes which you aren't apply the filters to?
For your information writesToDepthBuffer refers to:
SceneKit’s rendering process uses a depth buffer to determine the
ordering of rendered surfaces relative to the viewer. The default
value of this property is YES, specifying that SceneKit saves depth
information for each rendered pixel for use by later rendering passes.
Typically, you disable writing to the depth buffer when rendering
semitransparent objects, because later stages of the rendering process
may require depth information about the opaque objects behind them.
This example seems to be working fine:
/// Generates An SCNPlane & A Red & Green SCNSphere
func generateNodes(){
let planeNode = SCNNode(geometry: SCNPlane(width: 1, height: 0.5))
planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor.black
planeNode.position = SCNVector3(0, 0, -1)
let redSphereNode = SCNNode(geometry: SCNSphere(radius: 0.1))
redSphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
redSphereNode.position = SCNVector3(-0.3, 0, -1)
redSphereNode.filters = addBloom()
let greenSphereNode = SCNNode(geometry: SCNSphere(radius: 0.1))
greenSphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.green
greenSphereNode.position = SCNVector3(0.3, 0, -1)
greenSphereNode.filters = addBloom()
self.augmentedRealityView.scene.rootNode.addChildNode(planeNode)
self.augmentedRealityView.scene.rootNode.addChildNode(redSphereNode)
self.augmentedRealityView.scene.rootNode.addChildNode(greenSphereNode)
planeNode.geometry?.firstMaterial?.writesToDepthBuffer = false
}
/// Creates An Array Of CIBloom Filters
///
/// - Returns: [CIFilter]?
func addBloom() -> [CIFilter]? {
let bloomFilter = CIFilter(name:"CIBloom")!
bloomFilter.setValue(10.0, forKey: "inputIntensity")
bloomFilter.setValue(30.0, forKey: "inputRadius")
return [bloomFilter]
}
One thing to note however, which I did notice was that if I used an image with a transparent background for the contents of the SCNPlane it didn't work, although with another image it was fine.
Hope it points you in the right direction...

How do I update unlockable characters in SpriteKit game with Swift 3?

I have currently made a game featuring one player. I also made a character screen where the user can choose which character he/she wants to play with. How do I make it so that a certain high score unlocks a certain character, and allows the user to equip this character to use in the game?
Right now my player has his own swift file that defines all the properties of him:
import SpriteKit
class Player: SKSpriteNode, GameSprite {
var initialSize = CGSize(width:150, height: 90)
var textureAtlas: SKTextureAtlas = SKTextureAtlas(named: "Rupert")
let maxFlyingForce: CGFloat = 80000
let maxHeight: CGFloat = 900
var health:Int = 1
var invulnerable = false
var damaged = false
var damageAnimation = SKAction()
var dieAnimation = SKAction()
var forwardVelocity: CGFloat = 190
var powerAnimation = SKAction()
init() {
super.init(texture:nil, color: .clear, size: initialSize)
createAnimations()
self.run(soarAnimation, withKey: "soarAnimation")
let bodyTexture = textureAtlas.textureNamed("pug3")
self.physicsBody = SKPhysicsBody(texture: bodyTexture, size: self.size)
self.physicsBody?.linearDamping = 0.9
self.physicsBody?.mass = 10
self.physicsBody?.allowsRotation = false
self.physicsBody?.categoryBitMask = PhysicsCategory.rupert.rawValue
self.physicsBody?.contactTestBitMask = PhysicsCategory.enemy.rawValue | PhysicsCategory.treat.rawValue | PhysicsCategory.winky.rawValue | PhysicsCategory.ground.rawValue
func createAnimations() {
let rotateUpAction = SKAction.rotate(toAngle: 0.75, duration: 0.475)
rotateUpAction.timingMode = .easeOut
let rotateDownAction = SKAction.rotate(toAngle: 0, duration: 0.475)
rotateDownAction.timingMode = .easeIn
let flyFrames: [SKTexture] = [
textureAtlas.textureNamed("pug1"),
textureAtlas.textureNamed("pug2"),
textureAtlas.textureNamed("pug3"),
textureAtlas.textureNamed("pug4"),
textureAtlas.textureNamed("pug3"),
textureAtlas.textureNamed("pug2")
]
let flyAction = SKAction.animate(with:flyFrames, timePerFrame: 0.07)
flyAnimation = SKAction.group([SKAction.repeatForever(flyAction), rotateUpAction])
let soarFrames:[SKTexture] = [textureAtlas.textureNamed("pug5")]
let soarAction = SKAction.animate(with: soarFrames, timePerFrame: 1)
soarAnimation = SKAction.group([SKAction.repeatForever(soarAction), rotateDownAction])
This is not all the code but you get the point.
I then say: let player = Player() in my Gamescene file which essentially attaches all the attributes in the player file to my player that will be seen in the Gamescene. Even if I am able to replace the initial player with a certain different player, there are so many animations that I don't know how to replace everything at once. I want to set a condition that spans over both the gamescene class and the player class so that it can just sub out certain images for other ones and keep the same actions.
Thank you for any help!
Here are some techniques you can use for making things like this more manageable:
Have a naming convention for your character images and/or sprite sheets, so that you can pass in a name to your Player() constructor. Then, instead of loading texturenamed("pug3"), you load up texturenamed("\(playerName)3"). If the only difference between your characters are the sprite sheets, this is actually all you need on the Player end.
If your characters are more complex, with differences beyond just the images, like being larger or having more health, then you will probably want to go with a more data-oriented approach. There are a couple of approaches to this, but a handy one is to read your texture names, hitbox sizes, health levels, etc., out of a .plist file instead of hard-coding them. Then just pass in the name of the .plist file to load for the character you want. Then, to create a new character, you just create a new .plist file. Another approach would be to create a "character definition" struct that you could pass to the Player constructor that contains the information you need to construct the player (this, in turn, could be loaded from a .plist file, as well, but you could also hard-code them or save them directly using codable serialization).
If neither of the above approaches are sufficient, say if you need different behavior between characters, you could always go the subclassing route - pull the various parts and pieces out into functions, and then override those functions to add the specific functionality you need for more complex characters.

Swift - How to create an array of UITextFields and add them to a UIView

I currently have a controller with three different views (top, center and bottom). I would like to add several small textfields/boxes to the center area (kind of like the ones they use for a pin code). My current problem is that I need as many textfields as the amount of characters of a word I send to this view.
For example: if the word is "four", I need four textfields and if the word is "seven", I need five.
My idea was to create an array of textFields, loop through the word to count the amount of characters, and then fill the array. I think my code below does at least that, but now I want to add these small textfields to the centerView using anchors.
How can I add these boxes to the view so that they are horizontally displayed?
var arrayOfTextFields: [UITextField] = []
var game: Game? {
didSet {
if var answerContent = game?.answer {
for char in answerContent.characters {
let count = answerContent.characters.count
for _ in 1...count {
let myTextField: UITextField = UITextField(frame: CGRect(x: 0, y: 0, width: 40, height: 60))
self.arrayOfTextFields.append(myTextField)
answerContent = answerContent.replacingOccurrences(of: "\(char)", with: "_")
}
}
}
}
}
My centerView:
func createCenterView() -> UIView {
let centerView = UIView()
centerView.backgroundColor = Constants.MAIN_THEME_COLOR
centerView.translatesAutoresizingMaskIntoConstraints = false
return centerView
}
Create an array of views of uncertain count
In swift, there's no need to use loop.
let text = "seven"
let views = (0..<text.characters.count).map{ _ in UITextField(frame:.zero) }
Arrange these views horizontally
use UIStackView, or any other 3rd party StackView that support iOS 9 below.
or other layout system like Flexbox (SwiftBox)
or use UICollectionView.

fast-rendering array of CALayers vs array of CAShapeLayers in swift

I'm writing an app in which I need to populate 500 or so layers with previously-defined bezier paths and I'm running into either PERFORMANCE or INTERACTIVITY issues depending on which route I choose. Note that I require no animation features, as the paths i draw are static:
If I use CALayers, drawing the paths to screen takes about 15 seconds (bad), but the resulting interactive experience (i.e moving around the screen) is great.
If I use CAShapeLayers, drawing the paths to screen takes a fraction of a second (good), but the interactivity is terrible.
This is my code with CALayers:
func drawPathToCALayer (myView: UIImageView, pointArray: [CGPoint], bbox: CGRect, color: UIColor) {
// step 1. create path
let path = CGPathCreateMutable()
var pathOffset = CGAffineTransformMakeTranslation( (bbox.origin.x * -1), (bbox.origin.y * -1))
CGPathAddLines(path, &pathOffset, pointArray, pointArray.count)
CGPathCloseSubpath(path)
// step 2. draw to context -- this is the part that kills the amount of time that we call this function repeatedly
UIGraphicsBeginImageContextWithOptions(bbox.size, false, 0)
CGContextAddPath(UIGraphicsGetCurrentContext(), path)
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), color.CGColor)
CGContextFillPath(UIGraphicsGetCurrentContext())
// step 3. assign context drawing to sublayer
let sublayer = CALayer()
let strokeImage = UIGraphicsGetImageFromCurrentImageContext()
sublayer.frame = bbox
sublayer.contents = strokeImage.CGImage
UIGraphicsEndImageContext()
myView.layer.addSublayer(sublayer)
This is the code with CAShapeLayers
func drawPathToCAShapeLayer (myView: UIImageView, pointArray: [CGPoint], bbox: CGRect, color: UIColor) {
// step 1. create path
let path = CGPathCreateMutable()
var pathOffset = CGAffineTransformMakeTranslation( (bbox.origin.x * -1), (bbox.origin.y * -1))
CGPathAddLines(path, &pathOffset, pointArray, pointArray.count)
CGPathCloseSubpath(path)
// step 2. assign path to sublayer
let sublayer = CAShapeLayer()
sublayer.path = path
sublayer.fillColor = color
myView.layer.addSublayer(sublayer)
I like the succinctness and speed of the CAShapeLayer approach, but from an interactivity point of view, this route is a no go.
The question is (thanks for hanging in there), is there is a way to do a hybrid approach in which I draw to a CAShapeLayer temporarily, and use it to populate a CALayer like so?
func drawPathToHybrid (myView: UIImageView, pointArray: [CGPoint], bbox: CGRect, color: UIColor) {
// step 1. create path
let path = CGPathCreateMutable()
var pathOffset = CGAffineTransformMakeTranslation( (bbox.origin.x * -1), (bbox.origin.y * -1))
CGPathAddLines(path, &pathOffset, pointArray, pointArray.count)
CGPathCloseSubpath(path)
// step 2. assign path to sublayer
let sublayer = CALayer()
let tmplayer = CAShapeLayer()
tmplayer.path = path
tmplayer.fillColor = color
sublayer.contents = tmplayer.contents // ---> i know this doesn't work, but is there something similar I can take advantage of that doesn't rely on defining a context?
myView.layer.addSublayer(sublayer)
Or better yet, is there some other way that I can populate an array of CALayers with bezier paths to get both good INTERACTIVITY and PERFORMANCE?

Resources