Change swift SKSprite node animation based on velocity - ios

Value of type 'SKSpriteNode' has no member 'velocity'
I want to change the animation based on the velocity of my SKSprite node, but I'm getting the error shown above and I'm not sure why
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let location = touches.first?.location(in: self) {
let horizontalAction = SKAction.move(to: location, duration: 1.0)
horizontalAction.timingMode = SKActionTimingMode.easeOut
player?.run(horizontalAction)
let playerAnimatedAtlas = SKTextureAtlas(named: "animation")
var walkFrames: [SKTexture] = []
var lwalkFrames: [SKTexture] = []
let numImages = playerAnimatedAtlas.textureNames.count
for i in 1...numImages {
let playerTextureName = "player\(i)"
let playerLeftTextureName = "lplayer\(i)"
walkFrames.append(playerAnimatedAtlas.textureNamed(playerTextureName))
lwalkFrames.append(playerAnimatedAtlas.textureNamed(playerLeftTextureName))
}
walkingPlayer = walkFrames
lwalkingPlayer = lwalkFrames
let leftFrameTexture = lwalkingPlayer[0]
let firstFrameTexture = walkingPlayer[0]
player!.physicsBody?.isDynamic = true
player!.physicsBody = SKPhysicsBody(texture: firstFrameTexture,
size: player!.texture!.size())
if player!.velocity.dx < 0 {
//ERROR: Value of type 'SKSpriteNode' has no member 'velocity' ****
player! = SKSpriteNode(texture: leftFrameTexture)
}
else if player!.velocity.dx > 0 {
//ERROR: Value of type 'SKSpriteNode' has no member 'velocity' ****
player! = SKSpriteNode(texture: firstFrameTexture)
}
}
}

Try player!.physicsBody.velocity
(I know this isn't your question, but eventually try to not need the ! everywhere -- use if let and guard let)

Related

My animated SKSprite nodes atlas won't change during touchBegan

I created a sprite with an animated texture atlas. I then want that animation to change based on the direction the sprite is heading. I try to do so with "self.player!.texture = firstFrametexture(ofatlas)"
Here is where I build the player (put inside didMove but the animation starts without having to move?)
func buildPlayer() {
let playerAnimatedAtlas = SKTextureAtlas(named: "animation")
var walkFrames: [SKTexture] = []
let numImages = playerAnimatedAtlas.textureNames.count
for i in 1...numImages {
let playerTextureName = "player\(i)"
walkFrames.append(playerAnimatedAtlas.textureNamed(playerTextureName))
}
walkingPlayer = walkFrames
let firstFrameTexture = walkingPlayer[0]
player! = SKSpriteNode(texture: firstFrameTexture)
player!.position = CGPoint(x: frame.midX, y: frame.midY)
addChild(player!)
}
Here is where I animate the player
func animatePlayer() {
player!.run(SKAction.repeatForever(
SKAction.animate(with: walkingPlayer,
timePerFrame: 0.5,
resize: false,
restore: true)),
withKey:"walkingInPlacePlayer")
}
Here is where I try to change the animation. Everything works, the if statement are correctly executed. Just nothing changes
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let location = touches.first?.location(in: self) {
let horizontalAction = SKAction.move(to: location, duration: 1.0)
horizontalAction.timingMode = SKActionTimingMode.easeOut
player?.run(horizontalAction)
let playerAnimatedAtlas = SKTextureAtlas(named: "animation")
let lplayerAnimatedAtlas = SKTextureAtlas(named: "animationleft")
var walkFrames: [SKTexture] = []
var lwalkFrames: [SKTexture] = []
let numImages = playerAnimatedAtlas.textureNames.count
for i in 1...numImages {
let playerTextureName = "player\(i)"
let playerLeftTextureName = "lplayer\(i)"
walkFrames.append(playerAnimatedAtlas.textureNamed(playerTextureName))
lwalkFrames.append(lplayerAnimatedAtlas.textureNamed(playerLeftTextureName))
}
walkingPlayer = walkFrames
lwalkingPlayer = lwalkFrames
let firstFrameTexture = walkingPlayer[0]
let leftFrameTexture = lwalkingPlayer[0]
if location.x > player!.position.x {
self.player!.texture = firstFrameTexture
print("rightsuccess")
} else if location.x < player!.position.x {
self.player!.texture = leftFrameTexture
print("leftsuccess")
}
}
}

