SKPhysicsBody и [SKNode setScale:]
В моем текущем проекте, использующем SpriteKit, у меня есть куча спрайтов, которые нужно масштабировать независимо друг от друга в разное время. Проблема в том, что когда я масштабирую узел, физическое тело не масштабируется вместе с ним, поэтому оно портит физику. Вот небольшой пример, который я собрал для цели этого вопроса:
CGSize objectSize = CGSizeMake(100, 100);
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0, 0, self.size.width, self.size.height)];
SKSpriteNode *n1 = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:objectSize];
n1.position = CGPointMake(self.size.width/2, 2*self.size.height/3);
n1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:objectSize];
n1.physicsBody.affectedByGravity = YES;
[self addChild:n1];
SKSpriteNode *n2 = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:objectSize];
n2.position = CGPointMake(self.size.width/2, self.size.height/3);
n2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:objectSize];
n2.physicsBody.affectedByGravity = YES;
[self addChild:n2];
[n1 setScale:0.5];
Обратите внимание на то, как синий спрайт (уменьшенный) расположен поверх красного, но вы можете сказать, что его физическое тело все еще имеет размерность, о которой я говорил, и он не масштабируется.
Очевидно, что уменьшение узла не уменьшает физику тела. Итак, мой вопрос: если я должен сделать это вручную, как мне это сделать?
Я пытался поменять тело на размер одного правильного размера при масштабировании, но затем все стало действительно запутанным, если бы у старого тела были суставы и т. Д. Было бы намного проще, если бы я мог как-то масштабировать существующее тело.
6 ответов
Используя либо SKAction
или цикл обновления, вы можете создать новый SKPhysicsBody
с соответствующим масштабом и применить его к объекту. Однако, тем самым вы потеряете скорость. Чтобы это исправить, вы можете сделать следующее:
SKPhysicsBody *newBody = [SKPhysicsBody bodyWithRectangleOfSize:boxObject.size];
newBody.velocity = boxObject.physicsBody.velocity;
boxObject.physicsBody = newBody;
Физические фигуры не могут быть масштабированы. Определенно не в Box2D, который Sprite Kit использует внутри.
Помимо внутренних оптимизаций, масштабирование физического тела будет иметь далеко идущие последствия. Например, масштабирование фигуры может привести к застреванию тела при столкновении. Это повлияет на взаимодействие суставов. Это определенно изменит плотность или массу тела и, следовательно, его поведение.
Вы можете использовать SKNode, к которому вы добавляете спрайт без физического тела, а затем добавляете дополнительный SKNode с телами заданных размеров. Затем вы можете включить или отключить тела, когда начнете масштабировать спрайт. Если вы выберете правильное время, игрок не заметит, что форма столкновения просто изменилась от полного размера до половины, пока спрайт оживляет переход масштабирования.
Вы должны рассчитать плотность тела и, возможно, другие свойства в зависимости от его размера.
Для всех, кто читает этот ответ, я считаю, что теперь физические тела масштабируются с помощью SKNode. Я использую Xcode 8.2.1 и Swift 3.0.2. Я запускаю новый проект, выбираю игру, заполняю детали. Изменить 2 файла:
GameViewController.swift (добавить одну строку)
view.showsFPS = true
view.showsNodeCount = true
// add this to turn on Physics Edges
view.showsPhysics = true
Игра Scene.swift (замените функцию sceneDidLoad()) следующим образом:
override func sceneDidLoad() {
var found = false
self.enumerateChildNodes(withName: "testSprite") {
node, stop in
found = true
}
if !found {
let testSprite = SKSpriteNode(color: UIColor.white, size: CGSize(width: 200.0, height: 200.0))
testSprite.physicsBody = SKPhysicsBody(polygonFrom: CGPath(rect: CGRect(x: -100.0, y: -100.0, width: 200.0, height: 200.0), transform: nil))
testSprite.physicsBody?.affectedByGravity = false
testSprite.name = "testSprite"
let scaleAction = SKAction.repeatForever(SKAction.sequence([
SKAction.scale(to: 2.0, duration: 2.0),
SKAction.scale(to: 0.5, duration: 2.0)
]))
testSprite.run(scaleAction)
self.addChild(testSprite)
}
}
Граница физики масштабируется с помощью узла спрайта.
У меня была точно такая же проблема, я думаю, это ошибка в наборе спрайтов.
Попробуйте использовать действие для масштабирования, это работает на всей сцене.
[self runAction:[SKAction scaleTo:0.5 duration:0]];
Мой оригинальный вопрос
Это то, что вы хотите случиться
- (void) anthoertest
{
CGSize objectSize = CGSizeMake(100, 100);
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:CGRectMake(0, 0, self.size.width, self.size.height)];
SKSpriteNode *n1 = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:objectSize];
n1.position = CGPointMake(self.size.width/2, 2*self.size.height/3);
n1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:n1.size];
n1.physicsBody.affectedByGravity = YES;
[self addChild:n1];
SKSpriteNode *n2 = [SKSpriteNode spriteNodeWithColor:[UIColor redColor] size:objectSize];
n2.position = CGPointMake(self.size.width/2, self.size.height/3);
n2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:n2.size];
n2.physicsBody.affectedByGravity = YES;
[self addChild:n2];
[self shrinkMe:n1];
}
- (void) shrinkMe:(SKSpriteNode *) s
{
[s setScale:0.5];
s.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:s.size];
}
Физическое тело зависит от CGPath SKNode; вам нужно как-то изменить это. Вы можете попробовать масштабировать CGPath примерно так:
//scale up
CGFloat scaleFactor = 1.1;
CGAffineTransform scaleTransform = CGAffineTransformIdentity;
scaleTransform = CGAffineTransformScale(scaleTransform, scaleFactor, scaleFactor);
CGPathRef scaledPathRef = CGPathCreateCopyByTransformingPath(exampleNode.path, &scaleTransform);
exampleNode.path = scaledPathRef;
Но имейте в виду, что это не обновит внешний вид узлов уже в SKScene. Возможно, вам придется объединить изменение размера CGPath с SKAction, который масштабирует узел.
У меня была такая же проблема, все, что я делал, это определял свое физическое тело в функции обновления текущего времени, и оно масштабировалось вместе с ним.
Для вас это будет примерно так:
override func sceneDidLoad() {
SKSpriteNode *n1 = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:objectSize];
n1.position = CGPointMake(self.size.width/2, 2*self.size.height/3);
}
override func update(_ currentTime: TimeInterval) {
n1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:objectSize];
n1.physicsBody.affectedByGravity = YES;
}
Единственная проблема заключается в том, что вы должны каждый раз переопределять свои физические свойства в updateCurrentTime. Вы можете сделать некоторые из этих свойств такими, как их реституция равной переменной, так что вы можете изменить ее свойства, изменив переменную в другом месте. Переопределение его в любом другом месте работает только до вызова следующего кадра.