there seems to be a Cocos2dV3.4 display bug when adding lots of Chipmunk physics bodies quickly in the same spot in RELEASE MODE on a device. If the physics body is a rectangle its happens straight away, if it is a circle it takes longer but does happen. The key point is it always works fine in DEBUG mode but errors occur in RELEASE mode on the device. It always works fine in the simulator. (just Edit Scheme.. to change to release mode for RUN)
Example swift code to replicate. (simply add a dynamic polygon physics body at the same point quickly)
class MainTestScene: CCNode
{
var _rootPhysicsNode : CCPhysicsNode!
var count : CCTime = 0
override init()
{
super.init()
_rootPhysicsNode = CCPhysicsNode()
_rootPhysicsNode.gravity = ccp(0,-100)
self.addChild(_rootPhysicsNode)
}
override func update(delta: CCTime)
{
count += delta
if count > 0.2
{
count = 0
for var i = 0; i <= 10 ; i++
{
let item = CCSprite(imageNamed: "ccbResources/rainbowblinky.png")
item.anchorPoint = ccp(0.5,0.5)
item.scale = 0.3
item.position = ccp(CCDirector.sharedDirector().viewSize().width / 2, CCDirector.sharedDirector().viewSize().height / 2 )
item.physicsBody = CCPhysicsBody(rect: CGRectMake(0, 0, 100, 100), cornerRadius: 1)
_rootPhysicsNode.addChild(item)
}
}
}
}
In DEBUG mode you get sprites falling from a point and pushing each other out. (see picture) In RELEASE MODE on an iOS device the screen flicks every second shows a single sprite and slows down to a crawl.
Can someone help? Am I doing something wrong?
B
Related
I have the following code in one scene :
func setupPedestal()
{
// pedestal :
pedestal = SKSpriteNode(texture: pedestalTxt)
pedestal.position = CGPoint(x: 500, y: 10)
pedestal.physicsBody = SKPhysicsBody(rectangleOf: pedestalTxt.size())
pedestal.physicsBody?.isDynamic = false
pedestal.name = "pedestal"
self.addChild(pedestal)
}
this makes the pedestal appear where I want it to which is at the bottom of the screen.
Now I have the very same code on another scene and it appears in the middle. I can change the x value to move it across. However no matter what I put for the y value, it stays in the same place.
Other things such as the background isn't looping infinitely either.
Am I missing something in this new scene?
Thanks!
EDIT : Forgot to add, this is all on the same simulator for an iPhone 6.
Trying to make my first Swift app using the Sprite Kit.
The app has to create up to 5 objects that have to move across the screen randomly. The speed increases and size decreases every 5 seconds.
The scene setups correctly, objects appear and move but after some time the CPU usage becomes 100%, FPS falls to 0 and app hangs up. Sometimes it continues to work for some seconds, sometimes it doesn't.
What is the reason?
I'm just noob in Xcode - is it possible to find what block of code is working now during the simulation?
Here is the update function. I didn't include didMoveToView and touchesBegan part cause they seem not influencing the problem.
override func update(currentTime: CFTimeInterval) {
if (currentTime-oldTime)>1 {
if arrayofAims.isEmpty==false {
for currentAim in arrayofAims {
repeat {xvelocity=(Int(arc4random())%3 - 1) * aimSpeedX} while xvelocity == 0
repeat {yvelocity=(Int(arc4random())%3 - 1) * aimSpeedX} while yvelocity == 0
currentAim.physicsBody?.applyForce(CGVectorMake (CGFloat(xvelocity), CGFloat(yvelocity)))
}
}
oldTime=currentTime
timecount+=1
}
if timecount>5 {
if aimSpeedX < 5000 {
aimSpeedX+=200
}
if aimSizeRatioX > 0.3 {
aimSizeRatioX -= 0.05
aimSizeRatioY -= 0.05
}
timecount=0
if arrayofAims.count<6 { arrayofAims.append(createAnAim())
currentAim=arrayofAims[arrayofAims.count-1]
addChild(currentAim)
}
}
}
May by its some Sprite Kit collision model bug? A had
aim.physicsBody = SKPhysicsBody(texture: aim.texture!, size: aim.size)
aim.physicsBody?.categoryBitMask = aimCategory
aim.physicsBody?.collisionBitMask = aimCategory | boundCategory
I replaced it with
aim.physicsBody?.collisionBitMask = boundCategory
and freeze disappeared but objects don't interact with each others
I have an SKEmitterNode that is acting strange in different versions of iOS. If you look at the screenshot below, you will see that when my scene is loaded, the emitter I am using dumps an odd group of rain drops when the scene first loads.
The strangest part is that this happens in some versions of iOS like 8.1 and 9.2, but in some other versions like 9.0 and 7.0 it runs as expected. (meaning no initial clump of drops, just randomly spaced drops.)
Here is my code in didMoveToView:
let rainEmitterPath:NSString = NSBundle.mainBundle().pathForResource("homeScreenRain", ofType: "sks")!
self.rainEmitter01 = NSKeyedUnarchiver.unarchiveObjectWithFile(rainEmitterPath as String) as! SKEmitterNode
self.rainEmitter01.position = CGPointMake(device.x/2, device.y + 50)
self.rainEmitter01.targetNode = self
self.rainEmitter01.particleZPosition = 6.0
self.rainEmitter01.particleBirthRate = 300
self.rainEmitter01.particleSpeed = 5000 * worldScale
self.rainEmitter01.particleSpeedRange = worldScale * 1000
self.rainEmitter01.particleScale = worldScale
self.rainEmitter01.particleScaleRange = worldScale
self.rainEmitter01.particlePositionRange = CGVector(dx: device.x + 100, dy: 5)
//self.rainEmitter01.advanceSimulationTime(7.0)
self.addChild(rainEmitter01)
I have tried using the advanceSimulationTime method, but for some reason the group of drops still falls even after the delay.
I have also tried varying the y size of the position range because I figured that my height of 5 was causing this, but nothing changed even when I make the height equal to the size of the screen, and a height of 5 still works in other versions of iOS.
Anyone know why this is happening and a solution/workaround?
Thanks in advance.
On some iOS devices (iPhone 6s Plus) there is a partial and arbitrary disappearance of object parts.
How to avoid this?
All sticks must be the same and are clones of one SCNNode.
16 complex SCNNodes, from 3 SCNNode: box, ball and stick. Node containing a geometry by node.flattenedClone().
It must be like this:
Сode fragment:
func initBox()
{
var min: SCNVector3 = SCNVector3()
var max: SCNVector3 = SCNVector3()
let geom1 = SCNBox(width: boxW, height: boxH, length: boxL, chamferRadius: boxR)
geom1.firstMaterial?.reflective.contents = UIImage(data: BoxData)
geom1.firstMaterial?.reflective.intensity = 1.2
geom1.firstMaterial?.fresnelExponent = 0.25
geom1.firstMaterial?.locksAmbientWithDiffuse = true
geom1.firstMaterial?.diffuse.wrapS = SCNWrapMode.Repeat
let geom2 = SCNSphere(radius: 0.5 * boxH)
geom2.firstMaterial?.reflective.contents = UIImage(data: BalData)
geom2.firstMaterial?.reflective.intensity = 1.2
geom2.firstMaterial?.fresnelExponent = 0.25
geom2.firstMaterial?.locksAmbientWithDiffuse = true
geom2.firstMaterial?.diffuse.wrapS = SCNWrapMode.Repeat
let geom3 = SCNCapsule(capRadius: stickR, height: stickH)
geom3.firstMaterial?.reflective.contents = UIImage(data: StickData)
geom3.firstMaterial?.reflective.intensity = 1.2
geom3.firstMaterial?.fresnelExponent = 0.25
geom3.firstMaterial?.locksAmbientWithDiffuse = true
geom3.firstMaterial?.diffuse.wrapS = SCNWrapMode.Repeat
let box = SCNNode()
box.castsShadow = false
box.position = SCNVector3Zero
box.geometry = geom1
Material.setFirstMaterial(box, materialName: Materials[boxMatId])
let bal = SCNNode()
bal.castsShadow = false
bal.position = SCNVector3(0, 0.15 * boxH, 0)
bal.geometry = geom2
Material.setFirstMaterial(bal, materialName: Materials[balMatId])
let stick = SCNNode()
stick.castsShadow = false
stick.position = SCNVector3Zero
stick.geometry = geom3
stick.getBoundingBoxMin(&min, max: &max)
stick.pivot = SCNMatrix4MakeTranslation(0, min.y, 0)
Material.setFirstMaterial(stick, materialName: Materials[stickMatId])
box.addChildNode(bal)
box.addChildNode(stick)
boxmain = box.flattenedClone()
boxmain.name = "box"
}
Add nodes to the scene:
func Boxesset()
{
let Boxes = SCNNode()
Boxes.name = "Boxes"
var z: Float = -4.5 * radius
for _ in 0..<4
{
var x: Float = -4.5 * radius
for _ in 0..<4
{
let B: SCNNode = boxmain.clone()
B.position = SCNVector3(x: x, y: radius, z: z)
Boxes.addChildNode(B)
x += 3 * Float(radius)
}
z += 3 * Float(radius)
}
self.rootNode.addChildNode(Boxes)
}
This is tested and works great on the simulator - all devices,
on the physical devices - iPad Retina and iPhone 5.
Glitch is observed only at ultra modern iPhone 6s Plus (128 Gb).
The problem is clearly visible on the video ->
The problem with graphics can be solved by changing the Default rendering API to OpenGL ES...
...but you may have unexpected problems in pure computing modules that are not associated with graphics on iPhone 6S Plus. (the iPhone 6 has no such problems).
What's wrong?
TL;DR
Add scnView.prepareObject(boxmain, shouldAbortBlock: nil) to the end of your initBox.
I had a quick look at your code running on my 6s Plus and saw similar results. One of the corner nodes was missing, and was consistently missing each run. But we're not running the same code, mine's missing the materials data...
SceneKit is lazy, often things are not done until an object is added to a scene. I first came across this extracting geometry from a SceneKit primitive (SCNSphere etc), you're finding it when you clone a clone of something via the following lines.
let B: SCNNode = boxmain.clone()
...
boxmain = box.flattenedClone()
I'd say SceneKit is simply not completing the clone before the second clone occurs consistently. I have no way of knowing this for sure.
Removing the first clone fixes this issue for me. For example replace boxmain = box.flattenedClone() with boxmain = box. But I'd say what you've done is best practice, flattening these nodes will reduce the number of draw calls and improve performance (probably not an issue on the 6s).
SceneKit also provides a method - prepareObject:shouldAbortBlock: that will perform the operations required before an object is added to a scene (in this case the .flattenedClone()).
Adding the following line to the end of your initBox function also fixes the problem and is a better solution.
scnView.prepareObject(boxmain, shouldAbortBlock: nil)
Just say, I don't know the right answer to my question, but I found an acceptable solution for myself.
It turned out, it's all in the "diffuse" property of the SCNMaterial.
For whatever reason, Metal does not very like when diffuse = UIColor(...)
But if at least one element in a compound SCNNode (as in my case) is diffuse.contents = UIImage(...), then everything begins to work perfectly.
it works
diffuse=<SCNMaterialProperty: 0x7a6d50a0 | contents=<UIImage: 0x7a6d5b40> size {128, 128} orientation 0 scale 1.000000>
it doesn't work
diffuse=<SCNMaterialProperty: 0x7e611a50 | contents=UIDeviceRGBColorSpace 0.25 0.25 0.25 0.99>
I have found the solution of the problem is simply:
I just added one small, inconspicuous element with diffuse.contents = UIImage(...) to the existing 3 elements with diffuse.contents = UIColor(...) and it worked great.
So, my recommendations:
be careful when working with Metal. (I have a problems on the 5S devices and above)
thoroughly test the SceneKit applications on real devices, don't trust only the simulator
I hope, it's temporary bugs and it will be fix in future releases of Xcode.
Have a nice apps!
P.S. By the way, the finished app is completely free in the AppStore now
Qubic: tic-tac-toe 4x4x4
Im using a technique to control a sprite by rotating left/right and then accelerating forward. I have 2 questions regarding it. (The code it pasted together from different classes due to polymorphism. If it doesn't make sense, let me know. The movement works well and the off screen detection as well.)
When player moves off screen i call the Bounce method. I want the player not to be able to move off screen but to change direction and go back. This works on top and bottom but left and right edge very seldom. Mostly it does a wierd bounce and leaves the screen.
I would like to modify the accelerate algorithm so that i can set a max speed AND a acceleration speed. Atm the TangentalVelocity does both.
float TangentalVelocity = 8f;
//Called when up arrow is down
private void Accelerate()
{
Velocity.X = (float)Math.Cos(Rotation) * TangentalVelocity;
Velocity.Y = (float)Math.Sin(Rotation) * TangentalVelocity;
}
//Called once per update
private void Deccelerate()
{
Velocity.X = Velocity.X -= Friction * Velocity.X;
Velocity.Y = Velocity.Y -= Friction * Velocity.Y;
}
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1;
SoundManager.Vulture.Play();
}
//screen edge detection
public void CheckForOutOfScreen()
{
//Check if ABOVE screen
if (Position.Y - Origin.Y / 2 < GameEngine.Viewport.Y) { OnExitScreen(); }
else
//Check if BELOW screen
if (Position.Y + Origin.Y / 2 > GameEngine.Viewport.Height) { OnExitScreen(); }
else
//Check if RIGHT of screen
if (this.Position.X + Origin.X / 2 > GameEngine.Viewport.Width) { OnExitScreen(); }
else
//Check if LEFT of screen
if (this.Position.X - Origin.X / 2 < GameEngine.Viewport.X) { OnExitScreen(); }
else
{
if (OnScreen == false)
OnScreen = true;
}
}
virtual public void OnExitScreen()
{
OnScreen = false;
Bounce();
}
Let's see if I understood correctly. First, you rotate your sprite. After that, you accelerate it forward. In that case:
// Called when player hits screen edge
private void Bounce()
{
Rotation = Rotation * -1;
Velocity = Velocity * -1; //I THINK THIS IS THE PROBLEM
SoundManager.Vulture.Play();
}
Let's suposse your sprite has no rotation when it looks up. In that case, if it's looking right it has rotated 90º, and its speed is v = (x, 0), with x > 0. When it goes out of the screen, its rotation becomes -90º and the speed v = (-x, 0). BUT you're pressing the up key and Accelerate method is called so immediately the speed becomes v = (x, 0) again. The sprite goes out of the screen again, changes its velocity to v = (-x, 0), etc. That produces the weird bounce.
I would try doing this:
private void Bounce()
{
Rotation = Rotation * -1;
SoundManager.Vulture.Play();
}
and check if it works also up and bottom. I think it will work. If not, use two different Bounce methods, one for top/bottom and another one for left/right.
Your second question... It's a bit difficult. In Physics, things reach a max speed because air friction force (or another force) is speed-dependent. So if you increase your speed, the force also increases... at the end, that force will balance the other and the speed will be constant. I think the best way to simulate a terminal speed is using this concept. If you want to read more about terminal velocity, take a look on Wikipedia: http://en.wikipedia.org/wiki/Terminal_velocity
private void Accelerate()
{
Acceleration.X = Math.abs(MotorForce - airFriction.X);
Acceleration.Y = Math.abs(MotorForce - airFriction.Y);
if (Acceleration.X < 0)
{
Acceleration.X = 0;
}
if (Acceleration.Y < 0)
{
Acceleration.Y = 0;
}
Velocity.X += (float)Math.Cos(Rotation) * Acceleration.X
Velocity.Y += (float)Math.Sin(Rotation) * Acceleration.Y
airFriction.X = Math.abs(airFrictionConstant * Velocity.X);
airFriction.Y = Math.abs(airFrictionConstant * Velocity.Y);
}
First, we calculate the accelartion using a "MotorForce" and the air friction. The MotorForce is the force we make to move our sprite. The air friction always tries to "eliminate" the movement, so is always postive. We finally take absolute values because the rotation give us the direction of the vector. If the acceleration is lower than 0, that means that the air friction is greater than our MotorForce. It's a friction, so it can't do that: if acceleration < 0, we make it 0 -the air force reached our motor force and the speed becomes constant.
After that, the velocity will increase using the acceleration. Finally, we update the air friction value.
One thing more: you may update also the value of airFriction in the Deccelarate method, even if you don't consider it in that method.
If you have any problem with this, or you don't understand something (sometimes my English is not very good ^^"), say it =)