Super weird spritekit bug - ios

Okay I've reproduced this in a simple project and want to see if anyone can figure this out.
https://drive.google.com/file/d/0B4kzFbLmJr19QzFEa3NEZERLVnc/view?usp=sharing
tap to send yellow squares at the red square causing a collision. The red square SHOULD rotate but it doesnt. Go into didBeginContact, and uncomment the code noted, and you will see it rotate. super weird.
I've hit a bug that has me stumped. I really cant figure this one out. I've tried all the obvious things, and simplified it down pretty far.
When I try to rotate my sprite inside of didBeginContact I have to do so inside of an action for it to work. Look at the following example:
doesnt work
func didBeginContact(contact: SKPhysicsContact) {
println(self.hyperShip.zRotation) // prints 0 (next time STILL zero, NO PLACE IS THIS RESET)
self.hyperShip.zRotation = 1
println(self.hyperShip.zRotation) // prints 1
}
if i run the code in an action
works
func didBeginContact(contact: SKPhysicsContact) {
println(self.hyperShip.zRotation) // first time 0, thereafter 1
self.runAction(SKAction.runBlock({
self.hyperShip.zRotation = 1
}))
println(self.hyperShip.zRotation) // first time 0, thereafter 1
}
what is the difference? why would this behave any differently. it's so freaking weird. I can't see why they behave any differently.
if I trigger this code in my update or wherever else it works just fine the normal way
hypership is a subclass of another subclass of skspritenode (baseship).. not sure that why that'd make a difference, but i figured id include that detail
EDIT: how I assign hypership
I assign it at the top of hyperscene as a property
let hyperShip: HyperShip = HyperShip()
some setup in my scenes init
hyperShip.lives = self.lives
self.indicators.livesDisplay.text = "\(hyperShip.lives)"
hyperShip.addToParent(gameLayer)
addToParent function
override func addToParent(parentNode: SKNode){
super.addToParent(parentNode)
parentNode.addChild(self)
self.hyperScene = parentNode.scene as HyperScene
self.shipTrail.zPosition = -1
self.shipTrail.position = CGPoint(x: convertNum(-8), y: 0)
self.addChild(self.shipTrail)
self.hyperTrail.targetNode = self.hyperScene.starFieldFG.node
self.addChild(self.hyperTrail)
}
addToParent function in baseShip
func addToParent(parentNode: SKNode){
self.baseScene = parentNode.scene as BaseScene
}
hypership's init
override init(){
super.init()
}
baseship's init
override init() {
let texture = shipAtlas.textureNamed("ship")
super.init(texture: texture, color: nil, size: texture.size())
self.color = SKColor.grayColor()
self.zPosition = 1
}
I could copy paste more code, related to hyperships functionality, but I feel like its unrelated code. It's a fairly simple class.

Related

Swift Collision Not Working After SKReferenceNode Scale

I have a weird problem where my SKReferenceNode does not collide properly after being scaled up. It collides well in the center, but it ignores collisions and contacts on the edges.
Here is the first photo of the scene. The SKReferenceNode was scaled up significantly, and as seen in this photo, does not collide correctly on the edges. The PhysicsBody appears to be correct (with showPhysics on), yet the ball refuses to collide.
The SKReferenceNode is using an alpha collision mask, because I need to change it to a larger sprite in the future to do animations and such. Additionally, non-scaled objects work completely fine. Finally, after the ball collides with the center, which does work, and the block is reset, collisions start working as expected again. The code to fix it is probably in the reset function, but I reset everything before the level is loaded, so this wouldn't make sense. Here is a part of my reset code:
func reset() {
physicsWorld.gravity = CGVectorMake(0, -9.8)
for grav in gravityBlock {
grav.reset()
}
gravity = -9.8
//Resets blocks
for blocks in destroyedBlocks { //the blocks are stored in destroyedBlocks when collided with
blocks.reset()
}
destroyedBlocks = []
/*Irrelevant code removed*/
}
Here's my blocks.reset() function:
override func reset() {
super.reset()
self.physicsBody?.categoryBitMask = 1
removeAllActions()
self.texture = self.text
shadow.hidden = false
self.alpha = 0
shadow.alpha = 0
let appear = SKAction(named: "Appear")!
self.runAction(appear)
shadow.runAction(SKAction.fadeInWithDuration(0.25))
}
Here is super.reset()
func reset() {
self.hidden = false
state = .NotBroken
}
Thanks so much!
When you scale the sprite you are just changing it's visual representation. The Physics Body stays the same. That's why you are getting the "strange" during the collisions.
You can see that showing the physics uses in your scene, just open GameViewController and add this line
skView.showsPhysics = true
inside viewDidLoad, just after this line
skView.showsNodeCount = true
Then run again your game, the physics boundaries now will be highlighted.

