I'm moving a sprite by using the touchesMoved method. So far this is working fine, but the result looks a little bit unnatural. The sprite stops immediately, once the movement of my finger stops. A more natural impression would be, if the sprite continuous the movement a little time with an ease out function. Any idea how to implement this?
Here is my code:
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode?
var xOrgPosition: CGFloat = 0.0
override func didMoveToView(view: SKView) {
sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite!.xScale = 0.1
sprite!.yScale = 0.1
sprite!.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(sprite!)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let xTouchPosition = touch.locationInNode(self).x
if xOrgPosition != 0.0 {
// calculate the new position
sprite!.position = CGPoint(x: sprite!.position.x - (xOrgPosition - xTouchPosition), y: sprite!.position.y)
}
// Store the current touch position to calculate the delta in the next iteration
xOrgPosition = xTouchPosition
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Reset value for the next swipe gesture
xOrgPosition = 0
}
}
Update:
I've made a short sample project, based on the answers from hamobi
Tutorial
Git repository
i think this might be what youre going for. let me know if you have any questions :)
import SpriteKit
class GameScene: SKScene {
var sprite: SKSpriteNode?
var xOrgPosition: CGFloat?
override func didMoveToView(view: SKView) {
sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite!.xScale = 0.1
sprite!.yScale = 0.1
sprite!.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
self.addChild(sprite!)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
xOrgPosition = touch.locationInNode(self).x
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
xOrgPosition = nil
}
override func update(currentTime: NSTimeInterval) {
if let xPos = xOrgPosition {
sprite!.position.x += (xPos - sprite!.position.x) * 0.1
}
}
}
Related
I'm trying to write a "normalizing offset function" to give the effect that when the user touches/moves the sprite, that it does not snap to the center of the touch (the default behavior). The offset function should probably address the anchor point during "touches began, and touches move", and then revert back to the center when touches ended.enter link description here
class GameScene: SKScene {
private var redSquare : SKSpriteNode?
private var originalAnchorPoint: CGPoint = CGPoint(x: 0.5, y: 0.5)
override func didMove(to view: SKView) {
// Get label node from scene and store it for use later
self.redSquare = self.childNode(withName: "redSquare") as? SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
let location = touch.location(in: self)
redSquare?.position = location
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in (touches ) {
let location = touch.location(in: self)
if (self.redSquare?.contains(location))!{
redSquare?.position = location
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// set red sprite anchor position to originalAnchorPoint
}
I am trying to implement moving to another scene when a wheel stops rotating. The code I have is shown below. I cannot figure out how to detect when the velocity has reached 0.0?
import SpriteKit
import GameplayKit
class GameplayScene: SKScene {
var player: Player?;
override func didMove(to view: SKView) {
player = self.childNode(withName: "spinner") as! Player?;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "play_button" {
spin()
}
}
}
func spin () {
let random = GKRandomDistribution(lowestValue: 20, highestValue: 90)
let r = random.nextInt()
player?.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.frame.width))
player?.physicsBody?.affectedByGravity = false
player?.physicsBody?.isDynamic = true
player?.physicsBody?.allowsRotation = true
player?.physicsBody?.angularVelocity = CGFloat(r)
player?.physicsBody?.angularDamping = 1.0
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
}
So from here, I would like to execute the following when the wheel has stopped spinning:
let play_scene = Questions(fileNamed: "QuestionsScene")
play_scene?.scaleMode = .aspectFill
self.view?.presentScene(play_scene!, transition: SKTransition.doorsOpenVertical(withDuration: 1))
I have now edited the class and it looks as follows:
import SpriteKit
import GameplayKit
class GameplayScene: SKScene, SKSceneDelegate {
var player: Player?
override func didMove(to view: SKView) {
self.delegate = self
player = self.childNode(withName: "spinner") as! Player?;
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if atPoint(location).name == "play_button" {
spin()
}
}
}
func spin () {
let random = GKRandomDistribution(lowestValue: 20, highestValue: 90)
let r = random.nextInt()
player?.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.frame.width))
player?.physicsBody?.affectedByGravity = false
player?.physicsBody?.isDynamic = true
player?.physicsBody?.allowsRotation = true
player?.physicsBody?.pinned = true
player?.physicsBody?.angularVelocity = CGFloat(r)
player?.physicsBody?.angularDamping = 1.0
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func didSimulatePhysics() {
if ((player?.physicsBody?.angularVelocity)! <= CGFloat(0.01))
{
print("Got it")
}
}
}
Problem is I still receive an error on the if statement within didSimulatePhysics function. The error I receive is "Thread 1: EXC_BAD_INSTRUCTION...."
Your wheel's SKPhysicsBody has a built-in property, angularVelocity, that tells you how fast it's spinning. You're already using it when you set it to r to start the wheel spinning.
To watch angularVelocity you can use didSimulatePhysics(). It gets called once every frame, right after the physics calculations are done. That will look something like this:
func didSimulatePhysics() {
if wheelIsSpinning && angularVelocity != nil && angularVelocity! <= CGFloat(0.001) {
wheelIsSpinning = false
// wheel has stopped
// add your code here
}
}
Due to the vagaries of physics modeling, the angularVelocity might never be exactly zero. So instead we watch for it to be less than some arbitrary threshold, in this case 0.001.
You don't want it to execute every frame when the wheel isn't moving, so I added a property wheelIsSpinning to keep track. You'll need to add it as an instance property to GameplayScene, and set it to true in spin().
I have this code and I am trying to write a game. I want the car to go right one lane when there is a tap on the right side of the screen and left one lane when there is a tap on the left side of the screen. I wrote a function to move the car but I don't know how to get it to work. This is my code:
//
// GameScene.swift
// cargame
//
// Created by Pranav Sharan on 3/21/17.
// Copyright © 2017 Pranav Sharan. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
let player:SKSpriteNode = self.childNode(withName: "car") as! SKSpriteNode
}
func touchDown(atPoint pos : CGPoint) {
}
func touchMoved(toPoint pos : CGPoint) {
}
func touchUp(atPoint pos : CGPoint) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let position = touch.location(in: self)
print(position.x)
print(position.y)
if position.x < 0 {
moveLeft(object: player)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchUp(atPoint: t.location(in: self)) }
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
func moveLeft(object: SKSpriteNode){
let move = SKAction.moveTo(x: -20, duration: 0.5)
object.run(move)
}
}
Here is an example of how you can Accomplish this, Hope it helps.
class GameScene: SKScene {
var player = SKSpriteNode()
override func didMove(to view: SKView) {
player = SKSpriteNode(color: .purple, size: CGSize(width: 100, height: 100))
player.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
self.addChild(player)
}
func touchDown(atPoint position : CGPoint) {
if position.x < self.frame.midX
{
let move = SKAction.moveTo(x: self.frame.minX + 200, duration: 0.3)
player.run(move)
} else if position.x > self.frame.midX {
let move = SKAction.moveTo(x: self.frame.maxX - 200, duration: 0.3)
player.run(move)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches { self.touchDown(atPoint: t.location(in: self)) }
}
}
If a touch is less than the middle of the screen, The middle of the screen defined by self.frame.midX.
First thing I see is that you declare your player node in the didMove() function, which makes it invisible outside the scope of that function.
Declare the player node as a property of your GameScene class to make it available everywhere inside and outside of your class. Then, you assign your scene object to it. Your code would more likely look like that:
class GameScene: SKScene {
var player:SKSpriteNode
override func didMove(to view: SKView) {
self.player = self.childNode(withName: "car") as! SKSpriteNode
}
To avoid unexpected game behaviour I'd like to know coordinates of all fingers on the touchscreen at any given moment. Is there a way of doing this?
For example get coordinates of the fist finger when second is taken off from the screen.
Thanks in advance!
Here's the solution based on trojanfoe's idea.
import SpriteKit
class GameScene : SKScene {
struct fingerTrackData {
var fingerID : Int // used to identify fingers in other part of the code
var lastLocation : CGPoint // location of each finger
}
// array that stores information about each finger coordinates
var fingerTracks : [fingerTrackData] = []
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
// getAvailableFingerID() returns available fingerID to avoid ID duplication
self.fingerTracks.append(fingerTrackData(fingerID: self.getAvailableFingerID(), lastLocation: location))
}
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
self.updateFingerTracks(location)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
// deregistering unnecessary finger tracks
self.fingerTracks.removeAtIndex(self.getClosestFingerArrayIndexByLocation(location))
}
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
// touchesCancelled occurs e.g. when more than 5 fingers on iPhone is on the screen
// Deregistering all tracks here
self.fingerTracks.removeAll()
}
override func update(currentTime: NSTimeInterval) {
// to see in console what is actually happening in array
if fingerTracks.count > 0 {
for fingerTrack in self.fingerTracks {
print("Finger: \(fingerTrack.fingerID) Location: \(fingerTrack.lastLocation)")
}
} else {
print("---")
}
}
/*
* Below are some helper functions for convenience
*/
func getClosestFingerArrayIndexByLocation (location: CGPoint) -> Int {
var fingerTrackKeyToUpdate : Int = 0
var shortestDistance : CGFloat = 100500 // some big number that is much bigger than screen size
for (fingerTrackKey, fingerTrack) in self.fingerTracks.enumerate() {
// calculating distance from previous record for each finger
let calculatedDistance = sqrt(pow(fingerTrack.lastLocation.x - location.x,2) + pow(fingerTrack.lastLocation.y - location.y,2))
// shortest one gives us finger ID
if calculatedDistance < shortestDistance {
shortestDistance = calculatedDistance
fingerTrackKeyToUpdate = fingerTrackKey
}
}
return fingerTrackKeyToUpdate
}
func updateFingerTracks(location: CGPoint) {
let closestFingerArrayIndex = self.getClosestFingerArrayIndexByLocation(location)
let fingerIDToUpdate = self.fingerTracks[closestFingerArrayIndex].fingerID
self.fingerTracks[closestFingerArrayIndex] = fingerTrackData(fingerID: fingerIDToUpdate, lastLocation: location)
}
func getClosestFingerIdByLocation(location: CGPoint) -> Int {
return self.fingerTracks[self.getClosestFingerArrayIndexByLocation(location)].fingerID
}
func getAvailableFingerID() -> Int {
var availableFingerID : Int = 0
if self.fingerTracks.count > 0 {
var checkAgain = true
while checkAgain {
checkAgain = false
for fingerTrack in self.fingerTracks {
if availableFingerID == fingerTrack.fingerID {
checkAgain = true
availableFingerID++
}
}
}
}
return availableFingerID
}
}
Thanks all for your assistance!
class GameScene : SKScene {
// stores all touches at any moment
var touchTracks : [UITouch] = []
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
self.touchTracks.append(touch)
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
var touchIndexToRemove : Int?
for i in 0..<self.touchTracks.count {
if self.touchTracks[i] == touch {
touchIndexToRemove = i
}
}
self.touchTracks.removeAtIndex(touchIndexToRemove!)
}
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
// touchesCancelled occurs e.g. when more than 5 fingers on iPhone is on the screen
// Deregistering all tracks here
self.touchTracks.removeAll()
}
}
I'm building a 2D game, the aim of the game is to jump over obstacles on the ground and flying in the air. At present the character can jump multiple times in mid air and has the ability to fly.
I'm looking to only let the character jump every second or so, is there a way to set a time delay on 'touchesBegan'. Ideally id like to be able to alter the time delay to ensure gameplay is fun yet challenging.
Below is a copy of my touchesBegan, let me know if you need more script or the below isn't quite right.
Thank you.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
cat.physicsBody!.velocity = CGVectorMake(0, 0)
cat.physicsBody!.applyImpulse(CGVectorMake(0, 20))
}
let runningBar = SKSpriteNode(imageNamed: "ground")
var velocityY = CGFloat(0)
var onGround = true
func didMoveToView(view: SKView) {
initHero()
}
func initHero() {
hero = SKSpriteNode(imageNamed: "hero")
self.heroBaseline = self.runningBar.position.y + (self.runningBar.size.height / 2) +
(self.hero.size.height / 2)
self.hero.position = CGPointMake(
CGRectGetMinX(self.frame) + (self.hero.size.width / 3) + (self.hero.size.width / 4),
self.heroBaseline)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if self.onGround {
self.velocityY = -18.0
self.onGround = false
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
if self.velocityY < -9.0 {
self.velocityY = -9.0
}
}
You need to create a baseline (ground) and limit it to that like in the code above.