I'm trying to build a game using xcode 6.1 and swift and one of the key elements is having a ball go around in circles (not rotating around own axis but following circular path). How can I accomplish this the most efficiently? Here is the code I tried.
func rotate(){
newBall = balls(size: self.size, positionX: -36, positionY: 250)
var path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, 0, 0)
CGPathAddArc(path, nil, 0, 15, 15, CGFloat(M_PI_2), CGFloat(M_PI_2), true)
newBall.node.path = path
self.addChild(newBall.node)
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
rotate()
}
I seem to remember that creating apparently zero-length paths doesn't always have the expected results. Try splitting the path up:
CGPathAddArc(path, nil, 0, 15, 15, CGFloat(M_PI_2), -CGFloat(M_PI_2), true)
CGPathAddArc(path, nil, 0, 15, 15, -CGFloat(M_PI_2), CGFloat(M_PI_2), true)
there are a ton of ways to do this. I'll just show you one way
you can copy paste this code and modify to your needs
// add sprite to scene
let mysprite = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 10, height: 10))
mysprite.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
self.addChild(mysprite)
// the circle path's diameter
let circleDiameter = CGFloat(100)
// center our path based on our sprites initial position
let pathCenterPoint = CGPoint(
x: mysprite.position.x - circleDiameter/2,
y: mysprite.position.y - circleDiameter/2
)
// create the path our sprite will travel along
let circlePath = CGPathCreateWithEllipseInRect(CGRect(origin: pathCenterPoint, size: CGSize(width: circleDiameter, height: circleDiameter)), nil)
// create a followPath action for our sprite
let followCirclePath = SKAction.followPath(circlePath, asOffset: false, orientToPath: true, duration: 2)
// make our sprite run this action forever
mysprite.runAction(SKAction.repeatActionForever(followCirclePath))
Related
I'm fairly new to SpriteKit, and I've been following Paul Hudson's course "Hacking with Swift". On his Project 11, he has this relatively simple spritekit game.
I'm trying to replicate it myself, and I found something interesting which I couldnt explain.
Here is a snippet of my code:
import SpriteKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let background = SKSpriteNode(imageNamed: "background")
background.blendMode = .replace
background.position = CGPoint(x: 512, y: 384)
background.zPosition = -1
addChild(background)
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
addBouncer(at: CGPoint(x: 0, y: 0))
addBouncer(at: CGPoint(x: 256, y: 0))
addBouncer(at: CGPoint(x: 512, y: 0))
addBouncer(at: CGPoint(x: 768, y: 0))
addBouncer(at: CGPoint(x: 1024, y: 0))
}
func addBouncer(at position:CGPoint){
let bouncer = SKSpriteNode(imageNamed: "bouncer")
bouncer.position = position
bouncer.physicsBody = SKPhysicsBody(circleOfRadius: bouncer.size.width / 2.0)
// bouncer.physicsBody?.isDynamic = false
addChild(bouncer)
}
}
The code above produces this:
Interesting enough, if I enabled the commented code
bouncer.physicsBody?.isDynamic = false
Then I get the following:
My understanding, from Paul's video, is that the 'dynamic' attribute there affects how the 'bouncer' will react to collision with other objects. By setting to 'false', the bouncer will not move. It wont be affected by gravity either. If so, what could cause some bouncers to be rendered at the wrong location (on my first screenshot) ?
Thanks a lot for the help!
I am creating and endless runner for iphones in landscape mode using SpriteKit. I set up the scene as such:
override func viewDidLayoutSubviews() {
let scene = GameScene(size: CGSize(width: 812, height: 375))
let skView = view as! SKView
scene.backgroundColor = UIColor.red
scene.scaleMode = .aspectFill
skView.presentScene(scene)
skView.showsFPS = true
skView.showsNodeCount = true
print("Screen Size: \(GlobalProperties.screenSize.width) x \(GlobalProperties.screenSize.height)")
print("Scene Size: \(scene.size.width) x \(scene.size.height)")
}
I would like to position the player so that there is the same amount of pixels between the edge of the player and the right edge of the screen regardless of aspect ratio. Is this a reasonable practice for maintaining difficulty between devices? I have the layout setup as such:
override func didMove(to view: SKView) {
let player = SKSpriteNode(texture: SKTexture(imageNamed: "CuteMelon"))
player.anchorPoint = CGPoint(x: 0.5, y: 0.5)
player.position = CGPoint(x: frame.width - 600, y: frame.midY)
self.addChild(player)
let rect = SKSpriteNode(color: .orange, size: CGSize(width: 550, height: 200))
rect.anchorPoint = CGPoint(x: 1, y: 0.5)
rect.position = CGPoint(x: frame.width-50, y: frame.height/2)
self.addChild(rect)
}
I added the rectangle to see if in both cases the player was 600 pixels from the right (leaving a 50px gap to ensure it wasnt running off the edge)
The result is as follows:
iPhone XR: https://imgur.com/y6OYHkK which is working as intended
iPhone 8: https://imgur.com/nkrx5By which is not placing the rectangle 50 pixels from the right bound of the frame.
What do I have to do to fix this issue or should I go about solving it a different way entirely? Thank you
Simply figure out the difference between the scene size and the screen size, and shift the camera over by half that distance. The formula abs((sceneWidth - screenWidth * sceneHeight/screenHeight)/2) will get you that. What this does is scale the screen to whatever height the scene is, then subtract the two differences from the width and return half that value.
override func didMove(to view: SKView) {
let widthPadding = abs((self.frame.width - (UIScreen.main.bounds.width * self.frame.height / UIScreen.main.bounds.height )) / 2)
self.camera = self.camera ?? SKCameraNode()
self.camera.position.x += widthPadding
let player = SKSpriteNode(texture: SKTexture(imageNamed: "CuteMelon"))
player.anchorPoint = CGPoint(x: 0.5, y: 0.5)
player.position = CGPoint(x: frame.width - 600, y: frame.midY)
self.addChild(player)
let rect = SKSpriteNode(color: .orange, size: CGSize(width: 550, height: 200))
rect.anchorPoint = CGPoint(x: 1, y: 0.5)
rect.position = CGPoint(x: frame.width-50, y: frame.height/2)
self.addChild(rect)
}
I'm currently working with custom markers on a scatter plot and found myself with an issue that results in CPTPlotSymbol created from a CGPath upside down and distorted.
I've tested the path-creating code in a playground and it works without issues, drawing the path with the correct shape and orientation.
Here's the path drawing code:
private func getOuterPathInRect(rect: CGRect) -> CGPath {
let circlePath: CGPath = {
let p = CGMutablePath()
let topHundred = CGRect(x: 0, y: 0, width: 100, height: 100)
p.addEllipse(in: topHundred)
return p
}()
let arrowPath: CGPath = {
let p = CGMutablePath()
p.move(to: CGPoint(x: rect.midX, y: rect.maxY - 5.0))
p.addLine(to: CGPoint(x: rect.midX - 7.5, y: rect.maxY - 15.0))
p.addLine(to: CGPoint(x: rect.midX + 7.5, y: rect.maxY - 15.0))
return p
}()
let path = CGMutablePath()
path.addPath(circlePath)
path.addPath(arrowPath)
return path
}
And the code that creates the CPTPlotSymbol is:
func symbol(for plot: CPTScatterPlot, record idx: UInt) -> CPTPlotSymbol? {
let index = Int(idx)
guard items[index].requiresMarker else { return nil }
let rect = CGRect(x: 0, y: 0, width: 120, height: 120)
let marker = BallMarkerView()
marker.contentMode = .center
let path = marker.pathIn(rect: rect)
let symbol = CPTPlotSymbol.customPlotSymbol(with: path)
symbol.size = rect.size
symbol.fill = CPTFill(color: CPTColor.red())
return symbol
}
My goal was to use a custom UIView as a marker, but I couldn't find an API to do so, so I resorted to providing a path-based marker and fill it with an image representation of the marker.
Is this the proper way of doing it?
Why is my path being drawn distorted and upside down? The path being upside down could be explained by the difference in the coordinate system between UIKit and CoreGraphics, but that doesn't explain the distorsion.
Thanks!
Because Core Plot shares drawing code between the Mac and iOS, it uses the same drawing coordinate system on both platforms where (0, 0) is the lower-left corner of the drawing canvas. This is flipped from the normal drawing coordinate system on iOS.
I'm trying to create a game in which i have a object 1 rotatiing in a circle and another object appears and places itself ontop of object 1. currently the object just rotates around object 1 without stacking ontop of it. how do i get the object to stack itself on top and then follow it's orbit? here's my code now.
let player = SKSpriteNode(imageNamed: "Light")
override func didMoveToView(view: SKView) {
// 2
backgroundColor = SKColor.whiteColor()
// 3
player.position = CGPoint(x: size.width * 0.5, y: size.height * 0.5)
// 4
player.size = CGSize(width:70, height:60 )
addChild(player)
let dx = player.position.x - self.frame.width / 2
let dy = player.position.y - self.frame.height / 2
let rad = atan2(dy, dx)
circle = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2), radius: 120, startAngle: rad, endAngle: rad + CGFloat(M_PI * 4), clockwise: true)
let follow = SKAction.followPath(circle.CGPath, asOffset: false, orientToPath: true, speed: 100)
player.runAction(SKAction.repeatActionForever(follow))
}
func addMonster() {
let monster = SKSpriteNode(imageNamed: "plate")
// Determine where to spawn the monster along the Y axis
let actualY = random(min: monster.size.height/1, max: size.height - monster.size.height/1)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster.position = CGPoint(x: size.width * 0.5 + monster.size.width/2, y: actualY)
// Add the monster to the scene
addChild(monster)
// Determine speed of the monster
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(3.0))
// Create the actions
let actionMove = SKAction.moveTo(CGPoint(x: -monster.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
let follow = SKAction.followPath(circle.CGPath, asOffset: false, orientToPath:true, speed: 100)
monster.runAction(SKAction.repeatActionForever(follow))
}
Your verbal description appears to have something like the following in mind (you can copy and paste this into an iOS playground):
import UIKit
import SpriteKit
import XCPlayground
let scene = SKScene(size: CGSize(width: 400, height: 400))
let view = SKView(frame: CGRect(origin: .zero, size: scene.size))
view.presentScene(scene)
XCPlaygroundPage.currentPage.liveView = view
scene.backgroundColor = .darkGrayColor()
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let player = SKShapeNode(rectOfSize: CGSize(width: 70, height: 60), cornerRadius: 5)
let radius: CGFloat = 120
let circle = CGPathCreateWithEllipseInRect(CGRect(x: -radius, y: -radius, width: radius * 2, height: radius * 2), nil)
let followCircle = SKAction.followPath(circle, asOffset: false, orientToPath: true, speed: 100)
player.runAction(.repeatActionForever(followCircle))
let monsterSize = CGSize(width: 35, height: 30)
let monster = SKShapeNode(rectOfSize: monsterSize, cornerRadius: 4)
monster.fillColor = .redColor()
monster.runAction(.repeatActionForever(followCircle))
scene.addChild(player)
scene.addChild(monster)
Where you make use of a random(min:max:) function that is probably implemented along the lines of:
public func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return min + (CGFloat(arc4random()) / CGFloat(UInt32.max)) * (max - min)
}
But you also have the code that seems to be saying:
let y = random(
min: scene.size.height / -2 + monsterSize.height / 2,
max: scene.size.height / 2 - monsterSize.height / 2
)
let xFrom = scene.size.width / 2
let xTo = scene.size.width / -2
monster.position = CGPoint(x: xFrom, y: y)
monster.runAction(
.moveToX(xTo, duration: NSTimeInterval(random(min: 2, max: 3)))
)
But this is unused. Instead the monster is set to follow the same circle path as the player. And so I am not sure really what exactly you are trying to achieve. (By the way, the simplest way of getting the monster "stack on top" of the player would be to make it the child node of the player...)
Hope some of this guesswork helps you in some way (to refine your question and code sample if nothing else).
Like many here asking questions I'm new to programing, but I'm going to do my best to describe my dilemma. Thanks for any help in advance.
I've included a picture of my goal.
The Green line is a node that rotates around the center of the screen. I have its anchor point set to 1,1 and position x,y to the center of the device. Using SKActions I've made the node rotate so many degrees causing it to move like the second hand of a clock would around its center post. All the code to this point I've got down. Here is where I've reached a mental brick wall. I want to have a sprite constantly placed at the tip of the rotating node so that the sprite will move around the center in a circular motion. The sprite is represented by the Pink circle.
This was my plan of attack, but my lack of knowledge of the swift language prevents me from executing it.
Is there a way to add a reference point to the tip of the node? If so I was thinking of doing that, then in the update function set the position of the sprite to the x,y of that reference point. I hope I've included enough information. If anyone has a better approach to this, please let me hear it. Thanks so much in advance.
I am posting an answer, even though it was answered in the comments. If Leo posts the answer, I'll delete this one.
I am assuming that your ball is a child of the line, so you'd just offset the x position of your ball the width of the line. Which as noted in comments is the radius of the circle path your ball is following.
Your secondary issue could be solved by using another SKAction to offset the zRotation of the line. For example :
let lineRotateAction = SKAction.rotateByAngle(CGFloat(360).degreesToRadians, duration: 6)
let ballRotateAction = SKAction.rotateByAngle(CGFloat(-360).degreesToRadians, duration: 6)
You can simply put create the hands as children of an empty SKNode, position both hands correctly in relation to these SKNodes and then rotate the SKNodes themselves.
Two different ways: First using a parent node (which is potentially what you want):
let body = SKSpriteNode(color: .clear, size: CGSize(width: 20, height: 150))
body.anchorPoint = CGPoint(x: 0.5, y: 0)
body.position = CGPoint(x: frame.midX, y: frame.midY)
body.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(M_PI * 2), duration: 10)))
let hand = SKSpriteNode(color: .blue, size: CGSize(width: body.size.width/2, height: body.size.height))
hand.position = CGPoint(x: 0, y: body.size.height/2)
let ball = SKSpriteNode(color: .green, size: CGSize(width: body.size.width, height: body.size.width))
ball.position = CGPoint(x: 0, y: body.size.height - ball.size.height/2)
body.addChild(hand)
body.addChild(ball)
addChild(body)
Second is using physics, which produces a similar effect only on the surface but it might give you other ideas too:
let hand = SKSpriteNode(color: .blue, size: CGSize(width: 10, height: 150))
hand.anchorPoint = CGPoint(x: 0.5, y: 1)
hand.position = CGPoint(x: frame.midX, y: frame.midY)
hand.physicsBody = SKPhysicsBody(rectangleOf: hand.size)
hand.physicsBody?.affectedByGravity = false
hand.physicsBody?.isDynamic = false
hand.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(M_PI * 2), duration: 10)))
addChild(hand)
let ball = SKSpriteNode(color: .green, size: CGSize(width: 20, height: 20))
ball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
ball.position = CGPoint(x: frame.midX, y: frame.midY - hand.size.height)
ball.physicsBody = SKPhysicsBody(rectangleOf: ball.size)
addChild(ball)
let joint = SKPhysicsJointPin.joint(withBodyA: hand.physicsBody!,
bodyB: ball.physicsBody!,
anchor: ball.position)
physicsWorld.add(joint)
Have fun!