In my app I create mutiple SKSpriteNodes, gems. Code snippet 1
When I loop through nodes from the main scene in a tap gesture, Code snippet 2, the gems register in a perfect square shape even though they are not. Screenshot enclosed I have highlighted all areas that the orange gem registers in a tap as white.
Since the gem is itself not a square, I'd like to know if there is a way refine its shape so it would only show up in the list of nodes in UITapGestureRecognizer if the orange part is tapped. I have even tried by assigning it a physicsBody. But that made no difference.
Code Snippet 1
class MyGem : SKSpriteNode{
init(iColor : Gems) {
fileName = "\(iColor)_gem"
let skTexture = SKTexture(imageNamed: fileName)
super.init(texture: skTexture, color: .clear, size: .zero)
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.size = CGSize(width: myGV.gemSize.width, height: myGV.gemSize.height)
self.zPosition = theZ.gem
self.position = CGPoint(x: 0, y: 0 )
self.isUserInteractionEnabled = false
self.name = "gem"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Code Snippet 2
#objc func tappedView(_ sender:UITapGestureRecognizer) {
if sender.state == .ended{
var post = sender.location(in: sender.view)
post = self.convertPoint(fromView: post)
for node in self.nodes(at: post){
if let touchNode = node as? MyGem
print("touched gem")
highliteGem(theGem: touchNode, clearAll: false)
return
}
Related
I'm developing a game where you have a character. The character is a subclass of an SKSpriteNode:
class Character: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "character")
super.init(texture: texture, color: UIColor.white, size: texture.size())
self.isUserInteractionEnabled = true
self.zPosition = 10
self.position = CGPoint(x: 0, y: 0)
self.name = "character"
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("Touch!")
// Create small +1 sprite
let plusNode = SKSpriteNode(imageNamed: "plus1Node")
plusNode.zPosition = 10000 //To make sure its always in front
plusNode.position = self.position
self.addChild(plusNode)
}
The character is added to the game via GameScene.swift:
func spawnCharacter() {
//Random postion for the pigs
let randomX = Functions().randomBetweenNumbers(firstNum: -140, secondNum: 140)
let randomY = Functions().randomBetweenNumbers(firstNum: -240, secondNum: 240)
let newCharacter = Character()
newCharacter = CGPoint(x: randomX, y: randomY)
gameArea!.addChild(newCharacter)
}
When the player taps the character a little "+1" node is spawned to show the player that he has touched the character. I want that +1 node to be spawned on top of the character, but it's placed way off - like multiple points away.
If I move the +1 code to the GameScene (just after the character is created and added to the scene the position is spot on).
What am I missing here?
PulzeNode.position = CGpoint.zero
As you add it to the character, it should be at the origin of parent node
I'm trying to make infinite scrolling terrain by programmatically adding/deleting textured tiles as the player pans the view. New tiles should only be added next to existing tiles that have an open edge. To detect if a tile has an open edge, I plan to attach a small physics body that sticks out from all 4 sides of the tiles to act as a sensor. If the sensor contacts any other sensors, we know that edge of the tile is not open.
The problem I'm having is that the sensors do not always stay aligned with the tiles. To show this problem, I created a SpriteKit project with the code below.
Touch behavior includes a gesture recognizer in the GameScene class which causes the invisible Handle object to move. When the gesture ends, I use the handle's physics body to give it a little velocity on this line:
handle.physicsBody?.applyImpulse(CGVector(dx: velocity.x * multiplier, dy: -velocity.y * multiplier))
I'm also creating a Tile object (big green square below) and adding it as a child of the invisible handle. That's great, now all child tiles I add will move along with their parent handle.
Whenever a tile is instantiated, a Sensor object (small red square below) is created and added as a child of the tile. That's also great, now all sensors will move along with their parent tile which in turn moves with its parent, the invisible handle. There's just one problem...
When I pan the screen, both the green tile and its red sensor (shown below) move together in unison, as expected. When I release my pan gesture, the extra kick of velocity I give to the handle also carries over to its child tile, also as expected. But that velocity does not affect the child sensor of the tile. As soon as I release the gesture, the sensor stops dead on the screen while the tile continues moving along with the handle until they both slow to a halt. The desired behavior is for the sensor to keep moving along with its parent tile.
Here's a link to a video that might show what's happening better than I can describe it:
https://youtu.be/ccJKdZv-NsM
I can't understand why the tile is staying in sync with its parents motion but the sensor is not doing the same. Thanks for any insight into this problem.
GameScene.swift:
import SpriteKit
class GameScene: SKScene {
let handle = Handle()
let startTile = Tile()
override func didMove(to view: SKView) {
self.backgroundColor = .white
self.physicsWorld.gravity = CGVector(dx: 0, dy: 0)
self.addChild(handle)
startTile.position.x = handle.anchorPoint.x
startTile.position.y = handle.anchorPoint.y
handle.addChild(startTile)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanFrom))
panGestureRecognizer.cancelsTouchesInView = false
panGestureRecognizer.delaysTouchesEnded = false
self.view!.addGestureRecognizer(panGestureRecognizer)
}
func handlePanFrom(_ recognizer: UIPanGestureRecognizer) {
if recognizer.state == .changed {
var translation = recognizer.translation(in: recognizer.view)
translation = CGPoint(x: translation.x, y: -translation.y)
self.panForTranslation(translation)
recognizer.setTranslation(.zero, in: recognizer.view)
} else if recognizer.state == .ended {
let velocity = recognizer.velocity(in: self.view)
let multiplier = CGFloat(0.5)
handle.physicsBody?.applyImpulse(CGVector(dx: velocity.x * multiplier, dy: -velocity.y * multiplier))
}
}
func panForTranslation(_ translation: CGPoint) {
let position = handle.position
let newPosition = CGPoint(x: position.x + translation.x, y: position.y + translation.y)
handle.position = newPosition
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
handle.physicsBody?.isResting = true
}
}
Handle class:
import SpriteKit
class Handle : SKSpriteNode {
init() {
super.init(texture: nil, color: .clear, size: CGSize(width: 1, height: 1))
self.physicsBody = SKPhysicsBody(rectangleOf: self.size)
self.physicsBody?.mass = 1
self.physicsBody?.linearDamping = 2
self.physicsBody?.categoryBitMask = 0
self.physicsBody?.contactTestBitMask = 0
self.physicsBody?.collisionBitMask = 0
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Tile class:
import SpriteKit
class Tile : SKSpriteNode {
init() {
super.init(texture: nil, color: .green, size: CGSize(width: 300, height: 300))
let sensorA = Sensor()
self.addChild(sensorA)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Sensor class:
import SpriteKit
class Sensor : SKSpriteNode {
init() {
super.init(texture: nil, color: .red, size: CGSize(width: 50, height: 50))
self.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 50, height: 50))
self.physicsBody?.categoryBitMask = 0b1
self.physicsBody?.contactTestBitMask = 0b1
self.physicsBody?.collisionBitMask = 0
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
UPDATE:
The accepted answer provided by Whirlwind solved the problem I had with the child separating from the parent. I believe that the cause of the problem became clear in the comments of that answer.
My understanding of it is that the red square did not move because it has its own physics body which is not receiving any velocity after the handle stops moving. While the handle object (and its child tile) keeps moving because it does have velocity. So it sounds like the red box's own physics body was holding it back.
I don't really have a time to get into why your code does some things, but if you want to move another physics body along with handle's physics body, then you could pin it to it.
I will just modify your code to make it work, but you should worry by yourself about encapsulation. First make a sensor variable inside of Tile class visible to the outside world, so you can use it later in your scene:
class Tile : SKSpriteNode {
let sensorA = Sensor()
init() {
super.init(texture: nil, color: .green, size: CGSize(width: 300, height: 300))
self.addChild(sensorA)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Then in you scene pin sensor to a handle:
let pin = SKPhysicsJointPin.joint(withBodyA: self.startTile.sensorA.physicsBody!, bodyB: self.handle.physicsBody!, anchor: CGPoint.zero)
startTile.sensorA.physicsBody?.allowsRotation = false
self.physicsWorld.add(pin)
I guess this is what you wanted:
I'm new in Sprite Kit and I have a strange problem with my GameScene. Can't figure out, what causes the problem. I present my scene from controller in viewWillAppearMethod in this way:
let atlas = SKTextureAtlas(named: "Sprites")
atlas.preload { [unowned self] in
DispatchQueue.main.async {
self.gameScene = GameScene(level: self.level, size: self.gameSKView!.bounds.size)
self.gameScene.scaleMode = .resizeFill
self.gameSKView?.presentScene(self.gameScene)
self.gameSKView?.ignoresSiblingOrder = true
self.gameSKView?.showsNodeCount = true
}
}
My sprite atlas content looks like: link
Than i create my spaceship:
final class SpaceshipSpriteNode: SKSpriteNode {
required init(size: CGSize) {
let texture = SKTexture(image: #imageLiteral(resourceName: "Spaceship"))
super.init(texture: texture, color: .white, size: size)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
func configureSpaceship() {
let middleRow = Int(Double(unwrappedMatrix.rowsCount) / 2)
let middleColumn = Int(Double(unwrappedMatrix.columnsCount) / 2)
let xOffset = CGFloat(level.startPoint.column - middleColumn)
let yOffset = CGFloat(level.startPoint.row - middleRow)
spaceship = SpaceshipSpriteNode(size: spaceshipSize)
spaceship.position = CGPoint(x: frame.midX + (spaceshipSize.width * xOffset), y: frame.midY + (spaceshipSize.height * yOffset))
spaceshipObject.addChild(spaceship)
addChild(spaceshipObject)
}
configureSpaceship method is called in didMove(to view: SKView)
The problem is that sometimes(1 per 3/4/5/6 cases) my spaceship is missing from the scene. Visibility, position, size are always the same, a count of the nodes on scene is the same too. Some images here link
According to comments, I have changed zPosition for my objects:
tile.zPosition = 0
spaceship.zPosition = 1.0
backgroundSpriteNode.zPosition = -1
And everything start working correct, thanks guys.
I'm trying to position an SKCameraNode at the center of multiple objects in my scene. I'm thinking of placing all objects of interest into some sort of container, and then calculating the centroid every update(). I have two classes, Ball and Car, that inherit from PhysicalObject, which inherits from SKSpriteNode. What I'd like to do is something like
var cameraObjects: Array<Ball, Car>!
...
var ball1 = Ball()
var car1 = Car()
cameraObjects.append(ball1)
cameraObjects.append(car1)
...
// Loop through objects and calculate centroid
But this crashes at the first append().
Note that I'd rather not create a dummy protocol for each class, as mentioned here. Is there a simple way to do this?
PhysicalObject.swift
import SpriteKit
class PhysicalObject: SKSpriteNode {
var objectMass: CGFloat!
override init(texture: SKTexture!, color: UIColor, size: CGSize) {
super.init(texture: texture, color: color, size: size)
self.name = "physicalObject"
//Physics setup
physicsBody?.usesPreciseCollisionDetection = true
physicsBody?.affectedByGravity = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Ball.swift
import SpriteKit
class Ball: PhysicalObject {
let MAXBALLSPEED :CGFloat = 0.08
init(spawnPosition: CGPoint) {
/* Temporary initialization.. will have further customization for different classes */
let ballTexture = SKTexture(imageNamed: "ball")
let ballRadius = CGFloat(50)
let ballSize = CGSize(width: ballRadius, height: ballRadius)
super.init(texture: ballTexture, color: UIColor.clear, size: ballSize)
self.position = spawnPosition
self.name = "ball"
//Physics Setup
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(origin: spawnPosition, size: carSize) )
self.physicsBody = SKPhysicsBody(circleOfRadius: self.size.height/2)
self.objectMass = CGFloat(0.005)
physicsBody?.isDynamic = true // Default is true
physicsBody?.restitution = 1.0
self.physicsBody?.categoryBitMask = PhysicsCategory.Ball
self.physicsBody?.contactTestBitMask = PhysicsCategory.Boards | PhysicsCategory.Car
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Car.swift
import SpriteKit
class Car: PhysicalObject {
let MAXCARSPEED: CGFloat = 500
var steering: Bool
var diagonalLength: CGFloat
init(spawnPosition: CGPoint) {
/* Temporary initialization.. will have further customization for different classes of cars */
let carTexture = SKTexture(imageNamed: "BasicCar")
let carWidth = CGFloat(screenSize.width/20)
let carScale = CGFloat(183.0/140.0)
let carSize = CGSize(width: (carWidth*carScale), height: carWidth)
// Initialize movement variables
self.steering = false
self.diagonalLength = hypot(carSize.width, carSize.height)
//self.steerRight = false
//self.steerLeft = false
super.init(texture: carTexture, color: UIColor.clear, size: carSize)
self.speed = CGFloat(MAXCARSPEED)
self.position = spawnPosition
self.name = "car"
//Physics Setup
//self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRect(origin: spawnPosition, size: carSize) )
let carPhysicsSize = CGSize(width: self.size.width, height: self.size.height)
self.physicsBody = SKPhysicsBody(rectangleOf: carPhysicsSize)
self.objectMass = CGFloat(2000)
self.physicsBody?.friction = 0.5
self.physicsBody?.angularDamping = 1.0
self.physicsBody?.linearDamping = 1.0
self.physicsBody?.restitution = 1.0
physicsBody?.isDynamic = true // Default is true
self.physicsBody?.categoryBitMask = PhysicsCategory.Car
self.physicsBody?.contactTestBitMask = PhysicsCategory.Car | PhysicsCategory.Ball
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You can make an Array<PhysicalObject> (a.k.a. [PhysicalObject]), which can store both Ball objects, and Car objects.
You may find that the enumerateChildNodes method of SKNode is a better way to enumerate over all the objects with a certain name. That way, you don't need to manage the array yourself.
Take a look at Apple's documentation.
All you need to do is place all the objects you want be involved with the camera into a new parent SKNode, and use calculateAccumulatedFrame to get the frame around all the nodes, then just use the center of the parent's frame.
Ok. this code is driving me crazy. It just don't work. The only message I received is "Attemped to add a SKNode which already has a parent". Yes I know that there has been some discussions here, but none of them give the solution I need.
This is the code. I really appreciate any help.
import SpriteKit
class MyScene: SKScene {
let intervalShapeCreation:NSTimeInterval = 2.0 // Interval for creating the next Shape
let gravitationalAcceleration:CGFloat = -0.5 // The gravitational Y acceleration
let shapeSequenceAction = SKAction.sequence([
SKAction.scaleTo(1.0, duration: 0.5),
SKAction.waitForDuration(2.0),
SKAction.scaleTo(0, duration: 0.5),
SKAction.removeFromParent()
])
override init(size: CGSize) {
super.init(size: size)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
addBackground()
initializeScene()
}
// MARK: Level Building
func initializeScene() {
self.physicsWorld.gravity = CGVectorMake(0.0, gravitationalAcceleration)
runAction(SKAction.repeatActionForever(
SKAction.sequence([SKAction.runBlock(self.createShape),
SKAction.waitForDuration(intervalShapeCreation)])))
}
func addBackground() {
let backgroundAtlas = SKTextureAtlas(named: "background")
let background = SKSpriteNode(texture: backgroundAtlas.textureNamed("background"))
background.position = CGPoint(x: size.width/2, y: size.height/2)
background.anchorPoint = CGPointMake(0.5, 0.5)
background.zPosition = -1
background.name = "background"
self.addChild(background)
}
func createShape() {
let newShape = sSharedAllPossibleShapes[0]
print("\n shape creada: \(newShape.name)")
newShape.position = CGPointMake(size.width / 2, CGFloat( Int.random(fromZeroToMax: 500)))
self.addChild(newShape)
newShape.runAction(shapeSequenceAction)
}
}
createShape doesn't actually create a SKShapeNode. It gets the first shape from the sSharedAllPossibleShapes array, then adds it as child to self. The second time you call this method that shape already has a parent and can't be added again.
You have to create a new instance of SKShapeNode. The way I see it your array here really needs to contain the CGPath objects that define the shape, not the nodes themselves because you can't reuse nodes the way you intended to.