Sorry to be awkward but please can I have the full code on how to do this:
I would like a delay of 1 second after every bullet is shot in my game to prevent bullet spam. If possible without creating a separate function for the bullet spawning as I have done this in touchesEnded. So tap, shoot, wait. tap, shoot, wait. And in the wait, if screen is tapped, nothing happens. Thanks and sorry I am a beginner
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self)
//Set up initial location of bullet and properties
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.position = player.position
bullet.setScale(0.75)
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
bullet.physicsBody?.isDynamic = true
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.usesPreciseCollisionDetection = true
//Determine offset of location to bullet
let offset = touchLocation - bullet.position
//Stops Bullet from shooting backwards
if (offset.y < 0) { return }
addChild(bullet)
//Get the direction of where to shoot
let direction = offset.normalized()
//Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
//Add the shoot amount to the current position
let realDest = shootAmount + bullet.position
//Create the actions
if currentGameState == gameState.inGame {
let actionMove = SKAction.move(to: realDest, duration: 1.2)
let actionMoveDone = SKAction.removeFromParent()
bullet.run(SKAction.sequence([actionMove, actionMoveDone]))
}
}
Try this:
var fired = false
// Other code...
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self)
if fired == false {
fired = true
//Set up initial location of bullet and properties
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.position = player.position
bullet.setScale(0.75)
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
bullet.physicsBody?.isDynamic = true
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.usesPreciseCollisionDetection = true
//Determine offset of location to bullet
let offset = touchLocation - bullet.position
//Stops Bullet from shooting backwards
if (offset.y < 0) { return }
addChild(bullet)
//Get the direction of where to shoot
let direction = offset.normalized()
//Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
//Add the shoot amount to the current position
let realDest = shootAmount + bullet.position
//Create the actions
if currentGameState == gameState.inGame {
let actionMove = SKAction.move(to: realDest, duration: 1.2)
let actionMoveDone = SKAction.removeFromParent()
bullet.run(SKAction.sequence([actionMove, actionMoveDone]))
}
run(SKAction.wait(forDuration: 1), completion: { fired = false })
}
}
This means that every time that a bullet is fired, a timer for 1 second occurs which prevents the bullet code from being run until the time is over. At this point, the Boolean switches back to false allowing the code to be run again.
Related
I found this answer on how to make the enemies move towards the player. If I have one enemy on the GameScene, it works perfect. However, if I add another enemy to the scene, only one of them moves while the other is stationary.
Here is the code on how I have everything set up so far.
var player: SKSpriteNode?
var spawnZombie = SKSpriteNode()
override func didMove(to view: SKView) {
playerTexture = SKTexture(imageNamed: "player_2")
player = SKSpriteNode(texture: playerTexture)
player?.name = "player"
player?.position = (pSpawnPoint?.position)!
player?.setScale(0.5)
self.addChild(player!)
randomEnemy()
randomEnemy()
}
This is how my enemies are set up
func randomEnemy(){
spawnPoint0 = childNode(withName: "spawnPoint_0") as? SKSpriteNode
spawnPoint1 = childNode(withName: "spawnPoint_1") as? SKSpriteNode
spawnPoint2 = childNode(withName: "spawnPoint_2") as? SKSpriteNode
// Set up enemies
enemy = SKSpriteNode(imageNamed: "enemy_2")
enemy.name = "enemy"
enemy.setScale(0.5)
enemy.zPosition = 10
enemy.physicsBody = SKPhysicsBody(rectangleOf: enemy.size)
enemy.physicsBody?.categoryBitMask = PhysicsCatagory.Enemy
enemy.physicsBody?.collisionBitMask = PhysicsCatagory.None
enemy.physicsBody?.contactTestBitMask = PhysicsCatagory.Player
enemy.physicsBody?.isDynamic = true
enemy.physicsBody?.affectedByGravity = false
let choice = Int(arc4random_uniform(3))
switch choice {
case 0:
enemy.position = (spawnPoint0?.position)!
self.addChild(enemy)
break
case 1:
enemy.position = (spawnPoint1?.position)!
self.addChild(enemy)
break
case 2:
enemy.position = (spawnPoint2?.position)!
self.addChild(enemy)
break
default:
print("something went wrong")
}
}
override func update(_ currentTime: TimeInterval) {
let location = player?.position
//Aim
let dx = (location?.x)! - enemy.position.x
let dy = (location?.y)! - enemy.position.y
let angle = atan2(dy, dx)
enemy.zRotation = angle - 3 * .pi/2
//Seek
let velocityX = cos(angle) * enemySpeed
let velocityY = sin(angle) * enemySpeed
enemy.position.x += velocityX
enemy.position.y += velocityY
}
Any ideas why only one of the enemies moves whenever I run the function twice? Thanks in advance.
you are putting both of your enemies in the same enemy variable. so when you try to move enemy you are basically moving the last one assigned to that variable.
what you need to do is put them in an array and then move all of the enemies from the array.
let enemies = [SKSpriteNode]()
func randomEnemy() {
//...other code blah blah blah
enemies.append(enemy)
}
and in the update func
override func update(_ currentTime: TimeInterval) {
let location = player?.position
for enemy in enemies {
//Aim
let dx = (location?.x)! - enemy.position.x
let dy = (location?.y)! - enemy.position.y
let angle = atan2(dy, dx)
enemy.zRotation = angle - 3 * .pi/2
//Seek
let velocityX = cos(angle) * enemySpeed
let velocityY = sin(angle) * enemySpeed
enemy.position.x += velocityX
enemy.position.y += velocity
}
}
now you probably want to look at sub classing enemy and having the move functionality in that class but that is outside the scope of your question. This should get you started.
In my game, you tap anywhere on the screen and a bullet goes in that direction. The only problem is that you can shoot as fast as you can tap. Is there any way to add a delay after each shot. So I would like to shoot, wait 1 second then shoot. Here is my code in touchesEnded:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
let touchLocation = touch.location(in: self)
//Set up initial location of bullet and properties
let bullet = SKSpriteNode(imageNamed: "bullet")
bullet.name = "Bullet"
bullet.position = player.position
bullet.setScale(0.75)
bullet.zPosition = 1
bullet.physicsBody = SKPhysicsBody(circleOfRadius: bullet.size.width/2)
bullet.physicsBody?.isDynamic = true
bullet.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
bullet.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
bullet.physicsBody?.collisionBitMask = PhysicsCategory.None
bullet.physicsBody?.usesPreciseCollisionDetection = true
//Determine offset of location to bullet
let offset = touchLocation - bullet.position
//Stops Bullet from shooting backwards
if (offset.y < 0) { return }
addChild(bullet)
//Get the direction of where to shoot
let direction = offset.normalized()
//Make it shoot far enough to be guaranteed off screen
let shootAmount = direction * 1000
//Add the shoot amount to the current position
let realDest = shootAmount + bullet.position
//Create the actions
if currentGameState == gameState.inGame {
let actionMove = SKAction.move(to: realDest, duration: 1.0)
let actionMoveDone = SKAction.removeFromParent()
bullet.run(SKAction.sequence([actionMove, actionMoveDone]))
}
}
Thanks for any help.
This is a more simple approach, based on the use of Date:
var time = Date()
func shoot(after timeInterval: Double) {
guard Date() - timeInterval > time else {
print("WAIT")
return
}
print("SHOOT")
time = Date() // reset the timer
}
// CALL THIS INSIDE touchesEnded
shoot(after: 1)
Just modify for your needs :]
You could take a look at the Throttle implementation of RxSwift for one possible solution. Throttle is used to limit the number of events created in a defined time interval:
let timeIntervalSinceLast: RxTimeInterval
if let lastSendingTime = _lastSentTime {
timeIntervalSinceLast = now.timeIntervalSince(lastSendingTime)
}
else {
timeIntervalSinceLast = _parent._dueTime
}
let couldSendNow = timeIntervalSinceLast >= _parent._dueTime
if couldSendNow {
self.sendNow(element: element)
return
}
You can do this using action keys. An action key is a string that makes an action identifiable.
How to use it in this case?
As I said already in comments, you will fire a bullet, then run an action with a key, on a specific node, which will last one second. A presence of this key/action means that weapon is locked. So every time you try to fire a bullet, you check if this key is present on a specific node. When action finishes, the key will be automatically removed as well. Here is the code:
import SpriteKit
let kLockWeaponActionKey = "kLockWeaponActionKey"
class GameScene: SKScene {
func shoot(atPoint targetLocation:CGPoint){
// 1 check if weapon is unlocked, or return
guard self.action(forKey: kLockWeaponActionKey) == nil else {
print("Weapon locked")
return
}
let bullet = SKSpriteNode(color: .purple, size: CGSize(width: 20, height: 20))
addChild(bullet)
let shoot = SKAction.move(to: targetLocation, duration: 3)
//2 shoot
bullet.run(shoot)
//3 lock weapon
self.run(SKAction.wait(forDuration: 1), withKey: kLockWeaponActionKey)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let targetLocation = touch.location(in: self)
self.shoot(atPoint:targetLocation)
}
}
}
If you try to spam bullets fast, you will see a log in the console which says : "weapon locked".
I get the following error when there's contact between bullet and the plane.
fatal error: unexpectedly found nil while unwrapping an Optional value
Plane Function:
func addPlane() {
var myplane : SKSpriteNode?
myplane = SKSpriteNode(imageNamed: "plane")
let actualY = random(min: 100, max: size.height - myplane!.size.height/2)
myplane!.position = CGPoint(x: size.width + myplane!.size.width/2, y: actualY)
myplane!.physicsBody = SKPhysicsBody(rectangleOfSize: myplane!.size) // 1
myplane!.physicsBody?.dynamic = false // 2
myplane!.physicsBody!.categoryBitMask = CollisionCategoryPowerUpOrbs
myplane!.physicsBody!.collisionBitMask = 0
myplane!.name = "plan"
addChild(myplane!)//
let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
let actionMove = SKAction.moveTo(CGPoint(x: -myplane!.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
myplane!.runAction(SKAction.sequence([actionMove]))
}
When the user touches the screen, it will shoot a bullet with this funciton:
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else {
return
}
let touchLocation = touch.locationInNode(self)
var projectile : SKSpriteNode?
projectile = SKSpriteNode(imageNamed: "bomb")
projectile!.position = CGPoint(x: 50, y: 100.0)
projectile!.physicsBody = SKPhysicsBody(circleOfRadius: projectile!.size.width/2)
projectile!.physicsBody?.dynamic = true
projectile!.physicsBody!.allowsRotation = false
projectile!.physicsBody!.categoryBitMask = CollisionCategoryPlayer
projectile!.physicsBody!.contactTestBitMask = CollisionCategoryPowerUpOrbs
projectile!.physicsBody!.collisionBitMask = 0
projectile!.physicsBody!.linearDamping = 1.0
projectile!.name = "shot"
let offset = touchLocation - projectile!.position
if (offset.x < 0) { return }
addChild(projectile!)
let direction = offset.normalized()
let shootAmount = direction * 1000
let realDest = shootAmount + projectile!.position
let actionMove = SKAction.moveTo(realDest, duration: 2.0)
let actionMoveDone = SKAction.removeFromParent()
projectile!.runAction(SKAction.sequence([actionMove, actionMoveDone]))
}
Function to hide the plane on contact:
func didBeginContact(contact: SKPhysicsContact) {
let nodeB = contact.bodyA.node!
if nodeB.name == "plan" {
nodeB.removeFromParent()
}
}
I call the addPlane function in ViewDidLoad like this:
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addPlane),
SKAction.waitForDuration(1.5)
])
))
Change you code with a '?' to do a nil check:
func didBeginContact(contact: SKPhysicsContact) {
let nodeB = contact.bodyA.node
if nodeB?.name == "plan" {
nodeB?.removeFromParent()
}
}
This solution will prevent the crash but has the disadvantage that some unnecessary contact events are still triggered.
Removing a node inside didBeginContact should be avoided. This can lead to situations where the node is already removed (nil), but the PhysicsBody is still active.
(see also the comments from KnightOfDragon)
I took and modified a piece of code which allows me to shoot bullets in every direction. It works perfectly. But it is generating a random bullet in the moment when player touch is ended. I have 6 different bullets and for purposes of my game I need player to see them before he will tap the screen. I tried to initialize a bullet in a global function and pass it to didMoveToView. It's not working, bullet is generating once and stays at its place.
That is my code for initializing a bullet:
class GameScene: SKScene, SKPhysicsContactDelegate {
var gameOver = false
let cannon = SKSpriteNode(imageNamed: "cannon")
let bulletInCannon = SKSpriteNode()
var endOfScreenRight = CGFloat()
var endOfScreenLeft = CGFloat()
let bmrArray = ["blBl", "magBl", "rBl"]
let cgyArray = ["cBl", "gBl", "yBl"]
let yrmArray = ["yBl", "rBl", "magBl"]
let bulletArray = ["rBullet","magBullet", "blBullet", "cBullet", "gBullet", "yBullet"]
override func didMoveToView(view: SKView) {
endOfScreenLeft = (self.size.width / 2) * CGFloat(-1)
endOfScreenRight = self.size.width / 2
cannon.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
self.addChild(cannon)
cannon.zPosition = 0
var randomBullet = Int(arc4random_uniform(6))
bulletInCannon.texture = SKTexture(imageNamed: bulletArray[randomBullet])
bulletInCannon.position = cannon.position
addChild(bulletInCannon)
bulletInCannon.zPosition = 1000
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(addCgyLine),
SKAction.waitForDuration(1.0)])))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let bullet = SKSpriteNode(texture: bulletInCannon.texture)
bullet.position = cannon.position
addChild(bullet)
let offset = location - bullet.position
let direction = offset.normalized()
let shootAmount = direction * 1000
let realDestination = shootAmount + bullet.position
let actionMove = SKAction.moveTo(realDestination, duration: 2.0)
let actionDone = SKAction.removeFromParent()
bullet.runAction(SKAction.sequence([actionMove, actionDone]))
var randomBullet = Int(arc4random_uniform(6))
bulletInCannon.texture = SKTexture(imageNamed: bulletArray[randomBullet])
}
}
func addCgyLine () {
var cgyBlock = SKSpriteNode(imageNamed: "cyanBox")
cgyBlock.position = CGPointMake(size.width + cgyBlock.size.width/2, CGRectGetMidY(self.frame) + 60)
addChild(cgyBlock)
var randomCGY = Int(arc4random_uniform(3))
cgyBlock.texture = SKTexture(imageNamed: cgyArray[randomCGY])
let actionMove = SKAction.moveTo(CGPoint(x: -cgyBlock.size.width/2, y: CGRectGetMidY(self.frame) + 60), duration: 3)
let actionDone = SKAction.removeFromParent()
cgyBlock.runAction(SKAction.sequence([actionMove, actionDone]))
SKActionTimingMode.EaseOut
}
How can I generate a bullet and only then shoot it? I will be glad to hear any ideas, thanks!
You can generate a bullet in a cannon as a global variable, and then create a new bullet in touchesEnded which will be the bullet that actually fires from the cannon. You set that new bullet's texture to the global bullet's texture. After firing the action, give the global bullet a new texture. If you need to have a reload time, then remove the global bullet temporarily, and use a Boolean to "lock" the cannon from firing until the time has elapsed.
Here's some code that I used. If the cannon moves, then make sure to update the position of the global bullet. The subtraction of CGPoint gives me an error, but I'm using Xcode 6.4.
Edit: Quicker and easier way by giving the bulletInCannon a value
var bulletInCannon = SKSpriteNode(imageNamed: "rBullet")
override func didMoveToView(view: SKView) {
var randomBullet = Int(arc4random_uniform(6))
bulletInCannon.texture = SKTexture(imageNamed: bulletArray[randomBullet])
bulletInCannon.position = cannon.position
addChild(bulletInCannon)
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
// Create bullet that will fire from the cannon
let bullet = SKSpriteNode(texture: bulletInCannon.texture)
bullet.position = cannon.position
addChild(bullet)
let offset = location - bullet.position
let direction = offset.normalized()
let shootAmount = direction * 1000
let realDestination = shootAmount + bullet.position
let actionMove = SKAction.moveTo(realDestination, duration: 2.0)
let actionDone = SKAction.removeFromParent()
bullet.runAction(SKAction.sequence([actionMove, actionDone]))
// Generate a random texture for the global bullet
var randomBullet = Int(arc4random_uniform(6))
bulletInCannon.texture = SKTexture(imageNamed: bulletArray[randomBullet])
}
}
I just started to learn Swift and Sprite Kit. I need assistance with the following code.
I have a countUp that when the ball is touched the countUp starts but my problem is that every time I tap the ball the rate of speed in the countUp increases. I want the CountUp to be stable. Any help will be appreciated.
class Game: SKScene {
var Ball = SKSpriteNode(imageNamed: "Red.png")
var QuitOption = SKLabelNode()
var ScoreLabel = SKLabelNode()
var timescore = Int()
var timesecond = Int()
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.blackColor() // background for the display
self.physicsWorld.gravity = CGVectorMake(0, -9.8)
let SceneBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
SceneBody.friction = 0
self.physicsBody = SceneBody
Ball.size = CGSize(width: 120, height: 120)
Ball.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height*0.7)
Ball.physicsBody = SKPhysicsBody(circleOfRadius: 60)
Ball.physicsBody?.affectedByGravity = true
Ball.physicsBody?.restitution = 0.5
Ball.physicsBody?.linearDamping = 0
Ball.name = "Ball"
self.addChild(Ball)
QuitOption.text = "Quit"
QuitOption.fontName = "Noteworthy-Light"
QuitOption.fontColor = SKColor.greenColor()
QuitOption.fontSize = 35
QuitOption.position = CGPoint(x: self.frame.size.width/2 - 160, y: self.frame.size.height*1 - 110)
QuitOption.name = "Quit"
addChild(QuitOption)
ScoreLabel = SKLabelNode(fontNamed: "Noteworthy-Light")
ScoreLabel.fontSize = 50 // The + will move it to the right side and - to the left side for more accuracy.
ScoreLabel.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/4 + 400) // position of ScoreLabelNode
ScoreLabel.name = "Score+"
ScoreLabel.hidden = false
self.addChild(ScoreLabel)
}
// Making the ball jump after user touches ball
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var touch = touches.first as! UITouch
var location = touch.locationInNode(self)
var node = self.nodeAtPoint(location)
if (node.name == "Quit"){
let myScene = GameScene(size: self.size)
myScene.scaleMode = scaleMode
let reveal = SKTransition.fadeWithDuration(1)
self.view?.presentScene(myScene, transition: reveal)
}
if (node.name == "Ball"){
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
Ball.physicsBody?.allowsRotation = true
Ball.physicsBody?.velocity = CGVectorMake(0, 0)
Ball.physicsBody?.applyImpulse(CGVectorMake(0, 100))
}
}
var actionwait = SKAction.waitForDuration(0.5)
var actionrun = SKAction.runBlock({
self.timescore++
self.timesecond++
if self.timesecond == 60 {self.timesecond = 0}
self.ScoreLabel.text = " \(self.timescore/60):\(self.timesecond)"})
ScoreLabel.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
}
}
I guess that's happening because you are running same block over and over again after every touch. To ensure you run block only once, you can use some Bool indicator and after the first run, set its value appropriately, like this:
if(!self.locked){
self.locked = true // at start, this value should be false
var actionrun = SKAction.runBlock({
self.timescore++
self.timesecond++
if self.timesecond == 60 {self.timesecond = 0}
self.ScoreLabel.text = " \(self.timescore/60):\(self.timesecond)"})
ScoreLabel.runAction(SKAction.repeatActionForever(SKAction.sequence([actionwait,actionrun])))
}
}
Something as an addition to this topic and for future readers could be found here.