I am trying to hit a ball on the left and right side of a box with a speed depends upon the shake gesture speed/acceleration.
I am unable to get this i have tried many thing but i cannot get the speed/acceleration of gesture. I detect the gesture with core motion as well as with motionBegun and motionEnded method but unable to detect the speed so that i could create a logic of hitting the ball on both side of a box.
Here is my code that how i detect the direction of shake with CoreMotion and shake gesture detection with motionBegun and motionEnded
var startedLeftTilt = false
var startedRightTilt = false
var dateLastShake = NSDate(timeIntervalSinceNow: -2)
var dateStartedTilt = NSDate(timeIntervalSinceNow: -2)
var motionManager = CoreMotion.CMMotionManager()
let tresholdFirstMove = 3.0
let tresholdBackMove = 0.5
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
motionManager.startGyroUpdates(to: OperationQueue.current!, withHandler: { (gyroData, error) -> Void in
self.handleGyroData(rotation: (gyroData?.rotationRate)!)
})
}
private func handleGyroData(rotation: CMRotationRate) {
if fabs(rotation.z) > tresholdFirstMove && fabs(dateLastShake.timeIntervalSinceNow) > 0.3
{
if !startedRightTilt && !startedLeftTilt
{
dateStartedTilt = NSDate()
if (rotation.z > 0)
{
startedLeftTilt = true
startedRightTilt = false
}
else
{
startedRightTilt = true
startedLeftTilt = false
}
}
}
if fabs(dateStartedTilt.timeIntervalSinceNow) >= 0.3
{
startedRightTilt = false
startedLeftTilt = false
}
else
{
if (fabs(rotation.z) > tresholdBackMove)
{
if startedLeftTilt && rotation.z < 0
{
dateLastShake = NSDate()
startedRightTilt = false
startedLeftTilt = false
}
else if startedRightTilt && rotation.z > 0
{
dateLastShake = NSDate()
startedRightTilt = false
startedLeftTilt = false
}
}
}
}
override func becomeFirstResponder() -> Bool {
return true
}
override func motionBegan(_ motion: UIEventSubtype, with event: UIEvent?) {
if motion == .motionShake {
NSLog("Motion is Started");
}
}
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) {
if motion == .motionShake {
NSLog("Motion is Ended");
}
}
override func viewDidLoad() {
super.viewDidLoad()
motionManager.gyroUpdateInterval = 0.01
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Core motion has lots of options. You can use high level events like shake events, or you can get gyro or acellerometer updates.
The code you posted is monitoring gyro events. If you're looking for the force of a shake you want acellerometer events, not gyro events.
Use gyro and motion at the same time to get your trigger. Gyro will provide you the direction and motion will provide you some kind of speed analysis.
Implementing both at the same time with your custom logic will you what exactly you are trying to get.
Use the last answer for finding the direction.
StackAnswer
Related
So I have a background music looping in the background in my GameViewController. The pause music button is available in the GameScene where a user can mute or unmute the game music.
I have two global variables:
var muteButton = SKSpriteNode(imageNamed: "pause")
var mute: Bool = false
Inside my GameScene I've added, things work like they are suppose to (the print responses are triggered).
class GameScene: SKScene{
override func didMove(to view: SKView){
...
muteButton.position = CGPoint(x: self.size.width*0.2, y: self.size.height*0.90)
muteButton.name = "Mute Button"
muteButton.zPosition = 10
self.addChild(muteButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches{
let pointOfTouch = touch.location(in: self)
let nodeITapped = atPoint(pointOfTouch)
if nodeITapped.name == "Mute Button"{
if mute == false {
print("music will now turn OFF")
mute = true
}
else{
print("music will now turn ON")
mute = false
}
}
}
}
}
I suspect the mute variable is only being called once in the GameViewController viewDidLoad, and thus the if statement is being checked only once. Since I have multiple senses connected that all need to have music playing, the best place for me to put the backgroundAudio would be here.
In my GameViewController:
class GameViewController: UIViewController{
var backgroundAudio = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Background Audio plays throughout the game
let filePath = Bundle.main.path(forResource: "Track1",ofType:"mp3")
let audioNS_URL = NSURL(fileURLWithPath: filePath!)
if mute == false{
do{ backgroundAudio = try AVAudioPlayer(contentsOf: audioNS_URL as URL)}
catch { return print("No Audio Found")}
// audio will loop forever
backgroundAudio.numberOfLoops = -1
backgroundAudio.play()
}
else{
backgroundAudio.pause()
}
}
}
Add an observer inside GameViewController:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(switchBackgroundAudio), name: NSNotification.Name.init("switchBackgroundAudio"), object: nil)
//... other stuff here ...//
}
then add a function for switching the sound on/off:
#objc func switchBackgroundAudio() {
if mute == false {
backgroundAudio.play()
} else {
backgroundAudio.pause()
}
}
finally whenever inside your GameScene you touch the button, you may launch an event:
if nodeITapped.name == "Mute Button"{
//... stuff here ...//
NotificationCenter.default.post(Notification(name: NSNotification.Name("switchBackgroundAudio")))
}
I have seen multiple questions similar to this; however, none of them resolved my problem. My timer is simply going too slowly. I am only using an interval of 0.01 seconds. Here is my code:
#IBOutlet var timerLabel: UILabel!
var miliseconds = 0
var seconds = 0
func updateLabel() {
if miliseconds == 0 {
timerLabel.text = "\(seconds).00"
} else if miliseconds < 10 {
timerLabel.text = "\(seconds).0\(miliseconds)"
} else {
timerLabel.text = "\(seconds).\(miliseconds)"
}
}
var timer = NSTimer()
func updateTime() {
miliseconds++
if miliseconds == 100 {
miliseconds = 0
seconds++
}
updateLabel()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if timerState == 1 {
timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "updateTime", userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
timerLabel.textColor = UIColor.blackColor()
timerState = 2
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
if timerState == 0 {
miliseconds = 0
seconds = 0
updateLabel()
timerLabel.textColor = UIColor.greenColor()
timerState = 1
} else if timerState == 2 {
timerState = 0
timer.invalidate()
}
}
var timerState = 0
//timerState of 0 = Has not started
//timerState of 1 = About to start
//timerState of 2 = Timing
I have also tried using delays:
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure)
}
I called updateTime in viewDidLoad, and at the end of updateTime, I added:
delay(0.01) { () -> () in
self.updateTime()
}
However, it still went at the same speed as before.
How can I fix this issue? If I missed a question while researching, please let me know. Thanks!
There is a whole mess of problems here. Let's clean this up.
class ViewController: UIViewController {
#IBOutlet var timerLabel: UILabel!
First of all, don't use a timer to update the label. Use a CADisplayLink. A display link synchronizes with the screen refresh interval, which is 1/60 of a second on most iOS devices (not 1/100), so you don't do extra work:
private var displayLink: CADisplayLink?
Next, don't try to track the elapsed time by incrementing a counter when the timer (or link) fires, because the timer or link is not guaranteed to fire as often as you requested. Instead, store the time at which the timer was started, and the time at which it was stopped (if it was stopped):
private var startTime: CFAbsoluteTime = 0
private var endTime: CFAbsoluteTime = 0 {
didSet {
updateLabel()
}
}
Track the state using an enum instead of mysterious hard-coded numbers. And since the label color depends only the state, add a property to the state that gives the label color for that state:
private enum State {
case Stopped
case Pending
case Running
var labelColor: UIColor {
switch self {
case .Pending: return UIColor.greenColor()
case .Stopped, .Running: return UIColor.blackColor()
}
}
}
The elapsed time depends on the state, so add a method to compute it:
private var elapsedTime: NSTimeInterval {
switch state {
case .Stopped: return endTime - startTime
case .Running: return CFAbsoluteTimeGetCurrent() - startTime
case .Pending: return 0
}
}
Use a format string to convert the elapsed time to a string when updating the label:
private func updateLabel() {
timerLabel.text = String(format: "%.02f", elapsedTime)
}
Changing the timer state can change both the label color and the elapsed time, so update the label color and the label text when the state changes:
private var state = State.Stopped {
didSet {
timerLabel.textColor = state.labelColor
updateLabel()
}
}
When a touch begins, create the display link if needed, then update the state. The state's didSet will handle updating the label as necessary:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
createDisplayLinkIfNeeded()
switch state {
case .Stopped:
state = .Pending
case .Pending:
break
case .Running:
state = .Stopped
endTime = CFAbsoluteTimeGetCurrent()
displayLink?.paused = true
}
}
When a touch ends, start the timer if necessary:
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
if state == .Pending {
startTime = CFAbsoluteTimeGetCurrent()
displayLink?.paused = false
state = .Running
}
}
Here's how you create the display link:
private func createDisplayLinkIfNeeded() {
guard self.displayLink == nil else { return }
let displayLink = CADisplayLink(target: self, selector: "displayLinkDidFire:")
displayLink.paused = true
displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
self.displayLink = displayLink
}
And here's the method the display link will call:
func displayLinkDidFire(_: CADisplayLink) {
updateLabel()
}
} // end of ViewController
From my comment above ... (with encouragement)
NSTimer - from apple developer docs:
"A timer is not a real-time mechanism; it fires only when one of the
run loop modes to which the timer has been added is running and able
to check if the timer’s firing time has passed. Because of the various
input sources a typical run loop manages, the effective resolution of
the time interval for a timer is limited to on the order of 50-100
milliseconds. "
While a player is touching, the code below is suppose to move a node to the MaxX. Once they hit that point it is suppose to redirect the node's movement towards the MinX and vice versus.
As it stands right now, I can only move the player in once direction and it is ignoring the movePlayer() logic.
Action Declarations
var moveRightTest:Bool = true;
var moveLeftTest:Bool = false;
let moveRight = SKAction.moveByX(1000, y: 0, duration: 2);
let moveLeft = SKAction.moveByX(-1000, y: 0, duration: 2);
Inside the GameScene:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
movePlayer();
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
stopPlayer();
}
func movePlayer() {
if(moveRightTest == true) {
if(player.position.x <= CGRectGetMaxX(self.frame)) {
player.runAction(moveRight);
} else {
moveRightTest = false;
moveLeftTest = true;
}
}
else if(moveLeftTest == true) {
if(player.position.x >= CGRectGetMinX(self.frame)) {
player.runAction(moveLeft);
} else {
moveLeftTest = false;
moveRightTest = true;
}
}
}
func stopPlayer() {
player.removeAllActions();
}
Note: I subbed out some vars with ints/floats to remove the calculation from the code in order to present. Its not related to the issue. Im also just learning Swift.
Now your function movePlayer is performed only once when you're calling it, so the player comes to the right edge and stops. You need to repeat it after each run the following steps. To do this, you can use runAction with completion like this:
isRight: Bool = true
let moveRight = SKAction.moveToX(CGRectGetMaxX(self.frame), duration: 2)
let moveLeft = SKAction.moveToX(CGRectGetMinX(self.frame), duration: 2)
func movePlayer() {
if isRight {
player.runAction(moveRight, completion: { () -> Void in
isRight = false
self.movePlayer()
})
} else {
player.runAction(moveLeft, completion: { () -> Void in
isRight = true
self.movePlayer()
})
}
}
I have a UITableView with row where I added single tap and double tap gestures:
let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTap:")
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
let singleTap = UITapGestureRecognizer(target: self, action: "singleTap:")
singleTap.numberOfTapsRequired = 1
singleTap.numberOfTouchesRequired = 1
singleTap.requireGestureRecognizerToFail(doubleTap)
tableView.addGestureRecognizer(doubleTap)
tableView.addGestureRecognizer(singleTap)
Is there a way to reduce the time between when the first tap is made and when the gesture recognizer realize that it is a single tap and not a double tap?
I'm asking this because when I do a single tap, the new viewController appear quite late, giving a feeling that the app lags.
I found the answer on this link
The swift version:
class UIShortTapGestureRecognizer: UITapGestureRecognizer {
let tapMaxDelay: Double = 0.3
override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
super.touchesBegan(touches, withEvent: event)
delay(tapMaxDelay) {
// Enough time has passed and the gesture was not recognized -> It has failed.
if self.state != UIGestureRecognizerState.Ended {
self.state = UIGestureRecognizerState.Failed
}
}
}
}
With delay(delay: Double, closure:()->()):
class func delay(delay:Double, closure:()->()) {
dispatch_after(dispatch_time( DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), closure)
}
Full Implementation of Markus's Swift 3 version of eladleb's original solution.
Create subclass file UIShortTapGestureRecogninzer
import UIKit
import UIKit.UIGestureRecognizerSubclass
class UIShortTapGestureRecognizer: UITapGestureRecognizer {
let tapMaxDelay: Double = 0.3 //anything below 0.3 may cause doubleTap to be inaccessible by many users
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
DispatchQueue.main.asyncAfter(deadline: .now() + tapMaxDelay) { [weak self] in
if self?.state != UIGestureRecognizerState.recognized {
self?.state = UIGestureRecognizerState.failed
}
}
}
}
Note: When adding UIGestureRecognizer only doubleTap needs to be of type UIShortTapGestureRecognizer & singleTap.require(toFail: doubleTap) is required.
func addBoth (views: UIView, selectorSingle: Selector, selectorDouble: Selector) {
let doubleTap:UIShortTapGestureRecognizer = UIShortTapGestureRecognizer(target: self, action: selectorDouble)
doubleTap.numberOfTapsRequired = 2
views.addGestureRecognizer(doubleTap)
let singleTap:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: selectorSingle)
singleTap.numberOfTapsRequired = 1
singleTap.require(toFail: doubleTap)
views.addGestureRecognizer(singleTap)
}
Swift 5 implementation of Nico's accepted answer.
class UIShortTapGestureRecognizer: UITapGestureRecognizer {
var maximumTapLength: Double = 0.3
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
delay(delay: maximumTapLength) {
// Enough time has passed and the gesture was not recognized -> It has failed.
if self.state != .ended {
self.state = .failed
}
}
}
func delay(delay:Double, closure:#escaping ()->()) {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: closure)
}
}
for future, full implementation by Howard Yang, here's links:
https://github.com/oney/SingleDoubleTapGestureRecognizer
let tap = SingleDoubleTapGestureRecognizer(target: self, singleAction: Selector("singleTap"), doubleAction: Selector("doubleTap"))
tap.duration = 0.8
view.addGestureRecognizer(tap)
https://github.com/oney/SingleDoubleTapGestureRecognizer/blob/master/Pod/Classes/SingleDoubleTapGestureRecognizer.swift
//
// SingleDoubleTapGestureRecognizer.swift
// SingleDoubleTapGestureRecognizer
//
// Created by Howard Yang on 08/22/2015.
// Copyright (c) 2015 Howard Yang. All rights reserved.
//
import UIKit
public class SingleDoubleTapGestureRecognizer: UITapGestureRecognizer {
var targetDelegate: SingleDoubleTapGestureRecognizerDelegate
public var duration: CFTimeInterval = 0.3 {
didSet {
self.targetDelegate.duration = duration
}
}
public init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
targetDelegate = SingleDoubleTapGestureRecognizerDelegate(target: target, singleAction: singleAction, doubleAction: doubleAction)
super.init(target: targetDelegate, action: Selector("fakeAction:"))
numberOfTapsRequired = 1
}
}
class SingleDoubleTapGestureRecognizerDelegate: NSObject {
var target: AnyObject
var singleAction: Selector
var doubleAction: Selector
var duration: CFTimeInterval = 0.3
var tapCount = 0
init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
self.target = target
self.singleAction = singleAction
self.doubleAction = doubleAction
}
func fakeAction(g: UITapGestureRecognizer) {
tapCount = tapCount + 1
if tapCount == 1 {
delayHelper(duration, task: {
if self.tapCount == 1 {
NSThread.detachNewThreadSelector(self.singleAction, toTarget:self.target, withObject: g)
}
else if self.tapCount == 2 {
NSThread.detachNewThreadSelector(self.doubleAction, toTarget:self.target, withObject: g)
}
self.tapCount = 0
})
}
}
typealias DelayTask = (cancel : Bool) -> ()
func delayHelper(time:NSTimeInterval, task:()->()) -> DelayTask? {
func dispatch_later(block:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(time * Double(NSEC_PER_SEC))),
dispatch_get_main_queue(),
block)
}
var closure: dispatch_block_t? = task
var result: DelayTask?
let delayedClosure: DelayTask = {
cancel in
if let internalClosure = closure {
if (cancel == false) {
dispatch_async(dispatch_get_main_queue(), internalClosure);
}
}
closure = nil
result = nil
}
result = delayedClosure
dispatch_later {
if let delayedClosure = result {
delayedClosure(cancel: false)
}
}
return result;
}
func cancel(task:DelayTask?) {
task?(cancel: true)
}
}
I'd rather recommend to use canBePrevented(by:) function, it takes in account amount of taps to perform and won't run you double-tap gesture recognizer unless first one is recognized/failed.
canBePrevented(by:)
Inspired by Howard Yang's implementation, Swift 5.1 using DispatchWorkItem:
public class SingleDoubleTapGestureRecognizer: UITapGestureRecognizer {
var targetDelegate: SingleDoubleTapGestureRecognizerDelegate
public var timeout: TimeInterval = 0.3 {
didSet {
self.targetDelegate.timeout = timeout
}
}
public init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
targetDelegate = SingleDoubleTapGestureRecognizerDelegate(target: target, singleAction: singleAction, doubleAction: doubleAction)
super.init(target: targetDelegate, action: #selector(targetDelegate.recognizerAction(recognizer:)))
}
}
class SingleDoubleTapGestureRecognizerDelegate: NSObject {
weak var target: AnyObject?
var singleAction: Selector
var doubleAction: Selector
var timeout: TimeInterval = 0.3
var tapCount = 0
var workItem: DispatchWorkItem?
init(target: AnyObject, singleAction: Selector, doubleAction: Selector) {
self.target = target
self.singleAction = singleAction
self.doubleAction = doubleAction
}
#objc func recognizerAction(recognizer: UITapGestureRecognizer) {
tapCount += 1
if tapCount == 1 {
workItem = DispatchWorkItem { [weak self] in
guard let weakSelf = self else { return }
weakSelf.target?.performSelector(onMainThread: weakSelf.singleAction, with: recognizer, waitUntilDone: false)
weakSelf.tapCount = 0
}
DispatchQueue.main.asyncAfter(
deadline: .now() + timeout,
execute: workItem!
)
} else {
workItem?.cancel()
DispatchQueue.main.async { [weak self] in
guard let weakSelf = self else { return }
weakSelf.target?.performSelector(onMainThread: weakSelf.doubleAction, with: recognizer, waitUntilDone: false)
weakSelf.tapCount = 0
}
}
}
}
Usage:
let singleDoubleTapRecognizer = SingleDoubleTapGestureRecognizer(
target: self,
singleAction: #selector(handleTapGesture),
doubleAction: #selector(handleDoubleTapGesture)
)
I am currently learning Apple's new software programming language (Swift) and I have encountered this error:
Gamescene does not have a member named "waitEvent"
This is becoming quite frustrating, if anyone knows why this might be please respond.
This is in Swift and Sprite Kit!
Here is the code:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if (destroyerNode? != nil){
destroyerNode!.position = location
destroyerNode!.physicsBody?.velocity = CGVectorMake(0, 0)
destroyerNode!.physicsBody?.dynamic = true
let waitAction = SKAction.waitForDuration(2)
destroyerNode!.runAction(waitAction, completion: {
self.waitEvent()
//there is an issue with someEvent
}
)
}
else{
println("that destroyer was really not there")
}
}
func waitEvent(){
//make the destroyer a dynamic object
destroyerNode!.makeBodyDynamic()
println("The wait is up")
}
func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}