How to scale a radius to fit in all devices? - swift

I am makeing a game in with a ball follows a path, actually is just an illution because the scene is the one that rotates. The problem is that in some devices the ball get out of the screen and in others the radius looks very small. I have tryed to make the radius equal (orbita.size.width / 2) but it doesnt work. (orbita is the orbit that the ball follows)
class GameScene: SKScene {
let sprite = SKSpriteNode(imageNamed: "circulo")
var rotation:CGFloat = CGFloat(M_PI)
let radius:CGFloat = 168
override func didMoveToView(view: SKView) {
/* Setup your scene here */
scaleMode = .ResizeFill
node.position = view.center
// 3) Add the container to the scene
addChild(node)
// 4) Set the sprite's x position
sprite.position = CGPointMake(radius, 0)
// 5) Add the sprite to the container
node.addChild(sprite)
// 6) Rotate the container
rotate()
sprite.color = UIColor.whiteColor()
sprite.colorBlendFactor = 1.0
sprite.zPosition = 4.0
orbita = SKSpriteNode(imageNamed: "orbita")
let padding2:CGFloat = 32.0
orbita.size = CGSize(width:view.frame.size.width - padding2 , height: view.frame.size.width - padding2)
orbita.color = UIColor.whiteColor()
orbita.colorBlendFactor = 1
orbita.alpha = 1
orbita.position = view.center
self.addChild(orbita)
orbita.zPosition = 3
}

If your orbita.size is (it seems a circular orbita by "circulo" name):
orbita.size = CGSize(width:view.frame.size.width - padding2 , height: view.frame.size.width - padding2)
your radius would be:
let radius:CGFloat = (view.frame.size.width - padding2)/2

Related

How to rotate SKNode in swift

I want my SKNode To rotate like Image below :)
Image Here
Instead it is rotating around the bottom left corner of the screen!
Click Here To View Video of what is happening that I do not want
How do i get it to rotate counterClockWise or Clockwise in one position like image show above?
Thank you ahead of time for someone who can help me out. not sure if i have to change anchor points or what... thank you
Here is my code below in swift.
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var top = SKSpriteNode()
var bottom = SKSpriteNode()
var line = SKSpriteNode()
var RightSide = SKSpriteNode()
var LeftSide = SKSpriteNode()
var pointBar = SKSpriteNode()
var Second_point_Bar_For_First_Hoop = SKSpriteNode()
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
createHoop()
}
func createHoop() {
top = SKSpriteNode(imageNamed: "top")
top.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2 + 15)
top.size = CGSize(width: 100, height: 60)
top.zPosition = 0
bottom = SKSpriteNode(imageNamed: "bottom")
bottom.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2 - 45)
bottom.size = CGSize(width: 100, height: 60)
bottom.zPosition = 2
LeftSide = SKSpriteNode()
LeftSide.position = CGPoint(x: bottom.position.x - 40, y: bottom.position.y)
LeftSide.size = CGSize(width: 10, height: 10)
LeftSide.zPosition = 0
LeftSide.color = UIColor.blue
RightSide = SKSpriteNode()
RightSide.position = CGPoint(x: bottom.position.x + 40, y: bottom.position.y)
RightSide.size = CGSize(width: 5, height: 10)
RightSide.zPosition = 0
RightSide.color = UIColor.blue
pointBar = SKSpriteNode()
pointBar.position = CGPoint(x: bottom.position.x, y: bottom.position.y + 10)
pointBar.size = CGSize(width: 90, height: 2)
pointBar.zPosition = 100
pointBar.color = UIColor.green
pointBar.zPosition = 100
Second_point_Bar_For_First_Hoop = SKSpriteNode()
Second_point_Bar_For_First_Hoop.position = CGPoint(x: top.position.x, y: top.position.y - 10)
Second_point_Bar_For_First_Hoop.size = CGSize(width: 90, height: 2)
Second_point_Bar_For_First_Hoop.zPosition = 100
Second_point_Bar_For_First_Hoop.color = UIColor.green
Second_point_Bar_For_First_Hoop.zPosition = 100
let hoopPair = SKNode()
hoopPair.addChild(top)
hoopPair.addChild(pointBar)
hoopPair.addChild(Second_point_Bar_For_First_Hoop)
hoopPair.addChild(bottom)
hoopPair.addChild(LeftSide)
hoopPair.addChild(RightSide)
let rotate = SKAction.rotate(byAngle: 1, duration: 5)
let repeatRotation = SKAction.repeatForever(rotate)
hoopPair.run(repeatRotation)
self.addChild(hoopPair)
}
override func update(_ currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
By default, SKNode anchor point is always 0.5, 0.5. This means you need to work the positions so that everything goes off of the center of the node.
Now everything is going to be relative, so your top and bottom are relative to your hoop node.
Then you need to move the hoopnode position so that it is where you want it.
Here is the code to do that:
(Note I took out all needless code to get your image to rotate on center)
(Another Node: if size does not work, use frame.size)
import SpriteKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var top = SKSpriteNode() var bottom = SKSpriteNode() var line = SKSpriteNode() var RightSide = SKSpriteNode() var LeftSide = SKSpriteNode() var pointBar = SKSpriteNode() var Second_point_Bar_For_First_Hoop = SKSpriteNode()
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
createHoop()
}
func createHoop() {
top = SKSpriteNode(imageNamed: "top")
top.size = CGSize(width: 100, height: 60)
top.position = CGPoint(x: 0, y: top.size.height/2)
top.zPosition = 0
bottom = SKSpriteNode(imageNamed: "bottom")
bottom.size = CGSize(width: 100, height: 60)
bottom.position = CGPoint(x: 0, y: -bottom.size.height/2)
bottom.zPosition = 2
let hoopPair = SKNode()
hoopPair.addChild(top)
hoopPair.addChild(bottom)
let rotate = SKAction.rotate(byAngle: 1, duration: 5)
let repeatRotation = SKAction.repeatForever(rotate)
hoopPair.position = CGPoint(x:self.size.width/2,self.size.height/2)
hoopPair.run(repeatRotation)
self.addChild(hoopPair) }
override func update(_ currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
} }
What is the anchor point of the sprite? Sprites rotate about their anchor point and yours appears to be set to (0,0) i.e. the bottom-left corner. If so, try changing it to (0.5,0.5)

