How can I change the touch offset of a sprite in SpriteKit? - swift

When I touch the player mallet (Air Hockey), I want to make it so the mallet moves slightly above the touch. This way the mallet will be more visible in the game. I have found some solutions but am having a hard time implementing properly in my function.
Here is a sample of my touchesMoved() function:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
{
bottomTouchIsActive = true
var releventTouch:UITouch!
//convert set to known type
let touchSet = touches
//get array of touches so we can loop through them
let orderedTouches = Array(touchSet)
for touch in orderedTouches
{
//if we've not yet found a relevent touch
if releventTouch == nil
{
//look for a touch that is in the activeArea (Avoid touches by opponent)
if activeArea.contains(touch.location(in: parent!))
{
isUserInteractionEnabled = true
releventTouch = touch
}
else
{
releventTouch = nil
}
}
}
if (releventTouch != nil)
{
//get touch position and relocate player
let location = releventTouch!.location(in: parent!)
position = location
//find old location and use pythagoras to determine length between both points
let oldLocation = releventTouch!.previousLocation(in: parent!)
let xOffset = location.x - oldLocation.x
let yOffset = location.y - oldLocation.y
let vectorLength = sqrt(xOffset * xOffset + yOffset * yOffset)
//get eleapsed and use to calculate speed6A
if lastTouchTimeStamp != nil
{
let seconds = releventTouch.timestamp - lastTouchTimeStamp!
let velocity = 0.01 * Double(vectorLength) / seconds
//to calculate the vector, the velcity needs to be converted to a CGFloat
let velocityCGFloat = CGFloat(velocity)
//calculate the impulse
let directionVector = CGVector(dx: velocityCGFloat * xOffset / vectorLength, dy: velocityCGFloat * yOffset / vectorLength)
//pass the vector to the scene (so it can apply an impulse to the puck)
delegate?.bottomForce(directionVector, fromBottomPlayer: self)
delegate?.bottomTouchIsActive(bottomTouchIsActive, fromBottomPlayer: self)
}
//update latest touch time for next calculation
lastTouchTimeStamp = releventTouch.timestamp
}
}

Is the position var, which you take from the touch location, used to set the position of the mallet? If it is, then if you want the mallet above the touch, why not do something like position.y += 50 immediately after position = location to move it up by 50 points?
Alternatively, you might find it more logical to set the mallet's anchorPoint property (https://developer.apple.com/documentation/spritekit/skspritenode/1519877-anchorpoint and https://developer.apple.com/documentation/spritekit/skspritenode/using_the_anchor_point_to_move_a_sprite) to be somewhere other than the default poisition (the centre of the sprite) e.g. the point that corresponds to the part of the handle of the mallet where one would normally hold it.

Related

How to rotate and lock the rotation of a SCNNode by degrees?

I need to rotate a model that can be freely rotated, to exact degrees, regardless of how many times it's been rotated.
I have a UIPanGestureRecognizer that is rotating freely a 3D model around the Y axis. However I'm struggling to get it to lock to a integer degree when panning is stopped, and I'm struggling with being able to know it's rotation in degrees from 0-359.
let translation = recognizer.translation(in: self.view)
var newAngleY = Double(translation.x) * (Double.pi) / 180.0
newAngleY += self.currentAngle
self.shipNode?.eulerAngles.y = Float(newAngleY)
if (recognizer.state == .ended)
{
self.currentAngle = newAngleY
}
It rotates freely, but all attempts for locking to the closest exact degree, and being able to 'know' it's rotational degree in a value from 0-359.
I know that:
let degrees = newAngleY * ( 180 / Double.pi)
And I know that if degrees > 360 then -= 360 (pseudo code)
However, whilst the UIPanGestureRecognizer is doing it's thing, these checks seem to fail and I don't know why. Is it because when it's still being panned, you can't edit the private properties of the ViewController?
You can edit the value while the gesture is occurring.
Quite a few options, so this seems the simplest to start with:
You could try only applying euler when the state changes AND only when .x > .x * (some value, such as 1.1). This would provide a more "snap to" kind of approach, something like:
var currentLocation = CGPoint.zero
var beginLocation = CGPoint.zero
#objc func handlePan(recognizer: UIPanGestureRecognizer) {
currentLocation = recognizer.location(in: gameScene)
var newAngleY = Double(translation.x) * (Double.pi) / 180.0
newAngleY += self.currentAngle
switch recognizer.state
{
case UIGestureRecognizer.State.began: break
case UIGestureRecognizer.State.changed:
if(currentLocation.x > beginLocation.x * 1.1)
{
gNodes.bnode.eulerAngles.y = Float(newAngleY)
beginLocation.x = currentLocation.x
}
if(currentLocation.x < beginLocation.x * 0.9) { .etc. }
break
case UIGestureRecognizer.State.ended:
gNodes.bnode.eulerAngles.y = Float(newAngleY)
break
}
}
Then you could switch to an SCNAction (changing your math) to give more control, such as
let vAction = SCNAction.rotateTo(x: 0, y: vAmount, z: 0, duration: 0)
bnode.runAction(vAction)

