I am really really frustrated by this and I have been trying to get it to work for almost two full days now :(
How do I go about adding a click event to a SKSpriteNode in swift
let InstantReplay:SKSpriteNode = SKSpriteNode(imageNamed: "InstantReplay")
InstantReplay.position = CGPoint(x: size.width + InstantReplay.size.width/2, y: size.height/2)
InstantReplay.size = CGSize(width: size.width/1.4, height: size.height/8)
addChild(InstantReplay)
InstantReplay.runAction(SKAction.moveToX(size.width/2, duration: NSTimeInterval(1)))
All I want to happen is when "InstantReplay" is clicked for it to run a function called "InstantReplay_Clicked", How would I go about Implementing this?
Any help much appreciated :)
Give your SKSpriteNode a name so we can identify it in the touchesBegan or touchesEnded method of your SKScene.
Also set userInteractionEnabled of your SKSpriteNode to false, so that it doesn't trap touch events for itself, and not pass them up to the your Scene.
override init() {
super.init()
let instantReplay = SKSpriteNode(imageNamed: "InstantReplay")
instantReplay.position = CGPoint(x: size.width + instantReplay.size.width/2, y: size.height/2)
instantReplay.size = CGSize(width: size.width/1.4, height: size.height/8)
instantReplay.name = "InstantReplay"; // set the name for your sprite
instantReplay.userInteractionEnabled = false; // userInteractionEnabled should be disabled
self.addChild(instantReplay)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let location = touches.anyObject()?.locationInNode(self)
let node = self.nodeAtPoint(location!)
if (node.name == "InstantReplay") {
println("you hit me with your best shot!")
}
}
(oh - i also renamed your instantReplay variable to use lowerCamelCase, in line with Swift best practices.
Swift 4 Update
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for t in touches {
let node = self.atPoint(t.location(in :self))
if (node.name == "InstantReplay") {
print("you hit me with your best shot!")
}
}
}
Related
So I have these moving sprites with physics bodies that I initially have constrained to one area. Let's say call this area Box1. When the sprites bump the borders of Box1, they bump back and the SKConstraint is just an additional measure to make sure they stay within the box.
What I'm trying to do is make a button that causes all the sprites in Box1 move across the screen to a second box (Box2) that is basically identical to Box1 but right beside it. Box1 and Box2 share a border wall, so essentially the screen is divided in two sections.
My problem is when I call up my button function, the sprites get stuck at the wall and aren't able to go past it to Box2. I've tried setting both the physics bodies and the SKConstraints of the nodes to nil so I don't know what else is preventing them.
func moveSprites(){
if box1Sprites.isEmpty == false {
for sprite in box1Sprites{
sprite.constraints = nil
sprite.physicsBody = nil
let moveAction = SKAction.move(to: CGPoint(x: -300, y: 0), duration: 1) //This is a point within Box2
sprite.run(moveAction)
}
}
}
EDIT:
I also made it so that I could drag and drop the sprites from one box to the other and the code for that is pretty much the same and it works fine.
var currentMovingSprite: SKSpriteNode? = SKSpriteNode()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node.name == "sprite"{
node.constraints = nil
node.physicsBody = nil
currentMovingSprite = node
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let pos = touch.location(in: self)
if let sprite = currentMovingSprite{
sprite.position = pos
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
spritePhysics(sprite: currentMovingSprite) // resets the sprite to how it was before
currentMovingSprite = nil
}
I am trying to implement a pause button in my game. However every time I tap the pause button (SKSpriteNode) on my iOS Device nothing happens. I have tried making the button do other actions and tried making other sprites do the action. None have worked, although I am able to touch any location on the screen and the action is performed. I am using Swift 4 with the latest version of Xcode (9.4.1). The app is a iOS Game and I am using the GameScene.swift that is created along with the app.
Here is the part of the code for the button (irrelevant parts of the code are left out):
import SpriteKit
import CoreMotion
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var pauseButton:SKSpriteNode!
override func didMove(to view: SKView) {
pauseButton = SKSpriteNode(imageNamed: "pauseButton")
pauseButton.size = CGSize(width: 50, height: 50)
pauseButton.position = CGPoint(x: self.size.width -
pauseButton.size.width, y: self.size.height -
pauseButton.size.height)
pauseButton.zPosition = 5
self.addChild(pauseButton)
}
override func touchesEnded(_ touches: Set<UITouch>, with: UIEvent?) {
fireBullet() //This function does not relate to the pause button.
}
override func touchesBegan(_ touches: Set<UITouch>, with event:
UIEvent?) {
let touch = touches.first
if let location = touch?.location(in: self) {
let nodesArray = self.nodes(at: location)
if nodesArray.first?.name == "pauseButton" {
self.view?.isPaused = true
}
}
}
}
Thanks in advance for taking your time to reply it really helps me!
Thomas
Easy fix, in your touchesBegan method you are searching for touches on a node named "pauseButton" but there are no nodes named "pauseButton" so your search returns a false value.
Just add pauseButton.name = "pauseButton" to your code where you set up the pause button and it should work.
How do I rotate a SpriteNode with a one finger “touch and drag” so that:
It doesn’t jerk around
It moves in a circle (I’ve successfully accomplished this part several times- both with a code only solution and with an SKS file)
It produces a meaningful value (as a physical control knob would)
It moves while my finger is on it but not when my finger is off
The things I’ve tried:
Using CGAffineTransform’s CGAffineTransformMakeRotation to effect a rotation of the knob in SpriteKit. But I cannot figure out how to use CGAffineTransformMakeRotation on a SpriteNode. I could put a different sort of object into my Scene or on top of it, but that’s just not right.
For example, Matthijs Hollemans’ MHRotaryKnob https://github.com/hollance/MHRotaryKnob
. I translated Hollemans knob from Objective C to Swift 4 but ran into trouble attempting to use it in SpriteKit to rotate sprites. I didn’t get that because I could not figure out how to use knobImageView.transform = CGAffineTransformMakeRotation (newAngle * M_PI/180.0); in Swift with SpriteKit. I know I could use Hollemans Objective C class and push a UIImage over the top of my scene, but that doesn’t seem like the best nor most elegant solution.
I also translated Wex’s solution from Objective C to Swift
Rotate image on center using one finger touch
Using Allan Weir’s suggestions on dealing with the CGAffineTransform portions of the code https://stackoverflow.com/a/41724075/1678060 But that doesn’t work.
I've tried setting the zRotation on my sprite directly without using .physicalBody to no avail. It has the same jerky movement and will not stop where you want it to stop. And moves in the opposite direction of your finger drag- even when you put the '-' in front of the radian angle.
I’ve also tried 0x141E’s solution on Stack Overflow:
Drag Rotate a Node around a fixed point This is the solution posted below using an .sks file (somewhat modified- I've tried the un-modified version and it is no better). This solution jerks around, doesn’t smoothly follow my finger, cannot consistently move the knob to a specific point. Doesn’t matter if I set physicsBody attributes to create friction, mass, or angularDamping and linearDamping or reducing the speed of the SKSpriteNode.
I have also scoured the Internet looking for a good solution in Swift 4 using SpriteKit, but so far to no avail.
import Foundation
import SpriteKit
class Knob: SKSpriteNode
{
var startingAngle: CGFloat?
var currentAngle: CGFloat?
var startingTime: TimeInterval?
var startingTouchPoint: CGPoint?
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.setupKnob()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupKnob()
}
func setupKnob() {
self.physicsBody = SKPhysicsBody(circleOfRadius: CGFloat(self.size.height))
self.physicsBody?.pinned = true
self.physicsBody?.isDynamic = true
self.physicsBody?.affectedByGravity = false
self.physicsBody?.allowsRotation = true
self.physicsBody?.mass = 30.0
//self.physicsBody?.friction = 0.8
//self.physicsBody?.angularDamping = 0.8
//self.physicsBody?.linearDamping = 0.9
//self.speed = 0.1
self.isUserInteractionEnabled = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in:self)
let node = atPoint(location)
startingTouchPoint = CGPoint(x: location.x, y: location.y)
if node.name == "knobHandle" {
let dx = location.x - node.position.x
let dy = location.y - node.position.y
startingAngle = atan2(dy, dx)
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches{
let location = touch.location(in:self)
let node = atPoint(location)
guard startingAngle != nil else {return}
if node.name == "knobHandle" {
let dx:CGFloat = location.x - node.position.x
let dy:CGFloat = location.y - node.position.y
var angle: CGFloat = atan2(dy, dx)
angle = ((angle) * (180.0 / CGFloat.pi))
let rotate = SKAction.rotate(toAngle: angle, duration: 2.0, shortestUnitArc: false)
self.run(rotate)
startingAngle = angle
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
var touch: UITouch = touches.first!
var location: CGPoint = touch.location(in: self)
self.removeAllActions()
startingAngle = nil
startingTime = nil
}
}
Edit: If I remove the conversion to degrees and change the duration of the SKAction to 1.0 in SKAction.rotate(toAngle:duration: 1.0, shortestUnitArc:) then it almost works: not as jerky, but still jerks; the lever doesn't change directions well- meaning sometimes if you attempt to move it opposite of the direction it was traveling it continues to go the old direction around the anchorPoint instead of the new direction you're dragging it.
Edit 2: GreatBigBore and I discussed both the SKAction rotation and the self.zRotation- the code above and the code below.
Edit 3: sicvayne suggested some code for the SKScene and I've adapted to SKSpriteNode (below). It doesn't move consistently or allow to you stop in a specific place.
import Foundation
import SpriteKit
class Knob: SKSpriteNode {
var fingerLocation = CGPoint()
override init(texture: SKTexture?, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.setupKnob()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupKnob()
}
func setupKnob() {
self.isUserInteractionEnabled = true
}
func rotateKnob(){
let radians = atan2(fingerLocation.x - self.position.x, fingerLocation.y - self.position.y)
self.zRotation = -radians
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
fingerLocation = touch.location(in: self)
}
self.rotateKnob()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
}
/*override func update(_ currentTime: TimeInterval) { //this is a SKScene function
rotateKnob()
}*/
}
The Math was wrong. Here's what I learned you need:
How this looks in swift:
if point.x.sign == .minus {
angle = atan(point.y/point.x) + CGFloat.pi/2
} else {
angle = atan(point.y/point.x) + CGFloat.pi/2 + CGFloat.pi
}
Also, you have to get the coordinates of another object in the scene because the entire coordinate system rotates with the object:
let body = parent?.childNode(withName: "objectInScene")
let point = touch.location(in: body!)
I usually do something like this without any jittering or jerking issues.
var fingerLocation = CGPoint()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch: AnyObject in touches {
fingerLocation = touch.location(in: self)
}
}
func rotatePlayer(){
let radians = atan2(fingerLocation.x - playerNode.position.x, fingerLocation.y - playerNode.position.y)
playerNode.zRotation = -radians//this rotates the player
}
override func update(_ currentTime: TimeInterval) {
rotatePlayer()
}
Depending on how your images are facing, you're probably going to have to mess around with the radians. In my case, my "player" image is facing upwards. Hope this helped.
I'm currently working on a game in swift using spritekit. I've got my joystick working using this library, and thats all good. I'm also moving the joystick to where the user taps using this code:
func updateJoystickPosition(pos: CGPoint) {
let posX = -(self.sceneSize.width/2) + pos.x
let posY = (self.sceneSize.height/2) - pos.y
let newJoystickPos = CGPoint(x: posX, y: posY);
self.joystick.position = newJoystickPos
}
which also works just fine, however, the joystick doesn't engage on that one tap. You have to tap on the actual joystick node for the joystick to engage, so obviously, i'd want the user to put their finger down on the screen and immediately be able to start moving it around to move the player.
the joystick it's self starts tracking with this function
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(touches)
if let touch = touches.first, stick == atPoint(touch.location(in: self)) {
tracking = true
startHandler?()
}
}
Question: Is there anyway i can modify the touchesBegan function or any of my other functions to achieve the desired results?
You have to override touchesMoved(_ touches: ,with event:) and in this method just processing all touches.
For example:
var startTouchingPoint = CGPoint.zero // property
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
startTouchingPoint = touch.location(in: self.camera!))
yourControllerPlace.position = startTouchingPoint
stickOnYourJoystick.position = CGPoint.zero
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let point = touch.location(in: self.camera!))
let convertedPoint = CGPoint(x: point.x - startTouchingPoint.x,
y: point.y - startTouchingPoint.y)
stickOnYourJoystick.position = convertedPoint
}
}
In my main view in a UIViewController I have a mapView and a another view (Let's say view A) that is above mapView. Both of them have frames equal to self.view.bounds. The view A is a rectangle that is resizable similar to those used to crop images. My goal here is to let the user specify an area on the map. So, I want the user to be able to zoom in an out of the map as well as change the rectangle width and height proportions since only letting the view A to be an unrealizable square would limit it too much.
I got this project from GitHub https://github.com/justwudi/WDImagePicker from which I am using the resizable rectangle functionality. In the second picture of the Github link, there's a rectangle with 8 dots and a shaded area outside. I want to be able to let the touch pass to the map which is behind the view A if the user touches on the shaded area. Only if the user clicks on the area inside the dots or on the dots (so that he wants to resize the rectangle) I want the view A to recognize the touch. So, I modified the code on touch on the view A and have this:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if cropBorderView.frame.contains(touch.location(in: self)){
print("touch contains - touchesbegan")
//self.isUserInteractionEnabled = true
}
else{
print("Touch does not contain - touchesbegan")
self.touchesCancelled(touches, with: event)
//return
}
let touchPoint = touch.location(in: cropBorderView)
anchor = self.calculateAnchorBorder(touchPoint)
fillMultiplyer()
resizingEnabled = true
startPoint = touch.location(in: self.superview)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("inside touches moved")
if let touch = touches.first {
if cropBorderView.frame.contains(touch.location(in: self)){
print("touch contains - touchesmoved")
//self.isUserInteractionEnabled = true
}
else{
print("Touch does not contain - touchesmoved ")
self.touchesCancelled(touches, with: event)
//return
}
if resizingEnabled! {
self.resizeWithTouchPoint(touch.location(in: self.superview))
}
}
}
It is indeed recognizing the touch when I click inside and outside as I wanted, but it is not stopping the touch when I click outside. This means calling self.touchesCancelled(touches, with: event) is not working. Calling return gives a crash and does not work as well. Are there any solutions to this problem?
Thank you for your time and consideration.
touchesCancelled(_:with:) just a notification for UITouch, it will not work this way.
As far as I understand, you implemented touch handlers in your overlay UIView, if so, you can try to replace the call to self.touchesCancelled(touches, with: event) with cancelTracking(with:) function from UIControl class implementation:
else {
print("Touch does not contain - touchesmoved ")
self.cancelTracking(with event)
}
Update solution, based on hitTest:
I've checked possible solutions and it seems that you can use hitTest: to avoid unnecessary touch recognitions. The following example is Swift Playground, you can tap and drag touches and see what happens in the console:
import UIKit
import PlaygroundSupport
class JSGView : UIView {
var centerView = UIView()
override func didMoveToSuperview() {
frame = CGRect(x: 0, y: 0, width: 320, height: 480)
backgroundColor = UIColor.clear
centerView.frame = CGRect(x: 110, y: 190, width: 100, height: 100)
centerView.backgroundColor = UIColor.blue
addSubview(centerView)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
dump(event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if (hitTest(touch.location(in: self), with: event) != nil) {
print("Touch passed hit test and seems valid")
super.touchesCancelled(touches, with: event)
return
}
}
print("Touch isn't passed hit test and will be ignored")
super.touchesMoved(touches, with: event)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if centerView.bounds.contains(centerView.convert(point, from: self)) {
return centerView
}
return nil
}
}
class JSGViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
let customView = JSGView()
view.addSubview(customView)
}
}
let controller = JSGViewController()
PlaygroundPage.current.liveView = controller.view