How to flip a SKPriteNode upside down while being generated automatically - swift

I have a SKSpriteNode generated at every 1 second on a floor. Sometime it's over and sometime under it. The SpriteNode is an image and I want it to be flipped upside down when its under the floor. I tried
SKAction.scaleYto(scale, duration: 0.1
But it doesn't work!
Here is the code
func startGeneratingWallsEvery(seconds: NSTimeInterval) {
generationTimer = NSTimer.scheduledTimerWithTimeInterval(seconds, target: self, selector: "generateWall", userInfo: nil, repeats: true)
}
func stopGenerating() {
generationTimer?.invalidate()
}
func generateWall(){
var scale: CGFloat
let wall = ADWall()
let rand = arc4random_uniform(2)
if rand == 0 {
scale = -1.0
} else {
scale = 1.0
}
//let translate1 = SKAction.moveByX(0, y: scale*(wall.size.height), duration: 0.1)
let flip = SKAction.scaleYTo(scale, duration: 0.1)
wall.position.x = size.width/2 + wall.size.width/2
wall.position.y = scale * (kADGroundHeight/2 + wall.size.height/2)
walls.append(wall)
addChild(wall)
runAction(flip)
}
Help me please!!

I can think of a couple of different strategies for causing the sprit to be generated upside down automatically when they are created:
The first strategy is to have two different images at hand, one which is vertically flipped, and one which is not. When the sprite is created, if it spawns below the floor, simply retexture the SKSpriteNode with:
wall.texture = SKTexture(imageNamed:"flipped_image")
You can use that same method to retexture the sprite again should it ever appear above the floor.
Rotate the image. If you want to keep the texturing simple and but a single image for the wall object, another method you could try is to rotate the image by 180 degress, like so:
if belowFloor == true {
zRotation = CGFloat(M_PI)
}
HTH!

Related

Part of my animation is not working in SpriteKit

init () {
super.init(texture: nil, color: .clear, size: initialSize)
// Create physicsBody based on a frame of the sprite
// basically giving it gravity and physics components
let bodyTexture = textureAtlas.textureNamed("MainCharacterFlying1")
self.physicsBody = SKPhysicsBody(texture: bodyTexture, size: self.size)
// Lose momentum quickly with high linear dampening
self.physicsBody?.linearDamping = 0.9
// weighs around 30 kg
self.physicsBody?.mass = 30
// no rotation
self.physicsBody?.allowsRotation = false
createAnimations()
self.run(soarAnimation, withKey: "soarAnimation")
}
// Animations to make the main character seem like it is flying
func createAnimations() {
let rotateUpAction = SKAction.rotate(byAngle: 0, duration: 0.475)
rotateUpAction.timingMode = .easeOut
let rotateDownAction = SKAction.rotate(byAngle: -1, duration: 0.8)
rotateDownAction.timingMode = .easeIn
let flyFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying1"), textureAtlas.textureNamed("MainCharacterFlying2"),textureAtlas.textureNamed("MainCharacterFlying3"), textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying5"),textureAtlas.textureNamed("MainCharacterFlying4"),textureAtlas.textureNamed("MainCharacterFlying3"),textureAtlas.textureNamed("MainCharacterFlying2")]
var flyAction = SKAction.animate(with: flyFrames, timePerFrame: 0.1)
flyAction = SKAction.repeatForever(flyAction)
flyAnimation = SKAction.group([flyAction,rotateUpAction])
let soarFrames: [SKTexture] = [textureAtlas.textureNamed("MainCharacterFlying5")]
var soarAction = SKAction.animate(with: soarFrames, timePerFrame: 1)
soarAction = SKAction.repeatForever(soarAction)
let soarAnimation = SKAction.group([soarAction,rotateDownAction])
}
When I run this code on the IOS Simulator, I have to click on the screen once in order for my main sprite to show up on the screen, or else it will not. And when I click on the sprite, the sprite will start flapping its wings and go up (I have other code for that) however, the rotateUpAction and rotateDownAction are not showing up at all on the Simulator. So I was wondering if there were any solutions and anyone willing to answer.
Thank you for your time. Also, this code from the class of the main character, the name of the class is "Player"
you declare let soarAnimation inside your createAnimations function, meaning it's not in scope when you call self.run(soarAnimation). Solution: declare soarAnimation as a class property. Alternate solution: have createAnimations() return the SKAction and grab it in init that way

ARKit cannot rotate a SCNNode correctly on a vertical plane

