Apologies in advance as this Question involves allot of code. I have a GameScene with multiple layers holding Nodes/SpriteNodes for HUD/Scores/ etc. One layer, LayerProjectile, holds a Weapon Entity.
Said Entity has both Sprite and Physics Components. Problem is the Projectile nodes in the LayerProjectile do not interact with nodes in the main layer (worldLayer), of the GameScene.
All physics bodies are dynamic, and the GameScene has its PhysicWorldDelgate
class GamePlayMode: SGScene, SKPhysicsContactDelegate {...
// Weapon Enity (SGEntity is a GKEntity)
class ThrowWeapon : SGEntity {
var spriteComponent: SpriteComponent!
var physicsComponent: PhysicsComponent
init(position: CGPoint, size: CGSize, texture:SKTexture){
super.init();
//Initilse Compnemnets
spriteComponent = SpriteComponent(entity: self, texture: texture, size: size, position: position);
addComponent(spriteComponent);
physicsComponent = PhysicsComponent(entity: self, bodySize: CGSize(width: spriteComponent.node.size.width * 0.8, height: spriteComponent.node.size.height * 0.8), bodyShape: .square, rotation: true);
physicsComponent.setCategoryBitmask(ColliderType.Projectile.rawValue, dynamic: true);
physicsComponent.setPhysicsCollisions(ColliderType.None.rawValue);
physicsComponent.setPhysicsContacts( ColliderType.Destroyable.rawValue | ColliderType.Enemy.rawValue);
physicsComponent.setEffectedByGravity(false);
addComponent(physicsComponent);
//Final Steps of compnements
spriteComponent.node.physicsBody = physicsComponent.physicsBody;
spriteComponent.node.name = "weapon";
name = "weapon";
spriteComponent.node.zPosition = 150;
spriteComponent.node.userData = ["startX" : position.x];
// Handle Contacts
override func contactWith(entity: SGEntity) { //If contact with Enity in GemeScene
switch entity.name {
case "destructableEntity":
//Remove from Scsene self.parent?.parent?.runAction(SKAction.playSoundFileNamed("110548__noisehag__comp-cover-02.wav", waitForCompletion: false))
if let spriteComponentDestructable = entity.componentForClass(SpriteComponent.self){
let pos = spriteComponentDestructable.node.position;
spriteComponent.node.removeFromParent();
spriteComponentDestructable.node.parent?.runAction(SKAction.playSoundFileNamed("110548__noisehag__comp-cover-02.wav", waitForCompletion: false));
spriteComponentDestructable.node.removeAllActions();
spriteComponentDestructable.node.removeFromParent();
emitterExplosion(pos);
}
// self.runAction(SKAction.sequence([SKAction.fadeAlphaTo(0.0, duration: 0.5), SKAction.removeFromParent()]))
break;
case "enemyEntity":
if let spriteComponentEnemy = entity.componentForClass(SpriteComponent.self){
let pos = spriteComponentEnemy.node.position;
spriteComponent.node.removeFromParent();
spriteComponentEnemy.node.parent?.runAction(SKAction.playSoundFileNamed("110548__noisehag__comp-cover-02.wav", waitForCompletion: false));
spriteComponentEnemy.node.removeAllActions();
spriteComponentEnemy.node.removeFromParent();
emitterExplosion(pos);
} break;
default:
//
break;
}
LayerProjectile, which inherits from Layer- a SKNode, and which handles all weapon entities added to GameScene
class LayerProjectiles: Layer {
override func updateNodes(delta:CFTimeInterval,childNumber:Int,childNode: SKNode) {
print("Player movenment direction: \(playerMovingRighToLeft)");
if playerMovingRighToLeft {
// player moving to right so throw weapon same direction
childNode.position = childNode.position + CGPoint(x: delta * -weaponSpeed, y: 0.0);
childNode.zRotation = childNode.zRotation - (CGFloat(delta) * 10);
} else{
childNode.position = childNode.position + CGPoint(x: delta * weaponSpeed, y: 0.0)
childNode.zRotation = childNode.zRotation - (CGFloat(delta) * 10)
}
// When arrow within this layer moves out of the screen remove it, and the reverse
if childNode.position.x > ((childNode.userData!["startX"] as! CGFloat) + (SKMSceneSize!.width * 1.2)) || childNode.position.x < ((childNode.userData!["startX"]as! CGFloat) - (SKMSceneSize!.width * 1.2)){
childNode.removeFromParent()
}
}
Adding Instance of LayerProjectile and worldLayer (holding all other nodes, some of which have catBitMask set to notify when contact with Weapon Entity), to the GameScene (an instance of GamePlayMode, a SGScene)
class GameSceneState: GKState {
unowned let gs: GamePlayMode
init(scene: GamePlayMode) {
self.gs = scene
}
}
gs.physicsWorld.contactDelegate = gs
gs.physicsWorld.gravity = CGVector(dx: 0.0, dy: -1.0)
//Layers
gs.layerProjectile = LayerProjectiles();
gs.worldLayer = TileLayer(levelIndex: gs.levelIndex, typeIndex: .setMain)
gs.backgroundLayer = SKNode()
gs.overlayGUI = SKNode()
gs.addChild(gs.worldLayer)
gs.addChild(gs.layerProjectile);// For throwing wepaons, made in GamePlayMode
And finally the physics/sprite component settings for a Destrutable entity, which is a child of worldLayer (added to the GaemScene along with LayerProjectile)
class Destructable : SGEntity{
var spriteComponent: SpriteComponent!
var animationComponent: AnimationComponent!
var physicsComponent: PhysicsComponent!
var scrollerComponent: FullControlComponent!
init(position: CGPoint, size: CGSize, texture:SKTexture){
super.init();
//Initilse Compnemnets
spriteComponent = SpriteComponent(entity: self, texture: texture, size: size, position: position);
addComponent(spriteComponent);
physicsComponent = PhysicsComponent(entity: self, bodySize: CGSize(width: spriteComponent.node.size.width * 0.8, height: spriteComponent.node.size.height * 0.8), bodyShape: .square, rotation: false);
physicsComponent.setCategoryBitmask(ColliderType.Destroyable.rawValue , dynamic: true);
physicsComponent.setPhysicsCollisions(ColliderType.Wall.rawValue | ColliderType.Player.rawValue | ColliderType.Destroyable.rawValue); // collides with so bpounces off
physicsComponent.setPhysicsContacts(ColliderType.Wall.rawValue | ColliderType.Player.rawValue | ColliderType.Projectile.rawValue);
physicsComponent.setEffectedByGravity(true);
addComponent(physicsComponent);
//Final Steps of compnements
spriteComponent.node.physicsBody = physicsComponent.physicsBody;
spriteComponent.node.name = "destructableEntity";
name = "destructableEntity";
spriteComponent.node.zPosition = 150;
//spriteComponent.node.userData = ["startX" : position.x];
}
// Handle contacts
override func contactWith(entity: SGEntity) {
//
if entity.name == "weapon"{
print("Crate hit Projectile");
}
}
Instantionation of the Weapon Projectile (when user hits throw button during game play) and Destructible Entities when initializing GameScene and adding all sprite nodes:
let throwWeapon = ThrowWeapon(position: CGPoint(x: self.spriteComponent.node.position.x, y: self.spriteComponent.node.position.y + (self.spriteComponent.node.size.height / 2)), size: CGSize(width: 9, height: 40), texture: textureAtlas.textureNamed("kunai"));
playerEnt.gameScene.layerProjectile.addChild(throwWeapon.spriteComponent.node);
// Destructible Entity
gs.worldLayer.enumerateChildNodesWithName("placeholder_Destructable") { (node, stop) -> Void in
let crate = Destructable(position: node.position, size: CGSize(width: 32, height: 32), texture: tileAtlasInstance.textureNamed("Crate"));
//crate.name = "placeholder_Destructable";
//crate.spriteComponent.node.zPosition = GameSettings.GameParams.zValues.zWorldFront;
self.gs.addEntity(crate, toLayer: self.gs.worldLayer)
}
Any input appreciated.
Added: Category bitmask enum for ColliderTypes:
enum ColliderType:UInt32 {
case Player = 0
case Destroyable = 0b1
case Wall = 0b10
case Collectable = 0b100 // get points
case EndLevel = 0b1000
case Projectile = 0b10000
case None = 0b100000
case KillZone = 0b1000000
case arrow = 0b10000000;
case fire = 0b100000000; // die
case blueFire = 0b1000000000; // die
case greenCrystal = 0b10000000000; //more points like collectables
case barrel1 = 0b100000000000; //die
case barrell2 = 0b1000000000000; //die
case swordPoints = 0b10000000000000; //points
case spikes = 0b100000000000000; //die
case Enemy = 0b1000000000000000; // see Eneemy Entity
// against arrow and fire
}
// The SGEnity class where func handling physics contacts for Enities are called and overridden in Entity (Weapon, Destructible) classes
class SGEntity: GKEntity {
var name = ""
func contactWith(entity:SGEntity) { //Overridden by subclass
}
// PhyscisComponent
enum PhysicsBodyShape {
case square
case squareOffset;// so the player knowns where the tile physics body is
case circle
case topOutline
case bottomOutline
}
class PhysicsComponent: GKComponent {
var physicsBody = SKPhysicsBody()
init(entity: GKEntity, bodySize: CGSize, bodyShape: PhysicsBodyShape, rotation: Bool) {
switch bodyShape {
case.square:
physicsBody = SKPhysicsBody(rectangleOfSize: bodySize)
break
case.squareOffset:
physicsBody = SKPhysicsBody(rectangleOfSize: bodySize, center: CGPoint(x: 0, y: bodySize.height/2 + 2))
break
case .circle:
physicsBody = SKPhysicsBody(circleOfRadius: bodySize.width / 2)
break
case .topOutline:
physicsBody = SKPhysicsBody(edgeFromPoint: CGPoint(x: (bodySize.width/2) * -1, y: bodySize.height/2), toPoint: CGPoint(x: bodySize.width/2, y: bodySize.height/2))
break
case .bottomOutline:
physicsBody = SKPhysicsBody(edgeFromPoint: CGPoint(x: (bodySize.width/2) * -1, y: (bodySize.height/2) * -1), toPoint: CGPoint(x: bodySize.width/2, y: (bodySize.height/2) * -1))
break
}
physicsBody.allowsRotation = rotation
//Defaults
physicsBody.dynamic = true
physicsBody.contactTestBitMask = ColliderType.None.rawValue
physicsBody.collisionBitMask = ColliderType.None.rawValue
}
func setCategoryBitmask(bitmask:UInt32, dynamic: Bool) {
physicsBody.categoryBitMask = bitmask
physicsBody.dynamic = dynamic
}
func setPhysicsCollisions(bitmask:UInt32) {
physicsBody.collisionBitMask = bitmask
}
func setPhysicsContacts(bitmask:UInt32) {
physicsBody.contactTestBitMask = bitmask
}
func setEffectedByGravity (gravity: Bool){
physicsBody.affectedByGravity = gravity;
}
}
So, contactsBegan is called via PhysicsDelage of the GameScene, :
if let bodyA = contact.bodyA.node as? EntityNode, //SpriteNode
let bodyAent = bodyA.entity as? SGEntity, // COnverted to SGEntity
let bodyB = contact.bodyB.node as? EntityNode, //SprtiteNode
let bodyBent = bodyB.entity as? SGEntity //ERROR HERE : Cast to SGEnity
{
print("SGENity Description: \(bodyBent)");
contactBegan(bodyAent, nodeB: bodyBent)
contactBegan(bodyBent, nodeB: bodyAent)
}else{
print("Cast error")
}
Issue appears to be casting the Projectile Entity to a SGEntity, this fails and contactsWith... of Projectile Entity is never called. All other SGEnities (Destructible) cast to SGEntity with no issues.,...