How can I create an onscreen controller that works in multiple scenes in SpriteKit?

Working on a game in SpriteKit to learn. Its a platformer with an onscreen controller. I have this all working using touchesBegan and touchesEnded to know when the player is pushing the buttons or not. This works fine, however when i want to load the next scene for 'level 2' i need to implement the controller all over again. I could do a lot of copy and pasting but I feel this will lead to a lot of duplication of code. Every tutorial I've ever read said to try to adhere to the DRY principle.
Im sorry if this is simple, but I have <6 months programming experience and am trying to learn and improve. Im assuming I would need to create a separate class for the onscreen controller so it can be reused, but Im a little lost on where to start.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = (touch.location(in: playerCamera))
print("LocationX: \(location.x), LocationY: \(location.y)")
let objects = nodes(at: location)
print("\(objects)")
if rightButton.frame.contains(location) {
rightButtonPressed = true
playerFacingRight = true
playerFacingLeft = false
thePlayer.xScale = 1
let animation = SKAction(named: "running")!
let loopingAnimation = SKAction.repeatForever(animation)
thePlayer.run(loopingAnimation, withKey: "moveRight")
moveRight()
} else if leftButton.frame.contains(location) {
leftButtonPressed = true
playerFacingLeft = true
playerFacingRight = false
thePlayer.xScale = -1
let leftAnimation = SKAction(named: "running")!
let leftLoopingAnimation = SKAction.repeatForever(leftAnimation)
thePlayer.run(leftLoopingAnimation, withKey: "moveLeft")
moveLeft()
} else if upButton.frame.contains(location) {
upButtonPressed = true
print("upButton is pressed")
if playerAndButtonContact == true {
print("contact - player + button + upButtonPressed=true")
print("\(movingPlatform.position)")
button.texture = SKTexture(imageNamed: "switchGreen")
let moveRight = SKAction.moveTo(x: -150, duration: 3)
if movingPlatform.position == CGPoint(x: -355, y: movingPlatform.position.y) {
movingPlatform.run(moveRight)
thePlayer.run(moveRight)
button.run(moveRight)
}
}
if playerAndDoorSwitchContact == true {
let switchPressed = SKAction.run{
self.switchPressedSound()
self.doorSwitch.texture = SKTexture(imageNamed: "switchGreen")
self.door.texture = SKTexture(imageNamed: "DoorUnlocked")
}
let wait = SKAction.wait(forDuration: 2)
let doorOpen = SKAction.run {
let doorOpen = SKSpriteNode(imageNamed: "DoorOpen")
doorOpen.alpha = 0
doorOpen.position = self.door.position
doorOpen.size = self.door.size
doorOpen.size = self.door.size
self.door.zPosition = -2
doorOpen.zPosition = -1
let fadeIn = SKAction.fadeIn(withDuration: 0.5)
let start = SKAction.run {
self.addChild(doorOpen)
doorOpen.run(fadeIn)
}
let sound = SKAction.run {
self.doorOpeningSound()
}
let opening = SKAction.group([sound, start])
self.door.run(opening)
}
let sequence = SKAction.sequence([switchPressed, wait, doorOpen])
self.doorSwitch.run(sequence)
}
if playerAndDoorContact == true {
self.view?.presentScene(level1, transition: transition)
}
} else if downButton.frame.contains(location) {
}
else if shoot.frame.contains(location) {
shoot()
} else if jumpButton.frame.contains(location) {
self.pressed = true
let timerAction = SKAction.wait(forDuration: 0.05)
let update = SKAction.run {
if(self.force < Constants.maximumJumpForce) {
self.force += 2.0
} else {
self.jump(force: Constants.maximumJumpForce)
self.force = Constants.maximumJumpForce
}
}
let sequence = SKAction.sequence([timerAction, update])
let repeat_seq = SKAction.repeatForever(sequence)
self.run(repeat_seq, withKey: "repeatAction")
}
}
}

How to place a custom object (.scn) in ARKit on tap?