How to code the accelerometer to have a fixed x position

I have a ship that moves based on the direction of gravity which is changed by the accelerometer, but I want it to only move along the width of the screen (I already know how to do that), but I do not know how to keep it on a fixed horizontal line. Here's my code
class GameScene: SKScene {
var manager = CMMotionManager()
var ship = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let shipTexture = SKTexture(imageNamed: "EvadersShipVert2.png")
ship = SKSpriteNode(texture: shipTexture)
ship.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) - 250)
ship.size = CGSize(width: 90, height: 115)
shipTexture.filteringMode = SKTextureFilteringMode.Nearest
ship.zPosition = 2
ship.physicsBody = SKPhysicsBody(texture: shipTexture, size: CGSize(width: 90, height: 115))
ship.physicsBody?.dynamic = true
self.addChild(ship)
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = 0.1
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()) {
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!), CGFloat((data?.acceleration.y)!))
}
}
thanks in advance!
Replace
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!), CGFloat((data?.acceleration.y)!))
With
self.physicsWorld.gravity = CGVectorMake(0.0, CGFloat((data?.acceleration.y)!)
So you're not affecting the x position

how to scale the window to fit in all devices?

I want to know how can I scale the window to fit with the same size in all diveces. The problem is that in some diveces the objects doesnt cover the same space of how I want.
I have my scaleMode = .ResizeFill but the problem is that if I make it .AspectFill it doesnt appear in the correct place. I think that the problem is that I added a new container on the scene, but I dont know how to solve it.
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene(fileNamed:"GameScene") {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .ResizeFill
scene.position = view.center
skView.presentScene(scene)
}
}
override func didMoveToView(view: SKView) {
/* Setup your scene here */
scaleMode = .ResizeFill
node.position = view.center
// 3) Add the container to the scene
//addChild(node)
// 4) Set the sprite's x position
sprite.position = CGPointMake(radius, 0)
// 5) Add the sprite to the container
node.addChild(sprite)
// 6) Rotate the container
rotate()
sprite.color = UIColor.whiteColor()
sprite.colorBlendFactor = 1.0
sprite.zPosition = 4.0
circulo = SKSpriteNode(imageNamed: "circuloFondo2")
circulo.size = CGSize(width: 294, height: 294)
circulo.color = UIColor(red: 0.15, green: 0.15, blue: 0.15, alpha: 1)
circulo.colorBlendFactor = 1
circulo.alpha = 0.35
circulo.position = view.center
self.addChild(circulo)
circulo.zPosition = 1
}
The issue might be in code where you draw circle,You might be drawing
the circle with same radius for all screen sizes.
Instead of drawing the circle with the same radius, you need to
provide dynamic radius of the circle according to the device width.
Replace this
circulo.size = CGSize(width: 294, height: 294)
Use following snippet
let padding:CGFloat = 40.0
circulo.size = CGSize(width:view.frame.size.width - padding , height: view.frame.size.width - padding)

