Attack Button in Spritekit

Attack button in SpriteKit

The first half of your question is quite easy. You just need to set the new texture of your player in that function then once that animation is complete, revert back to the original player animation. So I would suggest creating those two animations at the class level and assigning a key to the default animation and use the completion handler of the attack action to set it back.

class GameScene: SKScene {
let frame2 = SKTexture(imageNamed: "Ttam2")
let frame3 = SKTexture(imageNamed: "Ttam3")
let frame4 = SKTexture(imageNamed: "Ttam4")

let attackFrame1 = SKTexture(imageNamed: "Ttam1_ATTACK")
let attackFrame2 = SKTexture(imageNamed: "Ttam2_ATTACK")

var animation: SKAction!
var attackAnination: SKAction!

override func sceneDidLoad(){
animation = SKAction.repeatForever(SKAction.animate(with: [playerTexture, frame2, frame3, frame4], timePerFrame: 0.2))

attackAnimation = SKAction.animate(with: [attackFrame1,attackFrame2],timePerFrame: 0.2)

playerNode.run(animation,withKey:"animate")
}

func handleAttackButtonClick(){
playerNode.removeAction(forKey:"animate")
playerNode.run(attackAnimation,completion:{
self.playerNode.run(animation,withKey: "animate")
})
}
}

As for your second half regarding collisions, what you want to use is contactTestBitmask. You add the SKPhysicsContactDelegate to your scene class and set the bitmasks of which nodes you want to register contacts on. There many questions and answers regarding that aspect all over SO. No need to answer again I don't think. Feel free to come back with a new question if you get hung up on it.

Swift + SpriteKit - Button is clickable even when not visible

It seems that whether or not the node is added to the parent, containsPoint method will behave the same. Means, it will always return true if the given point lies inside of parent's (in your case, button's) coordinate system.

You can check this by initializing the attackButton without adding it to the scene. If you tap in the lower left corner of the scene, the message from the touchesBegan will be still printed.

I guess, this is happening because of the fact that each node has its position property set to CGPoint(0,0) by default. Also, in your case, the button has its size. And it will have its size and position set even if it's not added to its parent (or if removed from its parent) because it is obviously defined as a property and there is a strong reference to it.

SpriteKit player jump button

I see the problem now.

You declare a local variable of player in your createPlayer() function, but set that instance of the player to the global variable playerNode. But you are trying to reference the local variable when you apply your impulse. Change the two lines to

self.playerNode.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
self.playerNode.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 20))

You are probably getting THREAD 1 EXC_BAD_INSTRUCTION. Which usually means the compiler doesn't exactly know what you're trying to do. In other words, it knows the variable player exists, but it can't use it. The error should be more explanatory.

Swift 3.0 - Sprite Kit - Multitouch

The problem you are facing is that you are mixing the contexts for your touches. This is making things more difficult and complicated than they need to be.

The easiest thing to do would be to make your virtual joystick a separate SKSpriteNode class that tracks its own touches and reports them. Same with the buttons - they track their own touches and report their state.

But if you want to continue with your current approach of having a high-level object track multiple touches, what you want to do is capture the context that each touch is associated with in touchesBegan, and then just update things on touchesMoved as necessary, canceling the touches in touchesEnded.

For instance, you want to associate a particular touch with the virtual joystick, because you don't want weirdness if they drag their finger off of it and over to the button, say. And you want to know exactly which touch is lifted off when the user lifts a finger.

Here's some sample code that should illustrate the process:

 //
// This scene lets the user drag a red and a blue box
// around the scene. In the .sks file (or in the didMove
// function), add two sprites and name them "red" and "blue".
//

import SpriteKit
import GameplayKit