Track the position of SKSpriteNode while doing a SKAction moveTo

I'm trying to figure a way to track a postions for multiple nodes, that spwan randomly on the screen so i can make a changes to them while moving when the reach random postion.
the nodes just move along the x axis and i want to be able to generate random number from 0 to postion.x of the ball, and change the color when it reachs the postion
override func update(currentTime: CFTimeInterval)
i tried tacking changes in update method but as soon as new node appers i lost track of the previos one
i also tried
let changecolor = SKAction.runBlock{
let wait = SKAction.waitForDuration(2, withRange: 6)
let changecoloratpoint = SKAction.runBlock { self.changecolorfunc(self.ball)}
let sequence1 = SKAction.sequence([wait, changecoloratpoint])
self.runAction(SKAction.repeatAction(sequence1, count: 3))
}
but it doesn't give me any control over the random postion
You have already all you needed.
Suppose you have a reference for your node:
var sprite1: SKSpriteNode!
And you want to spawn it to a random position (an example method..):
self.spawnToRandomPos(sprite1)
And suppose you want to moveTo your sprite1:
self.sprite1.runAction( SKAction.moveToY(height, duration: 0))
Everytime you check his position you know where is it:
print(sprite1.position)
So to know always your sprite1 position you could do this code and you see all it's movements:
override func update(currentTime: CFTimeInterval) {
print(sprite1.position)
}
P.S.:
In case you dont have references about your object spawned you can also give a name to a generic object based for example by a word followed to a counter (this is just an example):
for i in 0..<10 {
var spriteTexture = SKTexture(imageNamed: "sprite.png")
let sprite = SKSpriteNode(texture: spriteTexture, size: spriteTexture.size)
sprite.name = "sprite\(i)"
self.addChild(sprite)
}
After this to retrieve/re-obtain your sprite do:
let sprite1 = self.childNodeWithName("sprite1")
There are probably a hundred different ways of doing this. Here is how I would do it.
in your update func
checkForCollisions()
this will just scroll through "obstacles" that you randomly generate, and place whoever you want the color trigger to happen. They can be anything you want just change the name to match. if they overlap your "ball" then you can color one or the other.
func checkForCollisions() {
self.enumerateChildNodesWithName("obstacles") { node, stop in
if let obstacle: Obstacle = node as? Obstacle {
if self.ball.intersectsNode(obstacle) {
//color node or ball whichever you want
}
}
}
}

Create clickable body diagram with Swift (iOS)

