Spawning multiple nodes in different locations - swift

I'm trying to spawn multiple SKSpriteNodes within the set location, but when I add multiple nodes they just spawn on top of each other? Is there a way to not make this happen?
I'm also having a problem of having to refresh the page in order to get a new location for the node. Is there a way so if the node disappears on the page it will spawn in a new location within the set co-ords?
let rect = CGRectMake(x: 90, y: 360, width: 200, height: 200)
let x = rect.origin.x + CGFloat(arc4random()) % rect.size.width
let y = rect.origin.y + CGFloat(arc4random()) % rect.size.height
let randomPoint = CGPointMake(x, y)
self.redcircle.position = randomPoint
self.addChild(redcircle)
self.bluecircle.position = randomPoint
self.addChild(bluecircle)

You need 2 randomPoint's to put them on a another place.
You use 2 Nodes with one Position cause randomPoint is always the same.
Edit(question in comment):
You have to use min(x, y) and max(x, y)
let x = random(CGRectGetMinX(self.frame), max: CGRectGetMaxX(self.frame))
let y = random(CGRectGetMinY(self.frame), max: CGRectGetMaxY(self.frame))
let randomPoint = CGPointMake(x, y)
self.redcircle.position = randomPoint
self.addChild(red circle)
And here the random function:
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}

It would be easier for you to make a method which generates random point based on given rectangle. Or method which spawns sprite within given rectangle, like this:
import SpriteKit
class GameScene: SKScene {
let rect = CGRect(x: 90, y: 360, width: 200, height: 200)
override func didMoveToView(view: SKView) {
let debug = SKSpriteNode(color: SKColor.redColor(), size: rect.size)
debug.alpha = 0.2
debug.position = rect.origin
let origin = SKSpriteNode(color: SKColor.redColor(), size:CGSize(width: 5, height:5))
debug.addChild(origin)
self.addChild(debug)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let sprite = spawnSpriteAtRandomPositionWithinRect(rect)
self.addChild(sprite)
println("Sprite spawned at position x,y( \(sprite.position.x), \(sprite.position.y))")
}
func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum)
}
func spawnSpriteAtRandomPositionWithinRect(rectangle:CGRect)->SKSpriteNode{
let x = randomBetweenNumbers(rectangle.origin.x - rectangle.size.width / 2.0 , secondNum: rectangle.origin.x + rectangle.size.width/2.0)
let y = randomBetweenNumbers(rectangle.origin.y - rectangle.size.height / 2.0 , secondNum: rectangle.origin.y + rectangle.size.height/2.0)
let sprite = SKSpriteNode(color: SKColor.greenColor(), size:CGSize(width: 30, height: 30))
sprite.position = CGPoint(x: x, y: y)
return sprite
}
}
Sprite named debug is not needed actually, but it shows you visually given rectangle.

Related

Animating a 360 degree rotation around another view's center point

I'm making a loading spinner animation that pushes a view out from the middle, and then rotates all the way around the center view back to it's original location. This is what I am trying to achieve:
The inner arrow moves the view away from the center. I've already achieved this, the part I am stuck on is then rotating the view around the center view. I've read various other StackOverflow posts but have not been close to achieving this.
Code so far:
UIView.animate(withDuration: 0.5) {
self.topView.transform = CGAffineTransform(translationX: 20, y: -20)
} completion: { _ in
self.topView.setAnchorPoint(self.centerView.center)
// Rotate
}
}
Here is how I am setting the anchor point of the view. I'm using this as the view disappears when setting its anchor point otherwise.
func setAnchorPoint(_ point: CGPoint) {
var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y)
var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y);
newPoint = newPoint.applying(transform)
oldPoint = oldPoint.applying(transform)
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.position = position
layer.anchorPoint = point
}
Once the full 360 rotation is complete I would then need to move the view back in towards the center, completing the animation.
For the part when the loading view rotates around the circle view, you can use a UIBezierPath and create a CAKeyframeAnimation based on its path.
Take a look at this implementation. Hope it helps.
class LoadingViewController: UIViewController {
var circlePath: UIBezierPath!
lazy var loader = makeLoader()
lazy var centerView = makeCenterView()
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func makeLoader() -> UIButton {
let padding: CGFloat = 100
let width = self.view.frame.width - (2 * padding)
let b = UIButton(frame: CGRect(x: padding, y: 200, width: width, height: 50))
b.addTarget(self, action: #selector(didTap), for: .touchUpInside)
b.backgroundColor = .blue
return b
}
func makeCenterView() -> UIView {
let width: CGFloat = 20
let height: CGFloat = 20
let x = self.view.center.x - width/2
let y = self.view.center.y - height/2
let view = UIView(frame: CGRect(x: x, y: y, width: width, height: height))
view.backgroundColor = .green
return view
}
func setup() {
//create a UIBezierPath with a center that is at the center of the green view and a radius that has a length as the distance between the green view and the blue view.
let arcCenterX = centerView.center.x
let arcCenterY = centerView.center.y
let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
let radius = arcCenterY - loader.center.y
let startAngle = -CGFloat.pi/2
let endAngle = CGFloat.pi*(1.5)
let arcPath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
self.circlePath = arcPath
self.view.addSubview(loader)
self.view.addSubview(centerView)
}
#objc func didTap() {
//create a CAKeyframeAnimation with a path that matches the UIBezierPath above.
let loadAnimation = CAKeyframeAnimation(keyPath: "position")
loadAnimation.path = self.circlePath.cgPath
loadAnimation.calculationMode = .paced
loadAnimation.duration = 2.0
loadAnimation.rotationMode = .rotateAuto
loadAnimation.repeatCount = Float(CGFloat.greatestFiniteMagnitude)
loader.layer.add(loadAnimation, forKey: "circleAnimation")
}
}

