在Spritekit游戏中限制GUI元素的比例

7

GUI元素示例图像

很抱歉,因为本文内容较多,但是每个试图创建某种通用应用程序的人都知道这是相当棘手的事情,所以请对我宽容一些...

目标

我想要实现的目标(如上图所示)是在iPhone 5和6上使用@2x资源,并保持应用程序的相同外观。如果可能的话,不需要根据检测到的设备手动计算节点的比例和位置属性...所以简而言之,如何使应用程序自动约束元素(和场景)的比例和间距?同时,我希望在iPhone 6+上使用@3x资源时应用程序的外观相同,但由于简单起见,我只集中在iPhone 5和6上。

我在网上找到的是,有些人说iOS会自动进行此操作(降采样),例如他们建议这样做:

"制作iPhone 6大小的@2x资源,然后iOS将自动缩小到iPhone 5"。

但是,当涉及到Spritekit场景时,显然不是这样的,或者我错过了什么。

问题

尽管iPhone 6和iPhone 5具有相同的纵横比和相同的PPI,但使用相同的资源与场景大小不同(请参见菜单精灵在第一张和第二张图像上与场景大小的比较),因为PPI与像素密度相关,而iPhone 6拥有更多的空间(更大的对角线,更多的英寸),这意味着它比iPhone 5具有更多的像素。这就是我的问题所在,我不知道如何有效地处理它。

到目前为止我做了什么

对于GUI来说,第二张图像并不是一个问题,但对于游戏玩法而言,在我的情况下却是一个问题,因为我希望在不同的设备上具有相同的外观和感觉。 只需查看第一张和第三张图像。

多亏了Skyler Lauren的建议,我已经成功地在所有iPhone 5设备上实现了应用程序的相同外观,无论是7.1还是8.1系统以及iPhone 6。因此,现在的问题是如何使这个代码适应在iPhone 6+上使用@3x纹理,以及在iPhone 4s上使用。以下是iPhone 5和6的解决方案:

View controller.m

GameScene *scene = [GameScene sceneWithSize:CGSizeMake(375,677)];//fixed instead of view.bounds.size
 scene.scaleMode = SKSceneScaleModeAspectFill;

因此,该场景始终具有iPhone 6尺寸的固定大小,视图大小根据设备而变化。我使用启动图像资源目录而不是xib文件。图像的大小为推荐大小-4s的640x960px,5的640x1136px,6的750x1334px和6+型号的1242x2208。资源面向iPhone 6的尺寸因此对于该型号没有任何缩放。

与4s型号相关的是,当我使用上述方法时,每侧都有两个黑色条...

到目前为止,我只在模拟器和iPhone 6设备上测试过(我在设备或模拟器上看到的第一张图片)。

问题

目前,正如我所说,使用@2x资源在iPhone 4s(由于不同的纵横比而出现两个黑色条),5,6上都可以工作,但如何使用@3x资源使一切都适用于iPhone 6+? 如果我将375x667的尺寸用于该场景,则一切都定位正确且比例良好,但质量会受到影响(由于@2x的放大)。


你是否尝试在添加节点时,根据设备进行缩放?任何后续的动画(纹理变化)都将带有预设的比例因子。 - sangony
当然,如果可能的话,我正在尝试避免这种情况,但这肯定是处理这个问题的一种选择。我只是想找到更简单/自动化的解决方案,这将使我免于许多计算/手动设置基于设备等比例属性。 - Whirlwind
你是否对场景进行了缩放,或者你的场景和视图大小相等(即没有缩放)? - Epic Byte
根据我的测试,场景和视图的相同大小并不能像第三张图片中所示产生期望的效果。如果我理解正确,那么答案是否定的。一开始我没有缩放任何东西,场景是使用view.bounds.size进行初始化的。但后来,我尝试在视图控制器中使用类似CGSizeMake(375, 667)的方法来初始化场景,这给了我部分良好的结果。今晚稍后我会更新我的问题并附上这些信息... - Whirlwind
2个回答

5

统一的GUI和游戏玩法

就我所知,处理统一的GUI和游戏玩法的最佳方法是设置场景大小(无论设备如何),然后让SpriteKit从那里进行缩放。

GameScene *scene = [GameScene sceneWithSize:CGSizeMake(375,677)];//fixed instead of view.bounds.size
scene.scaleMode = SKSceneScaleModeAspectFill;

