UILongPressGestureRecognizer doesn't work properly - swift

I want to make right side of the screen to make the node jump, first half of the left part to left and second half to move right.
Some stuff that aren't okay:
when I tap fast left,right,left,right many times (it will stop and will not move the node)
when I tap the right part of the screen And tap of the left or right section not every time the node moves when is falling, some times the first problem will repeat
Here is an example of game with the same mechanics:
https://itunes.apple.com/us/app/staying-together/id923670329?mt=8
override func didMoveToView(view: SKView) {
/* Setup your scene here */
let tapper = UILongPressGestureRecognizer(target: self, action: "tappedScreen:");
tapper.minimumPressDuration = 0.1
view.addGestureRecognizer(tapper)
}
func jumpAction(){
if(!isJumping){
hero.physicsBody?.applyImpulse(CGVector(dx: 0, dy: ySpeed))
}
isJumping = true
}
func tappedScreen(recognizer: UITapGestureRecognizer){
if(recognizer.state == UIGestureRecognizerState.Began){
let moveLeft = SKAction.moveByX(-15, y: 0, duration: 0.1)
let moveRight = SKAction.moveByX(15, y: 0, duration: 0.1)
let touchY = self.convertPointFromView(recognizer.locationInView(self.view)).y
// only the bottom part of the screen, that way the UI button will be able to touch
if(touchY < self.frame.size.height/4){
if(touchX > 0){
println("up - longer");
} else if(touchX < -(self.frame.size.width/4)){
println("left - longer");
hero.runAction(SKAction.repeatActionForever(moveLeft), withKey: "longTap")
} else {
println("right - longer");
hero.runAction(SKAction.repeatActionForever(moveRight), withKey: "longTap")
}
}
} else {
if(touchX <= 0){
if (recognizer.state == UIGestureRecognizerState.Ended) {
println("ended, also stops the node from moving");
hero.removeActionForKey("longTap")
}
}
}
}
// I think this is need to mmove the node on single tap
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
let touch: UITouch = touches.first as! UITouch;
let location:CGPoint = touch.locationInNode(self);
let moveLeft = SKAction.moveByX(-15, y: 0, duration: 0.1)
let moveRight = SKAction.moveByX(15, y: 0, duration: 0.1)
// only the bottom part of the screen, that way the UI button will be able to touch
if(location.y < self.frame.size.height/4){
if(location.x > 0){
println("up");
self.jumpAction();
} else if(location.x < -(self.frame.size.width/4)){
hero.runAction(moveLeft);
println("left");
} else {
hero.runAction(moveRight);
println("right");
}
}
}

Its because tappedScreen has an argument that refers to UITapGestureRecognizer. you have to set it to UILongPressGestureRecognizer.
func tappedScreen(recognizer: UILongPressGestureRecognizer){
}
Hope it helps :)

Related

enumerateChildNodes not finding child nodes in sprite kit - Swift 4

