SKSpriteNode Subclass Position issue - swift

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

Related

How to refine the shape of an SKSpriteNode for tap gestures?

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
}

Is there a way I can add more than one sprite in SpriteKit globally?

I am struggling with one issue. Global declaration of my sprite so that I can interact with it. In this game, I have created a local sprite called enemy featured below:
func spawnEnemy() {
let enemy = SKSpriteNode(imageNamed: "as")
let yPosition = CGFloat(frame.maxY - enemy.size.height)
let getXvalue = GKRandomDistribution(lowestValue: Int(frame.minX + enemy.size.width), highestValue: Int(frame.maxX - enemy.size.width))
let xPosition = CGFloat(getXvalue.nextInt())
enemy.position = CGPoint(x: xPosition, y: yPosition)
enemy.name = "asteroid"
enemy.zPosition = 100
addChild(enemy)
let animationDuration:TimeInterval = 6
var actionArray = [SKAction]()
actionArray.append(SKAction.move(to: CGPoint(x: xPosition, y: 0), duration: animationDuration))
actionArray.append(SKAction.self.removeFromParent())
enemy.run(SKAction.sequence(actionArray))
}
I want to tap the enemy to make it disappear from the screen. The variable is declared locally and not globally so the touchesBegan function does not "see" enemy. However, when I move the statement:
let enemy = SKSpriteNode(imageNamed: "as")
outside of local declaration and into global. It works until the code tries to spawn in another enemy and i get an error of "Tried to add an SKNode who already has a parent" This is the code I have running in my view did load:
run(SKAction.repeatForever(SKAction.sequence([SKAction.run{self.spawnEnemy()
}, SKAction.wait(forDuration: 1.0)])))
Every time it spawns a new enemy it crashes and says that the SKNode already has a parent which i understand. However, for my game to function I need the player to be able to touch the individual instance of that enemy and remove it. Hence my code for
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 == "asteroid" {
print("Test")
enemy.removeFromParent()
print("Test Completed")
}
}
}
Now the error says unresolved use of "enemy" because the enemy is not global. I have been going back and forth on this issue for quite some time. If anyone has any potential solution or work around I would be very grateful, and thank you for your help.
Move your enemies to their own class and handle the touch for each of those enemies in their own class. This cleans up your GameScene and keeps your code more organized. You can now add as many instances of enemy as you want.
FYI not related to this question but somethings to consider after you get this working
when game over or level change or win make sure you have a clean up function to remove all enemies
you should strongly consider recycling your objects vs creating them on the fly...better performance
try to separate as much code to your objects class as possible
class enemy: SKSpriteNode {
init() {
super.init(texture: nil, color: .clear, size: CGSize.zero)
setup()
}
func setup() {
isUserInteractionEnabled = true
name = "asteroid"
zPosition = 100
let image = SKSpriteNode(imageNamed: "as")
imagine.zPosition = 1
addChild(image)
self.size = image.size
animate()
}
func animate() {
let animationDuration: TimeInterval = 6
let move = SKAction.move(to: CGPoint(x: xPosition, y: 0), duration: animationDuration)
let remover = SKAction.self.removeFromParent()
run(SKAction.sequence(move, remover))
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
removeFromParent()
}
}
class GameScene: SKScene {
override func didMove(to view: SKView) {
let sequence = SKAction.sequence([SKAction.run{ self.spawnEnemy()
}, SKAction.wait(forDuration: 1.0)])
run(SKAction.repeatForever(sequence))
}
func spawnEnemy() {
let enemy = Enemy()
let yPosition = CGFloat(frame.maxY - enemy.size.height)
let getXvalue = GKRandomDistribution(lowestValue: Int(frame.minX + enemy.size.width), highestValue: Int(frame.maxX - enemy.size.width))
let xPosition = CGFloat(getXvalue.nextInt())
enemy.position = CGPoint(x: xPosition, y: yPosition)
addChild(enemy)
}
}

How do I properly add a SKPhysicsBody to a drawn line using SKShapeNode in Spritekit?

