check if an instance of a spritenode is at a certain posion on the screen - sprite-kit

Im trying to make it so that the program will print something to the console when an instance of the sprite is in the center of the screen from top to bottom
var sprite = SKSpriteNode()
override func didMoveToView(view: SKView) {
//spawns an instance of the sprite
NSTimer.scheduledTimerWithTimeInterval(0.3, target: self, selector: Selector("spawnObject"), userInfo: nil, repeats: true)
//call the check middle function
checkMiddle()
}
//spawns a sprite
func spawnObject() {
let size = CGSizeMake(self.frame.size.width/3, self.frame.size.width3)
self.physicsWorld.gravity = CGVectorMake(0, -CGFloat(objectSpeed))
sprite = SKSpriteNode(imageNamed: "spriteImage")
sprite.position = CGPointMake(CGFloat(100), self.frame.height)
sprite.size = size
sprite.physicsBody = SKPhysicsBody(circleOfRadius: self.frame.size.width)
sprite.physicsBody?.affectedByGravity = true
addChild(self.sprite)
}
//PRINT WHEN SPRITE IS IN THE CENTER OF SCREEN FROM TOP TO BOTTOM
func checkMiddle() {
if sprite.position.y == self.frame.size.height/2 {
print("center")
}
}

if this is for debugging purposes you can use :
override func update(currentTime: NSTimeInterval)
{
checkMiddle()
}
The update() function is called before every frame so you need to be careful what you put in there
If you don't need it to be checked as frequently as that (60 x per second), you could also use an action:
let checkCenterAction = SKAction.runBlock(){ self.checkMiddle() }
let waitAction = SKAction.waitForDuration( 0.1 ) // every 1/10th of a second
let checkAndWaitAction = SKAction.sequence([checkCenterAction, waitAction])
sprite.runAction(SKAction.repeatForever(checkAndWaitAction))
And yet another option is to use the SKPhysicsBody to detect collisions

Related

How Can I increase radius of SKShapeNode by 1 pixel every second?

I am trying to write a function where I can increase SKShapeNode radius every second, but don't know how:(
var eRadius: CGFloat = 20
var eCircle = SKShapeNode()
override func didMove(to view: SKView) {
super.didMove(to: view)
eCircle = SKShapeNode(circleOfRadius: eRadius)
eCircle.strokeColor = .black
eCircle.glowWidth = 1.0
eCircle.fillColor = .white
eCircle.fillTexture = SKTexture(imageNamed: "e")
addChild(eCircle)
gameTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(addRadius), userInfo: nil, repeats: true)
}
#objc func addRadius(){
eRadius += 1
???? :(
}
Thank you!
Here's a working solution:
import SpriteKit
class GameScene: SKScene {
private var eRadius: CGFloat = 20
private var eCircle = SKShapeNode()
override func didMove(to view: SKView) {
eCircle.strokeColor = .black
eCircle.glowWidth = 1.0
eCircle.fillColor = .white
eCircle.fillTexture = SKTexture(imageNamed: "e")
addChild(eCircle)
run(SKAction.repeatForever(SKAction.sequence([SKAction.run {
self.updateCirclePath()
self.eRadius += 1
}, SKAction.wait(forDuration: 1.0)])))
}
private func updateCirclePath() {
eCircle.path = UIBezierPath(ovalIn: CGRect(x: -eRadius * 0.5, y: -eRadius * 0.5, width: eRadius, height: eRadius)).cgPath
}
}
Basically, you create the circle using a UIBezierPath and then update it as your radius changes. For the updating you should use a forever-repeating SKAction sequence that both sets the path, then changes the radius. I chose to do the path first, then update the radius for the next upcoming turn, for clarity.
You can also do it the opposite way, so run the update function first to set the initial path, then wait one second, after which you change the radius, then update the path based on that radius.
In SpriteKit, you should use SKActions for timers instead of scheduling or dispatch queues. With SKActions, everything conforms to the same game time, which you can then control by pausing and whatever.
Also, there are multiple ways to make a circle using UIBezierPath, including ovals, rounded rectangles and arcs: https://developer.apple.com/documentation/uikit/uibezierpath.

How to make object move farther longer you hold down?

