3

我有UITableView,它显示在我的 Layer `[[CCDirector sharedDirector].view addSubview:myTableView] 前面,并占据了屏幕的一部分。

一切正常:它检测到触摸。但是在 tableView 的滚动动画期间,所有 cocos2d 都会冻结,直到 tableView 结束此滚动。(在冻结下,我的意思是所有节点的动作都暂停)。

问题是:这种冻结的原因是什么,我该如何避免?


-(void) mainLoop:(id)sender
{
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
{
    return;
}

if (self.useCocoaFriendlyRendering)
{
    dispatch_sync(frameRenderingDispatchQueue, ^{ // HERE dispatch_async make black screen, i shows everything but still bad performance.
        CCGLView *openGLview = (CCGLView*)[self view];
        [EAGLContext setCurrentContext:openGLview.context];
        [self drawScene];
        dispatch_semaphore_signal(frameRenderingSemaphore);
    });
}
else
{
    CCGLView *openGLview = (CCGLView*)[self view];
    [EAGLContext setCurrentContext:openGLview.context];
    [self drawScene];
    dispatch_semaphore_signal(frameRenderingSemaphore);
}
}
4

2 回答 2

7

原因是因为 UIKit 视图没有设计为与其他视图协作。它们用于用户交互,因此滚动停止更新其他视图通常无关紧要。在用户界面驱动的应用程序中,这很有意义,因为您希望对具有用户焦点并需要大量资源来制作动画(即滚动)的视图提供最佳响应和感觉。

正确解决此问题的唯一方法是CCDirectorDisplayLink使用信号量改进 cocos2d 渲染循环:

-(void) mainLoop:(id)sender
{
    if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0)
    {
        return;
    }

    if (useCocoaFriendlyRendering)
    {
        dispatch_async(frameRenderingDispatchQueue, ^{
            [EAGLContext setCurrentContext:openGLView_.context];
            [self drawScene];
            dispatch_semaphore_signal(frameRenderingSemaphore);
        });
    }
    else
    {
        [EAGLContext setCurrentContext:openGLView_.context];
        [self drawScene];
        dispatch_semaphore_signal(frameRenderingSemaphore);
    }
}

这段代码将 cocos2d drawscene 方法放在它自己的调度队列中。我曾经明白它做了什么,但我不记得了,所以与其错误地解释它的作用或它为什么起作用,我希望有人可以在评论中填写它。:)

注意:可能不需要每次都调用最后一个调度信号量,但我这样做是因为切换 cocoaFriendlyRendering 标志可能会导致 semaphore_wait 无限期等待(冻结渲染)。所以我只是每次都发出信号。

我更新CCDirectorDisplayLink如下(CCDirectorIOS有 cocoaFriendlyRendering BOOL):

@interface CCDirectorDisplayLink : CCDirectorIOS
{
    id displayLink;
    dispatch_semaphore_t frameRenderingSemaphore;
    dispatch_queue_t frameRenderingDispatchQueue;
}
-(void) mainLoop:(id)sender;
@end

在 startAnimation 中创建信号量和调度队列:

- (void) startAnimation
{
        ...
    displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(mainLoop:)];
    [displayLink setFrameInterval:frameInterval];
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

    frameRenderingSemaphore = dispatch_semaphore_create(1);
    frameRenderingDispatchQueue = dispatch_queue_create("render", DISPATCH_QUEUE_SERIAL);
}

并在 dealloc 中发布:

-(void) dealloc
{
    dispatch_release(frameRenderingDispatchQueue);
    dispatch_release(frameRenderingSemaphore);
    [displayLink release];
    [super dealloc];
}

这样可以防止滚动视图阻塞 cocos2d 框架绘制。我添加了cocoaFriendlyRenderingBOOL(我假设您知道如何添加 BOOL 属性),因此我可以仅对使用滚动视图的场景启用此行为。我认为这可能会更好地提高性能并防止游戏过程中出现任何奇怪的问题,但这可能不是必需的。

此代码适用于 cocos2d 1.0.1,可能需要稍作调整才能与 cocos2d 2.x 一起使用,例如 EAGLView -> CCGLView、EAGLContext -> CCGLContext。

其他解决方案围绕计时器和/或降低帧速率 (animationInterval) 和/或更改显示链接对象的运行循环模式。它们并不能很好地工作,滚动可能不流畅或滚动可能突然停止。我尝试了所有这些解决方案,因为我不想弄乱 cocos2d 的渲染循环 - 但信号量是唯一完美工作的解决方案,使 UIKit 滚动与 cocos2d 合作(反之亦然)。

更新:

我忘了提,我创建了一个 UIScrollView 的子类来处理 cocoaFriendlyRendering(初始化时是,dealloc 时否),更重要的是,它覆盖了触摸事件,以便根据需要将它们转发到 cocos2d(只要滚动视图不滚动) .

这些是覆盖的触摸方法:

-(void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    if (self.dragging)
        [super touchesBegan:touches withEvent:event];
    else
        [[CCDirector sharedDirector].openGLView touchesBegan:touches withEvent:event];
}

-(void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
    if (self.dragging)
        [super touchesMoved:touches withEvent:event];
    else
        [[CCDirector sharedDirector].openGLView touchesMoved:touches withEvent:event];
}

-(void) touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
    [super touchesCancelled:touches withEvent:event];
    [[CCDirector sharedDirector].openGLView touchesCancelled:touches withEvent:event];
}

-(void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    [super touchesEnded:touches withEvent:event];
    [[CCDirector sharedDirector].openGLView touchesEnded:touches withEvent:event];
}
于 2013-03-03T12:51:09.467 回答
2

进入 CCDirector.m 或 CCDirectorIOS.m 文件并找到这一行:

[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

将其更改为:

[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
于 2013-10-14T10:20:46.990 回答