I'm trying to recreate something for a iOS app (Swift) which I already made in HTML5 (using map area-coords).
I want to show a human body diagram that interacts with user clicks/touches. The body exists of let's say 20 different parts, and the user can select one or more body-parts. For every body-part there is a selected-state image that should appear when a part is selected. Clicking on a selected part will deselect it. After selecting one or more parts, the user can continue and will get some information about these parts in a next viewcontroller. I am attaching a simplified image to explain my goal.
Can somebody explain what the best way is to achieve this goal? Is there a comparable technique that can be used in Swift to create such an interactive image?
Thanks!
There is no built-in mechanism for detecting taps in irregular shapes. The standard UIView tap detection uses frame rectangles. (Likewise with CALayers, as suggested by sketchyTech in his comment above.)
I would suggest drawing your body regions as bezier paths. You could create a custom subclass of UIView (BodyView) that would manage an array of BodyRegion objects each of which include a bezier path.
The UIBezierPath class includes a method containsPoint that lets you tell if a point is inside the path. Your BodyView could us that to decide which path object was tapped.
Bezier paths would handle both drawing your body regions and figuring out which part was tapped.
A very simple way to achieve this, would be to place invisible buttons over the areas, then hook every one up to an IBAction and put anything you want to happen inside.
#IBAction func shinTapped(sender: UIButton) {
tappedCounter = 0
if tappedCounter == 0 {
// set property to keep track
shinSelected = true
// TODO: set button image to selected state
// increment counter
tappedCounter++
}else{
// set property to keep track
shinSelected = false
// TODO: set button image to nil
// reset counter
tappedCounter = 0
}
This might get a little tricky to layout, so that every button sits in the right spot, but if you work with size classes it is totally doable.
I am sure there is a more elegant way to do it, but this is one way.
Fixed!
First I added sublayers to the UIImageView like this
var path = UIBezierPath()
path.moveToPoint(CGPointMake(20, 30))
path.addLineToPoint(CGPointMake(40, 30))
// add as many coordinates you need...
path.closePath()
var layer = CAShapeLayer()
layer.path = path.CGPath
layer.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.5).CGColor
layer.hidden = true
bodyImage.layer.addSublayer(layer)
Than I overrided the touchesbegan function in order to show and hide when the shapes are tapped.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first! as? UITouch {
// get tapped position
let position = touch.locationInView(self.bodyImage)
// loop all sublayers
for layer in self.bodyImage.layer.sublayers! as! [CAShapeLayer] {
// check if tapped position is inside shape-path
if CGPathContainsPoint(layer.path, nil, position, false) {
if (layer.hidden) {
layer.hidden = false
}
else {
layer.hidden = true
}
}
}
}
}

Implicit CALayer animations for custom property in Swift

I've created a animatable Core Graphics drawing using PaintCode. It's basically a circle meter (not unlike the Apple Watch rings), which basically fills up as a timer counts down. The meterLevel controls the fill level of the circle meter, from 0 to 100. Basically, if a timer is set to 10 seconds, I set the meterLevel every 1 seconds to 90, 80, 70, etc...
This works good, however the animation is only drawn ever 1 second, and looks quite choppy. Instead, I'd like it be a smooth continuous filling meter.
Looking around it seemed like subclassing CALayer and creating an implicit animation for the meterLevel property might be the way to go. So here is what I have:
import UIKit
class MeterControlView: UIView
{
var meterLevel: Int = 0 {
didSet {
self.layer.setValue(meterLevel, forKey: "meterLevel")
}
}
var meterText: String = "00:00:00" {
didSet {
self.layer.setValue(meterText, forKey: "meterText")
}
}
override class func layerClass() -> AnyClass {
return MeterControlLayer.self
}
override func drawRect(rect: CGRect) {
// Do nothing
}
}
class MeterControlLayer: CALayer
{
#NSManaged
var meterLevel: Int
var meterText: String = "00:00:00"
override class func needsDisplayForKey(key: String) -> Bool {
if (key == "meterLevel") {
return true
}
return super.needsDisplayForKey(key)
}
override func actionForKey(key: String) -> CAAction? {
if (key == "meterLevel") {
let anim: CABasicAnimation = CABasicAnimation.init(keyPath: key)
anim.fromValue = self.presentationLayer()?.meterLevel
anim.duration = 1.0
return anim
} else {
return super.actionForKey(key)
}
}
override func drawInContext(ctx: CGContext) {
super.drawInContext(ctx)
UIGraphicsPushContext(ctx)
XntervalStyleKit.drawMeterControl(frame: self.bounds, meterTime: meterText, meterLevelValue: CGFloat(meterLevel))
UIGraphicsPopContext()
}
}
This unfortunately, doesn't work exactly the way I would expect. The animation is still a bit choppy, though closer to what I want.
My question is more general though, is this the right way to go about accomplishing what I want to do? I couldn't figure out the right way to set the layer properties meterLevel and meterText, without using setValueForKey:. Is that the right way to do this?
Animation/Graphics is definitely new to me. I'm an embedded C software guy trying to get into iOS development as a hobby.
The animation is still a bit choppy, though closer to what I want.
Given this, it seems as if Core Animation is actually drawing your layer every frame (or trying to, anyway).
Unfortunately, once you perform custom layer drawing each frame, your performance becomes main thread-bound: that is, normally, for properties that Core Animation can natively animate (such as bounds), the animation itself is rendered in the render server, which operates out-of-process from your application and has its own, high-priority render thread. The main thread of your application is free to do whatever it wants during these types of animation without any interruption to the animation.
drawInContext(_:), however, is called on the main thread of your application. If you put a log statement or breakpoint there, is it called many times over the course of your animation duration? If so, then the layer is properly animating. Your drawing operations within this function may be what's holding up the animation.
Try setting drawsAsynchronously to true on your layer. This defers drawing commands to a background thread, and it can improve performance sometimes. (Note that most, if not all, UIGraphics and Core Graphics functions are thread safe as of iOS 4, so background drawing is safe.)
Additionally, depending on how complex your animation is, you may want to draw several intermediate representations in advance (in the background and in parallel, if possible). Store these somewhere in memory if they aren't too large so you can simply display some of these bitmaps in your layer instead of drawing them just-in-time.
I wrote a UIView subclass called ConcentricProgressRingView which does something similar to what you're trying to do.
https://github.com/lionheart/ConcentricProgressRingView
Here's an example of what it looks like:
Usage
At the top of your UIViewController, import the module:
import ConcentricProgressRingView
Then, in your viewDidLoad:
let rings = [
ProgressRing(color: UIColor(.RGB(232, 11, 45)), backgroundColor: UIColor(.RGB(34, 3, 11))),
ProgressRing(color: UIColor(.RGB(137, 242, 0)), backgroundColor: UIColor(.RGB(22, 33, 0))),
ProgressRing(color: UIColor(.RGB(0, 200, 222)), backgroundColor: UIColor(.RGB(0, 30, 28)))
]
let progressRingView = try! ConcentricProgressRingView(center: view.center, radius: radius, margin: margin, rings: rings, defaultColor: UIColor.clearColor(), defaultWidth: 18)
view.addSubview(progressRingView)
Once you've instantiated your ConcentricProgressRingView instance, animate a specific ring to a percentage with setProgress.
ring.arcs[1].setProgress(0.5, duration: 2)
How it works
I'm not exactly sure what I'm doing differently from your example code in the question, but I'm creating a CABasicAnimation and setting a few parameters on it to tweak the animation behavior. The code is open source, so check it out. Hope this helps!