I want to rotate a SCNNode, which is a painting image.
I can rotate it on the floor, but I cannot rotate it correctly on wall.
(I am using UIRotationGestureRecognizer)
...
func addPainting(_ hitResult: ARHitTestResult, _ grid: Grid) {
...
// Add the painting
let newPaintingNode = SCNNode(geometry: planeGeometry)
newPaintingNode.transform = SCNMatrix4(hitResult.anchor!.transform)
newPaintingNode.eulerAngles = SCNVector3(newPaintingNode.eulerAngles.x + (-Float.pi / 2), newPaintingNode.eulerAngles.y, newPaintingNode.eulerAngles.z)
newPaintingNode.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, hitResult.worldTransform.columns.3.z)
self.paintingNode = newPaintingNode
self.currentAngleY = newPaintingNode.eulerAngles.y
self.currentAngleZ = newPaintingNode.eulerAngles.z
augmentedRealityView.scene.rootNode.addChildNode(self.paintingNode!)
grid.removeFromParentNode()
}
...
#objc func rotateNode(_ gesture: UIRotationGestureRecognizer){
if let currentNode = self.paintingNode {
//1. Get The Current Rotation From The Gesture
let rotation = Float(gesture.rotation)
if self.paintingAlignment == "horizontal" {
log.verbose("rotate horizontal!")
//2. If The Gesture State Has Changed Set The Nodes EulerAngles.y
if gesture.state == .changed {
currentNode.eulerAngles.y = currentAngleY + rotation
}
//3. If The Gesture Has Ended Store The Last Angle Of The Cube
if(gesture.state == .ended) {
currentAngleY = currentNode.eulerAngles.y
}
} else if self.paintingAlignment == "vertical" {
log.verbose("rotate vertical!")
//2. If The Gesture State Has Changed Set The Nodes EulerAngles.z
if gesture.state == .changed {
currentNode.eulerAngles.z = currentAngleZ + rotation
}
//3. If The Gesture Has Ended Store The Last Angle Of The Cube
if(gesture.state == .ended) {
currentAngleZ = currentNode.eulerAngles.z
}
}
}
}
Does anyone know how can I rotate it correctly on wall? Thank you!
You're using eulerAngles instance property in your code:
var eulerAngles: SCNVector3 { get set }
According to Apple documentation:
SceneKit applies eulerAngles rotations relative to the node’s pivot property in the reverse order of the components: first roll (Z), then yaw (Y), then pitch (X).
...but three-component rotation can lead to Gimbal Lock.
Gimbal lock is the loss of one degree of freedom in a three-dimensional, three-gimbal mechanism that occurs when the axes of two of the three gimbals are driven into a parallel configuration, "locking" the system into rotation in a degenerate two-dimensional space.
So you need to use a four-component rotation property:
var rotation: SCNVector4 { get set }
The four-component rotation vector specifies the direction of the rotation axis in the first three components (XYZ) and the angle of rotation, expressed in radians, in the fourth (W).
currentNode.rotation = SCNVector4(x: 1,
y: 0,
z: 0,
w: -Float.pi / 2)
If you want to know more about SCNVector4 structure and four-component rotation (and its W component expressed in radians) look at THIS POST and THIS POST.
P.S.
In RealityKit framework instead of SCNVector4 structure you need to use SIMD4<Float> generic structure or simd_float4 type alias.
var rotation: simd_float4 { get set }
I tried SCNNode eulerAngles and SCNNode rotation and I have no luck...
I guess that is because I invoked SCNNode.transform() when I add the painting. When later I modify SCNNode.eulerAngles and invoke SCNNode.rotation(), they may have influence on the first transform.
I finally get it working using SCNMatrix4Rotate, not using eulerAngles and rotation.
func addPainting(_ hitResult: ARHitTestResult, _ grid: Grid) {
...
let newPaintingNode = SCNNode(geometry: planeGeometry)
newPaintingNode.transform = SCNMatrix4(hitResult.anchor!.transform)
newPaintingNode.transform = SCNMatrix4Rotate(newPaintingNode.transform, -Float.pi / 2.0, 1.0, 0.0, 0.0)
newPaintingNode.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, hitResult.worldTransform.columns.3.z)
self.paintingNode = newPaintingNode
...
}
...
#objc func rotateNode(_ gesture: UIRotationGestureRecognizer){
if let _ = self.paintingNode {
// Get The Current Rotation From The Gesture
let rotation = Float(gesture.rotation)
if gesture.state == .began {
self.rotationZ = rotation
}
if gesture.state == .changed {
let diff = rotation - self.rotationZ
self.paintingNode?.transform = SCNMatrix4Rotate(self.paintingNode!.transform, diff, 0.0, 0.0, 1.0)
self.rotationZ = rotation
}
}
}
And the above code works on both vertical and horizontal plane.

how to set physics properties for a circle so it follows given path