I'm trying to place a custom object in ARKit when the user taps the screen. I figured out how to place an object by creating it in code but would like to place an object that has been imported into project (ex. ship.scn). Here is my code to place object in code and its working:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let results = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitFeature = results.last else { return}
let hitTransform = SCNMatrix4.init(hitFeature.worldTransform)
let hitPosition = SCNVector3Make(hitTransform.m41, hitTransform.m42, hitTransform.m43)
createBall(hitPosition: hitPosition)
}
func createBall(hitPosition: SCNVector3) {
let newBall = SCNSphere(radius: 0.05)
let newBallNode = SCNNode(geometry: newBall)
newBallNode.position = hitPosition
self.sceneView.scene.rootNode.addChildNode(newBallNode)
}
I tried creating this function to place an imported .scn file but when I tap the screen nothing shows up:
func createCustomScene(objectScene: SCNScene?, hitPosition: SCNVector3) {
guard let scene = objectScene else {
print("Could not load scene")
return
}
let childNodes = scene.rootNode.childNodes
for childNode in childNodes {
sceneNode.addChildNode(childNode)
}
}
called like this: createCustomScene(objectScene: SCNScene(named: "art.scnassets/ship.scn"), hitPosition: hitPosition)
Does anyone have any clue why the .scn isn't showing up when the screen is tapped?
You should consider adding your childNode to the sceneView instead of adding it to your sceneNode.
for childNode in childNodes {
childNode.position = hitPosition
self.sceneView.scene.rootNode.addChildNode(childNode)
}

Developing an ARKit app that leaves text for others to view

I am creating an iOS AR app that sets text in a specific location and leaves it there for others to view. Is there a better way to implement it than what I am doing?
Currently, I have it set so that the text is saved to Firebase and loads it by setting the nodes relative to the camera’s position. I’m wondering if there is a way to save ARAnchors in a fashion similar to what I am doing but is that possible?
My current function for saving the text to the location via a user tapping the screen:
/*
* Variables for saving the user touch
*/
var touchX : Float = 0.0
var touchY : Float = 0.0
var touchZ : Float = 0.0
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// will be used for getting the text
let textNode = SCNNode()
var writing = SCNText()
// gets the user’s touch upon tapping the screen
guard let touch = touches.first else {return}
let result = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitResult = result.last else {return}
let hitTransform = SCNMatrix4.init(hitResult.worldTransform)
let hitVector = SCNVector3Make(hitTransform.m41, hitTransform.m42, hitTransform.m43)
// saves X, Y, and Z coordinates of touch relative to the camera
touchX = hitTransform.m41
touchY = hitTransform.m42
touchZ = hitTransform.m43
// Was thinking of adding the ability to change colors. Probably can skip next seven lines
var colorArray = [UIColor]()
colorArray.append(UIColor.red)
writing = SCNText(string: input.text, extrusionDepth: 1)
material.diffuse.contents = colorArray[0]
writing.materials = [material]
// modifies the node’s position and size
textNode.scale = SCNVector3(0.01, 0.01, 0.01)
textNode.geometry = writing
textNode.position = hitVector
sceneView.scene.rootNode.addChildNode(textNode)
// last few lines save the info to Firebase
let values = ["X" : touchX, "Y" : touchY, "Z" : touchZ, "Text" : input.text!] as [String : Any]
let childKey = reference.child("Test").childByAutoId().key
if input.text != nil && input.text != "" {
let child = reference.child("Test").child(childKey!)
child.updateChildValues(values)
} else {
let child = reference.child("Test").child(childKey!)
child.updateChildValues(values)
} // if
} // override func
/*
* Similar to the previous function but used in next function
*/
func placeNode(x: Float, y: Float, z: Float, text: String) -> Void {
let textNode = SCNNode()
var writing = SCNText()
let hitVector = SCNVector3Make(x, y, z)
touchX = x
touchY = y
touchZ = z
var colorArray = [UIColor]()
colorArray.append(UIColor.red)
writing = SCNText(string: text, extrusionDepth: 1)
material.diffuse.contents = colorArray[0]
writing.materials = [material]
textNode.scale = SCNVector3(0.01, 0.01, 0.01)
textNode.geometry = writing
textNode.position = hitVector
sceneView.scene.rootNode.addChildNode(textNode)
} // func
/*
* This next function is used in my viewDidLoad to load the data
*/
func handleData() {
reference.child("Test").observeSingleEvent(of: .value, with: { (snapshot) in
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let xCoord = Float(truncating: child.childSnapshot(forPath: "X").value as! NSNumber)
let yCoord = Float(truncating: child.childSnapshot(forPath: "Y").value as! NSNumber)
let zCoord = Float(truncating: child.childSnapshot(forPath: "Z").value as! NSNumber)
let inscription = child.childSnapshot(forPath: "Text").value
self.placeNode(x: xCoord , y: yCoord , z: zCoord , text: inscription as! String)
} // for
} // if
}) // reference
} // func
I have looked into a few things such as ARCore but that looks like it uses Objective-C. I’ve made this app in Swift and I am not sure if I can incorporate ARCore with how I have implemented my current application.
Do I just need to get over it and learn Objective-C? Can I still work with what I have?
I think that ARCore anchors are only available for 24 hours, so that could be a problem.
You probably need to use ARKit2.0's ARWorldMap and save it as data on firebase for others to see the text in the same place, otherwise you are assuming in your code that future users will start their AR session in the exact same position and direction as the person who left the text. You probably need to use core location first to see where in the world the user is.