Sprite Kit - Creating node from didBeginContact()

I am using a custom brick class:
import SpriteKit
class Brick: SKSpriteNode {
enum type { case Normal }
convenience init (type: Brick.type) {
self.init(color: .greenColor(), size: CGSizeMake(75, 25))
physicsBody.SKPhysicsBody(rectangleOfSize: size)
physicsBody!.mass = 9999
physicsBody!.affectedByGravity = false
position = CGPointMake(100, 50)
// Category and collision bitmasks ...
// Action to move the brick upwards ...
}
// Stuff
}
In my scene, I'm creating one initial brick, and everything works just fine. However, when I try to add a brick in the didBeginContact() function, I get unexpected results.
I have a SKNode at 1/5 height of the screen. So everytime a brick reaches that height, it will make contact with this invisible node and create a new brick:
// Inside SKScene
func didBeginContact(contact: SKPhysicsContact) {
// I set physics body A and B ...
if a.categoryBitMask == Category.brick && b.categoryBitMask == Category.brickSpawner {
addChild(Brick(type: .Normal))
}
}
So the problem is: when I create a new brick inside this function, the position is set to (0, 0) instead of (100, 50) as defined in the Brick class. Actually, if I go ahead and use println(brick.position) I get (100, 50), but in the screen it looks positioned at (0, 0).
If I create a brick anywhere else in the code, for example in the touchesBegan() or update() functions, the position is set correctly. This problem only happens when creating the node from didBeginContact().
This should help, it explains the steps in a single frame.
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Introduction/Introduction.html
You are creating it at the wrong time, so one of the steps behind the scenes is resetting it due to a few steps being missing. Queue that process up for the didFinishUpdate command. ( I would just create the object in the did contact, then throw it into an array, then on didFinishUpdate, go through the array and add them to the scene, and clear the array)

Resources