Placing an object in front of camera at Touch Location

The following code places the node in front of the camera but always at the center 10cm away from the camera position. I want to place the node 10cm away in z-direction but at the x and y co-ordinates of where I touch the screen. So touching on different parts of the screen should result in a node being placed 10cm away in front of the camera but at the x and y location of the touch and not always at the center.
var cameraRelativePosition = SCNVector3(0,0,-0.1)
let sphere = SCNNode()
sphere.geometry = SCNSphere(radius: 0.0025)
sphere.geometry?.firstMaterial?.diffuse.contents = UIColor.white
Service.addChildNode(sphere, toNode: self.sceneView.scene.rootNode,
inView: self.sceneView, cameraRelativePosition:
cameraRelativePosition)
Service.swift
class Service: NSObject {
static func addChildNode(_ node: SCNNode, toNode: SCNNode, inView:
ARSCNView, cameraRelativePosition: SCNVector3) {
guard let currentFrame = inView.session.currentFrame else { return }
let camera = currentFrame.camera
let transform = camera.transform
var translationMatrix = matrix_identity_float4x4
translationMatrix.columns.3.x = cameraRelativePosition.x
translationMatrix.columns.3.y = cameraRelativePosition.y
translationMatrix.columns.3.z = cameraRelativePosition.z
let modifiedMatrix = simd_mul(transform, translationMatrix)
node.simdTransform = modifiedMatrix
toNode.addChildNode(node)
}
}
The result should look exactly like this : https://justaline.withgoogle.com
We can use the unprojectPoint(_:) method of SCNSceneRenderer (SCNView and ARSCNView both conform to this protocol) to convert a point on the screen to a 3D point.
When tapping the screen we can calculate a ray this way:
func getRay(for point: CGPoint, in view: SCNSceneRenderer) -> SCNVector3 {
let farPoint = view.unprojectPoint(SCNVector3(Float(point.x), Float(point.y), 1))
let nearPoint = view.unprojectPoint(SCNVector3(Float(point.x), Float(point.y), 0))
let ray = SCNVector3Make(farPoint.x - nearPoint.x, farPoint.y - nearPoint.y, farPoint.z - nearPoint.z)
// Normalize the ray
let length = sqrt(ray.x*ray.x + ray.y*ray.y + ray.z*ray.z)
return SCNVector3Make(ray.x/length, ray.y/length, ray.z/length)
}
The ray has a length of 1, so by multiplying it by 0.1 and adding the camera location we get the point you were searching for.

SCNNode facing towards the camera