class GameScene: SKScene {

private var redTouch:UITouch?
private var blueTouch:UITouch?

override func didMove(to view: SKView) {
super.didMove(to: view)
isUserInteractionEnabled = true
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

// Grab some references to the red and blue sprites.
// (They must be direct children of the scene, or the
// paths must be amended.)
guard let redBox = childNode(withName: "red") else { return }
guard let blueBox = childNode(withName: "blue") else { return }

for touch in touches {
// Get the location of the touch in SpriteKit Scene space.
let touchLocation = touch.location(in: self)
// Check to see if the user is touching one of the boxes.
if redBox.contains( touchLocation ) {
// If we already have a touch in the red box, do nothing.
// Otherwise, make this our new red touch.
redTouch = touch
} else if blueBox.contains( touchLocation ) {
// If we already have a touch in the blue box, do nothing.
// Otherwise, make this our new blue touch.
blueTouch = touch
}
}

}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// We have already established which touches are active,
// and we have already tied them to the two contexts, so
// we just need to read their current location and update
// the location of the red and blue boxes for the touches
// that are active.
if let redTouch = redTouch {
guard let redBox = childNode(withName: "red") else { return }
let location = redTouch.location(in:self)
redBox.position = location
}
if let blueTouch = blueTouch {
guard let blueBox = childNode(withName: "blue") else { return }
let location = blueTouch.location(in:self)
blueBox.position = location
}
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// The parameter touches contains a list of ending touches,
// so we check the touches we are currently tracking to
// see if they are newly lifted. If so, we cancel them.
if let touch = redTouch {
if touches.contains( touch ) {
redTouch = nil
}
}
if let touch = blueTouch {
if touches.contains( touch ) {
blueTouch = nil
}
}
}

}

In the above code, we have separated out the touches on the red box and the blue box. We always know which touch is dragging the red box around and which touch is dragging the blue box around, if any. This is a simple example, but it's generalizable to your situation, where you'd have touches for the virtual joystick and each individual button.

Note that this approach works well for multitouch elements, too. If you have a map that you want to be zoomable, you can keep track of two touches so that you can compare them for pinch gestures. That way, if your pinch gesture accidentally strays over a button, you've already marked it as part of the pinch gesture, and know not to start triggering that button.

But again, a better approach would be to have a separate SKSpriteNode subclass that just tracks the joystick touches and reports its state to some higher-level manager class. You already know everything you need to know to do this - it's like what you have without all the extra checking to see if there are other buttons pressed. Same with the buttons. The only new part would be messaging "up the chain" to a manager, and that's pretty straightforward to deal with.

Stop objects from colliding using SpriteKit

There is a lot of documentation on these topics, but here is a practical example.

The power of categoryBitMasks

Pretend you have a collection of three nodes pool, basketball and bowlingball. Now, obviously, we want the basketball and bowlingball to collide with the each other. So you set the collisionBitMasks like so:

basketball.physicsBody?.collisionBitMask    = UInt32(2)
bowlingball.physicsBody?.collisionBitMask = UInt32(2)

Great. Now, we want the bowlingball to sink to the bottom of the pool, and the basketball to collide with the pool (might be more of a splash, but bear with me). How would we do this? We could try:

pool.physicsBody?.collisionBitMask = UInt32(2) // ?

But wait, that would make the basketball AND the bowlingball collide with the pool. We only want the basketball to collide with the pool , whereas we want the bowlingball to ignore the pool and sink straight to the bottom with no collisions. This is where categoryBitMasks come in handy:

let basketballBitMask     = UInt32(1)
let bowlingballBitMask = UInt32(2)
let poolBitMask = UInt32(4) // Why 4? See next section

basketball.physicsBody?.categoryBitMask = basketballBitMask
bowlingball.physicsBody?.categoryBitMask = bowlingballBitMask
pool.physicsBody?.categoryBitMask = poolBitMask

Because each object has a unique number assigned to it, you can now pick and choose which objects you'd like another object to collide with:

// basketball physics body collides with bowlingball(2) OR pool(4)
basketball.physicsBody?.collisionBitMask = bowlingballBitMask | poolBitMask
// ( '|' = logical OR operator)

// bowlingball physics body only collides with basketball(1)
bowlingball.physicsBody?.collisionBitMask = basketballBitMask

// pool physics body only collides with basketball(1)
pool.physicsBody?.collisionBitMask = basketballBitmask

If you're not sure what the strange '|' symbol is doing, I highly recommend the swift documentation on advanced operators to help you understand what's happening here.

Why not just use collisionBitMasks?

Okay so we've set some bit masks. But how are they used? If we only have two objects why can't we just compare collisionBitMasks?

Simply put, that's just not how it works. When a bowlingball comes into contact with the pool, the SpriteKit physics engine will AND ('&') together the bowlingball's categoryBitMask with the pool's collisionBitMask (or vice versa; the result is the same):

objectsShouldCollide = (bowlingball.physicsBody?.categoryBitMask & 
pool.physicsBody?.collisionBitMask)
// objectsShouldCollide = (ob010 & 0b100) = 0b000