I am trying to create a program where the longer you hold down the screen the higher an object will move. I want to do it where the longer you hold down the longer the function repeats increasing the distance. However, for some reason it is not working.
import SpriteKit
import GameplayKit
var calculateTimer = Timer()
var distance = 10
var choice = 1
class GameScene: SKScene, SKPhysicsContactDelegate {
var planet = SKNode()
override func didMove(to view: SKView) { // viewdidload
let planetTexture = SKTexture(imageNamed: "mercury.jpg")
planet = SKSpriteNode(texture: planetTexture)
planet.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
addChild(planet)
}
func calculateDistance() {
distance += 10
print(distance)
print(choice)
}
func touchDown(atPoint pos : CGPoint) {
calculateTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.calculateDistance), userInfo: nil, repeats: true)
print("test")
}
func touchUp(atPoint pos : CGPoint) {
planet.physicsBody!.isDynamic = true
//planet.physicsBody?.velocity = (CGVector(dx: 0, dy: 0))
planet.physicsBody!.applyImpulse(CGVector(dx: 0, dy: distance))
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
This is my code. If anyone has any suggestions they would be much appreciated!
In touchDown, set a flag to indicate a touch has started.
In touchUp, clear/remove the flag.
Whilst the flag is set, keep performing your movement code.

Rotate a sprite along an axis

In SpriteKit I need to rotate a sprite along an axis (e.g. the one that passes through the center of the sprite) just like a wheel to be spinned by the user.
I tried to use applyTorque function (to apply a force that is only angular and not linear), but I cannot handle the different forces caused by different movements on the screen (longer the touch on the screen, stronger the force to apply).
Can someone help me to understand how to deal with this problem?
Here is an answer that spins a ball according to how fast you swipe left / right:
class GameScene: SKScene {
let speedLabel = SKLabelNode(text: "Speed: 0")
let wheel = SKShapeNode(circleOfRadius: 25)
var recognizer: UIPanGestureRecognizer!
func pan(recognizer: UIPanGestureRecognizer) {
let velocity = recognizer.velocity(in: view!).x
// Play with this value until if feels right to you.
let adjustor = CGFloat(60)
let speed = velocity / adjustor
wheel.physicsBody!.angularVelocity = -speed
}
// Scene setup:
override func didMove(to view: SKView) {
removeAllChildren()
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
wheel.fillColor = .blue
wheel.physicsBody = SKPhysicsBody(circleOfRadius: 25)
wheel.physicsBody!.affectedByGravity = false
let wheelDot = SKSpriteNode(color: .gray, size: CGSize(width: 5, height:5))
wheel.addChild(wheelDot)
wheelDot.position.y += 20
wheel.setScale(3)
speedLabel.setScale(3)
speedLabel.position.y = (frame.maxY - speedLabel.frame.size.height / 2) - 45
recognizer = UIPanGestureRecognizer(target: self, action: #selector(pan))
view.addGestureRecognizer(recognizer)
addChild(wheel)
addChild(speedLabel)
}
override func didSimulatePhysics() {
speedLabel.text = "Speed: \(abs(Int(wheel.physicsBody!.angularVelocity)))"
}
}
Here is a basic example of spinning a wheel clockwise or counterclockwise depending on if you press on left / right side of screen. Hold to increase speed:
class GameScene : SKScene {
enum Direction { case left, right }
var directionToMove: Direction?
let wheel = SKShapeNode(circleOfRadius: 25)
let speedLabel = SKLabelNode(text: "Speed: 0")
override func didMove(to view: SKView) {
// Scene setup:
anchorPoint = CGPoint(x: 0.5, y: 0.5)
removeAllChildren()
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
wheel.fillColor = .blue
wheel.physicsBody = SKPhysicsBody(circleOfRadius: 25)
wheel.physicsBody!.affectedByGravity = false
let wheelDot = SKSpriteNode(color: .gray, size: CGSize(width: 5, height:5))
wheel.addChild(wheelDot)
wheelDot.position.y += 20
wheel.setScale(3)
speedLabel.setScale(3)
speedLabel.position.y = (frame.maxY - speedLabel.frame.size.height / 2) - 45
addChild(wheel)
addChild(speedLabel)
}
// Change this to touchesBegan for iOS:
override func mouseDown(with event: NSEvent) {
// Change this to touches.first!.location(in: self) for iOS.
let location = event.location(in: self)
// Determine if touch left or right side:
if location.x > 0 {
directionToMove = .right
}
else if location.x < 0 {
directionToMove = .left
}
}
override func mouseUp(with event: NSEvent) {
// Stop applying gas:
directionToMove = nil
print("lol")
}
override func update(_ currentTime: TimeInterval) {
// This is how much speed we gain each frame:
let torque = CGFloat(0.01)
guard let direction = directionToMove else { return }
// Apply torque in the proper direction
switch direction {
case .left:
wheel.physicsBody!.applyTorque(torque)
case .right:
wheel.physicsBody!.applyTorque(-torque)
}
}
override func didSimulatePhysics() {
// Speedometer:
speedLabel.text = "Speed: \(abs(Int(wheel.physicsBody!.angularVelocity)))"
}
}

how to make an SKSpriteNode follow another SKSpriteNode

Running into some trouble with my code. I'm trying to make zombies follow my player around with the following code:
class GameScene: SKScene, SKPhysicsContactDelegate {
func Enemies() {
Enemy = SKSpriteNode(imageNamed: "Spaceship")
Enemy.size = CGSize(width: 50, height: 50)
Enemy.color = UIColor(red: 0.9, green: 0.1, blue: 0.1, alpha: 1.0)
Enemy.colorBlendFactor = 1.0
//physics
Enemy.physicsBody = SKPhysicsBody(rectangleOf: Enemy.size)
Enemy.physicsBody?.isDynamic = true
Enemy.physicsBody?.affectedByGravity = false
Enemy.name = "Enemy"
Enemy.position.y = -frame.size.height/2
let positionX = arc4random_uniform(UInt32(frame.size.width))
Enemy.position.x = CGFloat(positionX)
addChild(Enemy)
}
override func didMove(to view: SKView) {
enemyTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(GameScene.Enemies), userInfo: nil, repeats: true)
override func update(_ currentTime: TimeInterval) {
Enemy.run(SKAction.move(to: ship.position, duration: 3))
}
If I run this code, I can spawn the zombies, but they will not follow my main player, they will just go to the position that he was at when they were spawned (i.e. zombie spawned at time = 0 will go to the ship position at time = 0, zombie spawned at time = 1 will go to the ship position at time = 1, and so on). However, if I run this code while only spawning one zombie like so:
override func didMove(to view: SKView) {
Enemies()
}
The lone zombie will follow my player around. Any idea why the code works for one zombie, but not for multiple zombies?
I would not recommend constantly adding actions on your update cycle, you are going to suffer from performance loss due to all the things that happen behind the scenes. Instead, use an SKAction.customAction that you would only add to your sprite once.
Here is an example of a custom action that will do what you want, remember only assign it once. (Code is not tested, so may need edits)
let customActionBlock =
{
(node,elapsedTime) in
let dx = ship.x - node.position.x
let dy = ship.y - node.position.y
let angle = atan2(dx,dy)
node.position.x += sin(angle) * speedPerFrame
node.position.y += cos(angle) * speedPerFrame
}
let duration = TimeInterval(Int.max) //want the action to run infinitely
let followPlayer = SKAction.customAction(withDuration:duration,actionBlock:customActionBlock)
Enemy.run(action:followPlayer,withKey:"follow")
Maybe you should removeAllActions() on Enemy before readding an action. It seems that you have actions that take 3 seconds, but you add an action every frame, so it has at most 180 actions for a node at once.

Spawn enemies randomly only within the screen width

I started working with SKS files to create games in swift, and in this game im trying to get enemies to spawn randomly within the width of the phone screen as opposed to all over the sks file scene
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector("spawnEnemy"), userInfo: nil, repeats: true)
}
func spawnEnemy(){
//supposed to pick random point within the screen width
let xPos = Int.random(self.frame.width)
enemy = SKSpriteNode(imageNamed: "enemy")
enemy.position = CGPointMake(CGFloat(xpos), self.frame.size.height/2)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: 7)
enemy.physicsBody?.affectedByGravity = true
enemy.physicsBody?.categoryBitMask = 0
enemy.physicsBody?.contactTestBitMask = 1
addChild(self.enemy)
}
Anyways, I wrote an example for you :)
So, here is how your example should look in order to work:
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
NSTimer.scheduledTimerWithTimeInterval(0.2, target: self, selector: Selector("spawnEnemy"), userInfo: nil, repeats: true)
}
func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum)
}
func spawnEnemy(){
//supposed to pick random point within the screen width
let xPos = randomBetweenNumbers(0, secondNum: frame.width )
let enemy = SKSpriteNode(imageNamed: "enemy") //create a new enemy each time
enemy.position = CGPointMake(CGFloat(xPos), self.frame.size.height/2)
enemy.physicsBody = SKPhysicsBody(circleOfRadius: 7)
enemy.physicsBody?.affectedByGravity = true
enemy.physicsBody?.categoryBitMask = 0
enemy.physicsBody?.contactTestBitMask = 1
addChild(enemy)
}
Method randomBetweenNumbers is borrowed from here.
And, this is another way of how you can spawn enemies by using SKAction:
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
let wait = SKAction .waitForDuration(1, withRange: 0.5)
let spawn = SKAction.runBlock({
self.spawnEnemy()
})
let spawning = SKAction.sequence([wait,spawn])
self.runAction(SKAction.repeatActionForever(spawning), withKey:"spawning")
}
Method spawnEnemy remains the same in both cases. To stop spawning you can remove an action for certain key ("spawning" in this case). You can do it like this:
if((self.actionForKey("spawning")) != nil){
self.removeActionForKey("spawning")
}