I am trying to make an app that uses 3D physics and detects when the various nodes in the scene have all come to rest. The logical way to do this would seem to be testing their isResting property on their physics body. However, no matter how stationary the objects are, this value is never set. I have checked that allowsResting is true and am testing the attribute as follows, but I have never seen even one object 'at rest' despite logging out its status on each update. I've also tried putting this in the didRender function. Anyone got any ideas as to why isResting never seems to be set?
var isTotallyAtRest = false
func renderer(aRenderer: SCNSceneRenderer, didSimulatePhysicsAtTime time: NSTimeInterval) {
for object in allObjects {
if let physics = object {
if physics.isResting == false {return}
}
}
isTotallyAtRest = true
}
Related
The physicsWorld(_:didBegin:) function should only be called when some node's category bit is set in another nodes contactTest bit mask. However, in my code, there is an impossible situation happening where a contact is occurring when neither of the contacting bodies have each other in their contactTest bit masks. Eg:
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
if (contact.nodeA.physicsBody!.categoryBitMask & contact.nodeB.physicsBody!.contactTestBitMask) != 0 {
print("All good")
} else if (contact.nodeA.physicsBody!.contactTestBitMask & contact.nodeB.physicsBody!.categoryBitMask) != 0 {
print("All good")
} else {
fatalError("Impossible") // << Execution reaches here
}
// .. Code handling the contact ...
}
I noticed this started happening when I added this line to the physicsWorld(_:didBegin:) function:
// The ball is the only node in the game that has a contactTestBitMask other than 0
let ball = contact.nodeA.name == "ball" ? contact.nodeA : contact.nodeB
ball.physicsBody!.contactTestBitMask = PhysicsCategory.dynamic
Regardless of what code I add to the function, that fatalError should never be reached. I don't know if changing bit masks within the didBeginContact method leads to undefined behaviour, but I still don't think the physicsWorld(_:didBegin:) should ever get called if categoryBitMask & contactTestBitMask == 0.
If I change (changing in both nodeA and nodeB) nodeA/B.physicsBody!.categoryBitMask to nodeA/B.categoryBitMask then it never crashes, but according to the documentation, a SCNNodes category bit mask property doesn't affect the SceneKit physics simulation.
If anyone has any suspicious of why this could happen please let me know as I'm convinced it's a bug.
Thanks
I have 2 delegate methods that are being called by notifications from a 3rd party library.
Method 1:
mediaContentWasUpdated()
Method 2:
adMediaDidBeginPlaying()
In Method 1, a key variable (adDuration) is set from a parameter that is passed in with the notification. As far as I can see this is the only place to get this information.
In Method 2, we check the adDuration and if it is greater than 0 then we update the UI to reflect that we are in fact play an ad.
A bug has appeared where sometimes these two methods are called in the wrong order. Meaning the adDuration is not set and Method 2 thinks there is no ad media to be played and does not update the UI accordingly.
My current attempt at a solution is to make adDuration optional and use an NSCondition to cause Method 2 to wait for Method 1 to set adDuration and then proceed.
var adDuration : Double?
let condition = NSCondition()
func mediaContentWasUpdated(notification: NSNotificiation) {
condition.lock()
if(notificationHasAdDurationInfo(notification)) {
self.adDuration = getAdDuration(notification)
condition.signal()
}
condition.unlock()
}
func adMediaDidBeginPlaying(notification: NSNotification) {
condition.lock()
while adDuration == nil {
condition.wait()
}
if adDuration! > Double(0) {
updateUIForAd()
}
condition.unlock()
}
This is my first time trying something like this and I worry I am doing something wrong. I also have some concerns about locking and unlocking threads needlessly (which would happen in a well timed run, or if there were no ad content to be played).
Outside factors are hindering my ability to test and I wanted to get some input to see if I am heading in the right direction while I wait for those issues to be resolved.
Your discussion of NSCondition got me on the same track with you, and I built two or three solutions using DispatchGroup (which is the better tool for this), but they always had little corner cases that could behave badly, and didn't really capture the intent.
(If you're interested in the DispatchGroup solutions, they're of the form: call .enter() in init, call .leave() when the duration comes in, call notify() when the playing starts. It works fine, but it introduces corner cases that can crash, just like NSCondition.)
Getting back to the real intent:
Update the UI when the duration is known and the ad has started playing.
There's no concurrency going on here. So pulling out GCD is not just overkill; it actually makes things worse because it introduces lots of complicated corner cases.
So I thought about how I'd have solved this back before GCD. And the answer is obvious: just check if you have the data you want, and then do the thing. (Reading through the comments, I see Paulw11 pointed this out as well.)
Personally I like to pull this kind of thing into its own type to make things more self-contained. I hate some of the names here, but the idea should be clear:
class AdPlayer {
private var readyToPlay = false
private var duration: Double = 0.0
private let completion: (Double) -> Void
func setDuration(from notification: Notification) {
if(notificationHasAdDurationInfo(notification)) {
duration = getAdDuration(notification)
}
playIfReady()
}
func play() {
readyToPlay = true
playIfReady()
}
private func playIfReady() {
if duration > 0 && readyToPlay {
completion(duration)
}
}
init(completion: #escaping (Double) -> Void) {
self.completion = completion
}
}
When you set each thing, see if you're ready to update, and if so, update. I've gotten rid of the optional as well, since I believe the intent is "0 duration is always wrong." But you could use an Optional so you could detect actually receiving a 0 from the notification.
With that, you just set up a player property:
player = AdPlayer(completion: updateUIForAd)
(Note that the above might be creating a retain loop, depending on what updateUIForAd is; you may need a [weak self] closure or the like here.)
And then update it as needed:
func mediaContentWasUpdated(notification: NSNotificiation) {
player.setDuration(from: notification)
}
func adMediaDidBeginPlaying(notification: NSNotification) {
player.play()
}
A big advantage of creating the AdPlayer type is that it's easy to reset the system when the ad is done (or if something goes wrong). Just throw away the whole object and create another one.
So I was reading the apple documentation for best sprite kit practices. I came across this:
For example, if your game uses the same textures for all its gameplay, you might create a special loading class that runs once at startup. You perform the work of loading the textures once, and then leave them in memory. If a scene object is deleted and recreated to restart gameplay, the textures do not need to be reloaded.
And this would significantly help performance in my application. Can someone point me in the right direction to how I would go about achieving this?
I presume I would call a function to load up texture's in my View Controller? And then access that texture atlas?
The thing is, do you really want to cache the resources like that? Can't say I ever found a need for something of that nature. Anyways, if doing that somehow helps with your app's performance, then you can make a TextureManager class which would be a singleton (create separate file for TextureManager class), like this:
class TextureManager{
private var textures = [String:SKTexture]()
static let sharedInstance = TextureManager()
private init(){}
func getTexture(withName name:String)->SKTexture?{ return textures[name] }
func addTexture(withName name:String, texture :SKTexture){
if textures[name] == nil {
textures[name] = texture
}
}
func addTextures(texturesDictionary:[String:SKTexture]) {
for (name, texture) in texturesDictionary {
addTexture(withName: name, texture: texture)
}
}
func removeTexture(withName name:String)->Bool {
if textures[name] != nil {
textures[name] = nil
return true
}
return false
}
}
Here you are using dictionary and associate each texture with its name. Pretty simple concept. If there isn't a texture with the same name in a dictionary, then add it. Just beware of premature optimization.
The usage:
//I used didMoveToView in this example, but more appropriate would be to use something before this method is called, like viewDidLoad, or doing this inside off app delegate.
override func didMoveToView(view: SKView) {
let atlas = SKTextureAtlas(named: "game")
let texture = atlas.textureNamed("someTexture1")
let dictionary = [
"someTexture2": atlas.textureNamed("someTexture2"),
"someTexture3": atlas.textureNamed("someTexture3"),
"someTexture4": atlas.textureNamed("someTexture4"),
]
TextureManager.sharedInstance.addTexture(withName: "someTexture", texture: texture)
TextureManager.sharedInstance.addTextures(dictionary)
}
As I said, you have to put TextureManager implementation in a separate file, to make it real singleton. Otherwise, if you define it in GameScene for example, you will be able to call that private init, and then TextureManager will not be a real singleton.
So, with this code you can create some textures at the very beginning of the app lifecycle, like it is said in the docs:
For example, if your game uses the same textures for all its gameplay,
you might create a special loading class that runs once at startup.
and fill the dictionary with them. Later on, whenever you need a texture, you will not use atlas.textureNamed() method, but rather load it from a dictionary property of a TextureManager class. Also, when transitioning between scenes, that dictionary will survive scene's deinits, and will persist while app is alive.
I am working on Cocos2d iphone SDK and stuck with an issues. Check my code here.
Obstacle Class
#objc class Obstacle: CCNode {
weak var __pipe: CCSprite!
var ignoreCollision:Bool = false
override init!() {
super.init()
//NSLog("init plain")
userInteractionEnabled = true
ignoreCollision = false
}
func didLoadFromCCB() {
...
}
}
The main scene where I have placed collision delegate methods. The method is called once the player object collides with obstacle object.
func ccPhysicsCollisionPreSolve(pair: CCPhysicsCollisionPair!, hero: Player!, platform: Obstacle!) -> ObjCBool {
if !isGameOn {
NSLog("PLATFORM: Game finished")
return false
}
if platform.ignoreCollision {
platform.ignoreCollision = !platform.ignoreCollision
// For score updates
hudLayer.updatePlatform(++scorePlatforms)
}
return true
}
Now here, I am just trying to use simple Bool property from platform object and what I get is a crash. My app crashes on the if... condition statement where I am using that property. I am unable to get what is with this as I am simply using a property from object.
I checked the object and found platform shows me of type Some instead ob Obstacle. I have tried using
var p: Obstacle = platform as Obstacle
and replaced all platform with p but yet I am facing the crash. I thought the type now shows me some random hex number which might be the issue.
Can anyone help me here as I am unable to find out how I should access property from this platform object in ccPhysicsCollisionPreSolve method?
Sorry guys for the trouble but it was my mistake. I was understanding the same incorrectly.
The Obstacle class represents the platform as well as its background layer having tripple height of the device screen. But my ball collides only with that __pipe sprite in Obstacle class and I am referring the whole Obstacle class which is wrong.
I used platform.parent!.ignoreCollision and problem is solved. :)
This little miss costed me 3-4 days of R&D and work extra.
I have created a subclass of SKSpriteNode. I connect instances of that class together with joints of type SKPhysicsJointLimit. I do this within my didEndContact(contact: SKPhysicsContact) in my GameScene:
var joint = SKPhysicsJointLimit.jointWithBodyA(contact.bodyA, bodyB: contact.bodyB, anchorA: pos1!, anchorB: pos2!)
self.physicsWorld.addJoint(joint)
This works well so far.
Then i come to the point where i want to release the node from the joint. According to the SKPhysicsBody docs there is a property called "joints" which is an array holding SKPhysicsJoint objects. I thought thats exactly what I need, but I am not able to iterate over an instance's joints and remove them from the physicsWorld. To do the job i added a method to my custom SKSpriteNode subclass.
func freeJoints(world: SKPhysicsWorld){
if let joints = self.physicsBody?.joints {
for joint in joints{
println("found a joint: \(joint)")
// example print:
//found a joint: <PKPhysicsJointRope: 0x7fbe39e95c50>
world.removeJoint(joint as SKPhysicsJoint)
}
}
}
Calling the method fails after the println() statement with the message "Swift dynamic cast failed". I would really appreciate your opinion in how to work with an SKPhysicsBody's joint property. More specifically: How to use (cast?) the items in the array to be able to remove them from a scene's SKPhysicsWorld.
I spent a little more time in investigating this. This is what I have come up with:
I decided to add an property to my SKSpriteNode subclass and manage the joints myself
var joints: [SKPhysicsJointLimit]
override init(){
...
self.joints = []
...
}
Everytime I add an joint to the scene's SKPHysicsWorld I also add it to the joints array of the SKNNode itself. Whilst iterating the SKPHysicsBody's joints-Array failed (see question) at the point I wanted to cast it to SKPhysicsJoint, removing items from the physics world works as intended when iterating the array of SKPhysicsJointLimit items:
func freeJoints(world: SKPhysicsWorld){
for item in self.joints{
println("removing item from physics world \(item)")
world.removeJoint(item)
}
self.joints.removeAll(keepCapacity: false)
}
}
This seems not to be the most elegant way to do the job, since there already is a framework managed array that promises to be same thing. But I was unable to utilize it and this works for now.