Spritekit Detecting Sprite Overlap with Collisions off

Currently I have a Swift/Spritekit app that drops a sprite from the sky, they have collisions off so they can fall through the floor, however I am trying to make a statement that will detect when the player sprite touches the sprite falling from the sky then deletes that child and adds a point to the score var.
Update: 04/29/18 2:10 CST
As of now this is what I have
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var activePlayer:SKSpriteNode! = SKSpriteNode() //Sets active character
var bg:SKSpriteNode! = SKSpriteNode()
var bananaCollected = 0 //Defines banana var
var timer: Timer?
let bananaCat : UInt32 = 0x1 << 1
let playerCat : UInt32 = 0x1 << 2
func bananaDrop() { //Defines bananaDrop
let banana = SKSpriteNode(imageNamed:"banana")
//print("- Debug: [bananaDrop] successfully initiated -")
banana.name = "banana"
//Size of banana
banana.xScale = 0.25
banana.yScale = 0.25
//Defines physics properties
let physicsBody = SKPhysicsBody(circleOfRadius: 15)
//let physicsBody = SKPhysicsBody()
//physicsBody.pinned = true //Suspend in air
physicsBody.allowsRotation = false
physicsBody.affectedByGravity = true
//physicsBody.collisionBitMask = 0
banana.physicsBody?.categoryBitMask = bananaCat
banana.physicsBody?.collisionBitMask = 0
banana.physicsBody?.contactTestBitMask = playerCat
//categoryBitMask is what the physics category of the object is
//banana.physicsBody!.categoryBitMask = playerCategory
//collisionBitMask is what the physics category of objects that this cannot pass through are...multiple categories would be typed like... `cat1 | cat2`
//banana.physicsBody!.collisionBitMask = 0
//contactTestBitMask is what the physics category of the objects that we get alerted to upon contact
//activePlayer.physicsBody!.contactTestBitMask = obstacleCategory
banana.physicsBody = physicsBody
//Starting Location Defined
var x: CGFloat = 0 //Defines X
let y: CGFloat = 400 //Defines how high up banana drops
let bananaDrop = GKShuffledDistribution(lowestValue: 1, highestValue: 11)
//Drop locations defined (relation to X)
switch bananaDrop.nextInt() {
case 1:
x = -170
case 2:
x = -160
case 3:
x = -120
case 4:
x = -80
case 5:
x = -40
case 6:
x = 0
case 7:
x = 40
case 8:
x = 80
case 9:
x = 120
case 10:
x = 160
case 11:
x = 170
default:
fatalError("Case num outside range")
}
banana.position = CGPoint(x: x, y: y)
//Adds banana
self.addChild(banana)
}
func bDropF() { //Defintes Banana Drop Final
Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] timer in
self?.timer = timer
self?.timerTime()
})
}
//Stop droping bananas
deinit {
self.timer?.invalidate()
}
func timerTime() {
bananaDrop()
}
override func didMove(to view: SKView) {
print("- Debug: Game Scene Loaded -")
bDropF() //Calls banana drop
if let setupBG:SKSpriteNode = self.childNode(withName: "bg") as? SKSpriteNode {
bg = setupBG
bg.name = "bg"
bg.physicsBody?.affectedByGravity = false
bg.zPosition = -1
}
func didBeginContact(_ contact: SKPhysicsContact){ //Banana Collect
if let firstNode = contact.bodyA.node as? SKSpriteNode, let secondNode = contact.bodyB.node as? SKSpriteNode {
let object1: String = firstNode.name!
let object2: String = secondNode.name!
if (object1 == "player") || (object2 == "banana") {
print("colliding!")
}
}
}
//Player Definitions
if let randoPlayer:SKSpriteNode = self.childNode(withName: "player") as? SKSpriteNode { //Test for Char type
activePlayer = randoPlayer //Char Set
activePlayer.physicsBody?.isDynamic = true //Set dynamic
activePlayer.physicsBody?.affectedByGravity = true //Set dynamic gravity
activePlayer.name = "player"
print("Player Initiated")
print("Physics set :: Dynamic(true):AffectedByGravity(true)")
} else {
print("Failed to initiate player")
}
}
func moveActivePlayerR() {//Right Touch Player Movements Defined
let walkAnimation:SKAction = SKAction(named: "WalkRight")!
let moveAction:SKAction = SKAction.moveBy(x: 100, y: 0, duration: 0.5) //Move Right Side
//let moveRight:SKAction = SKAction.group([walkAnimation, moveAction]) //Depricated
let sound = SKAction.playSoundFileNamed("walk.wav", waitForCompletion: false)
let finalWalkR:SKAction = SKAction.group([walkAnimation, moveAction, sound])
activePlayer.run(finalWalkR)
}
func moveActivePlayerL() { //Left Touch Player Movements Defined
let walkAnimation:SKAction = SKAction(named: "WalkLeft")!
let moveAction:SKAction = SKAction.moveBy(x: -100, y: 0, duration: 0.5) //Move Left Side
//let moveRight:SKAction = SKAction.group([walkAnimation, moveAction]) //Depricated
let sound = SKAction.playSoundFileNamed("walk.wav", waitForCompletion: false)
let finalWalkL:SKAction = SKAction.group([moveAction, sound, walkAnimation])
activePlayer.run(finalWalkL)
}
func touchDown(atPoint pos : CGPoint) {
print("touch \( pos.x),\(pos.y)") //Debug print
if(pos.x > 0) { //if touched right side of screen
print("Right touch")
moveActivePlayerR()
} else if (pos.x < 0) { //if touched left side of screen
print("Left touch")
moveActivePlayerL()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
self.touchDown(atPoint: t.location(in: self))
}
}
}
However even with these categories setup it still appears that I am unable to get them to collide properly. When they touch each other there is nothing in my console indicating that they have collided. Please help!
You do not have to have a collisionBitMask set in order to detect contact, you just need to set the contactTestBitMask to the category of the obstacle you want to detect the collision with. You then check for the collision in the didBegin func. Ensure that you have the SKPhysicsContactDelegate set on your scene.
class GameScene: SKScene, SKPhysicsContactDelegate
self.physicsWorld.contactDelegate = self
when setting up your objects
//categoryBitMask is what the physics category of the object is
object.physicsBody!.categoryBitMask = playerCategory
//collisionBitMask is what the physics category of objects that this cannot pass through are...multiple categories would be typed like... `cat1 | cat2`
object.physicsBody!.collisionBitMask = 0
//contactTestBitMask is what the physics category of the objects that we get alerted to upon contact
object.physicsBody!.contactTestBitMask = obstacleCategory
the didBegin func in your scene
func didBeginContact(_ contact: SKPhysicsContact) {
if let firstNode = contact.bodyA.node as? SKSpriteNode, let secondNode = contact.bodyB.node as? SKSpriteNode {
let object1: String = firstNode.name!
let object2: String = secondNode.name!
if (object1 == "obstacle") || (object2 == "obstacle") {
//run some code because these 2 have collided
}
}
}
...or...
func didBeginContact(_ contact: SKPhysicsContact) {
if contact.bodyA.categoryBitMask == PhysicsCategory.obstacleCategory || contact.bodyB.categoryBitMask == PhysicsCategory.obstacleCategory {
//obstacle has hit player do something
}
}

Resources