I was trying to move the player in x-axis using the built-in accelerometer but something is wrong. When I simulate the app on my iPhone the node keeps moving in all directions and only after 2-3 times when I close the app and launch it again the node moves from left to right which is what I wanted. So it works but only after closing and opening the app again.
Part of the code:
import Foundation
import SpriteKit
import CoreMotion
class Gameplay : SKScene{
private var player : Player?
var motionManager = CMMotionManager()
override func update(_ currentTime: TimeInterval) {
self.player?.move()
}
func initializeGame(){
player = childNode(withName: "player") as? Player!
player?.initPlayer()
}
override func didMove(to view: SKView) {
initializeGame()
motionManager.startAccelerometerUpdates()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { (data , _) in
if let accelerometerData = data{
print("x: \(accelerometerData.acceleration.x)")
print("y: \(accelerometerData.acceleration.y)")
self.physicsWorld.gravity = CGVector(dx: accelerometerData.acceleration.x*10, dy: 0) //only x axis?
} else {
print("NOPE")
}
}
}
Player.swift:
import Foundation
import SpriteKit
class Player : SKSpriteNode{
func initPlayer(){
name = "Player"
physicsBody?.affectedByGravity = true
physicsBody?.isDynamic = true
}
func move(){ //boundaries
if position.x >= 315{
position.x = 315
} else if position.x <= -315{
position.x = -315
}
}
func shoot(){
//...
}
}
Instead of changing the gravity of your Scene try to move the player directly. If you adjust the gravity it will mess up all your other physics bodies as well. So either move the player with a SKAction or better by applying a Force to it.
player?.physicsBody?.applyForce(CGVector(dx: 30 * CGFloat(accelerometerData.acceleration.x), dy: 0))
I would also recommend that you do not use the handler for the motion updates, I never got accurate results using it.
So remove this code block
motionManager.startAccelerometerUpdates(to: OperationQueue.current!) { (data , _) in
if let accelerometerData = data{
print("x: \(accelerometerData.acceleration.x)")
print("y: \(accelerometerData.acceleration.y)")
self.physicsWorld.gravity = CGVector(dx: accelerometerData.acceleration.x*10, dy: 0) //only x axis?
} else {
print("NOPE")
}
}
and instead go to the update method of your scene and fetch the motion data from there.
override func update(_ currentTime: TimeInterval) {
if let accelerometerData = motionManager.accelerometerData {
player?.physicsBody?.applyForce(CGVector(dx: 30 * CGFloat(accelerometerData.acceleration.x), dy: 0))
}
....
}
This will make everything much smoother and responsive. Adjust the 30 to get the desired result.
Hope this helps
Related
I'm trying to get a GKActor to position SKNodes in a scene. I thought I had this working. However, when I add a GKBehavior to the actor, I get very erratic position changes flicking all over the place and the actors behaviour isn't working.
I'm using an entity-component architecture in my project, which is a little too complicated to show an example here, so I've created a dramatically simplified playground to illustrate the issue I'm seeing.
//: A SpriteKit based Playground
import PlaygroundSupport
import SpriteKit
import GameplayKit
extension CGPoint {
init(point: vector_float2) {
self.init(
x: CGFloat(point.x),
y: CGFloat(point.y)
)
}
}
class GameScene: SKScene, GKAgentDelegate {
private let playerAgent = GKAgent2D()
private var player : SKShapeNode!
private let enemyAgent = GKAgent2D()
private var enemy : SKShapeNode!
override func didMove(to view: SKView) {
player = SKShapeNode(circleOfRadius: 40)
player.fillColor = .green
addChild(player)
playerAgent.position.x = .random(in: -(640/2)...640/2)
playerAgent.position.y = .random(in: -(480/2)...480/2)
playerAgent.delegate = self
enemy = SKShapeNode(circleOfRadius: 10)
enemy.fillColor = .red
addChild(enemy)
enemyAgent.position.x = .random(in: -(640/2)...640/2)
enemyAgent.position.y = .random(in: -(480/2)...480/2)
enemyAgent.delegate = self
enemyAgent.behavior = GKBehavior(
goals: [
//GKGoal(toSeekAgent: playerAgent)
]
)
}
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
playerAgent.update(deltaTime: currentTime)
enemyAgent.update(deltaTime: currentTime)
}
func agentDidUpdate(_ agent: GKAgent) {
if agent == enemyAgent {
enemy.position = CGPoint(point: enemyAgent.position)
print(enemy.position)
}
if agent == playerAgent {
player.position = CGPoint(point: playerAgent.position)
}
}
#objc static override var supportsSecureCoding: Bool {
get {
return true
}
}
}
let sceneView = SKView(frame: CGRect(x:0 , y:0, width: 640, height: 480))
if let scene = GameScene(fileNamed: "GameScene") {
scene.scaleMode = .aspectFill
sceneView.presentScene(scene)
}
PlaygroundSupport.PlaygroundPage.current.liveView = sceneView
This Playground shows the two nodes placed where their agents are positioned. It's working how I expet. However, once the GKGoal is uncommented, the position of the enemyAgent that is printed out wildly fluxuates and doesn't move toward the playerAgent. This is not what I expeted to happen.
Clearly I'm doing somthing wrong but I can't see where i've made a mistake. I'd love some help from someone with more experince working with GameplayKit. Thank You.
I figured out the issue. The GKAgents update function takes a delta time, and the SKScenes update function provides a current time. I didn't spot this mismatch as they are both TimeIntervals and I was passing it directly with out converting it.
For anyone stumbling across this the way I converted the currentTime to a deltaTime was storing a lastUpdate like this;
var lastUpdate: TimeInterval = 0
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
defer { lastUpdate = currentTime }
guard lastUpdate != 0 else {
return
}
let deltaTime = currentTime - lastUpdate
playerAgent.update(deltaTime: deltaTime)
enemyAgent.update(deltaTime: deltaTime)
}
I hope this helps someone, as it took me far too long to spot this. I think Apple could make this easy mistake more noticeable by printing an error if the delta time is too big or something.
I'm trying to learn ARKIT and make a small demo app to draw in 3D.
The following is the code I wrote and so far there are no problems:
import UIKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet weak var sceneView: ARSCNView!
#IBOutlet weak var DRAW: UIButton!
#IBOutlet weak var DEL: UIButton!
let config = ARWorldTrackingConfiguration()
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.session.run(config)
self.sceneView.delegate = self
}
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else {return}
let transform = pointOfView.transform
let cameraOrientation = SCNVector3(-transform.m31,-transform.m32,-transform.m33)
let cameraLocation = SCNVector3(transform.m41,transform.m42,transform.m43)
let cameraCurrentPosition = cameraOrientation + cameraLocation
DispatchQueue.main.async {
if (self.DRAW.isTouchInside){
let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.02))
sphereNode.position = cameraCurrentPosition
self.sceneView.scene.rootNode.addChildNode(sphereNode)
sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
print("RED Button is Pressed")
}else if (self.DEL.isTouchInside){
self.sceneView.scene.rootNode.enumerateChildNodes{
(node, stop) in
node.removeFromParentNode()
}
}else{
let pointer = SCNNode(geometry: SCNSphere(radius: 0.01))
pointer.name = "pointer"
pointer.position = cameraCurrentPosition
self.sceneView.scene.rootNode.enumerateChildNodes({(node,_) in
if node.name == "pointer"{
node.removeFromParentNode()
}
})
self.sceneView.scene.rootNode.addChildNode(pointer)
pointer.geometry?.firstMaterial?.diffuse.contents = UIColor.purple
}
}
}
}
func +(left:SCNVector3,right:SCNVector3) -> SCNVector3 {
return SCNVector3Make(left.x + right.x, left.y + right.y, left.z + right.z)
}
As you can see, I set the scene and configure it,
I create a button to draw when pressed, a pointer (or viewfinder) that takes the center of the scene and a button to delete the nodes inserted.
Now I would like to be able to move the cameraCurrentPosition to a different point from the center: I would like to move it if possible with a touch on the screen taking the position of the finger.
If possible, could someone help me with the code?
Generally speaking, you can't programmatically move the Camera within an ARSCN, the camera transform is the physical position of the device relative to the virtual scene.
With that being said, one way you could draw the user touches to the screen is using the touchesMoved method within your View Controller.
var touchRoots: [SCNNode] = [] // list of root nodes for each set of touches drawn
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// get the initial touch event
if let touch = touches.first {
guard let pointOfView = self.sceneView.pointOfView else { return }
let transform = pointOfView.transform // transformation matrix
let orientation = SCNVector3(-transform.m31, -transform.m32, -transform.m33) // camera rotation
let location = SCNVector3(transform.m41, transform.m42, transform.m43) // location of camera frustum
let currentPostionOfCamera = orientation + location // center of frustum in world space
DispatchQueue.main.async {
let touchRootNode : SCNNode = SCNNode() // create an empty node to serve as our root for the incoming points
touchRootNode.position = currentPostionOfCamera // place the root node ad the center of the camera's frustum
touchRootNode.scale = SCNVector3(1.25, 1.25, 1.25)// touches projected in Z will appear smaller than expected - increase scale of root node to compensate
guard let sceneView = self.sceneView else { return }
sceneView.scene.rootNode.addChildNode(touchRootNode) // add the root node to the scene
let constraint = SCNLookAtConstraint(target: self.sceneView.pointOfView) // force root node to always face the camera
constraint.isGimbalLockEnabled = true // enable gimbal locking to avoid issues with rotations from LookAtConstraint
touchRootNode.constraints = [constraint] // apply LookAtConstraint
self.touchRoots.append(touchRootNode)
}
}
}
override func func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let translation = touch.location(in: self.view)
let translationFromCenter = CGPoint(x: translation.x - (0.5 * self.view.frame.width), y: translation.y - (0.5 * self.view.frame.height))
// add nodes using the main thread
DispatchQueue.main.async {
guard let touchRootNode = self.touchRoots.last else { return }
let sphereNode : SCNNode = SCNNode(geometry: SCNSphere(radius: 0.015))
sphereNode.position = SCNVector3(-1*Float(translationFromCenter.x/1000), -1*Float(translationFromCenter.y/1000), 0)
sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.white
touchRootNode.addChildNode(sphereNode) // add point to the active root
}
}
}
Note: solution only handles a single touch, but it is simple enough to extend the example to add multi-touch support.
I would like to know if anyone has a way to move a SKSpriteNode in SpriteKit Watch Game Using WKCrownDelegate. either in Y direction or X direction
Hope this Helps Others that are Starting with WatchKit.
This is my GameElement.swift:
extension GameScene {
func addPlayer() {
player = SKSpriteNode(imageNamed: "Spaceship")
player.setScale(0.15)
player.position = CGPoint(x: 5, y: -60)
player.name = “ONE”
player.physicsBody?.isDynamic = false
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player2 = SKSpriteNode(imageNamed: "Spaceship")
player2.setScale(0.15)
player2.position = CGPoint(x: 5, y: -60)
player2.name = “ONE”
player2.physicsBody?.isDynamic = false
player2.physicsBody = SKPhysicsBody(rectangleOf: player2.size)
addChild(player)
addChild(player2)
playerPosition = player.position
}
}
This is my GameScene.swift:
class GameScene: SKScene, SKPhysicsContactDelegate, WKCrownDelegate {
var watchParticles:SKEmitterNode!
var player:SKSpriteNode!
var player2:SKSpriteNode!
var playerPosition:CGPoint!
override func sceneDidLoad() {
self.scaleMode = SKSceneScaleMode.aspectFill
watchParticles = SKEmitterNode(fileNamed: "watchParticles")
addChild(watchParticles)
self.physicsWorld.gravity = CGVector(dx: 0 , dy: 0)
physicsWorld.contactDelegate = self
addPlayer()
}
func moveSprite(player : SKSpriteNode,moveDirection: String){
switch moveDirection {
case "UP":
print("UP")
player.childNode(withName: "ONE")?.physicsBody?.applyImpulse(CGVector(dx: 60, dy: 0))
case "DOWN":
print("DOWN")
player.childNode(withName: "ONE")?.physicsBody?.applyImpulse(CGVector(dx: -60, dy: 0))
case "STOP":
print("STOPPED")
player.childNode(withName: "ONE")?.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
default:
break
}
}
}
This is My InterfaceController.swift:
class InterfaceController: WKInterfaceController, WKCrownDelegate {
#IBOutlet var skInterface: WKInterfaceSKScene!
private var moveDirection = ""
private var game = GameScene()
private var player = GameScene()
override func awake(withContext context: Any?) {
super.awake(withContext: context)
crownSequencer.delegate = self
crownSequencer.focus()
// Configure interface objects here.
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
self.skInterface.presentScene(scene)
crownSequencer.delegate = self
crownSequencer.focus()
// Use a value that will maintain a consistent frame rate
self.skInterface.preferredFramesPerSecond = 30
}
}
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
if rotationalDelta > 0{
moveDirection = "UP"
game.moveSprite(player: player.player, moveDirection: moveDirection)
}else if rotationalDelta < 0{
moveDirection = "DOWN"
game.moveSprite(player: player.player, moveDirection: moveDirection)
}
}
func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
moveDirection = "STOP"
game.moveSprite(player: player.player, moveDirection: moveDirection)
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
Welcome to SO!
Ok, there is a lot to unpack so get some popcorn... I think that you are on the right track here, mostly you need to check for nil when you get errors.
First, this is wrong in your interface controller, and the part that concerned me. Here, you are just instantiating new GameScene instances, which are completely separate from the gameScene instance created by your interface controller a few lines down. Then, you were sending the crown delegate functions to these totally empty gameScenes.:
private var game = GameScene() // You are referencing nothing here, just creating a new gamescene.
private var player = GameScene() // I don't think that player is supposed to be a gamescene!
I fixed it by doing assigning the actual gameScene you want to use to the properties (so they can be used by the crown delegate).
private var game: GameScene!
lazy private var player: SKSpriteNode = self.game.player
override func awake(withContext context: Any?) {
// ... Stuff...
if let scene = GameScene(fileNamed: "GameScene") {
game = scene
This was also changed to represent the new code in your crown delegates:
game.moveSprite(player: player, moveDirection: moveDirection)
In addPlayer you were doing this:
player.physicsBody?.isDynamic = true // This needs to go AFTER you init your pb.
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
...and I fixed it by swapping the lines.
Personally I like to do the following, to help ensure no small mistakes are made:
let pb = SKPhysicsBody(...)
pb.isDynamic = true
player.physicsBody = pb
moveSprite had a bunch of issues with it, so I'm not going to enumerate them as I did above. Check out what I did then ask me if you have any questions. Basically, you were doomed with this func from the start, because you were calling this method from the interface controller with the whacked out player values that were all nil.
Also, the .applyImpulse was giving me pretty bad controls, so I changed it to a plain adjustment of .position. There is still a small issue with coasting before the player stops, but that can be handled in another question :) (note, I only tested this on simulator.. may not be an issue on-device).
Also also, I hate errors caused by spelling mistakes in strings, so I converted this to an enum for you.
func moveSprite(player : SKSpriteNode, moveDirection: Direction) {
// This will give us an equal amount of pixels to move across the watch devices:
// Adjust this number for shorter / longer movements:
let percentageOfScreenToMovePerRotation = CGFloat(1) // One percent
let modifier = percentageOfScreenToMovePerRotation / 100
let amountToMove = self.frame.maxX * modifier
switch moveDirection {
case .UP:
player.position.x += amountToMove
case .DOWN:
player.position.x -= amountToMove
case .STOP:
break
}
}
The real moral of the story here is to check for nil. If you just use someOptional?.someMethod() all the time, then you likely will not be able to easily determine whether or not someMethod() is actually being called or not.. thus, you don't know if the problem is with the calling logic, the method, or with the object not existing, and etc.
Force unwrapping is frowned upon in production code, but IMO it is extremely valuable when first starting out--because it helps you to quickly identify errors.
Later on, you can start using things like if let and guard to help check for nil without crashing your programs, but that adds more clutter and complexity to your code when you are trying to just learn the basics of a new API and language.
And as a final tip, try to not use hard-coded strings whenever possible: put them into an enum or a constant as I have in your code:
// Because I hate string spelling erros, and you probably do too!
enum Direction {
case UP, DOWN, STOP
}
// Because I hate errors related to spelling in strings:
let names = (ONE: "ONE", TWO: "TWO")
Here are the two files in their entirety.. note, I had to comment out a few things to get it to work in my project:
GameScene:
// Because I hate string spelling erros, and you probably do too!
enum Direction {
case UP, DOWN, STOP
}
class GameScene: SKScene, SKPhysicsContactDelegate, WKCrownDelegate {
var watchParticles:SKEmitterNode!
var player: SKSpriteNode!
var player2: SKSpriteNode!
var playerPosition:CGPoint!
// Because I hate errors related to spelling in strings:
let names = (ONE: "ONE", TWO: "TWO")
func addPlayer() {
player = SKSpriteNode(color: .blue, size: CGSize(width: 50, height: 50))
// player = SKSpriteNode(imageNamed: "Spaceship")
// player.setScale(0.15)
player.position = CGPoint(x: 5, y: -60)
player.name = names.ONE
player.physicsBody = SKPhysicsBody(rectangleOf: player.size)
player.physicsBody!.isDynamic = true // This was placed *before* pb initialzier (thus never got called)
player2 = SKSpriteNode(color: .yellow, size: CGSize(width: 50, height: 50))
// player2 = SKSpriteNode(imageNamed: "Spaceship")
// player2.setScale(0.15)
player2.position = CGPoint(x: 5, y: -60)
player2.name = names.TWO
player2.physicsBody = SKPhysicsBody(rectangleOf: player2.size)
player2.physicsBody!.isDynamic = false // This was placed *before* pb initialzier (thus never got called)
addChild(player)
addChild(player2)
playerPosition = player.position
}
override func sceneDidLoad() {
self.scaleMode = SKSceneScaleMode.aspectFill
//watchParticles = SKEmitterNode(fileNamed: "watchParticles")
//addChild(watchParticles)
self.physicsWorld.gravity = CGVector.zero
physicsWorld.contactDelegate = self
addPlayer()
}
func moveSprite(player : SKSpriteNode, moveDirection: Direction) {
// This will give us an equal amount of pixels to move across the watch devices:
// Adjust this number for shorter / longer movements:
let percentageOfScreenToMovePerRotation = CGFloat(1) // One percent
let modifier = percentageOfScreenToMovePerRotation / 100
let amountToMove = self.frame.maxX * modifier
switch moveDirection {
case .UP:
player.position.x += amountToMove
case .DOWN:
player.position.x -= amountToMove
case .STOP:
break
}
}
}
InterfaceController:
class InterfaceController: WKInterfaceController, WKCrownDelegate {
#IBOutlet var skInterface: WKInterfaceSKScene!
private var moveDirection = Direction.STOP
private var game: GameScene!
lazy private var player: SKSpriteNode = self.game.player
override func awake(withContext context: Any?) {
super.awake(withContext: context)
crownSequencer.delegate = self
crownSequencer.focus()
if let scene = GameScene(fileNamed: "GameScene") {
game = scene // VERY IMPORTANT!
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
self.skInterface.presentScene(scene)
crownSequencer.delegate = self
crownSequencer.focus()
// Use a value that will maintain a consistent frame rate
self.skInterface.preferredFramesPerSecond = 30
}
else {
fatalError("scene not found")
}
}
func crownDidRotate(_ crownSequencer: WKCrownSequencer?, rotationalDelta: Double) {
if rotationalDelta > 0{
moveDirection = .UP
game.moveSprite(player: player, moveDirection: moveDirection)
} else if rotationalDelta < 0{
moveDirection = .DOWN
game.moveSprite(player: player, moveDirection: moveDirection)
}
}
func crownDidBecomeIdle(_ crownSequencer: WKCrownSequencer?) {
moveDirection = .STOP
game.moveSprite(player: player, moveDirection: moveDirection)
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
I have 2 different textures for my character that overlap/are displayed too fast
while moving the character. How can I set a duration for the animation, so the textures always switch at the same speed while moving the character?
This is my code:
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let animatePlayerStart = SKAction.setTexture(SKTexture(imageNamed: "Player\(i).png"))
// Determine speed for character movement
var minDuration:CGFloat = 0.7;
var maxDuration:CGFloat = 1.8;
var rangeDuration:CGFloat = maxDuration - minDuration;
var actualDuration:NSTimeInterval = NSTimeInterval((CGFloat(arc4random())%rangeDuration) + minDuration)
let move = SKAction.moveTo(location, duration:actualDuration)
player.runAction(SKAction.sequence([animatePlayerStart, move]))
// i determines which texture is going to be displayed
if(self.i == 2) {
self.i = 1
}
else{
self.i++
}
}
}
You are changing texture in touchesMoved which is called fast, thus the effect you are currently getting. To change textures after pre-defined period of time you can use this method:
+ animateWithTextures:timePerFrame:
import SpriteKit
class GameScene: SKScene {
let hero = SKSpriteNode(imageNamed: "heroState_A")
let textureA = SKTexture(imageNamed: "heroState_A")
let textureB = SKTexture(imageNamed: "heroState_B")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
//Because hero is already initialized with textureA, start from textureB
let animation = SKAction.animateWithTextures([textureB,textureA], timePerFrame:0.5)
hero.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
addChild(hero)
//Start animation
hero.runAction(SKAction.repeatActionForever(animation),withKey:"heroAnimation")
//Removing heroAnimation
//You can stop this animation by hero.removeAllActions, but if you run animation with key, you can remove just that particular action, which gives you more control
let stop = SKAction.runBlock({
if(self.hero.actionForKey("heroAnimation") != nil){
self.hero.removeActionForKey("heroAnimation")
}
})
//Just an example, in real app you will do this at certain events (eg. when player stops his movement)
runAction(SKAction.sequence([SKAction.waitForDuration(5),stop]))
}
}
I am trying to detect when the player object collides with the other objects in my game. This is my current code:
import SpriteKit
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: “Box”)
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
player.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(player)
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addObject),
SKAction.waitForDuration(1)
])
))
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addSecondObject),
SKAction.waitForDuration(1)
])
))
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
player.position = location
}
}
func EndGame() {
println("GAME OVER")
}
func Collision() {
if (CGRectIntersectsRect(player.frame, object.frame )) {
[EndGame];
}
if (CGRectIntersectsRect(player.frame, object1.frame)) {
[EndGame];
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func addObject() {
let object = SKSpriteNode(imageNamed: "object1”)
object.name = "object1”
object.position = CGPoint(x: size.width/4, y: size.height/4)
self.addChild(object)
}
func addSecondObject() {
let object = SKSpriteNode(imageNamed: "object2”)
object.name = "object2”
object.position = CGPoint(x: size.width/2, y: size.height/2)
self.addChild(object)
}
}
So you can see my collision code is this:
func Collision() {
if (CGRectIntersectsRect(player.frame, object.frame )) {
[EndGame];
}
if (CGRectIntersectsRect(player.frame, object1.frame)) {
[EndGame];
}
}
The problem is that because object and object 1 variables are private to func (addObject) and func (addSecondObject), I can't call them in the above code. When they collide, currently I just want EndGame() to run which prints "Game Over" in the console.
I don't know if the method I have taken for collision detection is correct, but any help would be great! Thanks :)
For very basic sprites, yes, its right. You can imagine a diagonal line image "/" would trigger a collision if the top left corner of it overlapped with something, even though the actual object doesn't overlap.
Apple has a nice page discussing collisions here : https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/CodeExplainedAdventure/HandlingCollisions/HandlingCollisions.html
Also, numerous youtube videos talk about how to get this sort of thing working. eg : https://www.youtube.com/watch?v=dX0KvKtc3_w
You need to make it so the collision detection code runs(possible on a timer or something) and then, perhaps, pass in the objects you want to check or make those objects members of the class (IBOutlet) or similar strategy to gain access to them.
You have to use SKPhysicsbody:
struct PhysicsCategory {
static let None : UInt32 = 0
static let Player : UInt32 = 0b1
static let Object : UInt32 = 0b10
}
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size) // 1
player.physicsBody?.dynamic = true // 2
player.physicsBody?.categoryBitMask = PhysicsCategory.Player// 3
player.physicsBody?.contactTestBitMask = PhysicsCategory.Object//4
player.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
You set the SKPhysicsBody and make it the size of your player-object
Then you make it dynamic. That the physicsbody is dynamic.
That is the categoryBitMask. It's like the identifier of your SKNode. So that other SKNodes can register on you and say: "Hey I want to know if I touch that guy".
That's the contactTestbitMask which handles which SKNodes should interact with you. So you say "If that node touches me, say something".
What should happen, if this node collides.
For more informations and a nice starter tutorial, use raywenderlichs tutorial.