How to create realistic spinning wheel in SpriteKit

I am trying to create a spinning fortune wheel action via SKAction. I have a SKNode which used as wheel, this SKNode is a circle that divided to four quarters (each quarter in different color). also I set an SKAction (which is repeating for 10 counts) that spin the SKNode around fixed point (the node's center). The problem is that the action is running well but it stops suddenly and not slowing down - like a real wheel. I don't really have an idea how to set this animation, I mean to slow the spinning down before the action is stop.
Here is my code so far:
class GameScene: SKScene {
let colors = [SKColor.yellow, SKColor.red, SKColor.blue, SKColor.purple]
override func didMove(to view: SKView) {
createWheel()
let sq = CGRect(x: size.width/2, y: size.height/2, width: 300, height: 300)
let sqx = SKShapeNode(rect: sq)
sqx.lineWidth = 2
sqx.fillColor = .clear
sqx.setScale(1.0)
addChild(sqx)
}
func createWheel() {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: -200))
path.addArc(withCenter: CGPoint.zero,radius: 200,startAngle: CGFloat(0.0), endAngle: CGFloat(3.0 * Double.pi / 2),clockwise: false)
path.addLine(to: CGPoint(x: 200, y: 0))
let obstacle = obstacleByDuplicatingPath(path, clockwise: true)
obstacle.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(obstacle)
let rotateAction = SKAction.rotate(byAngle: CGFloat((3.0 * CGFloat(Double.pi / 2)) - 90), duration: 0.5)
//obstacle.run(SKAction.repeatForever(rotateAction))
obstacle.run(SKAction.repeat(rotateAction, count: 10))
}
func obstacleByDuplicatingPath(_ path: UIBezierPath, clockwise: Bool) -> SKNode {
let container = SKNode()
var rotationFactor = CGFloat(Double.pi / 2)
if !clockwise {
rotationFactor *= -1
}
for i in 0...3 {
let section = SKShapeNode(path: path.cgPath)
section.fillColor = colors[i]
section.strokeColor = colors[i]
section.zRotation = rotationFactor * CGFloat(i);
let origin = CGPoint(x: 0.0, y: 0.0)
switch i {
case 0:
section.position = CGPoint(x: (origin.x + 10), y: (origin.y - 10))
case 1:
section.position = CGPoint(x: (origin.x + 10), y: (origin.y + 10))
case 2:
section.position = CGPoint(x: (origin.x - 10), y: (origin.y + 10))
case 3:
section.position = CGPoint(x: (origin.x - 10), y: (origin.y - 10))
default:
print("bolbol")
}
container.addChild(section)
}
return container
}
}
edit:
I was thinking about it and I tried to do it via SKAction, I set another action but this time I set their duration to a long one. first it run a action of duration 0.5, then of 2 and at end of 4. I looks pretty good but still not smooth as I want it to be.
here is my code:
let rotateAction = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 0.5)
let rotateAction2 = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 2)
let rotateAction3 = SKAction.rotate(byAngle: CGFloat(2.0 * CGFloat(M_PI)), duration: 4)
let wait = SKAction.wait(forDuration: 5)
let g1 = SKAction.repeat(rotateAction, count: 10)
let group = SKAction.group([wait, g1, rotateAction2, rotateAction3])
what do you think? there is any way to do it better??
edit 2:
Continued to #Ali Beadle answer, I tried to do it via physics body, the problem now is the when I drag finger on the screen the SKShapeNode (shape) in continue to rotate and never stops. can you detect what is wrong?
class GameScene: SKScene {
var start: CGPoint?
var end:CGPoint?
var startTime: TimeInterval?
let shape = SKShapeNode.init(rectOf: CGSize(width: 150, height: 150))
override func didMove(to view: SKView) {
self.physicsWorld.gravity = CGVector(dx: 0, dy: -9.8)
let sceneBody = SKPhysicsBody.init(edgeLoopFrom: self.frame)
sceneBody.friction = 0
self.physicsBody = sceneBody
shape.fillColor = SKColor.red
shape.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
shape.physicsBody = SKPhysicsBody.init(rectangleOf: CGSize(width: 50, height: 50))
shape.physicsBody?.affectedByGravity = false
shape.physicsBody?.isDynamic = true
addChild(shape)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.start = touch.location(in: self)
self.startTime = touch.timestamp
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
self.end = touch.location(in: self)
var dx = ((self.end?.x)! - (self.start?.x)!)
var dy = ((self.end?.y)! - (self.start?.y)!)
let magnitude:CGFloat = sqrt(dx*dx+dy*dy)
if(magnitude >= 25){
let dt:CGFloat = CGFloat(touch.timestamp - self.startTime!)
if dt > 0.1 {
let speed = magnitude / dt
dx = dx / magnitude
dy = dy / magnitude
print("dx: \(dx), dy: \(dy), speed: \(speed) ")
}
}
let touchPosition = touch.location(in: self)
if touchPosition.x < (self.frame.width / 2) {
self.shape.physicsBody?.angularVelocity = 10
self.shape.physicsBody?.applyAngularImpulse(-180)
} else {
self.shape.physicsBody?.angularVelocity = 10
self.shape.physicsBody?.applyAngularImpulse(180)
}
}}
I have created an open source prize spinning wheel in Spritekit that uses physics for realistic movement and flapper control. It also allows the user to drag the wheel to spin or generates a random spin by pushing the center of the wheel.
https://github.com/hsilived/SpinWheel
You can add realistic movement like this by using the built-in Physics simulation of SpriteKit. This will allow you to give your wheel a mass and friction and then use forces to rotate it. It will then slow down realistically.
In outline see Simulating Physics in the Apple Documentation:
To use physics in your game, you need to:
Attach physics bodies to nodes in the node tree and configure their physical properties. See SKPhysicsBody.
Define global characteristics of the scene’s physics simulation, such as gravity. See SKPhysicsWorld.
Where necessary to support your gameplay, set the velocity of physics bodies in the scene or apply forces or impulses to them. ...
The most appropriate method for your wheel is probably to make the wheel pinned to the scene and then rotate it with applyAngularImpulse.

