Moving and rotating sprite with touches using SKSpriteKit and Swift 4 - swift

I am wanting my sprite "Character" to move and rotate "follow" my finger as I move it around. I had it working fine but somehow have managed to screw it up. I'm not sure what I've done but can't for the life of me figure it out.
Any help would be greatly appreciated. Im sure its something stupid that I'm just overlooking. Thanks in advance.
Mark
GameScene.swift
import SpriteKit
import GameplayKit
//MARK: ---------------Global Variables Section
var nodeType: String = String()
var characterNode = Character()
let characterImage: String = String("default_pose")
var characterTexture:SKTexture = SKTexture()
let characterSize: CGSize = CGSize(width: 64, height: 64)
let characterPosition: CGPoint = CGPoint(x: 0, y: 0)
var enemyNode = Enemy()
let enemyImage: String = String("blob1")
var enemyTexture = SKTexture()
let enemySize: CGSize = CGSize(width: 50, height: 50)
let enemyPosition: CGPoint = CGPoint(x: 150, y: 150)
class GameScene: SKScene, SKPhysicsContactDelegate {
//MARK: --------------------Class Variables Section
var affectedByGravity: Bool = false
var dynamic: Bool = true
var rotation: Bool = false
var lastUpdateTime: CFTimeInterval = 0
//MARK: --------------------DidMove to View Section
override func didMove(to view: SKView) {
var node = SKSpriteNode()
node = addNode(NodeType: "Character")
applyPhysics(physicsNode: node, nodeImage: characterImage, nodeSize: characterSize, nodeName: node.name!)
node.zPosition = 10
addChild(node)
node = addNode(NodeType: "Enemy")
applyPhysics(physicsNode: node, nodeImage: enemyImage, nodeSize: enemySize, nodeName: node.name!)
node.zPosition = 9
addChild(node)
}
//MARK: -----------------------Add Node Section
func addNode(NodeType: String) -> SKSpriteNode {
var node = SKSpriteNode()
switch NodeType {
case "Enemy":
node = enemyNode.setupNode(nodeName: "Enemy", nodeImage: "blob1", nodeSize: enemySize, nodePosition: enemyPosition)
default:
node = characterNode.setupNode(nodeName: "Player", nodeImage: "default_pose", nodeSize: characterSize, nodePosition: characterPosition)
}
return node
}
//MARK: ------------------------Physics Section
func applyPhysics(physicsNode: SKSpriteNode, nodeImage: String, nodeSize: CGSize, nodeName: String) {
let nodeTexture: SKTexture = SKTexture(imageNamed: nodeImage)
physicsNode.physicsBody = SKPhysicsBody(texture: nodeTexture, size: nodeSize)
physicsNode.physicsBody?.isDynamic = dynamic
physicsNode.physicsBody?.affectedByGravity = affectedByGravity
physicsNode.physicsBody?.allowsRotation = rotation
physicsNode.physicsBody?.categoryBitMask = 1
physicsNode.physicsBody?.contactTestBitMask = 1
physicsNode.physicsBody?.collisionBitMask = 1
}
//MARK: ------------------------Movement and Rotation Section
func moveAndRotate(sprite: Character, toPosition position: CGPoint) {
let angle = atan2(position.y - sprite.position.y, position.x - sprite.position.x)
let rotateAction = SKAction.rotate(toAngle: angle - .pi / 2, duration: 0.05, shortestUnitArc:true)
sprite.run(rotateAction)
let offsetX = position.x - (sprite.position.x)
let offsetY = position.y - (sprite.position.y)
let normal = simd_normalize(simd_double2(x: Double(offsetX), y:Double(offsetY)))
characterNode.velocity = CGVector(dx: CGFloat(normal.x) * sprite.movePointsPerSecond, dy: CGFloat(normal.y) * sprite.movePointsPerSecond)
}
//MARK: ----------------------Touches Section
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
characterNode.isMoving = true
moveAndRotate(sprite: characterNode, toPosition: touchLocation)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
moveAndRotate(sprite: characterNode, toPosition: touchLocation)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
characterNode.isMoving = false
}
//MARK: ------------------Update Section
override func update(_ currentTime: TimeInterval) {
let deltaTime = max(1.0/30, currentTime - lastUpdateTime)
lastUpdateTime = currentTime
update(character: characterNode, dt: deltaTime)
}
func update(character: Character, dt: CFTimeInterval) {
if characterNode.isMoving == true {
let newX = character.position.x + character.velocity.dx * CGFloat(dt)
let newY = character.position.y + character.velocity.dy * CGFloat(dt)
character.position = CGPoint(x: newX, y: newY)
}
}
}
Character.swift
import SpriteKit
import GameplayKit
class Character: SKSpriteNode {
let movePointsPerSecond: CGFloat = 150.0
var velocity = CGVector(dx: 0.0, dy: 0.0)
var isMoving = false
var node: SKSpriteNode = SKSpriteNode()
func setupNode(nodeName: String, nodeImage: String, nodeSize: CGSize, nodePosition: CGPoint) -> SKSpriteNode {
node = SKSpriteNode(imageNamed: nodeImage)
node.name = nodeName
node.position = nodePosition
return node
}
}

