Перевернутая x-scale прерывает обработку столкновений (SpriteKit 7.1)

Я использую свойство SKNode xScale, чтобы перевернуть мои спрайты по горизонтали. Теперь, после обновления iOS до версии 7.1 горизонтальное отражение заставляет мои объекты тонуть в земле. (Смотрите анимацию ниже). Проблема возникает только со свойством xScale. Вертикальные сальто работают нормально.

// Init
{
    SKSpriteNode* ground = [SKSpriteNode spriteNodeWithColor:[UIColor blackColor] size:CGSizeMake(winSize.width, 150)];
    ground.position = CGPointMake(winSize.width/2, ground.size.height/2);
    ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size center:CGPointZero];
    ground.physicsBody.dynamic = NO;
    ground.physicsBody.categoryBitMask = 0x01;
    ground.physicsBody.collisionBitMask = 0x02;
    ground.physicsBody.contactTestBitMask = 0x02;
    [self.world addChild:ground];

    SKSpriteNode* turtle = [SKSpriteNode spriteNodeWithImageNamed:@"turtle.png"];
    turtle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:turtle.size.width/2];
    turtle.physicsBody.categoryBitMask = 0x02;
    turtle.physicsBody.collisionBitMask = 0x01;
    turtle.physicsBody.contactTestBitMask = 0x01;
    turtle.position = CGPointMake(winSize.width/2, winSize.height/2);
    [self.world addChild:turtle];
    self.turtle = turtle;
}

// Somewhere else
{
    self.turtle.xScale *= -1;
}

проблема переворота по шкале Х

3 ответа

Решение

Я убежден, что это ошибка в SpriteKit.

В любом случае, вот одно решение проблемы (на самом деле, это скорее обходной путь, чем реальное решение, но...): оберните спрайт в контейнерный узел. Кроме того, контейнерный узел хранит physBody, в то время как дочерний узел является просто графическим узлом. Таким образом, вы можете безопасно переворачивать спрайт с помощью xScale, не влияя на физику узла.

// Init
{
    SKSpriteNode* turtleSprite = [SKSpriteNode spriteNodeWithImageNamed:@"turtle.png"];
    self.turtleSprite = turtleSprite;

    SKNode* turtleWrapper = [SKNode node]; 
    turtleWrapper.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:turtleSprite.size.width/2];
    turtleWrapper.physicsBody.categoryBitMask = 2;
    turtleWrapper.physicsBody.collisionBitMask = 1;
    turtleWrapper.physicsBody.contactTestBitMask = 1;

    [turtleWrapper addChild:turtleSprite];
    [self.world addChild:turtleWrapper];
}

// Elsewhere
{
    self.turtleSprite.xScale *= -1;
}

Я столкнулся с этой проблемой пару дней назад. Я хотел инвертировать спрайт в зависимости от его движения (вправо или влево) и обнаружил, что настройка xScale отключает любые столкновения / контакты.

Однако я использовал эту строку каждый раз, когда устанавливал свойство xScale, и все возвращалось в норму.

node.xScale = -1.0
node.physicsBody = node.physicsBody;

Альтернативное решение

В моем случае я обычно создаю подкласс SKSpriteNode для представления узлов в моей сцене, а затем инкапсулирую их поведение (анимацию и движение) в подклассе. Решение " Обтекание спрайта в контейнерном узле" не работает в моем случае, так как у меня есть действия, которые анимируют текстуру спрайтов, а также перемещают спрайт по очереди.

Например:

class Player: SKSpriteNode{

    override init() {
        ...
        super.init(texture: texture, color: nil, size: texture.size())
        physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(70,75))
    }

    func attack(){
        let action = SKAction.sequence([
            SKAction.moveTo(enemyPosition, duration: 0),
            SKAction.animateWithTextures(frames, timePerFrame: 0.5)
            ])
        runAction(action)
    }
}

Временное решение

В моем случае я решил эту проблему, добавив эти методы в мой класс Player:

class Player: SKSpriteNode{
    private var flipped: Bool
    ...

    func flip(){
        flipped = !flipped
        position = CGPointMake(-position.x, position.y)
    }

    //MARK: Workaround for iOS7 -xScale physics bugs
    func willSimulatePhysics(){
        xScale = 1
    }

    func didSimulatePhysics(){
        xScale = flipped ? -1 : 1
    }
}

А затем переопределите методы SKScene:

class MyScene: SKScene{
    ...

    override func update(currentTime: CFTimeInterval) {
        super.update(currentTime)
        player.willSimulatePhysics()
    }

    override func didSimulatePhysics() {
        super.didSimulatePhysics()
        player.didSimulatePhysics()
    }
}

Это работает, потому что переворот узла отключен непосредственно перед симуляцией физики для сцены. Отражение узла затем применяется непосредственно перед визуализацией кадра.

Другие вопросы по тегам