Nodes in a Scene from different layers will not in

2019-04-13 11:06发布

问题:

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.,...