这是iPhone 6的点数。由于SpriteKit使用点数,但设备显示像素,因此@2x设备的场景大小为750px x 1354px像素,iPhone 6+为1125px x 2031px(设备实际像素为1080 x 1920)。
那么它如何与资源配合使用呢?
对于.atlas文件夹中的1x和2x资源,它的效果非常好。同样,因为所有内容都转换成了点,所以你可以在纹理集中放置button.png和button@2x.png,在所有iPhone上的定位和外观将保持一致。
那@3x呢?
这是一个更好的问题要问苹果。显然,SpriteKit不支持纹理集中的@3x图像。已经有一些问题在SO上尝试解决这个问题。
例如... Spritekit - not loading @3x images from SKTextureAtlas

看起来 Xcode 6.2 中也没有解决这个问题。如果您正在阅读此内容并想要 @3x,可能值得向 Apple 提交一个 radar。需要注意的一件事是,我在文档中没有看到纹理图集应该支持 @3x(或者甚至 @2x)的任何地方。当它们被支持时,您不必更改代码,只需将 @3x 资源放入 .atlas 文件夹即可。

关于 @3x 资源,我应该怎么做?

我的建议是不用担心,使用 @2x 资源即可。SpriteKit 可以很好地缩放图像,有很多应用程序不支持 @3x。作为一位 iPhone 6+ 用户,这只是我目前学会了接受的事情。我希望 Apple 很快能支持 .atlas 文件夹中的 @3x 图像。

警告

您正在要求所有设备缩小,除了iPhone 6(和放大的iPhone 6+)。在大多数情况下,您不应该注意到艺术品(或性能)方面的大差异(根据我的测试),但是如您所知,如果缩小图像,则可能会看起来略有不同。此外,4s上存在黑色条纹问题,目前我还没有为您找到解决方案。
结束语:
如果您设置场景大小并将场景设置为SKSceneScaleModeAspectFill,则您的应用程序在所有设备上都会获得完全相同的外观和感觉,但是您正在要求旧设备缩小。我认为这样做可以节省大量时间和计划,缺点很小。
希望这可以帮助您,并祝您的应用程序好运。

谢谢你的回答Skyler,目前似乎iPhone 6+模拟器在使用图集时出现了一些问题。事实上,在使用@2x资源时差异并不太明显,但如果能够修复这个问题会更好,因为当高细节位图图像被缩放时,可能会出现伪影。我尝试了那些链接中的解决方案以及许多其他方案,但即使使用正确生成图集的Texturepacker或根据屏幕比例选择正确的图集,似乎iPhone 6+模拟器仅喜欢x2图像。 - Whirlwind
为了避免在缩放时出现伪影,我计划使用从矢量图形动态生成图像的方法。首先,我使用PaintCode创建图像,然后将相应的代码导出以生成符合需求尺寸的视图。最后,从此视图创建png文件,然后用于纹理。 - Dominique Vial
事实上,PaintCode的最新版本2.3.2已经具备了代码生成功能! - Dominique Vial

5
您主要的问题似乎是如何处理各种屏幕尺寸与图像资源以及屏幕对象坐标之间的关系。
我的解决方案是,将您的代码编写成为iPhone 6 Plus编码。将所有图片调整为@3x大小,并将您的屏幕布局坐标设置为iPhone 6屏幕尺寸。
只需少量代码,我就能够在iPhone 6 Plus、6、5和4屏幕尺寸上获得统一的布局。我已经包含了每个屏幕尺寸的截图。角色图像大小为300x300像素。2个按钮图像大小为100x100像素。
static const float kIphone6PlusScaleFactorX = 1.0;
static const float kIphone6PlusScaleFactorY = 1.0;
static const float kIphone6ScaleFactorX = 0.9;
static const float kIphone6ScaleFactorY = 0.9;
static const float kIphone5ScaleFactorX = 0.772;
static const float kIphone5ScaleFactorY = 0.772;
static const float kIphone4ScaleFactorX = 0.772;
static const float kIphone4ScaleFactorY = 0.652;

#import "GameScene.h"

@implementation GameScene {
    float scaleFactorX;
    float scaleFactorY;

    SKSpriteNode *node0;
    SKSpriteNode *node1;
    SKSpriteNode *node2;
    SKLabelNode *label0;
}