I am trying to put SCNCylinder node in the scene on the touch point. I always want to show the cylinder shape diameter facing towards camera. Its working fine for horizontal scene but it have a problem in vertical scene. In vertical scene I can see the cylinder sides but I want to show the full diameter facing towards the camera no matter whats the camera orientation is. I know there is some transformation needs to be applied depending on the camera transform but don't know how. I am not using plane detection its the simple node which is directly added to the scene.
Vertical Image:
Horizontal Image:
The code to insert the node is as follows,
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
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 {
print("returning because couldn't find the touch point")
return
}
let hitTransform = SCNMatrix4(hitResult.worldTransform)
let position = SCNVector3Make(hitTransform.m41, hitTransform.m42, hitTransform.m43)
let ballShape = SCNCylinder(radius: 0.02, height: 0.01)
let ballNode = SCNNode(geometry: ballShape)
ballNode.position = position
sceneView.scene.rootNode.addChildNode(ballNode)
}
Any help would be appreciated.
I'm not certain this is the right way to handle what you need but here is something which may help you.
I think CoreMotion could be useful to help you determine if the device is at a horizontal or vertical angle.
This class has a property called attitude, which describes the rotation of our device in terms of roll, pitch, and yaw. If we are holding our phone in portrait orientation, the roll describes the angle of rotation about the axis that runs through the top and bottom of the phone. The pitch describes the angle of rotation about the axis that runs through the sides of your phone (where the volume buttons are). And finally, the yaw describes the angle of rotation about the axis that runs through the front and back of your phone. With these three values, we can determine how the user is holding their phone in reference to what would be level ground (Stephan Baker).
Begin by importing CoreMotion:
import CoreMotion
Then create the following variables:
let deviceMotionDetector = CMMotionManager()
var currentAngle: Double!
We will then create a function which will check the angle of our device like so:
/// Detects The Angle Of The Device
func detectDeviceAngle(){
if deviceMotionDetector.isDeviceMotionAvailable == true {
deviceMotionDetector.deviceMotionUpdateInterval = 0.1;
let queue = OperationQueue()
deviceMotionDetector.startDeviceMotionUpdates(to: queue, withHandler: { (motion, error) -> Void in
if let attitude = motion?.attitude {
DispatchQueue.main.async {
let pitch = attitude.pitch * 180.0/Double.pi
self.currentAngle = pitch
print(pitch)
}
}
})
}
else {
print("Device Motion Unavailable");
}
}
This only needs to be called once for example in viewDidLoad:
detectDeviceAngle()
In your touchesBegan method you can add this to the end:
//1. If We Are Holding The Device Above 60 Degress Change The Node
if currentAngle > 60 {
//2a. Get The X, Y, Z Values Of The Desired Rotation
let rotation = SCNVector3(1, 0, 0)
let vector3x = rotation.x
let vector3y = rotation.y
let vector3z = rotation.z
let degreesToRotate:Float = 90
//2b. Set The Position & Rotation Of The Object
sphereNode.rotation = SCNVector4Make(vector3x, vector3y, vector3z, degreesToRotate * 180 / .pi)
}else{
}
I am sure there are better ways to achieve what you need (and I would be very interested in hearing them too), but I hope it will get you started.
Here is the result:

TouchesMoved Lag with Pathfinding

I'm making a game where the player follows a touch. The game has obstacles and pathfinding, and I'm experiencing extreme lag with touches moved. Upon loading the game, everything works, but after a few seconds of dragging my finger around (especially around obstacles) the game freezes to the point where moving a touch will freeze all physics (0 fps) until the touch is ended.
I'm assuming the culprit is my function makeGraph(), which finds a path for the player from player.position:CGPoint() to player.destination:CGPoint(), storing a path in player.goto:[CGPoint()], the update function then takes care of moving to the next goto point.
This function is called every cycle of touchesmoved, so the player can switch directions if the finger moves over an obstacle
My question is: 1) what in this code is causing the unbearable lag, 2) how can I make this code more efficient?
My code:
initializing vars:
var obstacles = [GKPolygonObstacle(points: [float2()])]
var navgraph = GKObstacleGraph()
setting up a graph (called at start of level):
func setupGraph(){
var wallnodes :[SKSpriteNode] = [SKSpriteNode]()
self.enumerateChildNodes(withName: "wall") {
node, stop in
wallnodes.append(node as! SKSpriteNode)
}
self.enumerateChildNodes(withName: "crate") {
node, stop in
wallnodes.append(node as! SKSpriteNode)
}
obstacles = SKNode.obstacles(fromNodeBounds: wallnodes)
navgraph = GKObstacleGraph(obstacles: obstacles, bufferRadius: Float(gridSize/2))
}
touchesmoved: I keep track of multiple touches using strings. only one finger may act as the "MoveFinger." this string is created at touchesbegan and emptied at touchesended
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
touchloop: for touch in touches {
if MoveFinger == String(format: "%p", touch) {
let location = touch.location(in: self)
player.destination = location
makeGraph()
player.moving = true
player.UpdateMoveMode()
continue touchloop
}
// .... more if statements ....
}
}
and graph function (called every touchesmoved cycle):
func makeGraph(){
let startNode = GKGraphNode2D(point: float2(Float(player.position.x), Float(player.position.y)))
let endNode = GKGraphNode2D(point: float2(Float(player.destination.x), Float(player.destination.y)))
let graphcopy = navgraph
graphcopy.connectUsingObstacles(node: startNode)
graphcopy.connectUsingObstacles(node: endNode)
let path = graphcopy.findPath(from: startNode, to: endNode)
player.goto = []
for node:GKGraphNode in path {
if let point2d = node as? GKGraphNode2D {
let point = CGPoint(x: CGFloat(point2d.position.x), y: CGFloat(point2d.position.y))
player.goto.append(point)
}
}
if player.goto.count == 0 {
//if path is empty, go straight to destination
player.goto = [player.destination]
} else {
//if path is not empty, remove first point (start point)
player.goto.remove(at: 0)
}
}
any ideas?