Fading action of SKNode() with SKCropNode() children

I have a SKNode() which has SKCropNode() children. I am able to run all kind of actions to rotate and scale my SKNode(), but when I want to use any kind of fading actions the result always is alpha 1 or alpha 0. No way of fading.
By replacing the SKCropNode() with a SKShapeNode() the fading action is working fine.
Can anyone tell my why? Isn't it possible to fade masked nodes?
import SpriteKit
class GameScene: SKScene {
let player = SKNode()
let playerSize = CGFloat(50)
let playerCrop = SKCropNode()
let playerMask = SKSpriteNode(color: SKColor.blackColor(), size: CGSizeMake(CGFloat(100), CGFloat(100)))
let playerCircle = SKShapeNode(circleOfRadius: CGFloat(100))
let playerCenterMask = SKShapeNode(circleOfRadius: CGFloat(100))
let playerCenterCrop = SKCropNode()
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.blackColor()
player.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
player.alpha = 0
addChild(player)
playerCenterMask.lineWidth = 20
playerCenterCrop.maskNode = playerCenterMask
playerMask.position.x = -playerSize
playerMask.position.y = playerSize
playerCircle.lineWidth = 0
playerCircle.fillColor = SKColor.redColor()
playerCrop.addChild(playerCircle)
playerCrop.maskNode = playerMask
playerCenterCrop.addChild(playerCrop)
player.addChild(playerCenterCrop)
// Animations
let playerScale = SKAction.scaleTo(2.0, duration: 5)
let playerFadeIn = SKAction.fadeInWithDuration(5)
let playerAnimation = SKAction.group([playerScale,playerFadeIn])
player.runAction(playerAnimation, completion: {})
}
}
As mentioned in the comments:
SKCropNode uses alpha < 0.5 to not draw. >= 0.5 to draw, and you can't set the blend mode, so it is probably doing source blend mode. Which means it is overwriting the alpha. Children afterwards get blended.
The player context is created, it draws at the given alpha, then the SKCropNode is drawn, overwriting alpha instead of blending.
Run the fade in action on your child, not your parent to get the results you are looking for.
Here is what your source looks like with changes:
import SpriteKit
class GameScene: SKScene {
let player = SKNode()
let playerSize = CGFloat(50)
let playerCrop = SKCropNode()
let playerMask = SKSpriteNode(color: SKColor.blackColor(), size: CGSizeMake(CGFloat(100), CGFloat(100)))
let playerCircle = SKShapeNode(circleOfRadius: CGFloat(100))
let playerCenterMask = SKShapeNode(circleOfRadius: CGFloat(100))
let playerCenterCrop = SKCropNode()
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.blackColor()
player.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
player.alpha = 1
addChild(player)
playerCircle.alpha = 0
playerCenterMask.lineWidth = 20
playerCenterCrop.maskNode = playerCenterMask
playerMask.position.x = -playerSize
playerMask.position.y = playerSize
playerCircle.lineWidth = 0
playerCircle.fillColor = SKColor.redColor()
playerCrop.addChild(playerCircle)
playerCrop.maskNode = playerMask
playerCenterCrop.addChild(playerCrop)
player.addChild(playerCenterCrop)
// Animations
let playerScale = SKAction.scaleTo(2.0, duration: 5)
let playerFadeIn = SKAction.fadeInWithDuration(5)
let playerAnimation = playerScale
player.runAction(playerAnimation, completion: {})
playerCircle.runAction(playerFadeIn, completion: {})
}
}