-(void)didMoveToView:(SKView *)view {
self.backgroundColor = [SKColor blackColor];

    if(view.frame.size.height == 736) {
        NSLog(@"iPhone 6 plus");
        scaleFactorX = kIphone6PlusScaleFactorX;
        scaleFactorY = kIphone6PlusScaleFactorY;
    }
    if(view.frame.size.height == 667) {
        NSLog(@"iPhone 6");
        scaleFactorX = kIphone6ScaleFactorX;
        scaleFactorY = kIphone6ScaleFactorY;
    }
    if(view.frame.size.height == 568) {
        NSLog(@"iPhone 5");
        scaleFactorX = kIphone5ScaleFactorX;
        scaleFactorY = kIphone5ScaleFactorY;
    }
    if(view.frame.size.height == 480) {
        NSLog(@"iPhone 4");
        scaleFactorX = kIphone4ScaleFactorX;
        scaleFactorY = kIphone4ScaleFactorY;
    }

    node0 = [SKSpriteNode spriteNodeWithImageNamed:@"Pic"];
    node0.position = CGPointMake(self.size.width/2, self.size.height/2);
    [node0 setScale:scaleFactorX];
    [self addChild:node0];

    node1 = [SKSpriteNode spriteNodeWithImageNamed:@"button0"];
    node1.position = CGPointMake(100*scaleFactorX, 100*scaleFactorY);
    [node1 setScale:scaleFactorX];
    [self addChild:node1];

    node2 = [SKSpriteNode spriteNodeWithImageNamed:@"button1"];
    node2.position = CGPointMake(314*scaleFactorX, 100*scaleFactorY);
    [node2 setScale:scaleFactorX];
    [self addChild:node2];

    label0 = [SKLabelNode labelNodeWithFontNamed:@"HelveticaNeue-Bold"];
    label0.text = @"Big Game Menu";
    label0.fontSize = 48*scaleFactorX;
    label0.fontColor = [SKColor whiteColor];
    label0.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeCenter;
    label0.verticalAlignmentMode = SKLabelVerticalAlignmentModeCenter;
    label0.position = CGPointMake(207*scaleFactorX,690*scaleFactorY);
    [self addChild:label0];
}

iPhone 4

enter image description here

iPhone 5

enter image description here

iPhone 6

enter image description here

iPhone 6+

enter image description here

请注意,即使是文本标签也被正确地缩小,不仅仅是字体大小,还有位置。

供您参考,我在GameViewController中使用了标准代码,因为我发现这样更容易使用一个简化版本。这是我用来呈现我的SKView的代码:

- (void)viewDidLoad {
    [super viewDidLoad];

    SKView * skView = (SKView *)self.view;
    SKScene *scene = [GameScene sceneWithSize:skView.bounds.size];
    scene.scaleMode = SKSceneScaleModeAspectFill;
    [skView presentScene:scene];
}

1
不错的解决方案。唯一的问题是你必须为每个位置使用scaleFactorX和scaleFactorY。你认为使用一个类别会让这更容易吗? - Skyler Lauren
@sangony 你是指要给所有图像添加3x后缀吗?还是只要将它们制作成3倍大小的图像,并使用1x的标准命名方式?我喜欢这个解决方案,因为它似乎可以正确地完成定位和缩放,即使在4s上也一切都看起来很正常(没有黑色边框)。唯一的缺点是由于3x资源和稍微多一些编码而导致的内存消耗,但我也会测试这个方案,以查看结果。 - Whirlwind
@SkylerLauren - 肯定需要一些额外的编码来实现这种兼容性。苹果最好能够提出一些后台功能来自动化该过程,但我不指望会有这种功能。如果不用包括iPhone 4系列,每个设备只需要一个比例因子而不是2个(x, y)。正是由于iPhone 4的长度需要加入额外的y因子。你所说的“类别”是什么意思? - sangony
@sangony 好的,我明白了。我已经测试过这个方案,它是可行的,所以在我做出最终决定之前,我会点赞它。当我使用操作时,我必须看看这会增加多少复杂性。我正在考虑可能使用Skyler的解决方案,但以你处理它的方式来处理4s。最好的解决方案是苹果允许开发者按屏幕大小声明支持的设备,但这可能会引起其他问题(也许是商业问题)或谁知道什么。 - Whirlwind
@sangony。不错的解决方案。为了改进它,我建议使用PaintCode从矢量图片生成PNG文件。这样你就可以避免缩放操作。 - Dominique Vial
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接