I'm creating a game where the user must draw a line to collide with a ball to move it from one location to another. I just can't seem to find a way to put a physics body that collides with another object. The drawn line goes through other objects. I've tried several different SKPhysicBody init's on my shape, and none of them seem to work.
What's the best SKPhysicsBody initializer for a drawn line?
Are my category bit mask correct?
I understand the SkShapeNode can be converted to a SkSpriteNode, but I don't want to do this unless its the only way.
import SpriteKit
import UIKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var lines: [Line] = []
var lastTouch: CGPoint?
var touchLocation: CGPoint!
var linePhysics: CGPoint?
var ballCategory:UInt32 = 0b1
var lineCategory:UInt32 = 0b1 << 1
var goalCategory:UInt32 = 0b1 << 2
var floorCategory:UInt32 = 0b1 << 3
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
// assigns metal ball that was a color Sprite node, and is now a SKSpriteNode
let metalBall:SKSpriteNode = self.childNode(withName: "ball") as! SKSpriteNode
//assigns physical properties
metalBall.position = CGPoint(x: -150, y: 100)
metalBall.physicsBody? = SKPhysicsBody(circleOfRadius: metalBall.size.width / 2)
metalBall.physicsBody?.affectedByGravity = true
metalBall.physicsBody?.isDynamic = true
metalBall.physicsBody?.categoryBitMask = ballCategory
metalBall.physicsBody?.collisionBitMask = floorCategory | lineCategory
let floor: SKSpriteNode = self.childNode(withName: "floor") as! SKSpriteNode
floor.position = CGPoint (x: 0, y: -76)
floor.physicsBody = SKPhysicsBody(rectangleOf: floor.size)
floor.physicsBody?.affectedByGravity = false
floor.physicsBody?.isDynamic = false
floor.physicsBody?.categoryBitMask = floorCategory
floor.physicsBody?.collisionBitMask = ballCategory
}
/**
* Assigns the first touch location to a variable lastTouch
**/
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let firstTouch = touches.first {
lastTouch = firstTouch.location(in: self)
}
}
/**
* Assigns a path to where the finger touches then adds it to the line array and draws a path using shape nodes
**/
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let firstTouch = touches.first {
touchLocation = firstTouch.location(in: self)
//adds coordinates of lines to the line array
lines.append(Line(start: lastTouch!, end: touchLocation))
lastTouch = touchLocation
}
// path is a changeable variable that assigns a path depending on touch
let path = CGMutablePath()
for line in lines {
path.move(to: CGPoint(x: line.start.x, y: line.start.y))
path.addLine(to: CGPoint(x: line.end.x , y: line.end.y))
}
// colors in the path with ui color black
let shape = SKShapeNode()
shape.path = path
shape.strokeColor = UIColor.black
shape.lineWidth = 2
addChild(shape)
**shape.physicsBody = SKPhysicsBody(edgeChainFrom: shape.path!)**
shape.physicsBody?.affectedByGravity = true
shape.physicsBody?.isDynamic = false
shape.physicsBody?.friction = 1
shape.physicsBody?.restitution = 0.1
shape.physicsBody?.angularDamping = 0.0
shape.physicsBody?.linearDamping = 0
**shape.physicsBody?.categoryBitMask = lineCategory
shape.physicsBody?.collisionBitMask = ballCategory**
}
}

SpriteKit partial texture mapping

Is it possible to create a SKSpriteNode that displays only a part of a texture?
For example, can I create a square with size 100x100 displaying the specific region of a texture of size 720x720 like from x1=300 to x2=400 and y1=600 to y2=700?
Thanks for your help.
Try something like this:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
let visibleArea = SKSpriteNode(color: .black, size: CGSize(width:100,height:100))
let parentNode = SKSpriteNode(color: .white, size: CGSize(width:200, height:200))
override func didMove(to view: SKView) {
let cropNode = SKCropNode()
let texture = SKSpriteNode(imageNamed: "Spaceship")
visibleArea.position = CGPoint(x: 0, y: 100)
cropNode.maskNode = visibleArea
cropNode.addChild(texture)
addChild(cropNode)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let previousPosition = touch.previousLocation(in: self)
let translation = CGPoint(x: location.x - previousPosition.x , y: location.y - previousPosition.y )
visibleArea.position = CGPoint(x: visibleArea.position.x + translation.x , y: visibleArea.position.y + translation.y)
}
}
}
Overriden touchesMoved method is there just because of better example. What I did here, is:
created SKCropNode
added a texture to it which will be masked
defined visible area which is SKSpriteNode and assigned it to crop node's mask property, which actually does the magic
Here is the result:
If you want to break up a texture into smaller chunks of textures to be used as puzzle pieces, then you want to use SKTexture(rect: in texture:)
Here is an example of how to use it:
let texture = SKTexture(...) //How ever you plan on getting main texture
let subTextureRect = CGRect(x:0,y:0.width:10,height:10) // The actual location and size of where you want to grab the sub texture from main texture
let subTexture = SKTexture(rect:subTextureRect, in:texture);
You now have a chunk of the sub texture to use in other nodes.

Scale SpriteNode from a Specific Point

When scaling a spriteNode to a smaller size (or larger size), the spriteNode by default scales from the centre, which is usually fine. However, I need my sprite to scale from the very left of it's body. Is there any way of changing the spriteNode's centre point so that I can achieved this?
I tried adjusting the anchor point of the spriteNode (below), which does scale from the left of the body, but it also repositions the spriteNode, which I do not want.
import SpriteKit
var mySprite: SKSpriteNode!
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
anchorPoint = CGPointMake(0.5, 0.5)
backgroundColor = UIColor.greenColor()
mySprite = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(100, 50))
mySprite.anchorPoint = CGPointMake(0.0, 0.5)
addChild(mySprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
mySprite.runAction(SKAction.scaleTo(0.5, duration: 1))
}
I was hoping for something like the below (which doesn't exist):
mySprite.centerPoint.x = -mySprite.size.width/2
Also, the below gets the result I want, but I don't really want to have to adjust the spriteNode position everytime I adjust the anchorPoint:
var mySprite: SKSpriteNode!
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
anchorPoint = CGPointMake(0.5, 0.5)
backgroundColor = UIColor.greenColor()
mySprite = SKSpriteNode(color: UIColor.redColor(), size: CGSizeMake(100, 50))
mySprite.anchorPoint = CGPointMake(0.0, 0.5)
mySprite.position.x = -mySprite.size.width/2
addChild(mySprite)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
mySprite.runAction(SKAction.scaleTo(0.5, duration: 1))
}
Try to delete mySprite.anchorPoint = CGPointMake(0.0, 0.5) in didload and in touchesBegan:
mySprite.anchorPoint = CGPointMake(0.0, 0.5)
func repositionAncorPoint() {
mySprite.anchorPoint = CGPointMake(0.5, 0.5)
}
let resetAncor = SKAction.runBlock({ repositionAncorPoint() })
let rescale = SKAction.scaleTo(0.5, duration: 1)
let sequence = [rescale,resetAncor]
mySprite.runAction(SKAction.sequence(sequence))
I've not test it... :)