SCNCamera limit arcball rotation

I have a scene setup with SCNCamera that rotates around an object.
What would be the best way to limit the extents of rotation the camera can achieve around the object?
Example: instead of being able to rotate around a whole sphere, how would I limit rotation to a single hemisphere?
My first attempt was to see if there was any clamps for .allowsCameraControl. Could not find anything.
I then tried adapting c# Unity : mouse orbit script, no luck.
Some pointers on how to approach or solve this would great.
Boilerplate Arcball thanks to this answer.
var lastWidthRatio: Float = 0
var lastHeightRatio: Float = 0
let camera = SCNCamera()
let cameraNode = SCNNode()
let cameraOrbit = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
camera.usesOrthographicProjection = true
camera.orthographicScale = 9
camera.zNear = 0
camera.zFar = 100
cameraNode.position = SCNVector3(x: 0, y: 0, z: 50)
cameraNode.camera = camera
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraOrbit)
// retrieve the ship node
let ship = scene.rootNode.childNodeWithName("ship", recursively: true)!
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// add a tap gesture recognizer
let gesture = UIPanGestureRecognizer(target: self, action: "panDetected:");
scnView.addGestureRecognizer(gesture);
}
func panDetected(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view!)
let widthRatio = Float(translation.x) / Float(sender.view!.frame.size.width) + lastWidthRatio
let heightRatio = Float(translation.y) / Float(sender.view!.frame.size.height) + lastHeightRatio
self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * widthRatio
self.cameraOrbit.eulerAngles.x = Float(-M_PI) * heightRatio
print(Float(-2 * M_PI) * widthRatio)
if (sender.state == .Ended) {
lastWidthRatio = widthRatio % 1
lastHeightRatio = heightRatio % 1
}
}
Maybe this could be useful for readers.
class GameViewController: UIViewController {
var cameraOrbit = SCNNode()
let cameraNode = SCNNode()
let camera = SCNCamera()
//HANDLE PAN CAMERA
var lastWidthRatio: Float = 0
var lastHeightRatio: Float = 0.2
var WidthRatio: Float = 0
var HeightRatio: Float = 0.2
var fingersNeededToPan = 1
var maxWidthRatioRight: Float = 0.2
var maxWidthRatioLeft: Float = -0.2
var maxHeightRatioXDown: Float = 0.02
var maxHeightRatioXUp: Float = 0.4
//HANDLE PINCH CAMERA
var pinchAttenuation = 20.0 //1.0: very fast ---- 100.0 very slow
var lastFingersNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = SCNLightTypeOmni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = UIColor.darkGrayColor()
scene.rootNode.addChildNode(ambientLightNode)
//Create a camera like Rickster said
camera.usesOrthographicProjection = true
camera.orthographicScale = 9
camera.zNear = 1
camera.zFar = 100
cameraNode.position = SCNVector3(x: 0, y: 0, z: 50)
cameraNode.camera = camera
cameraOrbit = SCNNode()
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraOrbit)
//initial camera setup
self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * lastWidthRatio
self.cameraOrbit.eulerAngles.x = Float(-M_PI) * lastHeightRatio
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
//allows the user to manipulate the camera
scnView.allowsCameraControl = false //not needed
// add a tap gesture recognizer
let panGesture = UIPanGestureRecognizer(target: self, action: "handlePan:")
scnView.addGestureRecognizer(panGesture)
// add a pinch gesture recognizer
let pinchGesture = UIPinchGestureRecognizer(target: self, action: "handlePinch:")
scnView.addGestureRecognizer(pinchGesture)
}
func handlePan(gestureRecognize: UIPanGestureRecognizer) {
let numberOfTouches = gestureRecognize.numberOfTouches()
let translation = gestureRecognize.translationInView(gestureRecognize.view!)
if (numberOfTouches==fingersNeededToPan) {
widthRatio = Float(translation.x) / Float(gestureRecognize.view!.frame.size.width) + lastWidthRatio
heightRatio = Float(translation.y) / Float(gestureRecognize.view!.frame.size.height) + lastHeightRatio
// HEIGHT constraints
if (heightRatio >= maxHeightRatioXUp ) {
heightRatio = maxHeightRatioXUp
}
if (heightRatio <= maxHeightRatioXDown ) {
heightRatio = maxHeightRatioXDown
}
// WIDTH constraints
if(widthRatio >= maxWidthRatioRight) {
widthRatio = maxWidthRatioRight
}
if(widthRatio <= maxWidthRatioLeft) {
widthRatio = maxWidthRatioLeft
}
self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * widthRatio
self.cameraOrbit.eulerAngles.x = Float(-M_PI) * heightRatio
print("Height: \(round(heightRatio*100))")
print("Width: \(round(widthRatio*100))")
//for final check on fingers number
lastFingersNumber = fingersNeededToPan
}
lastFingersNumber = (numberOfTouches>0 ? numberOfTouches : lastFingersNumber)
if (gestureRecognize.state == .Ended && lastFingersNumber==fingersNeededToPan) {
lastWidthRatio = widthRatio
lastHeightRatio = heightRatio
print("Pan with \(lastFingersNumber) finger\(lastFingersNumber>1 ? "s" : "")")
}
}
func handlePinch(gestureRecognize: UIPinchGestureRecognizer) {
let pinchVelocity = Double.init(gestureRecognize.velocity)
//print("PinchVelocity \(pinchVelocity)")
camera.orthographicScale -= (pinchVelocity/pinchAttenuation)
if camera.orthographicScale <= 0.5 {
camera.orthographicScale = 0.5
}
if camera.orthographicScale >= 10.0 {
camera.orthographicScale = 10.0
}
}
It looks like you're almost there, using just the #Rickster code from
the answer you cited.
The change you could make would be in these lines:
self.cameraOrbit.eulerAngles.y = Float(-2 * M_PI) * widthRatio
self.cameraOrbit.eulerAngles.x = Float(-M_PI) * heightRatio
which implicitly allow pitch and yaw to cover the entire
sphere. That's where you can do your limiting. For instance,
instead of allowing the pitch (eulerAngles.x) to vary from 0
to -π, you could do
self.cameraOrbit.eulerAngles.x = Float(-M_PI_2) + Float(-M_PI_2) * heightRatio
to vary smoothly between -π/2 and -π, using full screen
vertical scrolling to cover that range. Or you could put
hard min/max limits/checks in those two lines to constrain
to a particular area of the globe.
(Edit to address the inertia comment)
For rotational damping, or inertia, I'd approach it by using the built in SceneKit Physics, and perhaps put the camera on an invisible (no geometry) SCNNode. That camera node becomes a gimbal, similar to the approach taken in this project: An interactive seven-foot globe created entirely in RubyMotion and SceneKit.
The virtual gimbal then gets an SCNPhysicsBody (you add that, it doesn't come with one by default) with some damping. Or perhaps you put the physics on your central object, and give that object some angularDamping.