The movement of a physics body circle is too erratic for what I want to achieve. I would like to restrict it so it follows a certain path touching specific points (or a range of points) as shown in the image below. How can I set the physics properties to traverse a similar path?
how to set physics properties for a circle so it follows given path
So essentially you are looking to move a node to a particular point using real-time motion. I have an answer here showing how to do this, however given the number of up votes this question has received, I will provide a more detailed answer.
What the answer I linked to doesn't provide is traversing a path of points. So below I have provided a solution showing how this can be done below. It simply just moves to each point in the path, and each time the node reaches a point, we increment the index to move to the next point. I also added a few variables for travel speed, rate (to make the motion more smooth or static) and whether or not the node should repeat the path. You could further expand upon this solution to better meet the needs of your game. I would definitely consider subclassing a node and building this behavior into it so you can re-use this motion for multiple nodes.
One final note, you may notice the calculation for the impulse varies between my solution below and the answer I linked to above. This is because I am avoiding using angle calculation because they are very expensive. Instead I am calculating a normal so that the calculation is more computationally efficient.
One final note, my answer here explains the use of the rate factor to smooth the motion and leave room for motion distortions.
import SpriteKit
class GameScene: SKScene {
var node: SKShapeNode! //The node.
let path: [CGPoint] = [CGPoint(x: 100, y: 100),CGPoint(x: 100, y: 300),CGPoint(x: 300, y: 300),CGPoint(x: 300, y: 100)] //The path of points to travel.
let repeats: Bool = true //Whether to repeat the path.
var pathIndex = 0 //The index of the current point to travel.
let pointRadius: CGFloat = 10 //How close the node must be to reach the destination point.
let travelSpeed: CGFloat = 200 //Speed the node will travel at.
let rate: CGFloat = 0.5 //Motion smoothing.
override func didMoveToView(view: SKView) {
node = SKShapeNode(circleOfRadius: 10)
node.physicsBody = SKPhysicsBody(circleOfRadius: 10)
node.physicsBody!.affectedByGravity = false
self.addChild(node)
}
final func didReachPoint() {
//We reached a point!
pathIndex++
if pathIndex >= path.count && repeats {
pathIndex = 0
}
}
override func update(currentTime: NSTimeInterval) {
if pathIndex >= 0 && pathIndex < path.count {
let destination = path[pathIndex]
let displacement = CGVector(dx: destination.x-node.position.x, dy: destination.y-node.position.y)
let radius = sqrt(displacement.dx*displacement.dx+displacement.dy*displacement.dy)
let normal = CGVector(dx: displacement.dx/radius, dy: displacement.dy/radius)
let impulse = CGVector(dx: normal.dx*travelSpeed, dy: normal.dy*travelSpeed)
let relativeVelocity = CGVector(dx:impulse.dx-node.physicsBody!.velocity.dx, dy:impulse.dy-node.physicsBody!.velocity.dy);
node.physicsBody!.velocity=CGVectorMake(node.physicsBody!.velocity.dx+relativeVelocity.dx*rate, node.physicsBody!.velocity.dy+relativeVelocity.dy*rate);
if radius < pointRadius {
didReachPoint()
}
}
}
}
I did this pretty quickly so I apologize if there is a mistake. I don't have time now but I will add a gif showing the solution later.
A note about collisions
To fix the erratic movement during a collision, after the 2 bodies collide set the "rate" property to 0 or preferably a very low number to reduce the travel velocity impulse which will give you more room for motion distortion. Then at some point in the future (maybe some time after the collision occurs or preferably when the body is moving slow again) set the rate back to its initial value. If you really want a nice effect, you can actually ramp up the rate value over time from 0 to the initial value to give yourself a smooth and gradual acceleration.
Quick implementation using followPath:duration:
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor.blackColor()
let xWidth: CGFloat = CGRectGetMaxX(self.frame)
let yHeight: CGFloat = CGRectGetMaxY(self.frame)
let ball = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(50.0, 50.0))
let offset : CGFloat = ball.size.width / 2
// Moving path
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, offset, yHeight / 2)
CGPathAddLineToPoint(path, nil, xWidth * 2 / 3, yHeight - offset)
CGPathAddLineToPoint(path, nil, xWidth - offset, yHeight * 2 / 3)
CGPathAddLineToPoint(path, nil, xWidth / 2, offset)
CGPathAddLineToPoint(path, nil, offset, yHeight / 2)
// Movement
let moveByPath = SKAction.followPath(path, asOffset: false, orientToPath: false, duration: 4.0)
let moveForever = SKAction.repeatActionForever(moveByPath)
ball.runAction(moveForever)
self.addChild(ball)
}
In GameViewController.swift, I changed the default GameScene.unarchiveFromFile method to let scene = GameScene(size: view.bounds.size) for creating the scene's size.
Preview:

How to drag a SKSpriteNode without touching it with Swift

I'm trying to move SKSpriteNode horizontally by dragging. To get the idea of what I try to achieve you can watch this. I want player to be able to drag sprite without touching it. But I don't really know how to implement it correctly.
I tried to do something like:
override func didMoveToView(view: SKView) {
/* Setup your scene here */
capLeft.size = self.capLeft.size
self.capLeft.position = CGPointMake(CGRectGetMinX(self.frame) + self.capLeft.size.height * 2, CGRectGetMinY(self.frame) + self.capLeft.size.height * 1.5)
capLeft.zPosition = 1
self.addChild(capLeft)
let panLeftCap: UIPanGestureRecognizer = UIPanGestureRecognizer(target: capLeft, action: Selector("moveLeftCap:"))
And when I'm setting a moveLeftCap function, code that I've found for UIPanGestureRecognizer is requiring "View" and gives me an error. I also wanted to limit min and max positions of a sprite through which it shouldn't go.
Any ideas how to implement that?
You probably get that error because you can't just access the view from any node in the tree. You could to refer to it as scene!.view or you handle the gesture within you scene instead which is preferable if you want to keep things simple.
I gave it a try and came up with this basic scene:
import SpriteKit
class GameScene: SKScene {
var shape:SKNode!
override func didMoveToView(view: SKView) {
//creates the shape to be moved
shape = SKShapeNode(circleOfRadius: 30.0)
shape.position = CGPointMake(frame.midX, frame.midY)
addChild(shape)
//sets up gesture recognizer
let pan = UIPanGestureRecognizer(target: self, action: "panned:")
view.addGestureRecognizer(pan)
}
var previousTranslateX:CGFloat = 0.0
func panned (sender:UIPanGestureRecognizer) {
//retrieve pan movement along the x-axis of the view since the gesture began
let currentTranslateX = sender.translationInView(view!).x
//calculate translation since last measurement
let translateX = currentTranslateX - previousTranslateX
//move shape within frame boundaries
let newShapeX = shape.position.x + translateX
if newShapeX < frame.maxX && newShapeX > frame.minX {
shape.position = CGPointMake(shape.position.x + translateX, shape.position.y)
}
//(re-)set previous measurement
if sender.state == .Ended {
previousTranslateX = 0
} else {
previousTranslateX = currentTranslateX
}
}
}
when you move you finger across the screen, the circle gets moves along the x-axis accordingly.
if you want to move the sprite in both x and y directions, remember to invert the y-values from the view (up in view is down in scene).

Keeping an image on screen in Swift

I have a game where I use a UISwipeGestureRecognizer to move the screen. I am trying to figure out how to make it so that the image that I am swiping cannot move off the screen. I have looked around the internet and have not been able to find anything.
Here is my swipe method:
func swipedRight(sender: UISwipeGestureRecognizer) {
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("moveBackground"), userInfo: nil, repeats: true)
if imageView.frame.origin.x > 5000 {
imageView.removeFromSuperview()
}
}
Here is my moveBackground method:
func moveBackground() {
self.imageView.frame = CGRect(x: imageView.frame.origin.x - 100, y: imageView.frame.origin.y, width: 5500, height: UIScreen.mainScreen().bounds.height)
}
The only things I have tried so far is to check if the view is in a certain position and remove it if it is but that hasn't worked. I added that code to the swipe method.
It will be very helpful if you post your class code here, so we can be more specific helping you.
By the way, you should check the object position each time the function is called, before move it, taking in mind its size.
Lets say you have a playing cards game and you are swiping a card along the board.
To avoid swiping the card off the board (off the screen) you will have to do something like this:
// I declare the node for the card with an image
var card = SKSpriteNode(imageNamed: "card")
// I take its size from the image size itself
card.physicsBody = SKPhysicsBody(rectangleOfSize: card.size)
// I set its initial position to the middle of the screen and add it to the scene
card.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2)
self.addChild(card)
// I set the min and max positions for the card in both X and Y axis regarding the whole scene size.
// Fit it to your own bounds if different
let minX = card.size.width/2
let minY = card.size.height/2
let maxX = self.frame.size.width - card.size.height/2
let maxY = self.frame.size.width - card.size.height/2
// HERE GOES YOUR SWIPE FUNCTION
func swipedRight(sender: UISwipeGestureRecognizer) {
// First we check the current position for the card
if (card.position.x <= minX || card.position.x >= maxX || card.position.y <= minY || card.position.y >= maxY) {
return
}
else {
// ...
// YOUR CODE TO MOVE THE ELEMENT
// ...
}
}
I hope this helps!
Regards.