I am trying to create a SpriteKit game where a ball moves across the screen. When the ball leaves the screen I would like to remove it from the parent and switch to a different scene (GameOverScene).
I am using enumerateChildNodes however it doesn't as if that is working. I am not really sure what the problem is however I think it may have something to do with the parent/child relationship...
func createBall(forTrack track: Int) {
setupTracks()
player?.physicsBody?.linearDamping = 0
player = SKSpriteNode(imageNamed: "small")
player?.name = "BALL"
player?.size = CGSize(width: 100, height: 100)
ballValue = 1
randFloat = Float(arc4random()) / Float(UINT32_MAX)
if randFloat > 0.001 {
ballSpeed = randFloat / 50
}
else {
ballSpeed = randFloat / 50
}
let ballPosition = trackArray?[track].position
player?.position = CGPoint(x: (ballPosition?.x)!, y: (ballPosition?.y)!)
player?.position.y = (ballPosition?.y)!
if ballDirection == "right" {
player?.position.x = 0
moveRight()
}
else {
player?.position.x = (self.view?.frame.size.height)!
moveLeft(speed: ballSpeed)
}
self.addChild(player!)
self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in
if node.position.x < -100 || node.position.x > (self.size.width) + 100 {
print("balls Out")
node.removeFromParent()
let transition = SKTransition.fade(withDuration: 1)
self.gameScene = SKScene(fileNamed: "GameOverScene")
self.gameScene.scaleMode = .aspectFit
self.view?.presentScene(self.gameScene, transition: transition)
}
}
}
I call this function twice, first in override func didMove():
override func didMove(to view: SKView) {
createHUD()
createBall(forTrack: track)
}
And second in override func touchesBegan:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.previousLocation(in: self)
let node = self.nodes(at: location).first
if node?.name == "BALL" {
currentScore += ballValue
player?.removeFromParent()
createBall(forTrack: track)
}
else {
let transition = SKTransition.fade(withDuration: 1)
gameScene = SKScene(fileNamed: "GameOverScene")
gameScene.scaleMode = .aspectFit
self.view?.presentScene(gameScene, transition: transition)
}
}
}
update:
The line self.enumerateChildNodes(withName: "BALL") { (node: SKNode, nil) in works so it is not a child parent relationship issue. The if statement is not working.
reading your code I think it's not about a solution, but a different approach. These suggestions will make your life easier:
Start with a smaller code, avoid or comment out all unneeded (like the if randFloat)
Force unwrap player = SKSpriteNode(imageNamed: "small")! with the ! because you actually want to crash if the initialization fails, then you can get rid of all ?
in touchesBegan, better use for touch in touches { as it is more simple to manage
let location = touch.previousLocation(in: self) you probably mean let location = touch.location(in: self)
When the ball leaves the screen I would like to remove it from the parent so this is something happening at some time in the game. You would like to call your self.enumerateChildNodes(withName: "BALL") { into the update call, not every time you touch the screen
If this is not enough, feel free to post the least amount of code to make a playground and let me test it for you :]

Move sprite forever with random postion on screen

I tried to move random my sprites (the squares) but all sprite go to same position and ×´vibrating×´.
What is the best way to move sprite forever and with random position?
//Make random shape
func makeShape () {
//Check if have more than 12 shapes
if shapesamount <= 4 {
sprite = shape.copy() as! SKShapeNode
sprite.name = "Shpae \(shapesamount)"
shapes.addChild(sprite)
shapesamount += 1
moveRandom(node: sprite)
}
}
//Move shape radmonly
func moveRandom (node: SKShapeNode) {
move = SKAction.move(to: CGPoint(x:CGFloat.random(min: frame.minX, max: frame.maxX), y:CGFloat.random(min: frame.minY
, max: frame.maxY)), duration: shapespeed)
node.run(move)
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
if istouching && !isOver {
let dt:CGFloat = 1.0/50.0
let distance = CGVector(dx: touchpoint.x - circle.position.x, dy: touchpoint.y - circle.position.y)
let velocity = CGVector(dx: distance.dx/dt, dy: distance.dy/dt)
self.circle.physicsBody!.velocity = velocity
}
if isOver == false {
for nodee in shapes.children {
moveRandom(node: nodee as! SKShapeNode)
}
}
}
}
shapes is SKNode
You are constantly adding move actions to your nodes, this is why it is vibrating. You are literally making them move in every direction every frame.
Instead, add a check to see if any actions are running, if not, then change the position:
if isOver == false {
for nodee in shapes.children {
guard !nodee.hasActions else {continue}
moveRandom(node: nodee as! SKShapeNode)
}
}

How do I rotate a colour sprite by moving finger along y-axis in the BOTTOM HALF of the screen?

Code:
for touch in touches{
let location = touch.location(in: self)
let maxTouchHeight = self.frame.midY
if currentGameType == .wobble{
main.zRotation = (location.y)/512
if location.y > maxTouchHeight { return }
}
}
}
So I am trying to rotate a paddle depending on where your finger is on the y-axis in a type of pong game. However, I have been unable to make it so that it only works in the bottom half of the screen i.e. the two extremities of rotation are at the very top and very bottom of the screen but I want the top extremity to be at the top of the bottom half of the screen. (main is a pong paddle).
Any help is appreciated :)
After followed discussion from comments and repeated answer updates, I have a solution for you. Please let me know if questions:
class GameScene: SKScene {
let paddle = SKSpriteNode()
let maximumRotation = CGFloat(45)
override func didMove(to view: SKView) {
paddle.color = .blue
paddle.size = CGSize(width: 200, height: 15)
let deg45 = CGFloat(0.785398)
let degNeg45 = -deg45
let constraint = [SKConstraint.zRotation(SKRange(lowerLimit: degNeg45, upperLimit: deg45))]
paddle.constraints = constraint
addChild(paddle)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let location = touches.first?.location(in: self) else { return }
guard let lastLocation = touches.first?.previousLocation(in: self) else { return }
guard location.y < self.frame.midY else { return }
// Our vertical plots:
let yVal = location.y
let lastY = lastLocation.y
// How much we moved in a certain direction this frame:
let deltaY = yVal - lastY
// This value represents 100% of a 45deg angle:
let oneHundredPercent = self.frame.height/2
assert(oneHundredPercent != 0)
// The % of 100%Val (45degrees) that we moved (in radians):
let absY = abs(deltaY)
let radToDegFactor = CGFloat(0.01745329252)
let multiplier = (absY / oneHundredPercent) * radToDegFactor
// I suggest a sensitivity of 2-4:
let sensitivity = CGFloat(3)
let amountToRotate = maximumRotation * (multiplier * sensitivity)
// Rotate the correct amount in the correct direction:
if deltaY > 0 {
// Rotate counter-clockwise:
paddle.run(.rotate(byAngle: amountToRotate, duration: 0))
} else {
// Rotate clockwise:
paddle.run(.rotate(byAngle: -amountToRotate, duration: 0))
}
}
}
Just open up a new project and try it out!
There may be an easier way to do this, but this is what popped in my head. It's a little verbose too so hopefully you can see each process step-by-step.
As far as doing something using the bottom half only, you can do something like this
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let fingerPosition = touch.location(in: self)
//this is the bottom
if fingerPosition.y < self.size.height/2 {
//just replace print with your own code
print("bottom half")
}
//this is the top half
if fingerPosition.y > self.size.width/2 {
//you can just leave this as it is or if ever you want to do something with the top part, just add your own code!
print("not bottom half")
}
}
}
Hopefully this helps.
put this inside of your touches functions:
for touch in touches{
let location = touch.location(in: self)
let maxTouchHeight = self.frame.midY
if location.y > maxTouchHeight { return }
if currentGameType == .wobble{
main.zRotation = (location.y)/512
}
}

