I'm working with SpriteKit, Xcode 6 and Swift
I have a spritenode declared like this:
let firstCircle = SKSpriteNode(imageNamed: "Circle")
firstCircle.physicsBody = SKPhysicsBody(circleOfRadius: 7)
firstCircle.physicsBody?.affectedByGravity = false
and another spritenode which is bigger circle declared like this :
let wall = SKSpriteNode(imageNamed: "Circle")
wall.physicsBody = SKPhysicsBody(circleOfRadius: 50)
wall.physicsBody?.affectedByGravity = false
The node firstcircle follows my finger and the node wall stay at the middle of the screen, and I want that the two nodes cannot enter in collision together, but with my actual code, firstcircle just cross the wall, what have I to do in order to fix that problem ?
Here is how I move the node :
let scale:CGFloat = 10.0
let damping:CGFloat = 0.94
var departCircleLocation = CGPoint()
var departTouchLocation = CGPoint()
var currentTouchLocation = CGPoint()
var isTouchActive = false
override func touchesBegan(touches: NSSet, withEvent event: UIEvent)
{
isTouchActive = true
for touch: AnyObject in touches
{
departTouchLocation = touch.locationInNode(self)
}
departCircleLocation = firstCircle.position
currentTouchLocation = departTouchLocation
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent)
{
for touch: AnyObject in touches
{
currentTouchLocation = touch.locationInNode(self)
}
moveCircles()
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent)
{
isTouchActive = false
}
override func update(currentTime: NSTimeInterval)
{
moveCircle()
}
func moveCircle()
{
if isTouchActive
{
var dx = CGFloat(departCircleLocation.x - departTouchLocation.x + currentTouchLocation.x - firstCircle.position.x) * scale
var dy = CGFloat(departCircleLocation.y - departTouchLocation.y + currentTouchLocation.y - firstCircle.position.y) * scale
firstCircle.physicsBody?.velocity = CGVectorMake(dx, dy)
}
else
{
let dx = firstCircle.physicsBody!.velocity.dx * damping
let dy = firstCircle.physicsBody!.velocity.dy * damping
firstCircle.physicsBody!.velocity = CGVectorMake(dx, dy)
}
}
Related
I am working on an arrow shooting game - The player need to shoot 3 arrows to a moving target (the target moves from left to right). When an arrow hits the target it should move with it (left to right). The most obvious thing to do would be to change the arrow parent to the target. From some reason its causing me some troubles -
I tried --- arrow.move(toParent:target) and I don't see the arrow on the screen even after I set a new location for it
If I simply --- target.addChild(arrow) I get a failure since I did not remove the arrow from its parent (which is the scene in this case)
When I --- arrow.removeFromParent() and then target.addChild(arrow) its causing other arrows to collide with each other and I still don't see the arrow on the screen.
This is my code -
class GameScene: SKScene, SKPhysicsContactDelegate {
var target:SKSpriteNode?
var arrows = [SKSpriteNode]()
var arrowContactPoint:CGPoint?
let noCategory:UInt32 = 0
let arrowCategory:UInt32 = 0b1
let targetCategory:UInt32 = 0b1 << 1
let obstacleCategory:UInt32 = 0b1 << 2
override func didMove(to view: SKView) {
allowCollisionDetection()
setTarget()
moveTargetFromSideToSide()
newArrow()
}
func didBegin(_ contact: SKPhysicsContact) {
let categoryBitMaskBodyA:UInt32 = contact.bodyA.categoryBitMask
let categoryBitMaskBodyB:UInt32 = contact.bodyB.categoryBitMask
if ((categoryBitMaskBodyA == targetCategory && categoryBitMaskBodyB == arrowCategory) || (categoryBitMaskBodyA == arrowCategory && categoryBitMaskBodyB == targetCategory)) {
arrowContactPoint = contact.contactPoint
arrowCollideWithTarget()
} else if (categoryBitMaskBodyA == obstacleCategory || categoryBitMaskBodyB == obstacleCategory) {
let obstacleNode:SKNode = ((categoryBitMaskBodyA == arrowCategory) ? contact.bodyA.node! : contact.bodyB.node)!
arrowCollideWithObstacle(obstacle:obstacleNode)
} else if (categoryBitMaskBodyA == arrowCategory && categoryBitMaskBodyB == arrowCategory) {
newGame()
} else {
print("Something went wrong")
}
newArrow()
}
func touchDown(atPoint pos : CGPoint) {
}
func touchMoved(toPoint pos : CGPoint) {
}
func touchUp(atPoint pos : CGPoint) {
shootArrow()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
func allowCollisionDetection() {
self.physicsWorld.contactDelegate = self
}
func setTarget() {
target = self.childNode(withName: "target") as? SKSpriteNode
//Set the target bit mask, it's tag
target?.physicsBody?.categoryBitMask = targetCategory
//Set with which objects the target collide
target?.physicsBody?.collisionBitMask = noCategory
//Set to which coliision we want to responde/handle - didBegin will get triggered
target?.physicsBody?.contactTestBitMask = arrowCategory
}
func moveTargetFromSideToSide() {
let moveRight = SKAction.moveBy(x: frame.size.width - (target?.size.width)!, y: 0, duration: 2)
let moveLeft = SKAction.moveBy(x: -(frame.size.width - (target?.size.width)!), y: 0, duration: 2)
let moveBackAndForth = SKAction.repeatForever(SKAction.sequence([moveRight, moveLeft]))
target?.run(moveBackAndForth)
}
func newArrow() {
let arrow = SKSpriteNode(imageNamed: "arrow1")
let arrowTexture = SKTexture(imageNamed: "arrow1")
arrow.position = CGPoint.zero
self.addChild(arrow)
arrow.physicsBody = SKPhysicsBody(texture: arrowTexture, size: arrowTexture.size())
arrow.physicsBody?.isDynamic = true
arrow.physicsBody?.allowsRotation = true
arrow.physicsBody?.affectedByGravity = false
arrow.physicsBody?.friction = 0.2
arrow.physicsBody?.restitution = 0.2
arrow.physicsBody?.linearDamping = 0.1
arrow.physicsBody?.angularDamping = 0.1
arrow.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
arrow.physicsBody?.categoryBitMask = arrowCategory
arrow.physicsBody?.collisionBitMask = noCategory
arrow.physicsBody?.contactTestBitMask = arrowCategory | obstacleCategory | targetCategory
arrows.append(arrow)
}
func shootArrow(){
print("shootArrow")
arrows.last!.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 80))
}
func arrowCollideWithTarget() {
print("arrowCollideWithTarget")
arrows.last!.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
arrows.last!.move(toParent: target!)
}
func arrowCollideWithObstacle(obstacle:SKNode) {
print("arrowCollideWithObstacle")
arrows.last!.removeFromParent()
arrows.removeLast()
}
func newGame() {
print("New Game")
for i in 0 ..< (arrows.count) {
arrows[i].removeFromParent()
}
arrows.removeAll()
}
}
What eventually solved it for me was using this method ---
move(toParent: )
This is my code -
func arrowCollideWithTarget() {
arrows.last!.move(toParent:target!)
}
I am trying to move sprite to the right when I press a button on the screen. However, when I try doing it I only have a solution to move the sprite to a certain point. So... I want the sprite to move to the right forever or until i do something else.
This is in Xcode using Swift in SpriteKit.
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch : AnyObject in touches{
let pointInTouch = touch.locationInNode(self)
let tappedNode = nodeAtPoint(pointInTouch)
let tappeNodeName = tappedNode.name
if tappeNodeName == "Btn"{
player.physicsBody?.velocity = CGVectorMake(0, 0)
let action = SKAction.applyImpulse(CGVectorMake(400, 0), duration: 1)
player.runAction(SKAction.repeatActionForever(action))
print("Touched!")
}
}
}
You can simply move your node to the right untill it does exist the scene
class GameScene: SKScene {
private var player: SKSpriteNode!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else { return }
let pointInTouch = touch.locationInNode(self)
let tappedNode = nodeAtPoint(pointInTouch)
if tappedNode.name == "Btn"{
let moveToRight = SKAction.moveToX(self.frame.width + player.frame.width, duration: 5)
player.runAction(moveToRight)
}
}
}
Update: constant speed
If you want the player to move with constant speed you can use this code.
As you can see I am calculating the duration using space / speed. You just need to find the best constant value for speed for your scenario.
class GameScene: SKScene {
private var player: SKSpriteNode!
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else { return }
let pointInTouch = touch.locationInNode(self)
let tappedNode = nodeAtPoint(pointInTouch)
let deltaX = self.frame.width + player.frame.width - player.position.x
let speed: CGFloat = 10 // <-- change this to find the best value for you
let duration: NSTimeInterval = NSTimeInterval(deltaX / speed)
if tappedNode.name == "Btn"{
let moveToRight = SKAction.moveByX(deltaX, y:0, duration: duration)
player.runAction(moveToRight)
}
}
}
I'm working on simple game which will based on paiting background by player. In left and right corner there will be buttons which will move character to left or right. I've already implemented that (character is moving and lefts painted background behind), but with adding another circles fps's drops really fast. Is there any solution to that?
import SpriteKit
class GameScene: SKScene {
var playerDot:PlayerDot = PlayerDot(imageNamed:"player")
var isTurningLeft:Bool = false
var isTurningRight:Bool = false
var lastLocation:CGPoint = CGPoint()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let myLabel = SKLabelNode(fontNamed:"Helvetica")
myLabel.name = "left"
myLabel.text = "Left"
myLabel.fontSize = 30
myLabel.horizontalAlignmentMode = .Left
myLabel.position = CGPoint(x:CGRectGetMinX(self.frame), y:CGRectGetMinY(self.frame))
self.addChild(myLabel)
let myLabel2 = SKLabelNode(fontNamed:"Helvetica")
myLabel2.name = "right"
myLabel2.text = "Right"
myLabel2.fontSize = 30
myLabel2.horizontalAlignmentMode = .Right
myLabel2.position = CGPoint(x:CGRectGetMaxX(self.frame), y:CGRectGetMinY(self.frame))
self.addChild(myLabel2)
playerDot.position = CGPoint(x:CGRectGetMaxX(self.frame)/2, y:CGRectGetMinY(self.frame))
self.addChild(playerDot)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = true
isTurningRight = false
}
else if theName == "right" {
isTurningRight = true
isTurningLeft = false
}
}
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = false
}
else if theName == "right" {
isTurningRight = false
}
}
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
if let theName = self.nodeAtPoint(location).name {
if theName == "left" {
isTurningLeft = false
}
else if theName == "right" {
isTurningRight = false
}
}
}
}
override func update(currentTime: CFTimeInterval) {
if(isTurningLeft){
playerDot.increaseAngle()
} else if (isTurningRight){
playerDot.decreaseAngle()
}
//calculates new character position based on angle of movement changed
playerDot.updatePosition()
drawCircle()
}
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let circle = SKShapeNode(circleOfRadius: 10 )
circle.position = playerDot.position
circle.fillColor = SKColor.orangeColor()
circle.strokeColor = SKColor.orangeColor()
self.addChild(circle)
lastLocation = playerDot.position
}
}
func distanceFromCGPoints(a:CGPoint,b:CGPoint)->CGFloat{
return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));
}
func random() -> CGFloat {
return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
func random(min min: CGFloat, max: CGFloat) -> CGFloat {
return random() * (max - min) + min
}
}
EDIT:
drawCircle with SKShapeNode replaced with SKSpriteNote
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let playerDot2 = SKSpriteNode(imageNamed:"player")
playerDot2.position = playerDot.position
self.addChild(playerDot2)
lastLocation = playerDot.position
}
}
If you are having frame rate drops then some parts of your code are slowing down execution too much. You need to optimise your code. Use Instruments (Time Profiler) to find out which lines/functions are causing problems speed wise. If you have never used it before read up on it and use it a couple times to get the gist, then I recommend watching the WWDC video Profiling In-Depth
Based on your edit (switching to SKSpriteNode), this is how you could do it:
Declare a texture as a property of your scene. This way, you load it once and reuse it later:
let yourTexture = SKTextureAtlas(named: "yourAtlas").textureNamed("yourTexture")
Then in your drawCircle method:
func drawCircle(){
if(distanceFromCGPoints(lastLocation, b: playerDot.position)>2){
let playerDot2 = SKSpriteNode(texture: yourTexture)
playerDot2.position = playerDot.position
self.addChild(playerDot2)
lastLocation = playerDot.position
}
}
This way (by using SKSpriteNode instead of SKShapeNode) you have reduced number of draw calls required to render all those circles. Also because you are reusing the same texture, instead of allocating it every time using imageNamed you have reduced greatly an amount of memory that app consumes. Now, SpriteKit can render hundreds of nodes #60 fps if those nodes are rendered in batches. Still there is a limit before fps start dropping. And that depends on a device.
I am creating a game like Doodle Jump (without the accelerometer).
I have been trying to figure this out, but I can't seem to make it run as smoothly as I've been hoping.
Here is my code for my touches functions:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
lastTouch = nil
}
override func update(currentTime: CFTimeInterval) {
if let touch = lastTouch {
let impulseVector = CGVector(dx: 400, dy: 400)
player.physicsBody?.applyImpulse(impulseVector)
}
}
From what I'm gathering, you want to change the impulseVector that is applied to the player based on where the touch is. I would imagine the code looking something like this:
// At the top of your code
let scale = 0.5
// Update function
override func update(currentTime: CFTimeInterval) {
if let touch = lastTouch {
let xOffset = (touch.x - player.position.x)*scale
let yOffset = (touch.y - player.position.y)*scale
let impulseVector = CGVector(dx: xOffset, dy: yOffset)
player.physicsBody?.applyImpulse(impulseVector)
}
}
This will pull the player node up with more force the further away it is from the touch. If you are trying to pull the player with the same amount of force, no matter where they are (which may be more likely in this case), you would do something like this:
// At the top of your code
let xPlayerForce = 20
let yPlayerForce = 30
// Update function
override func update(currentTime: CFTimeInterval) {
if let touch = lastTouch {
var xForce = 0.0
var yForce = 0.0
let xTouchOffset = (touch.x - player.position.x)
let yTouchOffset = (touch.y - player.position.y)
if xTouchOffset > 0.0 {
xForce = xPlayerForce
} else if xTouchOffset < 0.0 {
xForce = -xPlayerForce
} // else we do nothing
if yTouchOffset > 0.0 {
yForce = yPlayerForce
} // here you can choose whether you want it to push
// the player node down, using similar code from the
// above if statement
let impulseVector = CGVector(dx: xForce, dy: yForce)
player.physicsBody?.applyImpulse(impulseVector)
}
}
I am trying to make a bubble shooter kind of game but I cannot move my paddle. Any idea why this happens?
//
// GameScene.swift
// gggame
//
// Created by divy on 6/17/15.
// Copyright (c) 2015 divy. All rights reserved.
//
import SpriteKit
class GameScene: SKScene , SKPhysicsContactDelegate {
var istouchingpaddle = false
let ballcat: UInt32 = 0x1 << 0
let paddlecat : UInt32 = 0x1 << 1
override func didMoveToView(view: SKView) {
let border = SKPhysicsBody(edgeLoopFromRect: self.frame)
border.friction = 0
self.physicsBody = border
self.physicsWorld.gravity = (CGVectorMake(0,-9.8))
self.physicsWorld.contactDelegate = self
//ball setting
let ball = childNodeWithName("ball") as SKSpriteNode
ball.physicsBody?.applyImpulse(CGVectorMake(30, -30))
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.friction = 0
ball.physicsBody?.restitution = 1
ball.physicsBody?.angularDamping = 0
ball.physicsBody?.linearDamping = 0
ball.physicsBody?.categoryBitMask = ballcat
}
func didBeginContact(contact: SKPhysicsContact) {
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
//touch setting
var touch = touches.anyObject() as UITouch!
var location = touch.locationInNode(self)
if let body = self.physicsWorld.bodyAtPoint(location)
{
if body.node!.name == "paddle"
{
istouchingpaddle = true
}
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
istouchingpaddle = false
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
if istouchingpaddle{
var touch = touches.anyObject() as UITouch!
var currentlocation = touch.locationInNode(self)
var prevlocation = touch.locationInNode(self)
var paddle = childNodeWithName("paddle") as SKSpriteNode
var xpos = paddle.position.x + (currentlocation.x - prevlocation.x)
xpos = max(xpos, paddle.size.width/2)
xpos = min(xpos, size.width - paddle.size.width/2)
paddle.position = CGPointMake(xpos, paddle.position.y)
}
}
override func update(currentTime: CFTimeInterval) {
}
}
I can't see where in your code you have created physics bodies for paddle and a ball. So, you are actually trying to use physics bodies before they exist. Here is an working example to give you a basic idea about how to create physics bodies and how to move a paddle (you can adjust this to your needs):
//
// GameScene.swift
// gggame
//
// Created by divy on 6/17/15.
// Copyright (c) 2015 divy. All rights reserved.
//
import SpriteKit
class GameScene: SKScene , SKPhysicsContactDelegate {
var istouchingpaddle = false
let ballcat: UInt32 = 0x1 << 0
let ball = SKShapeNode(circleOfRadius: 20) //change this to create a ball from image, because using SKShapeNode can be inefficient
let paddle = SKSpriteNode(color: SKColor.grayColor(), size: CGSize(width: 100.0,height: 30.0))
let paddlecat : UInt32 = 0x1 << 1
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
let border = SKPhysicsBody(edgeLoopFromRect: self.frame)
border.friction = 0
self.physicsBody = border
//ball setting
ball.name = "ball"
ball.position = CGPoint(x: CGRectGetMidX(self.frame),y: CGRectGetMidY(self.frame))
ball.physicsBody = SKPhysicsBody(circleOfRadius: ball.frame.size.width/2)
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.dynamic = true
ball.physicsBody?.restitution = 1
ball.physicsBody?.categoryBitMask = ballcat
ball.physicsBody?.collisionBitMask = paddlecat
ball.physicsBody?.contactTestBitMask = paddlecat
paddle.name = "paddle"
paddle.position = CGPoint(x: CGRectGetMidX(self.frame) ,y: CGRectGetMinY(self.frame)+100.0)
paddle.physicsBody = SKPhysicsBody(rectangleOfSize: paddle.size)
paddle.physicsBody?.allowsRotation = false
paddle.physicsBody?.dynamic = false
paddle.physicsBody?.affectedByGravity = false
paddle.physicsBody?.restitution = 1
paddle.physicsBody?.categoryBitMask = paddlecat
paddle.physicsBody?.collisionBitMask = ballcat
paddle.physicsBody?.contactTestBitMask = ballcat
self.addChild(ball)
self.addChild(paddle)
}
func didBeginContact(contact: SKPhysicsContact) {
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
//touch setting
var touch = touches.anyObject() as UITouch!
var location = touch.locationInNode(self)
if let body = self.physicsWorld.bodyAtPoint(location)
{
if body.node!.name == "paddle"
{
istouchingpaddle = true
}
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
istouchingpaddle = false
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
if istouchingpaddle{
var touch = touches.anyObject() as UITouch!
var currentlocation = touch.locationInNode(self)
paddle.position = CGPointMake(currentlocation.x, paddle.position.y)
}
}
}