ios7 - Zoom y desplazamiento en SpriteKit
image-zoom pan (5)
Soy nuevo en SpriteKit. Necesito mostrar una imagen a través de UIImageView O SpriteNode (es una imagen de fondo para un juego). Sin embargo, necesito que el usuario pueda acercarse al fondo y realizar un recorrido panorámico. Logré esto fácilmente sin usar SpriteKit, pero no puedo entender cómo hacer que un nodo acepte el zoom y la panorámica. Si utilizo UIScrollView en el guión gráfico, todos los sprites agregados en mi escena no se vuelven secundarios y no se acercarán ni se desplazarán.
¿Es posible y puede señalarme en la dirección correcta?
EDITAR:
Estoy un poco confundido por tu respuesta, pero esto es lo que hice:
En mi controlador de vista raíz .m:
- (void)viewDidLoad
{
[super viewDidLoad];
SKView * showDesigner = (SKView *)self.view;
SKScene * scene = [iMarchMyScene sceneWithSize:showDesigner.bounds.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
[showDesigner presentScene:scene];
}
En mi escena .m: (se comentan sus pasos)
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
//create SKView (step 1)
SKView *skview_field = [[SKView alloc]initWithFrame:(CGRectMake(0, 0, size.width, size.height))];
//create UIScrollView with same dimensions as your SpriteKit view (step 2)
UIScrollView* uiscrollview_field = [[UIScrollView alloc]initWithFrame:skview_field.frame];
//initialize the field (field is the game''s background node)
SKSpriteNode *field = [SKSpriteNode spriteNodeWithImageNamed:@"Field"];
field.position = CGPointMake(CGRectGetMidX(self.frame), (CGRectGetMidY(self.frame)));
field.size = CGSizeMake(self.size.width, self.size.height / 3);
//create a UIView (called scrollViewContent) with dimensions of background node (step 3)
UIView* scrollViewContent = [[UIView alloc]initWithFrame:field.frame];
//set UIScrollView contentSize with same dimensions as your scrollViewContent and add a scrollContentview as your UISCrollView subview (step 4)
[uiscrollview_field setContentSize:(CGSizeMake(scrollViewContent.frame.size.width, scrollViewContent.frame.size.height))];
scrollViewContent.frame = field.frame;
[uiscrollview_field addSubview:scrollViewContent];
//[self addChild:field]; -- my original code
//.....does a bunch of other inits
return self;
}
Y aquí es donde me pierdo: Paso 5: Ahora, a su vista raíz, agregue su SKView y UIScrollView en la parte superior de SKView.
Si te entendí correctamente, necesito ir a myRootViewController.m y en el método viewDidLoad, agregar SKView y UIScrollView (que se inicializan en myScene.m) como subvistas a SKView inicializadas aquí en el método viewDidLoad? No veo cómo hacer esa parte.
Aquí está mi solución al problema del desplazamiento. Se trata de "robar" el comportamiento de UIScrollView. Aprendí esto de un video de WWDC de 2012 sobre la mezcla de UIKit con OpenGL.
- Agregue los métodos UIScrollViewDelegate a su ViewController y configure el delegado de la vista de desplazamiento en ViewController
- Agregar / Mover el PanGestureRecognizer desde el UIScrollView al SKView
- Utilice el método de devolución de llamada scrollViewDidScroll para controlar cualquier nodo que desee cuando el usuario se desplaza
Proyecto de ejemplo: https://github.com/bobmoff/ScrollKit
Como mencioné en un comentario anterior, estoy experimentando un pequeño retraso en aproximadamente el 30% de las veces que ejecuto la aplicación. El FPS todavía tiene 60 años, pero parece que hay un pequeño conflicto o algo así con la forma en que a menudo se llama el método de delegado, ya que a veces se siente un poco rezagado. Si alguien logra resolver este problema, me gustaría escucharlo. Parece ser que solo cuando estoy sosteniendo mi dedo hacia abajo, nunca se retrasa cuando está ocurriendo la desaceleración.
He creado mi propio método para hacer zoom en un nodo específico sin tener que hacer zoom en toda la escena y esto es lo básico, sin embargo, no es perfecto (y de hecho, he creado su propia solicitud de ayuda aquí: Zooming un SKNode inconsistente )
Esta instrucción if va en el método touchesMoved de la escena:
if (touches.count == 2) {
// this means there are two fingers on the screen
NSArray *fingers = [touches allObjects];
CGPoint fingOneCurr = [fingers[0] locationInNode:self];
CGPoint fingOnePrev = [fingers[0] previousLocationInNode:self];
CGPoint fingTwoCurr = [fingers[1] locationInNode:self];
CGPoint fingTwoPrev = [fingers[1] previousLocationInNode:self];
BOOL yPinch = fingOneCurr.y > fingOnePrev.y && fingTwoCurr.y < fingTwoPrev.y;
BOOL yUnpinch = fingOneCurr.y < fingOnePrev.y && fingTwoCurr.y > fingTwoPrev.y;
BOOL xPinch = fingOneCurr.x > fingOnePrev.x && fingTwoCurr.x < fingTwoPrev.x;
BOOL xUnpinch = fingOneCurr.x < fingOnePrev.x && fingTwoCurr.x > fingTwoPrev.x;
if (xUnpinch | yUnpinch) {
if (YES) NSLog(@"This means an unpinch is happening");
mapScale = mapScale +.02;
[map setScale:mapScale];
}
if (xPinch | yPinch) {
if (YES) NSLog(@"This means a pinch is happening");
mapScale = mapScale - .02;
[map setScale:mapScale];
}
}
El único problema que tengo con él es el comportamiento incoherente y el desplazamiento desapasionado. Utilizo la palabra "solo" en ese sentido con gran liberalidad.
Justin,
puedes usar un pequeño truco, lo estoy usando y funciona muy bien para mí. Aquí está la configuración:
- crear SKView
- crea UIScrollView con las mismas dimensiones que tu vista de SpriteKit
- crea una UIView (permite ponerle nombre scrollContentView) con las mismas dimensiones que tu nodo de fondo
- configure su UIScrollView contentSize con las mismas dimensiones que su scrollContentView y agregue scrollContentView como su subvista UIScrollView
Ahora, a su vista raíz (UIView) agregue su SKView y UIScrollView encima de SKView. Es algo como esto (ver controlador):
self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds.size];
self.scrollContentView = [[UIView alloc] init];
self.scrollView.delegate = self;
self.spriteKitView = [[SKView alloc] initWithFrame:[UIScreen mainScreen].bounds.size];
self.scrollContentView.frame = self.scene.bacgroundNode.frame;
self.scrollView.contentSize = self.scrollContentView.frame.size;
[self.scrollView addSubview:self.scrollContentView];
[self.view addSubview:self.spriteKitView];
[self.view addSubview:self.scrollView];
[self.spriteKitView presentScene:self.scene];
La última parte del truco es implementar UIScrollViewDelegate:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
//here you set your background node position based on scrollView contentoffset
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
//here you set background node scale based on scrollView zoomScale
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
//just return scrollContentView
}
De esta forma, puede simular SKView dentro de UISCrollView. Espero que esto ayude.
EDITAR 2014-04-20
Tengo un componente de fuente abierta para panoramizar y desplazar escenas en SpriteKit, eche un vistazo a: https://github.com/pzbyszynski/PIOSpriteKit
Puede comenzar con este tutorial de Ray W Tutorial de SpriteKit sobre cómo arrastrar y soltar, que incluye algunas de las cosas que solicita, incluidos los reconocedores de gestos.
Esto es bueno: Apple reconoce la referencia del reconocedor
Otro de Ray W: uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more
Lo anterior es bueno para empezar.
Si está utilizando un SKCameraNode
, puede calcular la diferencia entre dos toques para definir el zoom y el paneo de una vez.
- Defina un
SKCameraNode
para su SKView - Compruebe si hay dos toques
- Si es así, calcule la diferencia entre los toques previos y los toques actuales
- Dile a la cámara que escale tanto
- Desplazarse por la cámara ya sea por el promedio de los dos puntos, o por el primero (si solo hay uno)
El resultado debería ser algo como esto. Tenga en cuenta que utilicé el event?.allTouches
lugar del conjunto recibido, si un dedo no se mueve entre toques, no está en el conjunto de touches
, lo que causaría una panorámica donde un usuario esperaría un zoom.
let cameraNode = SKCameraNode()
override func didMove(to view: SKView) {
cameraNode.position = CGPoint(x: size.width / 2, y: size.height / 2)
addChild(cameraNode)
camera = cameraNode
}
override func touchesMoved(_ touch: Set<UITouch>, with event: UIEvent?) {
guard let touches = event?.allTouches else {
return
}
let arr = Array(touches)
guard let touch = arr.first else {
return
}
var positionInScene = touch.location(in: self)
var previousPosition = touch.previousLocation(in: self)
if touches.count > 1 {
let touch2 = arr[1]
let positionInScene2 = touch2.location(in: self)
let previousPosition2 = touch2.previousLocation(in: self)
let oldDistance = distance(previousPosition, previousPosition2)
let newDistance = distance(positionInScene, positionInScene2)
let diff = (oldDistance / newDistance)
if diff.isNormal && diff != 1 {
let scaleAction = SKAction.scale(by: diff, duration: 0)
cameraNode.run(scaleAction)
}
previousPosition = average(previousPosition, previousPosition2)
positionInScene = average(positionInScene, positionInScene2)
}
let translation = CGPoint(x: positionInScene.x - previousPosition.x, y: positionInScene.y - previousPosition.y)
let panAction = SKAction.moveBy(x: -translation.x, y: -translation.y, duration: 0)
cameraNode.run(panAction)
}
func average(_ a: CGPoint, _ b: CGPoint) -> CGPoint {
return CGPoint(x: (a.x + b.x) / 2, y: (a.y + b.y) / 2)
}
func distance(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
let xDist = a.x - b.x
let yDist = a.y - b.y
return CGFloat(sqrt((xDist * xDist) + (yDist * yDist)))
}