How to keep multiple SKSpriteNode loosely together - ios

How can I ensure the cards always stay close to each other as shown below?
The player may pick one or more cards, and I need to ensure the remaining cards snap back together. Also if the player abandons picking (thats is draws out and releases), they need to come back together.
What is the best way to go about building this?

I do not know about a "best way", but one way would be to have a place holder node, and when your card is not at (0,0) on the place holder, send it back.
example:
class PlayingCard : SKSpriteNode
{
override var position : CGPoint
{
didSet
{
if self.position != CGPoint.zero, actionForKey("reset") == nil
{
self.run(SKAction.moveTo(CGPoint.zero,duration:0.5).withKey:"reset")
}
}
}
}
To use:
class GameScene
{
.....
//however you are setting this up
let placeHolder = SKNode()
placeHolder = placeHolderPosition
let card = PlayingCard()
placeHolder.addChild(card)
self.addChild(placeHolder)
....
}
Now keep in mind, depending on how you are moving your card, the didSet may not be getting called on it, so you may have to do this in the update, or you can cheat and in update just do:
func update()
{
...
// do my stuff
...
//maintain an array of all your playing cards
playingCards.forEach{$0.position = $0.position}
}

Related

Detecting when a SKNode is tapped on Apple Watch

I'm writing an app for Apple Watch using SpriteKit, so I don't have access to functions like touchesBegan and I have to use a WKTapGestureRecognizer to detect taps, no big deal, but I have issues detecting taps on a node.
In my InterfaceController I have:
#IBAction func handleTap(tapGestureRecognizer: WKTapGestureRecognizer){
scene?.didTap(tapGesture: tapGestureRecognizer)
}
And in my Scene file I have
func didTap(tapGesture:WKTapGestureRecognizer) {
let position = tapGesture.locationInObject()
let hitNodes = self.nodes(at: position)
if hitNodes.contains(labelNode) {
labelNode.text = "tapped!"
}
Problem is the Tap Gesture Recognizer gives me the absolute coordinates of the touch point (for example 11.0, 5,0) while my node is positioned relatively to the center of the screen (so its position is -0.99,-11.29 even though is at the center of the screen) therefore the tap is hitting the node not when actually tapping it, but when I tap on the top left of the screen. I searched everywhere and it looks like this is the way to do it yet I don't find people having the same issues. The node has been added via the editor. What am I doing wrong?
So you have the right idea. You are getting this wrong because hitNodes is an array of SKNodes. Those are newly created. So when you use hitNodes.contains the addresses of the labelNode and the address of the newly created SKNode that is being compared would be completely different. Therefore it would never be tapped.
Here's what I would do. This would be in my Scene File. Your InterfaceController class is correct.
func didTap(tapGesture:WKTapGestureRecognizer) {
let position = tapGesture.locationInObject()
if labelNode.contains(position) {
labelNode.text = "tapped!"
}
}
OR another way would be this. I like this way because you only have one function which would be in the WKInterfaceControlller And you would need no functions in your Scene File.
#IBAction func tapOnScreenAct(_ sender: WKGestureRecognizer) {
if scene.labelNode.contains(sender.locationInObject()) {
scene.labelNode.text = "tapped!"
}
}
Either way, both should work. Let me know if you have any more questions or clarifications.

Issue with pass by value vs pass by reference in Swift?