Move a node based off of the randomized position of another node

I want to make it so my node (ball) always ends up resetting above my other node block1 based on where block 1 ends up. However, I could not think of any code that would help me do that. I was thinking of making a function saying something ball.position = block1 + 20 but I have no ideas of how to go about that. Thanks for any help!
Code:
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
func resetScene (){
let ball = childNode(withName: BallCategoryName) as! SKSpriteNode
ball.removeFromParent()
ball.physicsBody?.velocity = CGVector( dx: 0, dy: 0 )
ball.physicsBody?.collisionBitMask = BallCategory
ball.physicsBody?.collisionBitMask = BorderCategory | PaddleCategory
ball.zRotation = 0.0
addChild(ball)
let block1 = childNode(withName: Block1Name) as! SKSpriteNode
block1.removeFromParent()
let actualX = random(min:85, max: 300)
block1.position = CGPoint(x: actualX, y: 190)
addChild(block1)
//ball.position = CGPoint(x: , y: )
//This is the line i would like to change
canRestart = false
}
assuming you are just trying to set the Y position of your ball you can use
ball.position.y = block1.position.y + 20
or if you need to set the x and the y
ball.position = CGPoint(x: block1.position.x, y: block1.position.y + 20)

Rotating a CGPoint around another CGPoint

Okay so I want to rotate CGPoint(A) 50 degrees around CGPoint(B) is there a good way to do that?
CGPoint(A) = CGPoint(x: 50, y: 100)
CGPoint(B) = CGPoint(x: 50, y: 0)
Here's what I want to do:
This is really a maths question. In Swift, you want something like:
func rotatePoint(target: CGPoint, aroundOrigin origin: CGPoint, byDegrees: CGFloat) -> CGPoint {
let dx = target.x - origin.x
let dy = target.y - origin.y
let radius = sqrt(dx * dx + dy * dy)
let azimuth = atan2(dy, dx) // in radians
let newAzimuth = azimuth + byDegrees * CGFloat(M_PI / 180.0) // convert it to radians
let x = origin.x + radius * cos(newAzimuth)
let y = origin.y + radius * sin(newAzimuth)
return CGPoint(x: x, y: y)
}
There are lots of ways to simplify this, and it's a perfect case for an extension to CGPoint, but I've left it verbose for clarity.
public extension CGFloat {
///Returns radians if given degrees
var radians: CGFloat{return self * .pi / 180}
}
public extension CGPoint {
///Rotates point by given degrees
func rotate(origin: CGPoint? = CGPoint(x: 0.5, y: 0.5), _ byDegrees: CGFloat) -> CGPoint {
guard let origin = origin else {return self}
let rotationSin = sin(byDegrees.radians)
let rotationCos = cos(byDegrees.radians)
let x = (self.x * rotationCos - self.y * rotationSin) + origin.x
let y = (self.x * rotationSin + self.y * rotationCos) + origin.y
return CGPoint(x: x, y: y)
}
}
Usage
var myPoint = CGPoint(x: 40, y: 50).rotate(45)
var myPoint = CGPoint(x: 40, y: 50).rotate(origin: CGPoint(x: 0, y: 0), 45)