How do I replace an image of an SKSpriteNode for the duration of an action then return it to it's original texture atlas loop

I am trying to make a basic run and jump game in SpriteKit.
When the view loads I wish for the sprite node to Run using images from a texture atlas. This I have managed to do.
When the screen is touched I wish this image to change to another image in the texture atlas called Jump.
When the character returns to the ground I wish it to go back to the original texture atlas loop.
So far I have coded the following:
import UIKit
import SpriteKit
class Level1: SKScene {
var Hero : SKSpriteNode!
//Creates an object for the Hero character.
let textureAtlas = SKTextureAtlas(named:"RunImages.atlas")
//Specifies the image atlas used.
var spriteArray = Array<SKTexture>();
//Creates a variable for the image atlas of him running.
var HeroBaseLine = CGFloat (0)
//This is where the Hero character sits on top of the ground.
var onGround = true
//Creates a variable to specify if Hero is on the ground.
var velocityY = CGFloat (0)
//Creates a variable to hold a three decimal point specification for velocity in the Y axis.
let gravity = CGFloat (0.6)
//Creates a non variable setting for gravity in the scene.
let movingGround = SKSpriteNode (imageNamed: "Ground")
//Creates an object for the moving ground and assigns the Ground image to it.
var originalMovingGroundPositionX = CGFloat (0)
//Sets a variable for the original ground position before it starts to move.
var MaxGroundX = CGFloat (0)
//Sets a variable for the maximum
var groundSpeed = 4
//Sets the ground speed. This number is how many pixels it will move the ground to the left every frame.
override func didMoveToView(view: SKView) {
//Misc setup tasks.
backgroundColor = (UIColor.blackColor())
//Sets the background colour when the view loads.
//Ground related tasks.
self.movingGround.anchorPoint = CGPointMake(0, 0.5)
//Positions the Ground image hard left in the X axis.
self.movingGround.position = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame) + (self.movingGround.size.height / 2))
//Positions the Ground image at the bottom of the screen relative to half the height of the image.
self.addChild(self.movingGround)
//Creates an instance of the Ground image that follows the parameters set in the lines above when the view loads.
self.originalMovingGroundPositionX = self.movingGround.position.x
//Sets the starting position for the ground image in the x before it start to move.
self.MaxGroundX = self.movingGround.size.width - self.frame.size.width
//Sets the maximum ground size minus the width of the screen to create the loop point in the image.
self.MaxGroundX *= -1
//This multiplies the size of the ground by itself and makes the max ground size a negative number as the image is moving towards the left in x which is negative.
//Hero related tasks.
spriteArray.append(textureAtlas.textureNamed("Run1"));
spriteArray.append(textureAtlas.textureNamed("Run2"));
spriteArray.append(textureAtlas.textureNamed("Run3"));
spriteArray.append(textureAtlas.textureNamed("Run2"));
Hero = SKSpriteNode(texture:spriteArray[0]);
self.HeroBaseLine = self.movingGround.position.y + (self.movingGround.size.height / 2) + 25
//self.Hero.position = CGPointMake(CGRectGetMinX(self.frame) + 50, self.HeroBaseLine)
self.Hero.position = CGPointMake(CGRectGetMinX(self.frame) + 50, self.HeroBaseLine)
//Sets where the character will appear exactly.
self.Hero.xScale = 0.15
self.Hero.yScale = 0.15
addChild(self.Hero);
//Adds an instance of Hero to the screen.
let animateAction = SKAction.animateWithTextures(self.spriteArray, timePerFrame: 0.15);
let moveAction = SKAction.moveBy(CGVector(dx: 0,dy: 0), duration: 0.0);
//Although currently set to 0, the above line controls the displacement of the character in the x and y axis if required.
let group = SKAction.group([ animateAction,moveAction]);
let repeatAction = SKAction.repeatActionForever(group);
self.Hero.runAction(repeatAction);
//Animation action to make him run. Here we can affect the frames and x, y movement, etc.
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if self.onGround {
self.velocityY = -18
self.onGround = false
}
}
//This block specifies what happens when the screen is touched.
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if self.velocityY < -9.0 {
self.velocityY = -9.0
}
}
//This block prevents Hero from jumping whilst already jumping.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if self.movingGround.position.x <= MaxGroundX {
self.movingGround.position.x = self.originalMovingGroundPositionX
}
//This is how the ground is positioned at the beginning of each update (each frame refresh)
movingGround.position.x -= CGFloat (self.groundSpeed)
//This is how the ground is moved relative to the ground speed variable set at the top. The number in the variable is how many pixels the frame is being moved each frame refresh.
self.velocityY += self.gravity
self.Hero.position.y -= velocityY
if self.Hero.position.y < self.HeroBaseLine {
self.Hero.position.y = self.HeroBaseLine
velocityY = 0.0
self.onGround = true
}
//This is the code for making Hero jump in accordance to the velocity and gravity specified at the top of the class in relation to the base line.
}
}
I have tried to add code in the touchesBegan section to change the image texture of the sprite node to another image in my image atlas called Jump.
I then wish for the texture atlas to return to animating once the touches ended action is called.
Any help would be greatly appreciated.
Update:
I have implemented what you have suggested but it still doesn't work quite correclty. The hero is changing to the jumping image but does not actually jump and is stuck in the jump image.
I created a JumpImages.atlas and added "Jump" image to that folder.
I have modified the code to the following:
import UIKit
import SpriteKit
class Level1: SKScene {
//Creates an object for the Hero character.
var Hero : SKSpriteNode!
//Specifies the image atlas used.
let textureAtlas = SKTextureAtlas(named:"RunImages.atlas")
//Creates a variable for the image atlas of him running.
var spriteArray = Array<SKTexture>();
var jumpArray = Array<SKTexture>();
let jumpAtlas = SKTextureAtlas(named:"JumpImages.atlas")
//jumpArray.append(jumpAtlas.textureNamed("Jump")) Didn't work in this area, moved it to the did move to view.
//This is where the Hero character sits on top of the ground.
var HeroBaseLine = CGFloat (0)
//Creates a variable to specify if Hero is on the ground.
var onGround = true
//Creates a variable to hold a three decimal point specification for velocity in the Y axis.
var velocityY = CGFloat (0)
//Creates a non variable setting for gravity in the scene.
let gravity = CGFloat (0.6)
//Creates an object for the moving ground and assigns the Ground image to it.
let movingGround = SKSpriteNode (imageNamed: "Ground")
//Sets a variable for the original ground position before it starts to move.
var originalMovingGroundPositionX = CGFloat (0)
//Sets a variable for the maximum
var MaxGroundX = CGFloat (0)
//Sets the ground speed. This number is how many pixels it will move the ground to the left every frame.
var groundSpeed = 4
override func didMoveToView(view: SKView) {
//Misc setup tasks.
//Sets the background colour when the view loads.
backgroundColor = (UIColor.blackColor())
//Ground related tasks.
//Positions the Ground image hard left in the X axis.
self.movingGround.anchorPoint = CGPointMake(0, 0.5)
//Positions the Ground image at the bottom of the screen relative to half the height of the image.
self.movingGround.position = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame) + (self.movingGround.size.height / 2))
//Creates an instance of the Ground image that follows the parameters set in the lines above when the view loads.
self.addChild(self.movingGround)
//Sets the starting position for the ground image in the x before it start to move.
self.originalMovingGroundPositionX = self.movingGround.position.x
//Sets the maximum ground size minus the witdth of the screen to create the loop point in the image.
self.MaxGroundX = self.movingGround.size.width - self.frame.size.width
//This multiplies the size of the ground by itself and makes the max ground size a negative number as the image is moving towards the left in x which is negative.
self.MaxGroundX *= -1
//Hero related tasks.
spriteArray.append(textureAtlas.textureNamed("Run1"));
spriteArray.append(textureAtlas.textureNamed("Run2"));
spriteArray.append(textureAtlas.textureNamed("Run3"));
spriteArray.append(textureAtlas.textureNamed("Run2"));
Hero = SKSpriteNode(texture:spriteArray[0]);
self.HeroBaseLine = self.movingGround.position.y + (self.movingGround.size.height / 2) + 25
//Sets where the character will appear exactly.
self.Hero.position = CGPointMake(CGRectGetMinX(self.frame) + 50, self.HeroBaseLine)
//Scales the image to an appropriate size.
self.Hero.xScale = 0.15
self.Hero.yScale = 0.15
//Adds an instance of Hero to the screen.
addChild(self.Hero);
//Added this here as it didn't appear to work in the place recommended.
jumpArray.append(jumpAtlas.textureNamed("Jump"));
//I added this so that he runs when the view loads.
if self.onGround {
run()
}
}
//Animation function to make him run. Here we can affect the frames and x, y movement, etc.
func run() {
let animateAction = SKAction.animateWithTextures(self.spriteArray, timePerFrame: 0.15);
//Although currently set to 0, the above line controls the displacement of the character in the x and y axis if required.
let moveAction = SKAction.moveBy(CGVector(dx: 0,dy: 0), duration: 0.0);
let group = SKAction.group([animateAction,moveAction]);
let repeatAction = SKAction.repeatActionForever(group);
self.Hero.runAction(repeatAction);
}
//Animation function to make him jump.
func jump() {
self.velocityY = -18
self.onGround = false
let jumpAnimation = SKAction.animateWithTextures(jumpArray, timePerFrame: 0.15)
self.Hero.runAction(SKAction.repeatActionForever(jumpAnimation))
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
//This block specifies what happens when the screen is touched.
if self.onGround {
jump()
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
//This block prevents Hero from jumping whilst already jumping.
if self.velocityY < -9.0 {
self.velocityY = -9.0
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
//This is how the ground is positioned at the beginning of each update (each frame refresh)
if self.movingGround.position.x <= MaxGroundX {
self.movingGround.position.x = self.originalMovingGroundPositionX
}
//This is how the ground is moved relative to the ground speed variable set at the top. The number in the variable is how many pixels the frame is being moved each frame refresh.
movingGround.position.x -= CGFloat (self.groundSpeed)
//This is the code for making Hero jump in accordance to the velocity and gravity specified at the top of the class in realation to the base line and run when he hits the ground.
if self.Hero.position.y < self.HeroBaseLine {
self.Hero.position.y = self.HeroBaseLine
velocityY = 0.0
if self.onGround == false {
self.onGround = true
run()
}
}
}
}
Is there anything obvious I am doing wrong? Thanks for your help.
Since you have already made your sprite run, to jump is not a hard thing. Just replace the texture of run animation with the texture of jump animation in proper place.
Firstly, I wrap the code of run animation for reuse later.
func run() {
let animateAction = SKAction.animateWithTextures(self.spriteArray, timePerFrame: 0.15);
let moveAction = SKAction.moveBy(CGVector(dx: 0,dy: 0), duration: 0.0);
let group = SKAction.group([animateAction,moveAction]);
let repeatAction = SKAction.repeatActionForever(group);
self.Hero.runAction(repeatAction);
}
Next step is for texture atlas of Jump. For demo, I just add one frame animation for jumping. Add these line after you create textureAtlas and spriteArray for Run.
var jumpArray = Array<SKTexture>()
let jumpAtlas = SKTextureAtlas(named:"JumpImages.atlas")
jumpArray.append(jumpAtlas.textureNamed("Jump"))
After you write function jump(), you can call it in touchesBegan.
func jump() {
self.velocityY = -18
self.onGround = false
println("jump over ground")
let jumpAnimation = SKAction.animateWithTextures(jumpArray, timePerFrame: 0.15)
self.Hero.runAction(SKAction.repeatActionForever(jumpAnimation))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent?) {
if self.onGround {
jump()
}
}
Last but not least, resume running animation after back to the ground in update.
override func update(currentTime: CFTimeInterval) {
...
if self.Hero.position.y < self.HeroBaseLine {
self.Hero.position.y = self.HeroBaseLine
velocityY = 0.0
if self.onGround == false {
self.onGround = true
println("on the ground")
run()
}
}
}
Now you should get the result below. If you have any problem with the code, just let me know.