Hi / Low - Res Image Support

DanielDoubledayDanielDoubleday DEMember
edited August 2014 in CocosSharp

Hi all

I'm currently looking into the effort of porting a cocos2d-xna app to CocosSharp.

With cocos2d-xna I could use 2 different version of image assets for low and hi resolution ios devices with the Director.SetDesignResolutionSize and Director.ContentScaleFactor. ContentScaleFactor is gone. Did CocosSharp drop support for different assets resolutions or is there another way to do it?

Thanks,
Daniel

Posts

  • kjpou1kjpou1 LUMember, Xamarin Team Xamurai
    edited August 2014

    Hello Daniel.

    We did drop support for the content scale factor. You can still set the directory the same way based on the size of the screen but the content scale factor we did not do when redoing the classes. We may look into this or another solution if there is a need for this.

  • RamiTabbaraRamiTabbara AUMember, Xamarin Team Xamurai
    edited August 2014

    Hi Daniel,

    Just to reiterate what was said - a global content scale factor has been replaced by the ability to overlay multiple CCLayers each with their own camera. In particular, this gives the a user a lot more flexibility to replicate the effect of different scale factors for each layer by choosing different camera visible bounds.

    
    ...
    // Window has its own pixel dimensions: e.g. 1024 x 768 px
    
    // Bounds of 1000pt x 500pt
    var bounds1 = new CCSize (1000, 500);
    layer1.Camera = new CCCamera(bounds1);
    
    // Bounds of 2000pt x 1000pt
    var bounds2 = new CCSize (2000, 1000);
    layer2.Camera = new CCCamera(bounds2);
    
    var contentSize = new CCSize(100,100);
    
    var node1 = new CCNode(contentSize);
    var node2 = new CCNode(contentSize);
    
    layer1.AddChild(node1);
    
    // Relative to node1, node2 will appear half the size
    layer2.AddChild(node2);
    
    

    Just note, if a camera is not specified when a layer is created, then the bounds of the camera will match the dimensions of the window. Please take a look at Key Differences to see some other ways you can now customise the initial layout of a game.

  • kjpou1kjpou1 LUMember, Xamarin Team Xamurai

    Also, Daniel

    Only the content scale factor is not there but the ResolutionPolicy is still there. Take a look at this from the CocosSharp wiki: https://github.com/mono/CocosSharp/wiki/Understanding-SceneResolutionPolicy.

  • Hi - thanks,

    I guess dropping low res support in my next project is an option for me.

    Still I was playing a little with the camera support (which was ironically dropped entirely from cocos v3.0) to see if I can come up with something for my old project. But I probably just don't get it.

    My problem is not only that I'm using low- / hi-res images but also that my design resolution is ios low res (320*480) (which used to be the common way to do it). So basically I would need a way to use a solution that works with upscaling the layout code (position and size) while still using hires images (or best case optimised image sizes).

    ContentScaleFactor is 1 for low res devices and 2 for retina. When I'm creating the layer camera with the content scale factor the sprites look fine but I have to multiply every position and size values as well (which kills the idea).

    Is there any way around this?

    I set up the app like this:

    mainWindow.SetDesignResolutionSize(320, 480, CCSceneResolutionPolicy.ShowAll);
    
    if (ContentScaleFactor > 1)
    {
        application.ContentSearchPaths.Add("hd");
    }
    else
    {
        application.ContentSearchPaths.Add("ld");
    }
    
    CCScene scene = TestLayer1.Scene(mainWindow);
    mainWindow.RunWithScene(scene);
    

    And my scene looks like this

    const int CSF = SandboxApplicationDelegate.ContentScaleFactor;
    
    protected override void AddedToScene()
    {
        base.AddedToScene();
        var bg = new CCSprite("iphone5-bg.png");
        bg.Position = ContentSize.Center;
        AddChild(bg);
    
        var motor = new CCSprite("motor.png");
        motor.Position = new CCPoint(160, 240) * CSF;
        AddChild(motor);
    
        var roundedBorder = new CCScale9Sprite("circle.png");
        roundedBorder.CapInsets = new CCRect(50 * CSF, 50 * CSF, 1, 1);
        roundedBorder.PreferredSize = new CCSize(300 * CSF, 300 * CSF);
        roundedBorder.AnchorPoint = CCPoint.AnchorMiddle;
        roundedBorder.Position = ContentSize.Center;
        AddChild(roundedBorder);
    }
    
    public static CCScene Scene(CCWindow window)
    {
        var scene = new CCScene(window);
    
        var camera = new CCCamera(
                         CCCameraProjection.Projection2D, window.DesignResolutionSize * CSF
                     );
    
        var layer = new TestLayer1();
        layer.Camera = camera;
    
        scene.AddChild(layer);
    
        return scene;
    }
    

    https://github.com/doubleday/cocos-sharp-sandbox

  • kjpou1kjpou1 LUMember, Xamarin Team Xamurai

    Hello Daniel

    What you are doing is exactly what the Content Scale Factor was doing internally when everything was a singleton. Internally the system calculated this by doing exactly the same thing using PointsToPixels and PixelsToPoints but that was when we were able to access that value because of the singleton value.

    For right now what I could suggest if you want to go this route is to create some extensions methods for CCRect, CCSize, CCPoint etc to do these conversions for you using your scale factor value.

    For example:

    float staticContentScaleFactor = 2.0f;

        public static CCRect PixelsToPoints(this CCRect r)
        {
            var cs = staticContentScaleFactor;
            return new CCRect(r.Origin.X / cs, r.Origin.Y / cs, r.Size.Width / cs, r.Size.Height / cs);
        }
    
        public static CCRect PointsToPixels(this CCRect r)
        {
            var cs = staticContentScaleFactor;
            return new CCRect(r.Origin.X * cs, r.Origin.Y * cs, r.Size.Width * cs, r.Size.Height * cs);
        }
    
        public static CCSize PixelsToPoints(this CCSize s)
        {
            var cs = staticContentScaleFactor;
            return new CCSize(s.Width / cs, s.Height / cs);
        }
    
        public static CCSize PointsToPixels(this CCSize s)
        {
            var cs = staticContentScaleFactor;
            return new CCSize(s.Width * cs, s.Height * cs);
        }
    
        public static CCPoint PixelsToPoints(this CCPoint p)
        {
            var cs = staticContentScaleFactor;
            return new CCPoint(p.X / cs, p.Y / cs);
        }
    
        public static CCPoint PointsToPixels(this CCPoint p)
        {
            var cs = staticContentScaleFactor;
            return new CCPoint(p.X * cs, p.Y * cs);
        }
    
  • Thanks again.

    Ok that's what I thought. Thing is that I would have to go through the entire app and than I could just as well change to down-scaling. So I'll stick to xna for this project.

    On a related note: I found it surprising (read: it bit me) that the default camera is using 3d projection. I think this is not to be expected for a predominantly 2d framework.

    It leads to rounding errors on the layer. So for instance if you set 640 x 1136 as design resolution the content size of the layer will be 645 x 1145.

    Also: are you guys actually interested in feedback on code internals and if so is this a good channel to discuss or do you rather want tickets / pull requests?

  • kjpou1kjpou1 LUMember, Xamarin Team Xamurai

    Daniel

    The camera internals were just changed last week to get some other parts of the system up and running correctly. Not sure if you have looked at the code for that or not.

    Actually, "predominantly 2d framework" is correct for now ;-)

    Are we interested in feedback on code internals? Off course!!!! Do not forget this is an open source project with open source licenses. Any input from the community is more than welcome. Pull Requests are more than welcome.

    We have a public irc channel : http://forums.xamarin.com/discussion/22231/public-irc-channel-cocossharp#latest that all the developers are on. I think Chris is the only one that has been out there.

  • RamiTabbaraRamiTabbara AUMember, Xamarin Team Xamurai

    Hi Daniel‌,

    I forked your cocos-sharp-sandbox to look at your sample and think I now understand what you're after.

    But just going off on a bit of a tangent I want to explain why CCDirector:contentScaleFactor was removed. Previously when setting up your game in AppDelegate, one would specify the PreferredBackBufferWidth/PreferredBackBufferHeight (in pixels) along with the DesignResolutionSize (in points).

    Then, the common convention seen in tests and samples would be to do something like:

    
    director.ContentScaleFactor = resourceSize.Height / designSize.Height;
    

    Already this is fraught with danger because we're using the ratio between the heights to determine the scale factor. But, if the aspect ratio of the window does not match the aspect ratio of the resolution size then we're going to encounter some potentially unpleasant scaling.

    Unfortunately this isn't the worst of it, as previously the library was dependent on the ContentScaleFactor to convert from screen to world coordinates (PixelsToPoints, PointsToPixels) while in reality the ContentScaleFactor should have nothing to do with this. In particular, if our resolution policy is not setup to be an exact fit of the entire screen then this method of conversion will be incorrect. This is especially important with CocosSharp which supports multiple viewport rendering.

    The true role of a ContentScaleFactor should be fairly simple. Normally, when creating a CCNode (or a derived class like CCSprite) a user should specify the ContentSize. However, as a convenience, when creating a CCSprite or similar a user can specify only the texture and by default it's assumed the content size matches the texture dimensions.

    So, I think what you're after is a way of altering the default texture scale when creating a CCSprite so that the ContentSize is some factor of the texture dimensions. With that in mind, in CCSprite we could add something like

    
            public static float DefaultTextureToContentScale
            {
                set { DefaultTextureToContentScaling = new CCSize (value, value); }
            }
    
            // Allowing for different width and height scale
            public static CCSize DefaultTextureToContentScaling 
            {
                get;
                set;
            }
    

    which would then allow you set set the scale in the AppDelegate in a similar fashion to how it was done previously.

    Please let me know if that's indeed something that would solve your problem.

  • kjpou1kjpou1 LUMember, Xamarin Team Xamurai

    Daniel

    Rami just landed a change that hopefully helps with the rounding problem. https://github.com/mono/CocosSharp/commit/df3dbea3d1d35445dde105bd52ae7b82ec4d43fe

    Please let us know.

  • Kenneth

    Yes - its much better now. Error is mostly gone.

    But I still get a 1 - pixel error for {640 x 960}: {640 x 961}. Since this can be a nuisance for layouting I would probably still set my layer cameras to 2d so I would still vote for 2d as default :-)

    But I guess its fine for most cases and its easy to change.

    Or ... make it configurable in CCWindow ...

  • DanielDoubledayDanielDoubleday DEMember
    edited August 2014

    Hi Rami -

    thanks for looking into it! I did a quick test following your suggestions. First: seems very promising!

    So what I did (hope that I got you right):

    • Added your static config props to CCSprite
    • Apply the scaling CCSprite.ContentSize.Set

    see: https://raw.githubusercontent.com/doubleday/cocos-sharp-sandbox/master/CCSprite.patch

    I did some testing with simple sprite and sprite sheet. And ... works like a charm.

    I updated my sandbox if you want to check.

    The only downside I can see right now is that all code that operates on the internals of textures such as CCScale9Sprite has to take the scaling into account.

    So in CCScale9Sprite I had to adjust the capInsets:

        public bool UpdateWithBatchNode(CCSpriteBatchNode batchnode, CCRect rect, bool rotated, CCRect capInsets)
        {
            var textScaling = CCSprite.DefaultTextureToContentScaling;
            if (textScaling != CCSize.Zero)
            {
                capInsets = CCAffineTransform
                    .ScaleCopy(CCAffineTransform.Identity, 1 / textScaling.Width, 1 / textScaling.Height)
                    .Transform(capInsets);
            }
    

    Also I will need to do some testing what works best for TTFonts and BitmapFonts. In a short test I just modified the font size and saw that this does not work out of the box for all fonts. But other than that - it might just work.


  • kjpou1kjpou1 LUMember, Xamarin Team Xamurai
    edited August 2014

    Daniel

    For the LabelBMFonts can you try using the Scale function like the following:

    bmFont.Scale = 0.3f;

    That should work but only in development and not the published NuGets yet. As I was explaining to Marco this morning we fixed up CCLabelBMFont and CCLabel last week.

    Also, for CCLabelTtf we have the following CCSpriteFontCache that might work for you.

            CCSpriteFontCache.FontScale = 0.6f;
            CCSpriteFontCache.RegisterFont("arial", 12, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 38, 50, 64);
            CCSpriteFontCache.RegisterFont("MarkerFelt", 16, 18, 22, 32);
            CCSpriteFontCache.RegisterFont("MarkerFelt-Thin", 12, 18);
            CCSpriteFontCache.RegisterFont("Paint Boy", 26);
            CCSpriteFontCache.RegisterFont("Schwarzwald Regular", 26);
            CCSpriteFontCache.RegisterFont("Scissor Cuts", 26);
            CCSpriteFontCache.RegisterFont("A Damn Mess", 26);
            CCSpriteFontCache.RegisterFont("Abberancy", 26);
            CCSpriteFontCache.RegisterFont("Abduction", 26);
    

    Edited for CCLabelTtf:

  • DanielDoubledayDanielDoubleday DEMember
    edited August 2014

    Hi Kenneth

    actually bm fonts work without any change (manual scaling). I pushed an example to my sandbox.
    It's basically using hi- and low res versions of the sprite font same way as the regular sprites. So the magic in CCSprite just does the trick. Pretty cool really.

    Only thing is that I'm using native assets. So for this to work I needed to patch the content manager.

    https://github.com/mono/CocosSharp/pull/8

    https://github.com/doubleday/cocos-sharp-sandbox/blob/master/CCContentManager.patch

  • RamiTabbaraRamiTabbara AUMember, Xamarin Team Xamurai

    Hi Daniel,

    Glad that you got your sample working!

    The one small issues with the patch is that I think scaling should only be performed when the ContentScale is set automatically for the user. For example, one such instance in CCSprite (lines 436-438):

    
    // If content size not initialized, assume worldspace dimensions match texture dimensions
    if(ContentSize == CCSize.Zero)
            ContentSize = textureRectInPixels.Size;
    

    should be changed to:

    
    // Should be initialised to 1,1 if a user doesn't change it
    var textScaling = CCSprite.DefaultTextureToContentScaling
     
    // If content size not initialized, assume worldspace dimensions match texture dimensions * scale
    if(ContentSize == CCSize.Zero)
            ContentSize = new CCSize(textureRectInPixels.Size.Width * textScaling.Width, textureRectInPixels.Size.Height * textScaling.Height);
    

    The problem with performing the scaling in the ContentSize property setter is that if a user manually sets the content size then they will unexpectedly find that the true size is different from what they set.

    But I agree that with we will also need to handle other classes like CCScale9Sprite which automatically set the content size in a similar way like you've shown, but it's definitely not a huge change. Let me know what you think.

  • Hey Rami - yes absolutely. Its definitely wrong to do it in the setter.

    Personally I think it would be a great addition. As far as I can see it would add a very sensible layout option which will feel very natural for people coming from the ios / cocos world. Also although low res iphone is probably losing importance I think that simple multi-resolution support for assets is a big win.

    So by any means do it :-)

  • DanielDoubledayDanielDoubleday DEMember
    edited August 2014

    Hm - but there's also a grey zone ...

    CCLabelBMFont works automagically right now. Maybe add a custom setter such as:

    public CCSize TextureSizeInPoints
    {
        set 
        { 
            var textScaling = CCSprite.DefaultTextureToContentScaling;
            ContentSize = new CCSize(value.Width * textScaling.Width, value.Height * textScaling.Height);
        }
    }
    

    which can be used from the outside.

    Although this can name clash with texturerect. But something along these lines

  • kjpou1kjpou1 LUMember, Xamarin Team Xamurai

    Daniel

    Glad to see things are working for you.

    That probably will not work very well because on every change or property or text the ContentSize will be calculated again internally. We were talking about the classes that extend SpriteBatchNode this morning and are looking at changing the internals. It should not effect users at all but will be a change for some of our classes.

    On that note, does anyone have any ideas where we can set this Scale Factor in a common place? Or do you guys think it is better to set this on a case by case, asset by asset basis. I believe developers would like this to be flexible but also think they would not like it to be an over whelming sea of properties that needs to be set.

    Ideas or suggestions?

  • I like the way it works with the scene resolution policy:

    • Configure a default in CCWindow
    • Allow overwrite in CCScene
Sign In or Register to comment.