Your node does not move because you never assign characterNode to the node on your screen
override func didMove(to view: SKView) {
var node = SKSpriteNode()
node = addNode(NodeType: "Character")
applyPhysics(physicsNode: node, nodeImage: characterImage, nodeSize: characterSize, nodeName: node.name!)
node.zPosition = 10
addChild(node)
characterNode = node //ADD THIS LINE
node = addNode(NodeType: "Enemy")
applyPhysics(physicsNode: node, nodeImage: enemyImage, nodeSize: enemySize, nodeName: node.name!)
node.zPosition = 9
addChild(node)
}

Related

How to prevent SKShapeNode handled with touch position from going beyond a circle path?

I'm using SpriteKit for iOS app with Swift.
I made a small SKShapeNode("ball") and a big circle path("room"), and I want a SKShapeNode to stay within the circle.
Here is my code:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var isFingerOnBall = false
var ball: SKShapeNode!
var room: SKShapeNode!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if let body = physicsWorld.body(at: touchLocation) {
if body.node!.name == "ball" {
isFingerOnBall = true
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if isFingerOnBall {
ball.position = touchLocation
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isFingerOnBall = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
let radius:CGFloat = 60
// BALL
ball = SKShapeNode(circleOfRadius: radius)
ball.name = "ball"
ball.fillColor = .red
ball.strokeColor = .clear
ball.position = CGPoint(x: 0, y: 150)
ball.physicsBody = SKPhysicsBody(circleOfRadius: radius)
ball.physicsBody?.isDynamic = true
scene?.addChild(ball)
// ROOM
room = SKShapeNode(circleOfRadius: radius * 5)
room.name = "room"
room.strokeColor = .white
room.lineWidth = 10
room.position = CGPoint(x: 0, y: 0)
room.physicsBody = SKPhysicsBody(edgeLoopFrom: room.path!)
room.physicsBody?.isDynamic = false
scene?.addChild(room)
}
}
I expected the room's SKPhysicsBody would limit the ball go out beyond the path,
OK image
but when my finger drags the ball out of the circle(room), it goes out too.
No good image
Thanks in advance.
There is a few ways to do this although the simplest I can think of is to replace you didMove(to view: SKView) method with this:
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
// BALL
let ball = SKShapeNode(circleOfRadius: 10)
ball.fillColor = .yellow
ball.strokeColor = .yellow
ball.lineWidth = 5
ball.physicsBody = SKPhysicsBody(circleOfRadius: 10)
ball.physicsBody?.isDynamic = true
ball.physicsBody?.affectedByGravity = true
ball.physicsBody?.categoryBitMask = 1
ball.physicsBody?.collisionBitMask = 2
ball.physicsBody?.contactTestBitMask = 0
addChild(ball)
// ROOM
let room = SKShapeNode(circleOfRadius: 200)
room.fillColor = .clear
room.strokeColor = .red
room.lineWidth = 5
room.physicsBody = SKPhysicsBody(edgeLoopFrom: room.path!)
room.physicsBody?.isDynamic = false
room.physicsBody?.affectedByGravity = false
room.physicsBody?.categoryBitMask = 2
room.physicsBody?.collisionBitMask = 0
room.physicsBody?.contactTestBitMask = 0
addChild(room)
}
Basically you just need to set the PhysicsBody CategoryBitMask and Collision Bit mask. Also don't assign a solid physics body notice the use of the "edgeFromLoop" to make the rooms physic body.
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let touchLocation = touch!.location(in: self)
if isFingerOnBall {
let roomRadius: CGFloat = 200
let hyp = sqrt(touchLocation.x * touchLocation.x + touchLocation.y * touchLocation.y)
if abs(hyp) > roomRadius {
ball.position = ball.position
} else {
ball.position = touchLocation
}
}
}
Here you are getting the hypotenuse of the new touch point and checking to see that its greater then the radius of the room. If it is then the position of the ball remains the same, if not then update the ball position as normal.
Hope this helps and enjoy