Because the bowlingball's categoryBitMask and the pool's collisionBitMask have zero bits in common, objectsShouldCollide is equal to zero, and SpriteKit will stop the objects from colliding.

But, in your case, you're not setting your objects' categoryBitMasks. So they have a default value of 2^32, or 0xFFFFFFFF (hexadecimal representation) or in binary, 0b11111111111111111111111111111111. So when an "object" hits a "second" object, SpriteKit does this:

objectsShouldCollide = (0b11111111111111111111111111111111 & // Default categoryBitMask for "object" 
0b00000000000000000000000000000001) // collisionBitMask for "second" object
// = 0b00000000000000000000000000000001

So when you haven't defined the object's categoryBitMask, no matter what you set as the second object's collisionBitMask, objectsShouldCollide will never be zero, and they will always collide.

Note: you could set an object's collisionBitMask to 0; but then that object would never be able to collide with anything.

Using powers of 2 (0,1,2,4,8, etc.) for categoryBitMasks

Now let's say we wanted to include multiple bowlingballs that collided with each other. Easy:

bowlingball.physicsBody?.collisionBitMask  = basketballBitMask | bowlingballBitMask
// bowlingball collision bit mask (in binary) = 0b10 | 0b01 = 0b11
// bowlingball collision bit mask (in decimal) = 2 | 1 = 3

Here you can see that if we had set the pools physicsCategory to UInt32(3), it would no longer be distinguishable from a bowlingball or basketball.

Further suggestions

Learn to name variables with purpose, even if you're just using them for testing (although, coincidentally, "object and second object" worked quite well).

Use a struct for bitmasks to simplify your code and improve readability:

struct PhysicsCategory {
static let Obj1 : UInt32 = 0b1 << 0
static let Obj2 : UInt32 = 0b1 << 1
static let Obj3 : UInt32 = 0b1 << 2
static let Obj4 : UInt32 = 0b1 << 3
}

obj1.physicsBody?.categoryBitmask = PhysicsCategory.Obj1 // etc

Detect touch within Sprite texture and not the entire frame iOS Swift SpriteKit?

If you attach a physics body to each button, you can detect which physics body your touch lands on.

You can generate a physics body from the button's texture (assuming the button is an SKSpriteNode) using SKPhysicsBody(texture:size:) or SKPhysicsBody(texture:alphaThreshold:size:), or you can create a CGPath describing the button's shape and use SKPhysicsBody(polygonFromPath:). Assign the body to the button's physicsBody property. Assuming you don't actually want the physics simulator to move your buttons, set each body's dynamic property to false.

Then you can use physicsWorld.bodyAtPoint(_:) to get one of the bodies that a touch lands on (where physicsWorld is a property of your SKScene). Use the body's node property to get back to the button node. If bodies overlap, bodyAtPoint returns an arbitrary body.

You can use physicsWorld.enumerateBodiesAtPoint(_:usingBlock:) if you need all of the bodies that your touch lands on.

A completely different approach, if you can create a CGPath describing the button's shape, is to use SKScene.convertPointFromView(_:) and then SKNode.convertPoint(_:fromNode:_) to convert the point into the button's coordinate system, and then use CGPathContainsPoint (a global function) to detect whether the point is in the path describing the button shape.

Trying to create method to access the buttons when pressed on swift?

Not sure entirely what you are asking for so going to state what I think you are asking.

You are simply asking on how to connect a button to action.

You're button is declared button = UIButton() in the loop then the action would be handleButton method declared below.

You can simply do addTarget(:) for the button.

button.addTarget(self, action:#selector(handleButton), for: .touchUpInside)

You can google Swift Classname, i.e. Swift UIButton to determine the methods for that class. For example inside UIButton, we can see there is a method identified as addTarget(_:action:for).

From Apple's Docs:

You connect a button to your action method using the addTarget(_:action:for:) method or by creating a connection in Interface Builder. The signature of an action method takes one of three forms, which are listed in Listing 1. Choose the form that provides the information that you need to respond to the button tap.

Edit:
OP decided to ask how to assign button functionality within a loop. To do this, you need a list of functions aka Selectors that you can reference.

let selectors:[Selector] = [#selector(handleButton1), #selector(handleButton2, #selector(handleButton3)]

for i in 0..<3 {
button.addTarget(self, action: selectors[i], for: .touchUpInside)
}


Related Topics



Leave a reply



Submit