How to find the distance between two CG points?

When we do multitouch with two fingers in a UIScrollView, we get two CG points. I want to find the distance between them. Then when again we do the pinch(inside or outside), Then we will again get two points. Then after finding the distance again between these two points , I want to decide whether I pinched in or out. If i have pinched in, surely the new distance will be lesser and vice versa.
But don't know how to find an accurate measurement for the distance between 2 points for doing comparison ? Is anyone having idea about this ?
You can use the hypot() or hypotf() function to calculate the hypotenuse. Given two points p1 and p2:
CGFloat distance = hypotf(p1.x - p2.x, p1.y - p2.y);
And that's it.
Distance between p1 and p2:
CGFloat xDist = (p2.x - p1.x);
CGFloat yDist = (p2.y - p1.y);
CGFloat distance = sqrt(xDist * xDist + yDist * yDist);
Put in a function:
func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(sqrt(xDist * xDist + yDist * yDist))
}
Background: Pythagorean theorem
If you only need to calculate if the distance between the points increases or decreases, you can omit the sqrt() which will make it a little faster.
For swift users
extension CGPoint {
func distance(to point: CGPoint) -> CGFloat {
return sqrt(pow(x - point.x, 2) + pow(y - point.y, 2))
}
}
With Swift 4, you may choose one of the 5 following Playground codes in order to get the distance between two CGPoint instances.
1. Using Darwin sqrt(_:) function
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
let xDistance = lhs.x - rhs.x
let yDistance = lhs.y - rhs.y
return sqrt(xDistance * xDistance + yDistance * yDistance)
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
2. Using CGFloat squareRoot() method
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
let xDistance = lhs.x - rhs.x
let yDistance = lhs.y - rhs.y
return (xDistance * xDistance + yDistance * yDistance).squareRoot()
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
3. Using CGFloat squareRoot() method and Core Graphics pow(_:_:) function
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
return (pow(lhs.x - rhs.x, 2) + pow(lhs.y - rhs.y, 2)).squareRoot()
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
4. Using Core Graphics hypot(_:_:) function
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
return hypot(lhs.x - rhs.x, lhs.y - rhs.y)
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
5. Using Core Graphics hypot(_:_:) function and CGFloat distance(to:) method
import CoreGraphics
func distance(from lhs: CGPoint, to rhs: CGPoint) -> CGFloat {
return hypot(lhs.x.distance(to: rhs.x), lhs.y.distance(to: rhs.y))
}
let point1 = CGPoint(x: -10, y: -100)
let point2 = CGPoint(x: 30, y: 600)
distance(from: point1, to: point2) // 701.141925718324
-(float)distanceFrom:(CGPoint)point1 to:(CGPoint)point2
{
CGFloat xDist = (point2.x - point1.x);
CGFloat yDist = (point2.y - point1.y);
return sqrt((xDist * xDist) + (yDist * yDist));
}
If you are using cocos2d
float distance = ccpDistance(point1, point2);
I wrote this, I use it a lot:
- (float) distanceBetween : (CGPoint) p1 and: (CGPoint) p2
{
return sqrt(pow(p2.x-p1.x,2)+pow(p2.y-p1.y,2));
}
Call like this:
float distanceMoved = [self distanceBetween touchStart and: touchEnd];
I normally use cocos2d, but I still use my own function for some things because when I was learning I wrote a bunch of my own functions for simple stuff rather than searching for the "official" higher order functions, and additionally I'm not a big fan of functions(vars, vars), I prefer [self functions vars and: vars]
#define rw_pointOffset(point1, point2) CGPointMake(point2.x - point1.x, point2.y - point1.y)
#define rw_pointDistance(point1, point2) sqrtf( powf(point2.x - point1.x, 2.0f) + powf(point2.y - point1.y, 2.0f))
And that´s how you use it:
CGPoint offset = rw_pointOffset(view1.center, view2.center);
float distance = rw_pointDistance(view1.center, view2.center);
If you want to find the absolute distance value between two points then you can use (for Cocos2d):
float distance = abs(ccpDistance(point1, point2));