Second node instance SKPhysicsBody not responding to collision

I've been trying to create a simple game in SK where the ball would bounce off the tops of trampolines that spawn gradually. Collisions work fine with the first trampoline but I'm having problems registering it when ball hits second trampoline spawned in run-time. Anyone might have a clue what I've been doing wrong?
Here is the code.
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var ball: SKShapeNode?
var trampoline: SKShapeNode?
var cam: SKCameraNode?
var hasHit: Bool?
var hasCollided: Bool?
var hasMoved: Bool?
func createBall() {
ball = SKShapeNode(circleOfRadius: 50)
ball?.position = CGPoint(x: 0, y: -600)
ball?.fillColor = .red
ball?.strokeColor = .red
let ballPhysics = SKPhysicsBody(circleOfRadius: 50)
ball?.physicsBody = ballPhysics
ballPhysics.categoryBitMask = 1
ballPhysics.collisionBitMask = 0
ballPhysics.contactTestBitMask = 0
ballPhysics.affectedByGravity = false //NO GRAVITY!
ballPhysics.isDynamic = true
addChild(ball!)
}
func createTrampoline(x: CGFloat, y: CGFloat) {
let rect = CGRect(x: x, y: y, width: 100, height: 20)
trampoline = SKShapeNode(rect: rect)
trampoline?.fillColor = .blue
trampoline?.strokeColor = .blue
let trampolinePhysics = SKPhysicsBody(rectangleOf: CGSize(width: 100, height: 20))
trampoline?.physicsBody = trampolinePhysics
trampolinePhysics.categoryBitMask = 2
trampolinePhysics.collisionBitMask = 0
trampolinePhysics.contactTestBitMask = 1
trampolinePhysics.affectedByGravity = false //NO GRAVITY!
trampolinePhysics.isDynamic = true
addChild(trampoline!)
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
cam = SKCameraNode()
self.camera = cam
self.backgroundColor = .darkText
hasHit = false
hasCollided = false
hasMoved = false
createBall()
createTrampoline(x: -50, y: -10)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touches: AnyObject in touches {
let location = touches.location(in: self)
let move = SKAction.moveTo(x: location.x, duration: 0.1)
ball?.run(move)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if (!hasMoved!) {
let vect = CGVector(dx: 0, dy: 600)
ball?.physicsBody?.applyImpulse(vect)
ball?.physicsBody?.affectedByGravity = true
hasMoved = true
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if (hasHit == true) {
cam?.position.y = (ball?.position.y)!
}
}
func didBegin(_ contact: SKPhysicsContact) {
let vect = CGVector(dx: 0, dy: 1500)
if (((contact.bodyB.node?.position.y)! - (contact.bodyA.node?.position.y)!) > 0) {
if (!hasCollided!){
hasHit = true
hasCollided = true
print("collision")
contact.bodyB.applyImpulse(vect)
createTrampoline(x: -50, y: ((ball?.position.y)! + CGFloat(3000)))
}
} else {
print("bad coll")
hasCollided = false
}
}
}
Thanks in advance.

Objects Position Not Matching Up (Spritekit)

I am building a spritekit game and have just started the project. I have a circle on the screen which starts in the center, and when i drag my finger from the circle outward, it will show a dotted line/bezierpath connected to the ball which will help the user see where it is aiming the ball. When the user lifts their finger, the ball will shoot in the opposite direction of the aim line. (Think a game like soccer stars or pool). The issue is that the maneuver works the first time when everything starts in the middle: I drag my finger and the ball shoots in opposite direction then stops. But when I try it again, the position of the aiming line says it is the same as the ball (It should be), but then it shows up like an inch away from the ball on the screen. I feel like this may be an issue that the scene(s) behind the objects may not be the same size? But I'm confused because I think I'm only using one scene.
GameViewController viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
scene.size = view.bounds.size
//scene.anchorPoint = CGPoint(x: 0.0, y: 0.0)
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
GameScene Code (Doubt you need all of it but whatever):
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKShapeNode(circleOfRadius: 35)
var touchingBall = false
var aimLine = SKShapeNode()
var startAimPoint = CGPoint()
var endAimPoint = CGPoint()
let damping:CGFloat = 0.94
override func didMove(to view: SKView) {
ball.fillColor = SKColor.orange
ball.name = "ball"
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
var physicsBody = SKPhysicsBody(circleOfRadius: 35)
ball.physicsBody = physicsBody
ball.physicsBody?.affectedByGravity = false
ball.physicsBody?.friction = 10.0
ball.position = CGPoint(x: frame.midX, y: frame.midY)
self.addChild(ball)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOUCHES BEGAN.")
for touch in touches {
print("TB: \(touchingBall)")
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "ball" {
// touched inside node
if ball.physicsBody!.angularVelocity <= 0.0{
touchingBall = true
startAimPoint = ball.position
print(touchingBall)
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOCUHES MOVED.")
for touch in touches {
let location = touch.location(in: self)
if touchingBall{
endAimPoint = location
assignAimLine(start: startAimPoint, end: endAimPoint)
print("Moving touched ball")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches ended. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x - startAimPoint.x) * 3, dy: -(endAimPoint.y - startAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches cancelled. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x - startAimPoint.x) * 3, dy: -(endAimPoint.y - startAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
print(ball.physicsBody!.velocity)
let dx2 = ball.physicsBody!.velocity.dx * damping
let dy2 = ball.physicsBody!.velocity.dy * damping
ball.physicsBody!.velocity = CGVector(dx: dx2, dy: dy2)
}
func assignAimLine(start: CGPoint, end: CGPoint){
aimLine.removeFromParent()
var bezierPath = UIBezierPath()
bezierPath.move(to: start)
bezierPath.addLine(to: shortenedEnd(startPoint: start, endPoint: end))
var pattern : [CGFloat] = [10.0, 10.0]
let dashed = SKShapeNode(path: bezierPath.cgPath.copy(dashingWithPhase: 2, lengths: pattern))
aimLine = dashed
aimLine.position = ball.position
aimLine.zPosition = 0
self.addChild(aimLine)
}
func hypotenuse(bp: UIBezierPath) -> Double{
var a2 = bp.cgPath.boundingBox.height * bp.cgPath.boundingBox.height
var b2 = bp.cgPath.boundingBox.width * bp.cgPath.boundingBox.width
return Double(sqrt(a2 + b2))
}
func hypotenuse(startP: CGPoint, endP: CGPoint) -> Double{
var bezierPath = UIBezierPath()
bezierPath.move(to: startP)
bezierPath.addLine(to: endP)
return hypotenuse(bp: bezierPath)
}
func shortenedEnd(startPoint: CGPoint, endPoint: CGPoint) -> CGPoint{
var endTemp = endPoint
//while hypotenuse(startP: startPoint, endP: endTemp) > 150{
endTemp = CGPoint(x: endTemp.x / 1.01, y: endTemp.y / 1.01)
//}
return endTemp
}
func addTestPoint(loc: CGPoint, color: UIColor){
var temp = SKShapeNode(circleOfRadius: 45)
temp.fillColor = color
temp.position = loc
self.addChild(temp)
}
}
I tried printing the frame size for the scene and it says 400 something x 700 something (I am testing on iPhone 6 Plus), and it says the UIScreen is same size so i don't know what issue is. Overall, I just need the aiming line to be on the center of the circle more than just the first time I try the maneuver. Thanks.
Like I mentioned in the comments, your problem was how you were laying out your paths. The code below makes the path relative to the ball instead of absolute to the scene. I also fixed the issue with creating new shapes every time.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var ball = SKShapeNode(circleOfRadius: 35)
var touchingBall = false
var aimLine = SKShapeNode()
var endAimPoint = CGPoint()
override func didMove(to view: SKView) {
ball.fillColor = SKColor.orange
ball.name = "ball"
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
self.physicsBody = borderBody
physicsWorld.gravity = CGVector(dx: 0.0, dy: 0.0)
ball.position = CGPoint(x: frame.midX, y: frame.midY)
let physicsBody = SKPhysicsBody(circleOfRadius: 35)
physicsBody.affectedByGravity = false
physicsBody.friction = 10.0
physicsBody.linearDamping = 0.94
ball.physicsBody = physicsBody
self.addChild(ball)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOUCHES BEGAN.")
for touch in touches {
print("TB: \(touchingBall)")
let location = touch.location(in: self)
let node : SKNode = self.atPoint(location)
if node.name == "ball" {
// touched inside node
if ball.physicsBody!.angularVelocity <= 0.0{
touchingBall = true
aimLine.path = nil
self.addChild(aimLine)
print(touchingBall)
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("TOCUHES MOVED.")
for touch in touches {
let location = touch.location(in: self)
if touchingBall{
endAimPoint = self.convert(location, to: ball)
assignAimLine(end: endAimPoint)
print("Moving touched ball")
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches ended. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x) * 3, dy: -(endAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touches cancelled. \(touchingBall)")
if touchingBall == true{
ball.physicsBody!.applyImpulse(CGVector(dx: -(endAimPoint.x) * 3, dy: -(endAimPoint.y) * 3))
}
touchingBall = false
aimLine.removeFromParent()
print(touchingBall)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
print(ball.physicsBody!.velocity)
//let dx2 = ball.physicsBody!.velocity.dx * damping
//let dy2 = ball.physicsBody!.velocity.dy * damping
//ball.physicsBody!.velocity = CGVector(dx: dx2, dy: dy2)
}
func assignAimLine(end: CGPoint){
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint.zero)
bezierPath.addLine(to: end)
let pattern : [CGFloat] = [10.0, 10.0]
aimLine.position = ball.position
aimLine.path = bezierPath.cgPath.copy(dashingWithPhase: 2, lengths: pattern)
aimLine.zPosition = 0
}
func addTestPoint(loc: CGPoint, color: UIColor){
var temp = SKShapeNode(circleOfRadius: 45)
temp.fillColor = color
temp.position = loc
self.addChild(temp)
}
}

My initial pipes are different from the following pipes

I'm making a game where the ball is suppose to go through some pipes, and when the player touches the pipes, the game stops. Kind of like flappy bird. The only problem I have is that the initial pipes blocks the entire screen, while the rest of the pipes are placed and randomized exactly as I want. How is this possible?
This is the ball class:
import SpriteKit
struct ColliderType {
static let Ball: UInt32 = 1
static let Pipes: UInt32 = 2
static let Score: UInt32 = 3
}
class Ball: SKSpriteNode {
func initialize() {
self.name = "Ball"
self.zPosition = 1
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.height /
2)
self.setScale(0.7)
self.physicsBody?.affectedByGravity = false
self.physicsBody?.categoryBitMask = ColliderType.Ball
self.physicsBody?.collisionBitMask = ColliderType.Pipes
self.physicsBody?.contactTestBitMask = ColliderType.Pipes |
ColliderType.Score
}
}
This is the Random Class:
import Foundation
import CoreGraphics
public extension CGFloat {
public static func randomBetweenNumbers(firstNum: CGFloat, secondNum:
CGFloat) -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum -
secondNum) + firstNum
}
}
This is the GameplayScene:
import SpriteKit
class GameplayScene: SKScene {
var ball = Ball()
var pipesHolder = SKNode()
var touched: Bool = false
var location = CGPoint.zero
override func didMove(to view: SKView) {
initialize()
}
override func update(_ currentTime: TimeInterval) {
moveBackgrounds()
if (touched) {
moveNodeToLocation()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event:
UIEvent?) {
touched = true
for touch in touches {
location = touch.location(in:self)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event:
UIEvent?) {
touched = false
}
override func touchesMoved(_ touches: Set<UITouch>, with event:
UIEvent?) {
for touch in touches {
location = touch.location(in: self)
}
}
func initialize() {
createBall()
createBackgrounds()
createPipes()
spawnObstacles()
}
func createBall() {
ball = Ball(imageNamed: "Ball")
ball.initialize()
ball.position = CGPoint(x: 0, y: 0)
self.addChild(ball)
}
func createBackgrounds() {
for i in 0...2 {
let bg = SKSpriteNode(imageNamed: "BG")
bg.anchorPoint = CGPoint(x: 0.5, y: 0.5)
bg.zPosition = 0
bg.name = "BG"
bg.position = CGPoint(x: 0, y: CGFloat(i) * bg.size.height)
self.addChild(bg)
}
}
func moveBackgrounds() {
enumerateChildNodes(withName: "BG", using: ({
(node, error) in
node.position.y -= 15
if node.position.y < -(self.frame.height) {
node.position.y += self.frame.height * 3
}
}))
}
func createPipes() {
pipesHolder = SKNode()
pipesHolder.name = "Holder"
let pipeLeft = SKSpriteNode(imageNamed: "Pipe")
let pipeRight = SKSpriteNode(imageNamed: "Pipe")
pipeLeft.name = "Pipe"
pipeLeft.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pipeLeft.position = CGPoint(x: 350, y: 0)
pipeLeft.xScale = 1.5
pipeLeft.physicsBody = SKPhysicsBody(rectangleOf: pipeLeft.size)
pipeLeft.physicsBody?.categoryBitMask = ColliderType.Pipes
pipeLeft.physicsBody?.affectedByGravity = false
pipeLeft.physicsBody?.isDynamic = false
pipeRight.name = "Pipe"
pipeRight.anchorPoint = CGPoint(x: 0.5, y: 0.5)
pipeRight.position = CGPoint(x: -350, y: 0)
pipeRight.xScale = 1.5
pipeRight.physicsBody = SKPhysicsBody(rectangleOf: pipeRight.size)
pipeRight.physicsBody?.categoryBitMask = ColliderType.Pipes
pipeRight.physicsBody?.affectedByGravity = false
pipeRight.physicsBody?.isDynamic = false
pipesHolder.zPosition = 5
pipesHolder.position.y = self.frame.height + 100
pipesHolder.position.x = CGFloat.randomBetweenNumbers(firstNum:
-250, secondNum: 250)
pipesHolder.addChild(pipeLeft)
pipesHolder.addChild(pipeRight)
self.addChild(pipesHolder)
let destination = self.frame.height * 3
let move = SKAction.moveTo(y: -destination, duration:
TimeInterval(10))
let remove = SKAction.removeFromParent()
pipesHolder.run(SKAction.sequence([move, remove]), withKey: "Move")
}
func spawnObstacles() {
let spawn = SKAction.run({ () -> Void in
self.createPipes()
})
let delay = SKAction.wait(forDuration: TimeInterval(1.5))
let sequence = SKAction.sequence([spawn, delay])
self.run(SKAction.repeatForever(sequence), withKey: "Spawn")
}
func moveNodeToLocation() {
// Compute vector components in direction of the touch
var dx = location.x - ball.position.x
// How fast to move the node. Adjust this as needed
let speed:CGFloat = 0.1
// Scale vector
dx = dx * speed
ball.position = CGPoint(x:ball.position.x+dx, y: 0)
}
}

How do I set it up so that only my player and an object have a physics body?

Creating a game like Doodle Jump, except for bouncing off platforms, my hero bounces off objects to collect points and progress in the game. As my hero "bounces off" the object, I set it to removeFromParent and it is working like it should. However, if my hero misses the object and accidentally hits the wall or ground, the object disappears as well which is odd because my ground doesn't have a physics body.
Here is my code for my GameScene:
import SpriteKit
var player: Player!
var ground: Ground!
var snowflake: Snowflake!
var isFingerOnPlayer = false
var gameOver = false
var playerTouched = false
var touching: Bool = false
var lastTouch: CGPoint? = nil
let xPlayerForce: CGFloat = 40
let yPlayerForce: CGFloat = 50
var touchPoint: CGPoint = CGPoint()
struct Collision {
static let player: UInt32 = 1
static let snowflake: UInt32 = 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMoveToView(view: SKView) {
player = Player()
player.position = CGPointMake(20, 150)
addChild(player)
ground = Ground()
ground.position = CGPointMake(10, -37)
addChild(ground)
player.physicsBody?.linearDamping = 0.0
player.physicsBody?.angularDamping = 500.0
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size)
player.physicsBody?.allowsRotation = false
player.physicsBody?.mass = 0
player.physicsBody?.friction = 1
player.physicsBody?.dynamic = true
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0.0, dy: -3.0)
self.backgroundColor = SKColor(red: 0.15, green: 0.15, blue: 0.3, alpha: 1.0)
snowflake = Snowflake()
addChild(snowflake)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let touchLocation = touch.locationInNode(self)
lastTouch = touchLocation
player.physicsBody?.categoryBitMask = snowCategory
player.physicsBody?.contactTestBitMask = playerCategory
player.physicsBody?.affectedByGravity = true
player.physicsBody?.collisionBitMask = playerCategory
player.physicsBody?.usesPreciseCollisionDetection = true
}
}
func touchedPlayer() {
playerTouched = true
snowflake.removeFromParent()
player.bounceOff()
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
lastTouch = nil
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
lastTouch = nil
}
override func update(currentTime: NSTimeInterval) {
if let touch = lastTouch {
var xForce: CGFloat = 0.0
var yForce: CGFloat = 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
} else if xTouchOffset > 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)
}
}
func didBeginContact(contact: SKPhysicsContact) {
touchedPlayer()
}
}
In spite of the fact that it's not clear what is snow flake entity.
There is a code:
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
You have an edge around your frame.
From the description:
Creates an edge loop from a CGRect. Edges have no volume and are intended to be used to create static environments. Edges can collide with body's of volume, but not with each other.
In the method
func didBeginContact(contact: SKPhysicsContact)
use contact.contactBodyA.categoryBitMask to ignore this contact.(Should be zero).