Swift 3 Bullet Firing Delay

In my game, you tap anywhere on the screen and a bullet goes in that direction. The only problem is that you can shoot as fast as you can tap. Is there any way to add a delay after each shot. So I would like to shoot, wait 1 second then shoot. Here is my code in touchesEnded:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self)
//Set up initial location of bullet and properties
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.position = player.position
bullet.setScale(0.75)
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
bullet.physicsBody?.isDynamic = true
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.usesPreciseCollisionDetection = true
//Determine offset of location to bullet
let offset = touchLocation - bullet.position
//Stops Bullet from shooting backwards
if (offset.y < 0) { return }
addChild(bullet)
//Get the direction of where to shoot
let direction = offset.normalized()
//Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
//Add the shoot amount to the current position
let realDest = shootAmount + bullet.position
//Create the actions
if currentGameState == gameState.inGame {
let actionMove = SKAction.move(to: realDest, duration: 1.0)
let actionMoveDone = SKAction.removeFromParent()
bullet.run(SKAction.sequence([actionMove, actionMoveDone]))
}
}
Thanks for any help.
This is a more simple approach, based on the use of Date:
var time = Date()
func shoot(after timeInterval: Double) {
guard Date() - timeInterval > time else {
print("WAIT")
return
}
print("SHOOT")
time = Date() // reset the timer
}
// CALL THIS INSIDE touchesEnded
shoot(after: 1)
Just modify for your needs :]
You could take a look at the Throttle implementation of RxSwift for one possible solution. Throttle is used to limit the number of events created in a defined time interval:
let timeIntervalSinceLast: RxTimeInterval
if let lastSendingTime = _lastSentTime {
timeIntervalSinceLast = now.timeIntervalSince(lastSendingTime)
}
else {
timeIntervalSinceLast = _parent._dueTime
}
let couldSendNow = timeIntervalSinceLast >= _parent._dueTime
if couldSendNow {
self.sendNow(element: element)
return
}
You can do this using action keys. An action key is a string that makes an action identifiable.
How to use it in this case?
As I said already in comments, you will fire a bullet, then run an action with a key, on a specific node, which will last one second. A presence of this key/action means that weapon is locked. So every time you try to fire a bullet, you check if this key is present on a specific node. When action finishes, the key will be automatically removed as well. Here is the code:
import SpriteKit
let kLockWeaponActionKey = "kLockWeaponActionKey"
class GameScene: SKScene {
func shoot(atPoint targetLocation:CGPoint){
// 1 check if weapon is unlocked, or return
guard self.action(forKey: kLockWeaponActionKey) == nil else {
print("Weapon locked")
return
}
let bullet = SKSpriteNode(color: .purple, size: CGSize(width: 20, height: 20))
addChild(bullet)
let shoot = SKAction.move(to: targetLocation, duration: 3)
//2 shoot
bullet.run(shoot)
//3 lock weapon
self.run(SKAction.wait(forDuration: 1), withKey: kLockWeaponActionKey)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let targetLocation = touch.location(in: self)
self.shoot(atPoint:targetLocation)
}
}
}
If you try to spam bullets fast, you will see a log in the console which says : "weapon locked".

