I am currently working on a game where enemies get spawned from the left of the screen and move right. I want to give these enemies their own attributes (health, strength, etc). So I am working on creating a Basic_fighter class. I also have a user sniper scope that the user uses to hit the enemies. The problem I have is how to access the enemies attributes in the DidBeginContact Function since the function only returns two nodes, and not the class information. I will put my code below
Basic_Fighter_Class
import Foundation
import SpriteKit
class Basic_Fighter {
var health = Int()
var type = SKSpriteNode()
init(sk:SKSpriteNode){
self.type = sk
self.health = 3
}
}
func spawn_enemies(){
let enemynode = SKSpriteNode(imageNamed: "Shooter")
enemynode.size = CGSize(width: 100, height: 40)
enemynode.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
enemynode.physicsBody = SKPhysicsBody(rectangleOfSize: enemynode.size)
enemynode.physicsBody?.affectedByGravity = false
enemynode.physicsBody?.categoryBitMask = BodyType.enemy
enemynode.physicsBody?.contactTestBitMask = BodyType.bullet
let enemy = Basic_Fighter(sk: enemynode)
addChild(enemynode)
}
I am able to detect the contact made between the user scope and the enemy in the DidBeginContact function, but I do not know how to access the information of the enemy, such as its health.
I think you can access it with enemynode.health, I'm not sure though.
Related
I'm currently working on a small iOS game. In its current iteration, 20 targets spawn and move across the screen space-invaders style, and you control a little ship to shoot and destroy them. The code for my targets, the player ship's bullets, and a simple collision detection function I've written in the interim are as follows:
class Red_Target: SKSpriteNode{
var game_scene: GameScene!
private var ship_texture: SKTexture!
convenience init(scale: CGFloat, game_world: GameScene){
self.init(texture: SKTexture(imageNamed: "Proto Target"))
self.ship_texture = SKTexture(imageNamed: "Proto Target")
self.setScale(scale)
game_scene = game_world
game_scene.addChild(self)
self.position = CGPoint(x: game_scene.view!.bounds.width/10, y: 9 * game_scene.view!.bounds.height/10)
//self.physicsBody = SKPhysicsBody(texture: ship_texture, size: self.size)
self.physicsBody = SKPhysicsBody(circleOfRadius: 13)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.categoryBitMask = CollisionType.Enemy.rawValue
self.physicsBody!.contactTestBitMask = CollisionType.Player_Bullet.rawValue
}
func move() {
self.run(space_invaders(scene: game_scene))
}
}
class PC_Bullet: SKSpriteNode{
convenience init(scale: CGFloat){
self.init(imageNamed: "Goodbullet")
self.setScale(scale)
self.physicsBody = SKPhysicsBody(circleOfRadius: 3)
self.physicsBody!.affectedByGravity = false
self.physicsBody!.categoryBitMask = CollisionType.Player_Bullet.rawValue
self.physicsBody!.collisionBitMask = 0x0
self.physicsBody!.contactTestBitMask = CollisionType.Enemy.rawValue
}
}
func didBegin(_ contact: SKPhysicsContact) {
contact.bodyA.node!.removeFromParent()
contact.bodyB.node!.removeFromParent()
}
}
This code, in its current iteration, works just fine. However, if the line defining the target's physicsbody as its texture is uncommented and the line defining physicsbody as circleOfRadius is removed, the game will consistently crash after the 5th target is destroyed, claiming that didBegin unwraps a nil value. Why does this only happen from physics bodies with textures? Is there any way I could change the code for this to work? I would love to be able to use the physics body from texture function later on, when working with more irregular shapes.
You are pulling a classic nooby mistake. You are removing your bodies too early. If you browse Stackoverflow you will find a plethera of ways to solve it.
The basic idea is do not remove your sprites until the end of the physics phase because your 1 sprite could have multiple contact points to handle. So come up with a way to flag sprites that need to be deleted, and remove them during the didSimulatePhysics function.
is it somehow possible to destroy an object on contact? Like not just delete it from the screen with body.removeFromParent(), I would like to have an animation.
I have a player and walls, and when the player has a special powerup, I want it to be able to destroy the walls on contact. I could imagine that I have like the wall split up as many little physics bodies and they hold together on like an anchor point and when my player hits it, they get an impulse from the player (just set isDynamic to true I guess) and losen the anchor point so all the sprite Nodes will fly their way and so the wall will be destroyed.
Can you give me some help / advise of a good way of doing that?
You don't need to have the nodes making up the wall held together in any way - just place them on the screen. If the player doesn't have the power-up, turn off the bit for the player in the wall nodes' physicsBodies collisionBitMask so that the wall nodes do not collide with the player. Then when the player hits the wall, the player will be affected by the collision (and bounce off) but the wall nodes will be unaffected.
When the player has the power-up, make the wall nodes affected by the collision and also turn on contacts between the player and the wall (it's enough just to turn on the bit for the wall category in the player's contactTestBitMask). Then the wall nodes will be affected by the collision (and move or spin away) and your didBegin() will be called and you can run an action on each wall node comprising of the animation you want and ending with removeFromParent().
A guide to collision and contactTest bit masks:
https://stackoverflow.com/a/40596890/1430420
Manipulating bit masks to turn collision & contacts off and on.
https://stackoverflow.com/a/46495864/1430420
Edit: SK demo showing an object hitting a wall made up of blocks:
Create a new SK project and use this as the GameScene,swift:
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
physicsWorld.gravity = CGVector(dx:0, dy:0)
let ball = SKSpriteNode.init(color: .red, size: CGSize(width: 50, height: 50))
ball.physicsBody = SKPhysicsBody.init(circleOfRadius: ball.size.width/2)
ball.position = CGPoint(x: 0, y: 0)
buildWall()
addChild(ball)
ball.physicsBody?.applyImpulse(CGVector(dx: 0, dy: 50))
}
func buildWall() {
let xStart : CGFloat = ((scene?.size.width)!/2) * -0.9
var brickPosition = CGPoint(x: xStart, y: 500)
let brickSize = CGSize(width: 20, height:20)
for wallRow in 1...10 {
for wallColumn in 1...30 {
let brick = SKSpriteNode(color: .yellow, size: brickSize)
brick.physicsBody = SKPhysicsBody.init(rectangleOf: brick.size)
brick.position = brickPosition
addChild(brick)
brickPosition.x += brickSize.width + 1
}
brickPosition.x = xStart
brickPosition.y -= 11
}
}
}
In my game, the user controls a ship which they move around.
How should I allow the user to select from a range of nodes?
For example, offering a red/blue/green ship which they can choose from or even unlock when their score = X.
Here is my code for the current player node:
let shipTexture = SKTexture(imageNamed: "ship1.png")
ship = SKSpriteNode(texture: shipTexture)
ship.position = CGPoint(x: self.frame.midX, y: -self.frame.height / 3)
ship.zPosition = 3
ship.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 30, height: 100))
ship.physicsBody!.isDynamic = false
ship.run(makeShipAnimate)
ship.physicsBody!.contactTestBitMask = ColliderType.ship.rawValue
ship.physicsBody!.categoryBitMask = ColliderType.ship.rawValue
ship.physicsBody!.collisionBitMask = ColliderType.ship.rawValue
ship.physicsBody?.affectedByGravity = false
self.addChild(ship)
Is there a way to allow the user to pick between "ship1.png" or "ship2.png" for example?
There are many ways to do this. Something like this should get you started:
import SpriteKit
class GameScene: SKScene {
var shipName = ""//create a var to hold the chosen ship
Then create some buttons or a way for the user to choose which ship and create a function to handle the selection by passing in an Int which corresponds to a particular color:
func chooseShip(ship: Int) {
switch ship {
case 0:
shipName = "blueShip.png"
break
case 1:
shipName = "greenShip.png"
break
default:
shipName = "greenShip.png"
}
}
So if the blue ship is chosen for example, that button would call the above function and pass in the selection:
chooseShip(ship: 0)
After the user has chosen you can load the image you want:
let shipTexture = SKTexture(imageNamed: shipName)
etc...
I'm making a simple game(using Swift & SpriteKit), where I have a circle that can be dragged around. but the circle is not allowed to go through walls.
My collision BitMask works perfectly, but when I drag fast enough, the circle ends up going through the walls.
The Initialisation of the Player Sprite goes Like this:
func initPlayerSprite(){
let playerTexture = SKTexture(imageNamed: "player.png")
let originX = CGRectGetMidX(self.frame)
let originY = CGRectGetMidY(self.frame)
player = SKSpriteNode(texture: playerTexture, size: CGSize(width: 26, height: 26))
player.position = CGPoint(x: originX , y: originY)
player.physicsBody = SKPhysicsBody(circleOfRadius: playerTexture.size().height/2)
player.physicsBody!.dynamic = true
player.physicsBody?.allowsRotation = false
player.physicsBody!.categoryBitMask = ColliderType.Player.rawValue
player.physicsBody!.contactTestBitMask = ColliderType.Floor.rawValue + ColliderType.Gap.rawValue
player.physicsBody!.collisionBitMask = ColliderType.Wall.rawValue + ColliderType.Floor.rawValue
self.addChild(player)
}
My code for moving the Sprite goes like this:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?){
var nodeTouched = SKNode()
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let dx = -player.position.x + location.x
let dy = -player.position.y + location.y
let movePlayer = SKAction.moveBy(CGVector(dx: dx, dy: dy), duration: 0.02)
}
}
Any Idea how to make sure the collision detection work even at high velocities?
From your code its hard to judge.
Why are you calling the collisions like
ColliderType.Wall.rawValue + ColliderType....
I haven't seen this before with a "+" as a separator, usually you use a "|"
to separate them.
ColliderType.Wall.rawValue | ColliderType....
Using a plus should add the two values together and will not treat it as 2 collision types, as far as I understand. I might be wrong here.
Also did you try using precise collision detection like so?
...physicsBody.usesPreciseCollisionDetection = true.
Apple describes this bool as follows
The default value is NO. If two bodies in a collision do not perform precise collision detection, and one passes completely through the other in a single frame, no collision is detected. If this property is set to YES on either body, the simulation performs a more precise and more expensive calculation to detect these collisions. This property should be set to YES on small, fast moving bodies.
Not sure this is helping
So I have a game where you need to shoot enemies. When touchesBegan a bullet comes under your finger, when touchesEnded – it fires at enemy. I made it with SKActions. It's working well until it's game over. I don't have a special scene for it, it's just a node with buttons. But when it appears, SKActions on bullet and enemies are still running by touch. I want to disable them when it's game over and don't know how to do it. For example, one of my enemis I created like this:
func addMiddleHeart() {
middleheart = SKSpriteNode(imageNamed: "redh")
middleheart.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame) + 100)
middleheart.zPosition = 1
middleheart.physicsBody = SKPhysicsBody(circleOfRadius: middleheart.size.width / 2)
middleheart.physicsBody?.dynamic = true
middleheart.physicsBody?.categoryBitMask = PhysicsCategory.MiddleHeart
middleheart.physicsBody?.contactTestBitMask = PhysicsCategory.Arrow
middleheart.physicsBody?.collisionBitMask = PhysicsCategory.None
addChild(middleheart)
let moveToPoint = SKAction.moveTo(CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) + leftHeart.size.height * 1.5), duration: 0.5)
SKActionTimingMode.EaseOut
middleheart.runAction(moveToPoint)
}
override func didMoveToView(view: SKView) {
runAction(SKAction.runBlock(addMiddleHeart))
}
gameScene.removeAllActions() is the statement that you want to use. It'll indiscriminately remove all running SKActions.