I'm new to Swift but not new to iOS dev. I'm having an issue that I believe is caused by pass by value.
Below code is in the VC and works fine when tapping each button, i.e. each button changes background to show if it's selected or not.
button.isSelected = card.isChosen // button is selected when the card is chosen
if button.isSelected {
button.setBackgroundColor(color: UIColor.darkGray, forState: UIControlState.selected)
}
I have an issue in the game logic which is a struct. Struct contains an array of type Card which is really just a deck of cards and maps back to the UIButtons. When a card(UIButton) in the VC is touched, it calls a method (chooseCard) in the Struct. Among other things, this action will toggle if the card is selected by setting the card.isSelected = !card.isSelected and maps back to the UI to indicate if the card is selected or not. All of this works fine so far.
When 3 cards have been selected, it calls some match logic, which is currently just stubbed out. If there was no match, each of the cards .isSelected is set to false so that in the UI those cards will no longer appear selected. This is what is not working and I can't figure out why. My hunch is that I'm doing something wrong in the way I'm dealing with pass by value since the logic object is a struct. After 3 cards are selected and they are not matched (they won't since it's only stubbed), each card.isSelected should be set back to false, but they are still showing selected in the UI. I set a breakpoint in the VC and it shows the card.isSelected is still set to true. VC has has a setGame property which has access to the array of cards. Here is the relevant code...any help is appreciated!
struct SetGame {
private(set) var cards = [Card]()
mutating public func chooseCard(at index: Int) {
//TODO: Assertion here for index being < cards.count
print("chooseCard(at index: \(index)")
cards[index].isChosen = !cards[index].isChosen // toggle isChosen when card is selected
if !cards[index].isMatched && cards[index].isChosen {
//print("Card not matched, so we're good to go...")
for var card in cards {
if card.isChosen {
matchingCards.append(card)
// see if we have enough cards to match
if matchingCards.count > 2 {
//TODO: Need to call match logic here and set each matched card to .isMatched = true if matched
if card.isMatched {
print("Yay, matched!")
} else {
print("Card is not matched, flipping back over")
/*** THIS LINE NOT REFLECTING IN THE UI! ***/
card.isChosen = !card.isChosen // flip the unmatched card back over
}
}
}
}
matchingCards.removeAll() // clear out all the cards from the matching
} else {
print("Card is either matched or being deselected...")
}
}
Your problem is that Card is a struct, so this line:
for var card in cards {
creates a copy of each card in cards, so setting any properties on that copy will not modify the card in your cards array.
To fix this, loop over the indices of the array and refer to the cards as cards[idx]:
struct SetGame {
private(set) var cards = [Card]()
mutating public func chooseCard(at index: Int) {
//TODO: Assertion here for index being < cards.count
print("chooseCard(at index: \(index)")
cards[index].isChosen = !cards[index].isChosen // toggle isChosen when card is selected
if !cards[index].isMatched && cards[index].isChosen {
//print("Card not matched, so we're good to go...")
for idx in cards.indices {
if cards[idx].isChosen {
matchingCards.append(cards[idx])
// see if we have enough cards to match
if matchingCards.count > 2 {
//TODO: Need to call match logic here and set each matched card to .isMatched = true if matched
if cards[idx].isMatched {
print("Yay, matched!")
} else {
print("Card is not matched, flipping back over")
/*** THIS LINE NOT REFLECTING IN THE UI! ***/
cards[idx].isChosen = !cards[idx].isChosen // flip the unmatched card back over
}
}
}
}
matchingCards.removeAll() // clear out all the cards from the matching
} else {
print("Card is either matched or being deselected...")
}
}
or consider making Card a class so that when you are referring to a Card you know you are referring to the same object.

ARKit API by example

I'm trying to wrap my head around Apple's ARKit API and I have pushed their example ARKitExample project up to GitHub.
In this demo/sample project, you move your phone camera around your environment, and it appears to automatically detect flat surfaces and place a set of "focus squares" over where your camera is centered over that surface. If you then press a "+" UI button and select from one of several objects (lamp, cups, vase, etc.) and it will render that virtual object in place of the focus squares. You can see all of this in action right here which is probably better than me trying to explain it!
I'm trying to find the place in the code where the virtual object is actually invoked and rendered onscreen. This would be just after it is selected, which I think takes place here:
#IBAction func chooseObject(_ button: UIButton) {
// Abort if we are about to load another object to avoid concurrent modifications of the scene.
if isLoadingObject { return }
textManager.cancelScheduledMessage(forType: .contentPlacement)
performSegue(withIdentifier: SegueIdentifier.showObjects.rawValue, sender: button)
}
But essentially, the user selects a virtual object and then it gets rendered wherever the focus square are currently located at -- I'm looking for where this happens, any ideas?
It adds the virtualObject instance (which is a subclass of SCNNode) as a child of the SCNScene's root node:
func virtualObjectSelectionViewController(_: VirtualObjectSelectionViewController, didSelectObjectAt index: Int) {
guard let cameraTransform = session.currentFrame?.camera.transform else {
return
}
let definition = VirtualObjectManager.availableObjects[index]
let object = VirtualObject(definition: definition)
let position = focusSquare?.lastPosition ?? float3(0)
virtualObjectManager.loadVirtualObject(object, to: position, cameraTransform: cameraTransform)
if object.parent == nil {
serialQueue.async {
self.sceneView.scene.rootNode.addChildNode(object)
}
}
}

Is it possible to have an SKaction repeat forever only when the sprite is in view of the camera/player?

This is for a 2D game:
I have certain quality of life SKactions repeating forever, the two big ones for me are coins rotating/bobbing up and down and water flowing.
According to Apple's documentation, SKactions are instanced. So as long as I have the action subclassed then its only running "once" regardless of how many sprites its being used on. For example, as long as I have all my coins getting their actions from the same "Coin" class, then the memory footprint being used by the coin's action is the same regardless if I have 1 or 20 coins.
All that being said, it seems like such a waste to have these actions going when they aren't even in view of the camera/player.
Is there a way to have repeating forever actions deactivate when they aren't in view of the camera? I know that defeats the purpose of "forever" but as far as I can tell its either choosing some some sort of static duration or choosing forever.
Any advice is greatly appreciated.
Is better to add node only when is visible, but if you have few nodes, you can use
func containedNodeSet() -> Set
Example
class Enemy: SKSprite {
func startAnimationForever() {
//do animation if is not already running
}
func stopAnimation() {//stop animation}
}
in your scene, suppose your cam i myCam:
//add all enemies in this var or search in the scene all enemy
var allEnemies = Set<Enemy>()
func allVisibleEnemy() -> Set<Enemy>() {
let allVisibleEnemy = myCam.containedNodeSet().enumerated().flatMap{node in
if let enemy = node as? Enemy {
return enemy
}
}
return Set(arrayLiteral: allVisibleEnemy)
}
func allInvisibleEnemy() -> Set<Enemy>() {
let allVisibleEnemy = allVisibleEnemy()
return allEnemies.substract(allVisibleEnemy)
}
override func update() {
//all your stuff
let allVisibleEnemy = getVisibleEnemy()
let allInvisibleEnemy = allInvisibleEnemy()
allVisibleEnemy.forEach{enemy in
enemy.startAnimationForever()
}
allInvisibleEnemy.forEach{enemy in
enemy.stopAnimation()
}
}
You can optimizate it if necessary
I've not the compiler, fell free to edit.
If for any reason you have nodes that are not on the screen that does need to be on the scene for anything, then you should be taking it off the scene to help improve performance. This would stop actions on the nodes from running, and stop your physics world from having to check if anything physics related needs to placed on it.
Now there are many ways to go about doing this, but a very basic principal would be to establish some kind of map that lays out your nodes (This could be an SKScene that you have not attached to the main scene) Then use the map(scene) to keep track of all of your nodes. Take your camera and find all the nodes on the map (scene) that is in the view of the camera, and move those nodes over to the main scene.

Abstracting gesture recognizer to two functions in Swift?

Imagine an iOS screen where you can move your finger up/down to do something (imagine say, "scale"),
Or,
as a separate function you can move your finger left/right to do something (imagine say, "change color" or "rotate").
They are
separate
functions, you can only do one at at time.
So, if it "begins as" a horizontal version, it is remains only a horizontal version. Conversely if it "begins as" a vertical version, it remains only a vertical version.
It is a little bit tricky to do this, I present exactly how to do it below...the fundamental pattern is:
if (r.state == .Began || panway == .WasZeros )
{
prev = tr
if (tr.x==0 && tr.y==0)
{
panway = .WasZeros
return
}
if (abs(tr.x)>abs(tr.y)) ... set panway
}
This works very well and here's exactly how to do it in Swift.
In storyboard take a UIPanGestureRecognizer, drag it to the view in question. Connect the delegate to the view controller and set the outlet to this call ultraPan:
enum Panway
{
case Vertical
case Horizontal
case WasZeros
}
var panway:Panway = .Vertical
var prev:CGPoint!
#IBAction func ultraPan(r:UIPanGestureRecognizer!)
{
let tr = r.translationInView(r.view)
if (r.state == .Began || panway == .WasZeros )
{
prev = tr
if (tr.x==0 && tr.y==0)
{
panway = .WasZeros
return
}
if (abs(tr.x)>abs(tr.y))
{
panway = .Horizontal
}
else
{
panway = .Vertical
}
}
if (panway == .Horizontal) // your left-right function
{
var h = tr.x - prev.x
let sensitivity:CGFloat = 50.0
h = h / sensitivity
// adjust your left-right function, example
someProperty = someProperty + h
}
if (panway == .Vertical) // bigger/smaller
{
var v = tr.y - prev.y
let sensitivity:CGFloat = 2200.0
v = v / sensitivity
// adjust your up-down function, example
someOtherProperty = someOtherProperty + v
}
prev = tr
}
That's fine.
But it would surely be better to make a new subclass (or something) of UIPanGestureRecognizer, so that there are two new concepts......
UIHorizontalPanGestureRecognizer
UIVerticalPanGestureRecognizer
Those would be basically one-dimensional panners.
I have absolutely no clue whether you would ... subclass the delegates? or the class? (what class?), or perhaps some sort of extension ... indeed, I basically am completely clueless on this :)
The goal is in one's code, you can have something like this ...
#IBAction func horizontalPanDelta( ..? )
{
someProperty = someProperty + delta
}
#IBAction func verticalPanDelta( ..? )
{
otherProperty = otherProperty + delta
}
How to inherit/extend UIPanGestureRecognizer in this way??
But it would surely be better to make a new subclass (or something) of UIPanGestureRecognizer, so that there are two new concepts......
UIHorizontalPanGestureRecognizer
UIVerticalPanGestureRecognizer
Those would be basically one-dimensional panners
Correct. That's exactly how to do it, and is the normal approach. Indeed, that is exactly what gesture recognizers are for: each g.r. recognizes only its own gesture, and when it does, it causes the competing gesture recognizers to back off. That is the whole point of gesture recognizers! Otherwise, we'd still be back in the pre-g.r. days of pure touchesBegan and so forth (oh, the horror).
My online book discusses, in fact, the very example you are giving here:
http://www.apeth.com/iOSBook/ch18.html#_subclassing_gesture_recognizers
And here is an actual downloadable example that implements it in Swift:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch05p203gestureRecognizers/ch18p541gestureRecognizers/HorizVertPanGestureRecognizers.swift
Observe the strategy used here. We make UIPanGestureRecognizer subclasses. We override touchesBegan and touchesMoved: the moment recognition starts, we fail as soon as it appears that the next touch is along the wrong axis. (You should watch Apple's video on this topic; as they say, when you subclass a gesture recognizer, you should "fail early, fail often".) We also override translationInView so that only movement directly along the axis is possible. The result (as you can see if you download the project itself) is a view that can be dragged either horizontally or vertically but in no other manner.
#Joe, this is something I scrambled together quickly for the sake of this question. For ease of making it, I simply gave it a callback rather than implementing a target-action system. Feel free to change it.
enum PanDirection {
case Horizontal
case Vertical
}
class OneDimensionalPan: NSObject {
var handler: (CGFloat -> ())?
var direction: PanDirection
#IBOutlet weak var panRecognizer: UIPanGestureRecognizer! {
didSet {
panRecognizer.addTarget(self, action: #selector(panned))
}
}
#IBOutlet weak var panView: UIView!
override init() {
direction = .Horizontal
super.init()
}
#objc private func panned(recognizer: UIPanGestureRecognizer) {
let translation = panRecognizer.translationInView(panView)
let delta: CGFloat
switch direction {
case .Horizontal:
delta = translation.x
break
case .Vertical:
delta = translation.y
break
}
handler?(delta)
}
}
In storyboard, drag a UIPanGestureRecognizer onto your view controller, then drag an Object onto the top bar of the view controller, set its class, and link its IBOutlets. After that you should be good to go. In your view controller code you can set its callback and pan direction.
UPDATE FOR EXPLANATION >
I want to clarify why I made the class a subclass of NSObject: the driving idea behind the class is to keep any unnecessary code out of the UIViewController. By subclassing NSObject, I am able to then drag an Object onto the view controller's top bar inside storyboard and set its class to OneDimensionalPan. From there, I am able to connect #IBOutlets to it. I could have made it a base class, but then it would have had to be instantiated programmatically. This is far cleaner. The only code inside the view controller is for accessing the object itself (through an #IBOutlet), setting its direction, and setting its callback.

Resources