How can I remove the button that is clicked and then replace it with another, then remove again?

Ok so I'm trying to make so when Button1 is clicked, it gets removed and replaced by Button2. When Button2 gets clicked, I want it to remove button2 and replace it with button1, etc.. Here's my code I have so far (The buttons are functional so that is not the problem.)
//Sound Button1
soundButton.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height * 0.42)
soundButton.zPosition = 15
self.addChild(soundButton)
Here's the function that runs when Button1 is clicked:
func sound() {
soundButton.removeFromParent()
soundButton2.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height * 0.42)
soundButton2.zPosition = 15
self.addChild(soundButton2)
}
Here's the function that runs when Button2 is clicked:
func sound1() {
soundButton2.removeFromParent()
self.addChild(soundButton)
}
Lastly, here is my code for the buttons getting clicked in the touchesEnded function:
//Sound1 Button Pressed
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if soundButton.containsPoint(location) {
sound()
for touch: AnyObject in touches {
_ = touch.locationInNode(self)
}
}
}
//Sound1 Button Pressed
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if soundButton2.containsPoint(location) {
sound1()
for touch: AnyObject in touches {
_ = touch.locationInNode(self)
}
}
}
I'd just use the hidden property to toggle the visibility of both buttons. Set the one you want displayed to hidden = false and the one you want to hide as hidden = true
You are overcomplicating this. What you need is just to remove the button which is tapped, and add an appropriate one:
import SpriteKit
class GameScene: SKScene{
let soundButton = SKSpriteNode(color: UIColor.purpleColor(), size:CGSize(width: 200, height: 200))
let soundButton2 = SKSpriteNode(color: UIColor.orangeColor(), size:CGSize(width: 200, height: 200))
override func didMoveToView(view: SKView) {
soundButton.name = "button1"
soundButton2.name = "button2"
soundButton.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
soundButton.zPosition = 15
self.addChild(soundButton)
soundButton2.position = soundButton.position
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first
if let location = touch?.locationInNode(self){
let node = nodeAtPoint(location)
if node.name == "button1" {
node.removeFromParent()
//Not really needed for this example, but a good habit
if soundButton2.parent == nil {
addChild(soundButton2)
}
}else if node.name == "button2"{
node.removeFromParent()
//Not really needed for this example, but a good habit
if soundButton.parent == nil {
addChild(soundButton)
}
}
}
}
}
The other way way would be to subclass a SKSpriteNode and make your Button class (Button:SKSpriteNode), set its userInteractionEnabled property to true, and implement its touchesBegan/touchesEnded methods.
Or you can use SKAButton class which has a lot of different functionalities (mimics the usefulness of UIButton).