I'm writing a 3D space game using SceneKit and I'm pretty happy with it so far, but I'm starting to hit some limitations and would like some advice.
I have a sphere which represents a star that sits on a point light, and I'd like to add some effects to this to make it look more realistic. I think I should use the sphere's shaderModifiers to do this, but I'm not sure which modifiers I should be looking at i.e. to achieve a lens flare effect. Infact if anyone can give me a clear explanation of the differences between: ShaderModifiers, SCNProgram and SCNTechnique that would be great!
I'd like to draw a 1px circle to represent an orbit. I've tried using a cylinder that is really thin, but this results in some visual artefacts (the ring seems to have gaps at larger distances and break up). Any ideas how I can do this and maintain a nice smooth circle?
The shortest way to have a flare effect for SceneKit models is to use a CoreImage framework's capabilities.
For making a visible orbit use a regular PNG-image technique with premultiplied alpha channel (RGBxA). Look at this link to find out how a png image with orbits looks like (save a file). And if you wanna automatically orient a plane toward a camera use a constraint.
And here's a code:
let orbitsOnPlane = SCNPlane(width: 10, height: 10)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named:"overlayOrbits.png")
orbitsOnPlane.materials = [material]
let orbitNode = SCNNode()
orbitNode.geometry = orbitsOnPlane
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
let constraint = SCNLookAtConstraint(target: orbitNode)
cameraNode.constraints = [constraint]
Related
Using SpriteKit for an iOS 9.0 app/game (and no physics body).
I have a few SpriteNodes on a scene. SpriteNodes are with oddly shaped images and runs through multiple frames for animation.
What is the best way to detect touches on a SpriteNode's image content, but not on transparent area of the entire image rectangle.
I see a lot of posts on using SKCropNode / MaskImage. In my scenario, I have multiple images/frames for each SpriteNode for the animation.
Please advice on an approach or point me in the right direction. Thanks.
Based on additional info from comments above:
With only 16 shapes, from the 16 frames, one of the more efficient ways would be to draw primitive shapes that roughly approximate the outlines of your character at each frame, and convert these to CGPaths. These can then be swapped in and out for each frame or (better) plucked out appropriately when there's a touch for testing based on the frame currently being shown at time of touch.
CGPaths are very lightweight structs, so there shouldn't be any performance problems with either approach.
CGPathContainsPoint is the old name of this test, which is now a modernised API for Swift 3 and onwards:
Troubles using CGPathContainsPoint SWIFT
There is an app called PaintCode, that's about $100 USD, which translates vectors into CGPaths, amongst other things, so you could use this to import your shapes. You can copy/paste vectors into it, which I suggest, because you might want to draw in a friendly drawing program. PaintCode doesn't have the world's best drawing experience.
Additionally, here's a free, online tool for doing the creation of a polygon path from textures. Probably more painful than using a drawing app and PaintCode, but it's FREE!
Alternative: Physics Bodies from Texture, and Contact with Touch
You can automagically create physics body shapes from a texture, like so, from the docs on this page:
let texturedSpaceShip = SKSpriteNode(texture: spaceShipTexture)
texturedSpaceShip.physicsBody = SKPhysicsBody(texture: spaceShipTexture,
size: CGSize(width: circularSpaceShip.size.width,
height: circularSpaceShip.size.height))
There have been reports of problems with determining if a point is within a given physics body, so it might be better to create a small physics object, place it where the touch is, and then determine if its in contact with the physics body shape appropriate for the current frame of the current game entity.
Caveat:
There's no telling (nor way to control, that I know of) how complex the resultant CGPaths are for these automagically created physics shapes.
I personally would just check the pixel of the current texture to see if is transparent or not. You can do this to get the pixel data:
(Note this code is hypothetical to get you started, I will try to work out a real example later on for you if you can't figure it out.)
(Also note, if you scale sprites, you need to handle that with converting touch location to texture)
let image = sprite.texture.cgImage()
if let dataProvider = image.dataProvider, let data = dataProvider.data
{
let screenScale = UIScreen.main.scale //let's handle retina graphics (may need work)
//touchedLocation is the location on the sprite, if the anchor was bottom left (it may be top left, will have to verify)
//So if I touch the bottom left corner of the sprite, I should get (0,0)
let x = touchedLocation.x * screenScale
let y = touchedLocation.y * screenScale
let width = sprite.texture.size().width * screenScale
let bpp = 4 // 4 bytes per pixel
let alphaChannel = 3 //Alpha channel is usually the highest byte
let index = bpp * (y * width + x) + alphaChannel
let byte = data.withUnsafeBytes({[UInt8](UnsafeBufferPointer(start:$0 + index,count:1))})
print(Alpha: \(byte))
}
else
{
//we have no data
}
I have a few images that can detect when they get touched right now I am using
plane.physicsBody = SKPhysicsBody(circleOfRadius: plane.frame.height / 2)
but I need it so it is the boarder of the image not a circle or rectangle.
We have a plane and you want a physical body.
Your first approach is circleOfRadius (the physical representation is a circle with a given radius). You can use another shape, such a rectangle:
plane.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(100, height: 100))
As explained in the comments you can also use the shape of a sprite (texture) to create the shape of the physics body:
plane.physicsBody = SKPhysicsBody(texure: plane.texture!, size: plane.size)
But a good way to choose it's this: if you want your game to run smootly on many different devices, don't make sure the physics calculations become too complicated. In other words if you have many object around your plane with physical behavior moving around and colliding with each other, you may choose to use simplified shapes to deal with the physics more efficiently, especially if a shape already looks a lot like a rectangle or a circle.
A little bit stuck on how to paint/draw an effect like an alpha Chanel onto an SKSpriteNode node i've started off with setting up the two images I need (ps if there is another way to do this is sprite-kit id love to know how to paint the masks
1)The hidden picture - SKSpriteNode *hiddenimageNode
2)The overlay that gets scratched away SKSpriteNode *myOverlay
3)And finally a mask node comprising of
UIImage *image;
SKTexture *maskTexture= [SKTexture textureWithImage:image];
maskNode = [SKSpriteNode spriteNodeWithTexture:maskTexture];
all of these are placed inside of a node "cropNode" [SKCropNode node];
this works more like a static image (that of a circle moving at touch location and not quite what I'm after, I'm hoping to be able to scratch away the entire image)
this works fine but its Not quite the look I'm after
Pictures: Dragging finger from pos1 to pos02, while "erasing purple layer to reveal a smileyface"
is there a way to make it look like I'm erasing the sprite?
nubie coder
//Updating project...
So since then I have tried using this code
https://github.com/oyiptong/CGScratch
and have added it to my SkScene by creating a subview then placing the UIView (Scratchview into it)the erasing is working however the erasing is not happening where the touches are occurring, any ideas why this might be happening?
If you are doing this in iOS 8, then your best bet is to just use SKSpriteNodes as your masking nodes, there is a weird bug with other kinds of nodes that causes distortion.
If you are doing this with iOS9 +, then SKShapeNodes are fixed.
I am going to explain the concept for iOS 9. To get this to work in iOS 8 is a real pain, since subtraction does not subtract alpha in iOS 8.
Now for your mask nodes, you only have 2 options for drawing, On and Off based on the alpha level of the pixels in your mask image. So what you want to do is incorporate subtraction alpha blending to create your desired effect.
let bigcircle = SKShapeNode(circleOfRadius: 80)
bigcircle = .whiteColor()
let littlecircle = SKShapeNode(circleOfRadius: 40)
littlecircle.position = CGPoint(x: 10, y: 10)
littlecircle.fillColor = .whiteColor()
littlecircle.blendMode = .Subtract
bigcircle.addChild(littlecircle)
maskNode = bigcircle
What this code is doing is making a big white circle with a radius of 80 points, and drawing a white circle inside of it at 40 points. Since we are using subtraction blending, it is going to take the new color and subtract it from the old (in our case white(1,1,1,1) - white(1,1,1,10 = transparent(0,0,0,0)) and get us a nice hole in our mask that will end up being cropped out of the layer over your smiley face.
I have a sphere that is a dynamic body. I would like to animate the scale of this sphere so that it grows in size:
let sphere = SCNNode(geometry: SCNSphere(radius: 1))
scene.rootNode.addChildNode(sphere)
sphere.physicsBody = SCNPhysicsBody.dynamicBody()
sphere.runAction(SCNAction.scaleTo(10, duration: 1))
However, this does not seem to work. The sphere falls down due to gravity, but stays the same size. If I comment out the line that gives the sphere its physics body, then the scaling animation plays out like it should.
This strange phenomenon is even observed without an animation. Simply changing the scale of the sphere directly doesn't work:
let sphere = SCNNode(geometry: SCNSphere(radius: 1))
scene.rootNode.addChildNode(sphere)
sphere.physicsBody = SCNPhysicsBody.dynamicBody()
sphere.scale = SCNVector3(x: 10, y: 10, z: 10)
The sphere drops down but remains with a radius of 1. Unless the sphere is scaled before its physics body is added. In which case, the sphere starts out scaled to 10 and drops down, keeping its radius of 10.
Interestingly, upon further inspection by printing out the sphere's scale after the animation has supposedly run, it is observed that the scale has indeed changed, it's just not visible unless the physics body is removed:
let sphere = SCNNode(geometry: SCNSphere(radius: 1))
scene.rootNode.addChildNode(sphere)
sphere.physicsBody = SCNPhysicsBody.dynamicBody()
sphere.runAction(SCNAction.scaleTo(10, duration: 1), completionHandler: {() in
println(sphere.scale.x)
sphere.physicsBody = nil
})
Why is it not possible to change the scale of dynamic body? (Note that static and kinematic bodies will scale just fine.) Is it possible to achieve this in some way?
You wouldn't just need a "scaling velocity" to interact with other objects as one changes size, you'd need a "scaling force". How strongly should it push on things that it collides with while growing? And what happens if it's in a situation that depends on its mass, like balancing on a seesaw?
Game engines are already loose approximations of real-world physics, so asking them to figure out Ant-Man physics on their own is a bit of a stretch. If they could do that, they could probably also start building killer robots, and that'd kinda ruin your day. :)
Anyway, depending on how you want your expanding sphere to affect things, you have a few options. One is to delete and re-create the physics body at intervals:
let duration = 5.0
node.runAction(.customActionWithDuration(duration, actionBlock: { node, progress in
let scale = 1.0 + progress / CGFloat(duration)
node.physicsBody = nil
node.scale = SCNVector3(x: scale, y: scale, z: scale)
node.physicsBody = .dynamicBody()
}))
This does it every frame, which could be costly — you might want to throw some gating on progress in there so it happens less often. (And depending on what other effects need to happen as a result of the sphere changing size, you could set things like mass when you re-create the physics body.)
Another option might be to look at SCNPhysicsField. Use a radial gravity field to make a region that shoves everything out of its area of influence, then animate its parameters to change size and strength over time.
I ran into this when trying to scale a model I was importing from a .scn file. Just like rickster did, I ended up removing the physics body, scaled the shape & then re-attached the physics body. This was only done during startup, so not a big performance impact.
Physics simulation is a kind of animation in addition of implicit/explicit animations or actions. So when change the animatable property to a node physics affected, take special consideration of animation state of node.
on the other hand,sceneKit apply result of physics simulation to presentationNode, which represents node's current render state. you can check information of presentationNode to confirm why your animation sucks.
I am trying to make a tube and its physics body like so in SceneKit.
let BoxGeometry = SCNTube(innerRadius: 5, outerRadius: 12.5, height: 4)
Box = SCNNode(geometry: BoxGeometry)
Box.pivot = SCNMatrix4MakeRotation(Float(M_PI_2/8), 0, 1, 0)
Box.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Static, shape: nil)
Box.physicsBody?.mass = 5
Box.categoryBitMask = colorCategory
scene.rootNode.addChildNode(Box)
However, when another object falls onto this object it does not pass through the center. Instead it sits appearing to be levitating in air. It is acting like the physics body is a complete cylinder, not like the tube with the hole in the middle. How can I fix this so that objects can pass through the center? The tubes appearance looks as expected.
Thanks!
Dynamic bodies in SceneKit must be convex. (If you look into the general theory behind collision detection in games, i.e. not just SceneKit, you'll find that there are massive differences in speed and efficiency between collision detection on convex versus concave shapes.) A tube is a concave shape — it has a hole in it.
Luckily, your tube is being used as a static body. For static bodies only, there's an option to make the physics shape (an approximation of) a concave geometry:
let shape = SCNPhysicsShape(geometry: tube,
options: [SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeConcavePolyhedron])
box.physicsBody = SCNPhysicsBody(type: .Static, shape: shape)
There is a performance cost to this. If you find your framerate limited by CPU usage after this change, or if you want to have a dynamic body be (effectively) concave, you might get better performance by making a physics shape that's a compound of several other shapes — e.g. build a dummy node hierarchy (not one that's actually in your scene) containing a bunch of cylinders or boxes arranged into a ring, then make a physics shape from them with SCNPhycsicsShape(node:options:).
Change the shape of your physicsBody to match the geometry of your tube.