【问题标题】:Zooming and scrolling in SpriteKitSpriteKit 中的缩放和滚动
【发布时间】:2013-10-05 14:38:08
【问题描述】:

我是 SpriteKit 的新手。我需要通过 UIImageView 或 SpriteNode 显示图像(它是游戏的背景图像)。但是,我需要用户能够放大背景并平移。我在不使用 SpriteKit 的情况下轻松完成了这项工作,但无法弄清楚如何让节点接受缩放和平移。如果我在情节提要中使用 UIScrollView,则添加到我的场景中的所有精灵都不会成为子对象,也不会缩放或平移。

有可能吗,你能指出我正确的方向吗?


编辑:

我对你的回答有点困惑,但我是这样做的:

在我的根视图控制器 .m 中:

- (void)viewDidLoad
{
    [super viewDidLoad];
    SKView * showDesigner = (SKView *)self.view;
    SKScene * scene = [iMarchMyScene sceneWithSize:showDesigner.bounds.size];
    scene.scaleMode = SKSceneScaleModeAspectFill;
    [showDesigner presentScene:scene];
}

在我的场景中.m:(你的步骤被注释掉了)

-(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;
}

这就是我迷路的地方: 第 5 步:现在,将 SKView 和 UIScrollView 添加到您的根视图中。

如果我理解正确,我需要转到 myRootViewController.m 并在 viewDidLoad 方法中,将 SKView 和 UIScrollView(在 myScene.m 中初始化)作为子视图添加到在 viewDidLoad 方法中初始化的 SKView 中?我不知道该怎么做。

【问题讨论】:

  • 我会使用动作,它们解决了我的缩放问题,并且似乎 sprite kit 拥有它的全部力量!

标签: ios7 zooming pan sprite-kit


【解决方案1】:

这是我对滚动问题的解决方案。它围绕“窃取” UIScrollView 的行为展开。我从 2012 年的 WWDC 视频中了解到这一点,关于将 UIKit 与 OpenGL 混合。

  1. 将 UIScrollViewDelegate 方法添加到您的 ViewController 并将滚动视图委托设置为 ViewController
  2. 将 PanGestureRecognizer 从 UIScrollView 添加/移动到 SKView
  3. 使用 scrollViewDidScroll 回调方法来控制用户滚动时想要的任何节点

示例项目: https://github.com/bobmoff/ScrollKit

就像我在上面的评论中提到的那样,在我运行应用程序的 30% 的时间里,我遇到了一些微小的延迟。 FPS 仍然是 60,但似乎有一些轻微的冲突或委托方法被调用的频率,因为它有时感觉有点迟钝。如果有人设法解决这个问题,我很想听听。似乎只有当我按住手指时,减速发生时它才不会滞后。

【讨论】:

  • 我想试试这个,但使用动作等很容易添加你自己的自定义滚动 - 而且它真的很流畅。
  • 是的@Smick,连接 tochesMoved 并让节点移动很容易,但你如何处理减速和弹跳,在我看来,这是获得足够好的滚动感觉的难点。
  • 只是试了一下,实际上效果很好。谢谢你的提示!现在尝试捏/缩放??? .....
  • 我去了我们的办公室,又拿起了一个。这是一个设置并运行滚动视图的示例项目:github.com/bobmoff/ScrollKit
  • @bobmoff 这是一个写得非常好的项目,谢谢你。我仔细阅读并手动复制它以更好地理解它,我不禁羡慕您的解决方案组织得井井有条和灵活。
【解决方案2】:

贾斯汀,

你可以使用一个小技巧,我正在使用它,它对我很有用。这是设置:

  • 创建 SKView
  • 创建与 SpriteKit 视图尺寸相同的 UIScrollView
  • 创建一个与背景节点尺寸相同的 UIView(让我们将其命名为 scrollContentView)
  • 将 UIScrollView contentSize 设置为与 scrollContentView 相同的尺寸,并将 scrollContentView 添加为 UIScrollView 子视图

现在,向您的根视图 (UIView) 添加您的 SKView,并将 UIScrollView 添加到 SKView 之上。 它是这样的(视图控制器):

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];


技巧的最后一部分是实现 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
}

这样你就可以在 UISCrollView 中模拟 SKView。希望这会有所帮助。

编辑 2014-04-20

我已经开源了我在 SpriteKit 中平移和滚动场景的组件,请查看:https://github.com/pzbyszynski/PIOSpriteKit

【讨论】:

  • 我正在使用这种方法,但有时(我无法找到发生这种情况的模式)当我按住手指并在我正在移动的对象周围移动它时会开始卡顿。帧速率仍然是 60fps,但它就像委托方法一样只收到大约一半的调用量,所以看起来我正在移动的对象大约有 10-15fps。如果有人能解决这个问题会很高兴。
  • 我尝试了您的解决方案,但有点卡住了。我用代码片段和我迷路的地方编辑了我的原始帖子。任何帮助我都将不胜感激。
  • 贾斯汀,你初始化 2 个 SKView 实例对我来说似乎很奇怪。我认为在您的场景中,您应该只设置场景子节点。将您的 UIScrlooView 设置代码移动到您的根视图控制器,并且只使用一个 SKView(我将在稍后使用一些代码 sn-ps 编辑我的答案)
  • @PiotrZbyszynski 我只是无法让它正常工作,您能否提供一个完整的代码示例,包括如何做到这一点? ViewController 和场景 h&m 文件?
  • 使用这种方法,scrollView 不会“吸收”触摸并阻止场景感应到任何触摸吗?
【解决方案3】:

您可以从这个 Ray W 教程 SpriteKit tutorial how to drag and drop 开始,其中包括您要求的一些内容,包括手势识别器。

这很好:Apple gesture Recognizer reference

另一位来自 Ray W:uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more

以上是很好的开始。

【讨论】:

  • 谢谢。我去看看。
  • 欢迎您,顺便说一句,我认为 scrollView 不适用于 SpriteKit,所以我想您必须自己使用 Gesture recs 来满足它..
  • 我目前正在尝试使用 setScale 在场景节点上进行缩放,但随后它弄乱了所有物理特性。精灵都按预期缩放,但物体的质量保持不变..
【解决方案4】:

我已经创建了自己的方法来缩放特定节点而不必缩放整个场景,这是它的基础,但它并不完美(实际上我在这里创建了自己的帮助请求:Zooming an SKNode inconsistent )

这个 if 语句进入场景的 touchesMoved 方法:

   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];
        }
    }

我遇到的唯一问题是行为不一致和滚动不流畅。我在这个意义上使用“仅”这个词时非常宽宏大量。

【讨论】:

    【解决方案5】:

    如果您使用的是SKCameraNode,您可以计算两次触摸之间的差异,以同时定义缩放和平移。

    • 为您的 SKView 定义 SKCameraNode
    • 检查是否有两次触摸
    • 如果是,请计算先前触摸和当前触摸之间的差异
    • 告诉相机缩放那么多
    • 相机平移两点的平均值或第一个点(如果只有一个点)

    结果应该是这样的。请注意,我使用了event?.allTouches 而不是接收到的集合,如果一个手指在触摸之间没有移动,则它不在touches 集合中,这将导致用户期望缩放的平移。

    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)))
    }
    

    【讨论】:

      猜你喜欢
      • 2014-03-17
      • 2016-06-19
      • 2018-06-15
      • 2012-07-25
      • 1970-01-01
      • 2017-05-20
      • 2014-08-29
      • 2017-06-10
      • 1970-01-01
      相关资源
      最近更新 更多