I want to generate an infinite amount of nodes that fall from the top of the screen and destroy them by just clicking on them, it is simple but I am having serious problems with it. When the nodes are generated by the loop I can only generate one and it starts moving instead of falling vertically. It also disappears and appears constantly.
Here is my code, hope you can help.
Thank you!!
import SpriteKit
class DestroyScene: SKScene , SKPhysicsContactDelegate{
var velocity:CGFloat = 0
let scoreText = SKLabelNode(fontNamed: "Arial Rounded MT Bold")
var score = 0
var lastYieldTimeInterval:NSTimeInterval = NSTimeInterval()
var lastUpdateTimerInterval:NSTimeInterval = NSTimeInterval()
var gameOver = false
var alien:SKSpriteNode = SKSpriteNode(imageNamed: "circuloAzulArt")
override func didMoveToView(view: SKView) {
self.physicsWorld.contactDelegate = self
self.physicsWorld.gravity = CGVectorMake(0, -velocity)
}
func addAlien(){
var alien:SKSpriteNode = SKSpriteNode(imageNamed: "circuloAzulArt")
alien.name = "alien"
alien.physicsBody = SKPhysicsBody(circleOfRadius: alien.size.width/2)
alien.physicsBody?.dynamic = true
var actionArray:NSMutableArray = NSMutableArray()
var actionArray2:NSMutableArray = NSMutableArray()
alien.removeFromParent()
if gameOver == false{
let minX = alien.size.width/2
let maxX = self.frame.size.width - alien.size.width/2
let rangeX = maxX - minX
let position:CGFloat = CGFloat(arc4random()) % CGFloat(rangeX) + CGFloat(minX)
alien.position = CGPointMake(position, self.frame.size.height + alien.size.height)
self.addChild(alien)
let minDuration = 3
let duration = Int(minDuration)
actionArray.addObject(SKAction.moveTo(CGPointMake(position, -alien.size.height), duration: NSTimeInterval(duration)))
actionArray.addObject(SKAction.removeFromParent())
alien.runAction(SKAction.sequence(actionArray))
}
}
func updateWithTimeSinceLastUpdate(timeSinceLastUpdate:CFTimeInterval){
var randomNum = Double(arc4random_uniform(20))
var xTime = ((randomNum / 20) + 0.25)
lastYieldTimeInterval += timeSinceLastUpdate
if (lastYieldTimeInterval > xTime){
lastYieldTimeInterval = 0
randomNum = Double(arc4random_uniform(25))
addAlien()
}
}
override func update(currentTime: CFTimeInterval) {
var timeSinceLastUpdate = currentTime - lastUpdateTimerInterval
lastUpdateTimerInterval = currentTime
if score < 60{
velocity = CGFloat(score*3)
}else{
velocity = CGFloat(210)
}
if (timeSinceLastUpdate > 1){
timeSinceLastUpdate = 1/60
lastUpdateTimerInterval = currentTime
}
updateWithTimeSinceLastUpdate(timeSinceLastUpdate)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.alien{
alien.removeFromParent()
score++
}
}
}
}
Everytime you add an alien you remove the last alien with this line of code:
alien.removeFromParent()
You have to create a new variable node in the addAlien() function and give the node a name
for example:
var alien:SKSpriteNode = SKSpriteNode(imageNamed: "alien")
alien.name = "alien"
Then you can remove the alien in the touchesBegan function by checking the node name
let node = self.nodeAtPoint(location)
if (node.name == "alien") {
}
And for the gravity you should move self.physicsWorld.gravity = CGVectorMake(0, -velocity) to the didMoveToView() function and change the last parameter to a constant
Related
I am trying to make my Bullet fire continuously while touching the screen. This is what I got so far, but it's not really working.
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.setScale(1.5) // Bullet Size
bullet.position = player.position
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet
bullet.physicsBody!.collisionBitMask = PhysicsCategories.None
bullet.physicsBody!.contactTestBitMask = PhysicsCategories.Enemy
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([bulletSound, moveBullet, deleteBullet])
let bulletRepeat = SKAction.repeatForever(bulletSequence)
bullet.run(bulletRepeat)
}
what you need is someway of turning on the fire when you press down on the screen and turning it off when you lift your finger. You can use the touches began to set a variable to true the says you should be firing isFiring = true and then catch that variable in the update. When you lift your finger the touches ended will turn the variable off.
Another nice touch is that you can adjust the firingInterval variable in real time to adjust the rate of fire.
private var updateTime: Double = 0
private var isFiring = false
//rate of fire, 1 means fire once a second
private var firingInterval: Double = 1
override func update(_ currentTime: TimeInterval) {
//check if we should be firing if not get outta here
guard isFiring else { return }
if updateTime == 0 {
updateTime = currentTime
}
if currentTime - updateTime > firingInterval {
self.fireBullet()
updateTime = currentTime
}
}
override touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isFiring = true
}
override touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
isFiring = false
}
func fireBullet() {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.setScale(1.5) // Bullet Size
bullet.position = player.position
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(rectangleOf: bullet.size)
bullet.physicsBody!.affectedByGravity = false
bullet.physicsBody!.categoryBitMask = PhysicsCategories.Bullet
bullet.physicsBody!.collisionBitMask = PhysicsCategories.None
bullet.physicsBody!.contactTestBitMask = PhysicsCategories.Enemy
self.addChild(bullet)
let moveBullet = SKAction.moveTo(y: self.size.height + bullet.size.height, duration: 1)
let deleteBullet = SKAction.removeFromParent()
let bulletSequence = SKAction.sequence([bulletSound, moveBullet, deleteBullet])
//let bulletRepeat = SKAction.repeatForever(bulletSequence)
bullet.run(bulletSequence )
}
You could use a Timer object, started in the touchesBegan and cancel it in the touchesEnded method.
For timers, I use the following class that I've written :
class MyTimerHandlerObject: NSObject {
var timerCounter: CGFloat = 0
var myTimer = Timer()
override init() {super.init()}
#objc func myTimerHandler(_ theTimer: Timer) {
timerCounter += 0.1
// Do what needs do be done here ...
}
func startMyTimer() {
timerCounter = 0
self.myTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(MyTimerHandlerObject.myTimerHandler(_:)), userInfo: nil, repeats: true)
}
func stopMyTimer() {
self.myTimer.invalidate()
timerCounter = 0
}
}
You can then easily start the timer and stop it, by declaring a variable of type MyTimerHandlerObject and use the methods 'StartMyTimer' and 'StopMyTimer'
Using Swift SpriteKit, making a game like Doodle Jump, I want the platform continuously bouncing from the left side of the screen to the right. What's the best way of doing this?
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
private var player: Player?
private var platform: Platform?
var playerCategory: UInt32 = 0b1
var platformCategory: UInt32 = 0b10
var edgeCategory: UInt32 = 0b100
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
//player
player = childNode(withName: "Player") as? Player!
player?.physicsBody?.categoryBitMask = playerCategory
player?.physicsBody?.collisionBitMask = platformCategory | edgeCategory
//placed platform
platform = childNode(withName: "Platform") as? Platform!
platform?.physicsBody?.categoryBitMask = platformCategory
platform?.physicsBody?.collisionBitMask = edgeCategory
platform?.physicsBody?.applyImpulse(CGVector(dx: 30, dy: 0))
//create frame boundary
let borderBody = SKPhysicsBody(edgeLoopFrom: self.frame)
borderBody.friction = 0
borderBody.restitution = 0
borderBody.categoryBitMask = edgeCategory
self.physicsBody = borderBody
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
player?.jump()
player?.physicsBody?.collisionBitMask = edgeCategory
}
override func update(_ currentTime: TimeInterval) {
changeCollisions()
//movePlatform()
}
func changeCollisions() {
if let body = player?.physicsBody {
let dy = body.velocity.dy
if dy > 0 {
// Prevent collisions if the hero is jumping
//body.collisionBitMask = 0
}
else {
// Allow collisions if the hero is falling
body.collisionBitMask = platformCategory | edgeCategory
}
}
}
func movePlatform() {
if ( (platform?.position.x)! <= /*-(scene?.size.width)!*/ 0 ) {
platform?.position.x += 5
} else {
platform?.position.x -= 5
}
}
}
What I currently have (the gray rectangle is the platform):
You can create a simple SKAction sequence with the desired animation:
let duration = 10 // Animation duration in seconds.
let moveRight = SKAction.moveTo(x: maxX, duration: duration/2)
let moveLeft = SKAction.moveTo(x: minX, duration: duration/2)
let moves = SKAction.repeatForever(.sequence([moveRight, moveLeft])
platform!.run(moves)
I am making a custom SKSpriteNode, when I step through calling createShip the heroShip constant in the class is correct, but when I jump back to the gameScene, the heroShip constant there does not have the properties I assigned when calling createShip, I am not sure what I am doing wrong. I have tried using class function but that doesn't work using the height and width properties.
Custom SKSpriteNode class
class hero: SKSpriteNode {
var width: CGFloat = 0.0
var height: CGFloat = 0.0
func createShip() -> SKSpriteNode {
let heroShip = SKSpriteNode(imageNamed: "heroShip")
heroShip.anchorPoint = CGPointMake(1.0, 0.5)
heroShip.physicsBody = SKPhysicsBody(rectangleOfSize: heroShip.size)
heroShip.physicsBody?.usesPreciseCollisionDetection = true
heroShip.zPosition = 1.0
heroShip.physicsBody?.mass = 0.02
heroShip.physicsBody?.dynamic = true
heroShip.physicsBody?.affectedByGravity = false
heroShip.physicsBody?.categoryBitMask = ObjectCategory.collisionHeroCategory.rawValue
heroShip.physicsBody?.contactTestBitMask = ObjectCategory.sceneCategory.rawValue
heroShip.physicsBody?.collisionBitMask = 0x0 | ObjectCategory.sceneCategory.rawValue
//heroShip.position = CGPointMake((scene?.frame.size.width)!/6.0, (scene?.frame.size.height)!/2.0)
heroShip.position = CGPointMake(width, height)
return heroShip
}
}
My GameScene
class GameScene: SKScene,SKPhysicsContactDelegate{
let background = SKSpriteNode(imageNamed: "background")
var score:Int = 0
let scoreLabel = SKLabelNode(fontNamed: "Courier")
let MotionManager = CMMotionManager()
var heroShip = hero()
override func didMoveToView(view: SKView) {
heroShip.width = self.size.width/6.0
heroShip.height = self.size.height/2.0
heroShip.createShip()
let enemyShip = SKSpriteNode(imageNamed: "enemyShip")
/* Setup your scene here */
self.physicsWorld.contactDelegate = self
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: CGRectMake(0,heroShip.size.width/1.25,frame.width,frame.height - heroShip.size.width*1.6))
scene?.physicsBody?.contactTestBitMask = ObjectCategory.sceneCategory.rawValue
scene?.physicsBody?.categoryBitMask = ObjectCategory.sceneCategory.rawValue
background.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame))
scoreLabel.fontColor = SKColor.whiteColor()
scoreLabel.text = String(format: "Score: %01u",score)
scoreLabel.position = CGPointMake(frame.size.width/2, frame.size.height - scoreLabel.frame.size.width/1.2)
scoreLabel.zPosition = 1.0
enemyShip.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
enemyShip.zPosition = 1.0
enemyShip.physicsBody = SKPhysicsBody(rectangleOfSize: heroShip.size)
enemyShip.physicsBody?.usesPreciseCollisionDetection = true
enemyShip.physicsBody?.mass = 0.02
enemyShip.physicsBody?.dynamic = true
enemyShip.physicsBody?.affectedByGravity = false
enemyShip.physicsBody?.categoryBitMask = ObjectCategory.collisionEnemyCategory.rawValue
enemyShip.physicsBody?.contactTestBitMask = ObjectCategory.collisionBulletCategory.rawValue
enemyShip.physicsBody?.collisionBitMask = 0x0
self.addChild(enemyShip)
self.addChild(background)
self.addChild(self.heroShip)
self.addChild(scoreLabel)
if MotionManager.accelerometerAvailable{
MotionManager.startAccelerometerUpdates()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.position = CGPointMake(heroShip.position.x, heroShip.position.y)
bullet.zPosition = 1.0
// Add physics body for collision detection
bullet.physicsBody = SKPhysicsBody(rectangleOfSize: bullet.frame.size)
bullet.physicsBody?.dynamic = true
bullet.physicsBody?.affectedByGravity = false
bullet.physicsBody?.categoryBitMask = ObjectCategory.collisionBulletCategory.rawValue
bullet.physicsBody?.contactTestBitMask = ObjectCategory.collisionHeroCategory.rawValue
bullet.physicsBody?.collisionBitMask = 0x0;
let action = SKAction.moveToX(CGRectGetMaxX(self.frame) + bullet.size.width, duration: 0.75)
self.addChild(bullet)
bullet.runAction(action, completion: {
bullet.removeAllActions()
bullet.removeFromParent()
})
}
func didBeginContact(contact: SKPhysicsContact) {
if contact.bodyB.categoryBitMask == ObjectCategory.collisionBulletCategory.rawValue && contact.bodyA.categoryBitMask == ObjectCategory.collisionEnemyCategory.rawValue{
score++
}
}
override func update(currentTime: CFTimeInterval) {
let data = MotionManager.accelerometerData
if data?.acceleration.x == nil{
print("nil")
}
else if fabs((data?.acceleration.x)!) > 0.2 {
heroShip.physicsBody?.applyForce(CGVectorMake(0.0, CGFloat(40 * (data?.acceleration.x)!)))
}
scoreLabel.text = String(format: "Score: %01u",score)
}
}
You run
heroShip.createShip()
and then never do anything with the SKSpriteNode that is returned. From what I can tell, the hero class is the hero ship. I am going off of this assumption for the rest of this answer.
Going from the top of the GameScene, you should do some refactoring.
var heroShip = hero() Is going to be replaced to init using the SKSpriteNode structure:
var heroShip = hero(imageNamed: "heroShip")
Moving to hero class, func createShip() -> SKSpriteNode { should be turned into func createShip() {. As you already have the node set up with the image texture now, and are using the hero class as the node, there is no need to return a SKSpriteNode.
Delete let heroShip = SKSpriteNode(imageNamed: "heroShip") as the hero class is going to be our heroShip.
Replace any usage of the heroShip.whatever variable with self.whatever. At the end, delete the return heroShip.
You seem to not be understanding what is happening with your code.
As of right now createShip() is creating a hero ship, but you never use it.
By looking at your code, it seems like createShip() is not even needed. From looking at it, hero IS heroShip, so do the code in your init.
convenience init(imageNamed: String,sceneSize:CGSize) {
let heroTexture = SKTexture(imageNamed: imageNamed)
self.init(texture: heroTexture, color: UIColor.whiteColor(), size: heroTexture.size()) //This may need to be tweeked, my mac is dead right now to verify.
self.anchorPoint = CGPointMake(1.0, 0.5)
self.physicsBody = SKPhysicsBody(rectangleOfSize: self.size)
self.physicsBody?.usesPreciseCollisionDetection = true
self.zPosition = 1.0
self.physicsBody?.mass = 0.02
self.physicsBody?.dynamic = true
self.physicsBody?.affectedByGravity = false
heroShip.physicsBody?.categoryBitMask = ObjectCategory.collisionHeroCategory.rawValue
heroShip.physicsBody?.contactTestBitMask = ObjectCategory.sceneCategory.rawValue
heroShip.physicsBody?.collisionBitMask = 0x0 | ObjectCategory.sceneCategory.rawValue
//self.position = CGPointMake((scene?.frame.size.width)!/6.0, (scene?.frame.size.height)!/2.0)
self.position = CGPointMake(sceneSize.width, sceneSize.height)
}
You now come across another problem, and that is your SKPhysicsBody will not align to your anchor point.
To fix this, create the SKPhysicsBody like this:
let centerPoint = CGPointMake(self.size.width / 2 - (self.size.width * self.anchorPoint.x), self.size.height / 2 - (self.size.height * self.anchorPoint.y))
self.physicsBody = SKPhysicsBody(rectangleOfSize: self.size, center: centerPoint)
Basically the game consists of a basket that the player moves across the screen, the aim of the game is for the player to catch balls falling from the top of the screen. I am currently trying to add collision detection between the balls and the basket, but am facing difficulties namely, implementing this collision detection. I am new to swift, sprite kit and app development, so please help. Any help would be appreciated. Another problem I am facing is that all the balls are falling in the centre of the screen. A line of code is supposed to execute when, the ball hits the basket and following that the ball should disappear, please help as I am new to Spritekit.
import SpriteKit
class GameScene: SKScene {
var basket = SKSpriteNode()
let actionMoveRight = SKAction.moveByX(50, y: 0, duration: 0.2)
let actionMoveLeft = SKAction.moveByX(-50, y: 0, duration: 0.2)
//let physicsBody = SKPhysicsBody(texture: , size: 3500)
override func didMoveToView(view: SKView) {
/* Setup your scene here */
self.physicsWorld.gravity = CGVectorMake(0.0, -0.5)
self.backgroundColor = SKColor.whiteColor()
basket = SKSpriteNode(imageNamed: "basket")
basket.setScale(0.5)
basket.position = CGPointMake(self.size.width/2, self.size.height/8)
basket.size.height = 50
basket.size.width = 75
self.addChild(basket)
let updateAction = SKAction.runBlock {
var choice = arc4random_uniform(3)
switch choice {
case 1 :
var ball1 = SKSpriteNode(imageNamed: "redBall")
ball1.position = CGPointMake(self.size.width/3, self.size.height)
ball1.setScale(0.5)
ball1.size.height = 20
ball1.size.width = 30
ball1.physicsBody = SKPhysicsBody(circleOfRadius: ball1.size.height / 2.75)
ball1.physicsBody!.dynamic = true
self.addChild(ball1)
println("0")
case 0 :
var ball2 = SKSpriteNode(imageNamed: "redBall")
ball2.position = CGPointMake(self.size.width/5, self.size.height)
ball2.setScale(0.5)
ball2.size.height = 20
ball2.size.width = 30
ball2.physicsBody = SKPhysicsBody(circleOfRadius: ball2.size.height / 2.75)
ball2.physicsBody!.dynamic = true
self.addChild(ball2)
println("1")
case 2 :
var ball3 = SKSpriteNode(imageNamed: "redBall")
ball3.position = CGPointMake(self.size.width*4/5, self.size.height)
ball3.setScale(0.5)
ball3.size.height = 20
ball3.size.width = 30
ball3.physicsBody = SKPhysicsBody(circleOfRadius: ball3.size.height / 2.75)
ball3.physicsBody!.dynamic = true
self.addChild(ball3)
println("2")
default :
println("Problem")
}
}
let waitDuration : NSTimeInterval = 1.0
let updateAndWaitAction = SKAction.sequence([updateAction,SKAction.waitForDuration(waitDuration)])
let repeatForeverAction = SKAction.repeatActionForever(updateAndWaitAction)
self.runAction(repeatForeverAction)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if location.x > basket.position.x {
if basket.position.x < self.frame.maxX {
basket.runAction(actionMoveRight)
}
}
else {
if basket.position.x > self.frame.minX {
basket.runAction(actionMoveLeft)
}
}
}
}
override func update(currentTime: CFTimeInterval) {
}
}
For now you have a code that typically used in situations where user is taping something. You need to use BodyA & BodyB and assign a bitmasks to your nodes.
self.basket.physicsBody?.categoryBitMask = ColliderType.basket.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball1.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball1.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball2.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball2.rawValue
self.basket.physicsBody?.contactTestBitMask = ColliderType.ball3.rawValue
self.basket.physicsBody?.collisionBitMask = ColliderType.ball3.rawValue
And do that for every ball too. And then in func didBeginContact you should say to Xcode what to do, if you have an animation or something:
if (contact.bodyA.categoryBitMask == ColliderType.ball1.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball1.rawValue) {
yourGameOverFunc()
}
if (contact.bodyA.categoryBitMask == ColliderType.ball2.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball2.rawValue) {
yourGameOverFunc()
}
if (contact.bodyA.categoryBitMask == ColliderType.ball3.rawValue || contact.bodyB.categoryBitMask == ColliderType.ball3.rawValue) {
yourGameOverFunc()
}
I want to remove a node generated by this function by touching it.
func cuadrado(){
var cuadradoRojo = SKSpriteNode(imageNamed: "cuadradoRojo")
cuadradoRojo.physicsBody = SKPhysicsBody(circleOfRadius: cuadradoRojo.size.width)
cuadradoRojo.physicsBody?.dynamic = true
cuadradoRojo.physicsBody?.categoryBitMask = BodyType.cuadrado.rawValue
cuadradoRojo.physicsBody?.contactTestBitMask = BodyType.colorAzul.rawValue | BodyType.colorRojo.rawValue
cuadradoRojo.physicsBody?.collisionBitMask = 0
var actionArray3:NSMutableArray = NSMutableArray()
if gameOver == false{
let minX = circuloAzul.size.width/2
let maxX = self.frame.size.width - circuloAzul.size.width/2
let rangeX = maxX - minX
let position:CGFloat = CGFloat(arc4random()) % CGFloat(rangeX) + CGFloat(minX)
cuadradoRojo.position = CGPointMake(position, self.frame.size.height + cuadradoRojo.size.height)
addChild(cuadradoRojo)
let minDuration = 3
let duration = Int(minDuration)
func touchesBegan(touches: NSSet, withEvent event: UIEvent){
for touch: AnyObject in touches {
let location = (touch as UITouch).locationInNode(self)
if self.nodeAtPoint(location) == self.cuadradoRojo {
cuadradoRojo.removeFromParent()
}
}
}
actionArray3.addObject(SKAction.moveTo(CGPointMake(position, -cuadradoRojo.size.height), duration: NSTimeInterval(duration)))
cuadradoRojo.runAction(SKAction.sequence(actionArray3))
}
It runs perfectly but it doesn't detect the touch, if I put the touches function outside it detects the touch, but the game crashes.
Thanks for your help!