Window Size or Fullscreen at runtime

This seems to have been discussed before but with no real solution.

In OSX or Windows, to change window size (one of a few predefined sizes) or go full screen you need to call one of

GLViewImpl::createWithFullScreen
GLViewImpl::create

How does one go about this during the game? Worst case looks like moving the setup code out of applicationDidFinishLaunching and then being able to recreate the view and the director without exiting out.

Thoughts?

I was looking a little more in to this an it looks like I could recreate an new GLView and attached it to the Director. The Director looks like it handles getting a new GLView. However I can’t see if or how I should destroyed the original GLView.

Trying this seems to work - but on OSX the hangs. The new windows gets drawn to etc. But you lose all input and have to force quit the app and Xcode!

deleting the GLView looks like it might do the trick, but I need to test now in a proper situation because of loading of assets etc…

Yes, that didn’t really work well when the app was in flight… :slight_smile:

Hey, great effort! What I did was offer them a setting and indicate it requires a game restart. Then in my game, before I create GLView, I check the setting: if it’s true, I call createWithFullScreen; otherwise, I call create. I’m with you, I’d love more options.

In fact, what I’d like to eventually do in mine is detect if the user has ultra-wide and use assets for that. Heck, it would be nice to recognize the user’s display resolution and work with it.

Good luck!

Hey, I haven’t tried this yet, but this looks interesting. Sharing it with you in case it helps.

1 Like

here you go! This guy’s comment answered it for me:

I now pass have a settings dialog popup and let the user select a display option { resolution, fullscreen }, and I pass that to a method. The important part of my method (after I set some parameters) is

if (fullscreen)
{
dynamic_cast<GLViewImpl*>(cocos2d::Director::getInstance()->getOpenGLView())->setFullscreen();
}
else
{
dynamic_cast<GLViewImpl*>(cocos2d::Director::getInstance()->getOpenGLView())->setWindowed(w, h);
}

Thanks. That kind of works, but I seem to lose input after the change. It looks like something goes wrong with the camera as none of the UI elements are now deemed as being inside the camera’s viewpoint. So all the touch checks fail to find a UI element.

Yes, that’s it. The current default camera has not been reset to the new screen size and therefore all the points are out - just need to work out how to reset it!!!

Actually no. It looks like onGLFWWindowSizeFunCallback is not being called so the new sizes are not rippling through.

Ok, mine is more complicated that it needs to be, because I’m letting the user choose between 16:9 or 21:9 (ultrawide) and so I have to purge fonts and sprite images and reload them. I don’t think you need to purge any cached data.

Maybe we create the initial glview differently?

I don’t create mine as fullscreen initially. Depending on the display, I set mine for normal (basically, the old iphone5 ratio) or ultrawide (basically, the iphonex). My code goes way back, so I’m sure the approach has changed a lot over time.

I determine the width and height. For resolutionPolicy, I opted for ResolutionPolicy::EXACT_FIT but I’ve also played with SHOW_ALL.

glview = GLViewImpl::createWithRect("PSF", cocos2d::Rect(0, 0, w, h), 1.0f, true);
glview->setDesignResolutionSize(w, h, resolutionPolicy);
Director::getInstance()->setContentScaleFactor(1.0);

Mine’s not much different to be honest as mine goes back to my original engine Marmalade. In Desktop mode I allow the user to choose between Fullscreen, Window Small, Window Medium, Window Large. The windows sizes being based on the Desktop Size and a multiplier. I managed to to debug the Callback issue as being that my resolution policy was UNKNOWN because I never set it, because I don’t use it. I now set it to EXACT_FIT - fundamentally it’s working now… I just have a problem with my resolution management when the resolution goes smaller.

I have different size assets depending on the resolution - but on desktop mode now I’ve just moved over to always using the largest assets. It all works fine when the app starts in whichever resolution - just not quite there yet on the swap… debug ahoy!

Medium window when started from new

Medium window when changed from Small in the options

Font has been destroyed
And the scale of the sprites are out

I guess I should reload all the imagery… is there an easy way to just refresh it?

This is not the best user experience, but you can transition to a “change resolution” Scene and then after changing the frame size resolution you can transition back to main menu Scene. If you look at the change resolution menu itself you can see that it just does a transition to itself to have the window update.

GameManager::get()->runSceneWithID(SceneType::OptionsResolution, 0);

which basically is just calling this eventually:

Scene *nextScene = /* .. create scene based on enum .. */
Director::getInstance()->replaceScene(nextScene);

Besides swapping Scenes (which does most of the work), I think the relevant portion is similar to the code in this discussion.

Notes:

  • static_changeResolution is the part you’ll care about.
  • I work/test mainly on Mac, and haven’t really released for Desktop (been using Unity for Desktop), so YMMV
  • glfwSetWindowPos prob. all you need, tbh? And then you have to transition to a new scene to see the change affect winSize, origin, etc
  • EDIT: whoops, glfwSetWindowPos is only used for centering, GLViewImpl::setFrameSize is what I should’ve said, and then also update any game-specific scaling factors (retina, @2x vs @3x depending on window height, for example, etc)

If you want to be able to resize the window in real-time you’re going to have to write all the code to support resizing UI elements. The game view itself is probably somewhat resizable, although you may get artifacts if you’re rendering pixel art, like most games I’ve worked on.

Anyway, I don’t really deal with Cocos2d on desktop, so I’ll repeat … YMMV. :smiley:

AFAIK, Cocos2D is not setup to handle resizing a running Scene. All the sizing of Nodes and UI elements are set during creation (ie: Node::create). You could implement your own resize code for each of your node types, or just once in your game scene itself and setup an event listener to get notified when the window is resized.

auto dispatcher = Director::getInstance()->getEventDispatcher();
auto eventName = cocos2d::GLViewImpl::EVENT_WINDOW_RESIZED;
dispatcher->addCustomEventListener(eventName, [](cocos2d::EventCustom *event) {
    // Window was resized!
});

// NOTE: you have to make sure your GLFW window is resizable:
bool resizable = true;
auto winRect = cocos2d::Rect(0, 0, w, h)
glview = GLViewImpl::createWithRect("Title", winRect, 1.0f, resizable);

It is actually somewhat common for games to require a restart when resolution is changed, in part due to the fact that many/most engines do size elements at creation and not at run-time. This probably is due, in part, to the fact that games are often (or even commonly) run in full-screen mode, and I would even bet that most players don’t change resolution while they’re playing the game, but rather just once the first time they play (and then rarely after).

Anyway, good luck!

1 Like

Many thanks. I will look through the code.

I have it triggering a scene from new after the resolution change so all the layout is being handled, and all the nodes are being recreated. It’s just the sprite here which is causing the issue. It’s taken from an ATLAS which hasn’t been reloaded, so I am wondering if there is something cached here that I need to flush. Or just unload the atlas and reload it. Font too.

I am now using only the high resolution resources for the desktop. Before moving from Marmalade to Cocos the game used 3 sizes Small, Medium, Large. On mobile I only use the relevant one, but then they are not changing resolution. On desktop I figured it can handle using the highest. Which it does.

FWIW, I have the whole thing working, including switching out assets. Like you, I have different assets based on their format (for Windows, I’m allowing them to choose 16:9 small, 16:9 large, 21:9 small, 21:9 large, and fullscreen). I added this because I have an ultrawide monitor and so the iphonex assets look much better in full screen than the iphone5 ones.

I hope to add more high-res assets next year.

I had to fix my fonts by purging them and reloading them. Here’s my full method, and it works flawlessly during a game. I can even watch a play, switch to another resolution and watch the replay. I hope this helps! You’ll notice I do this only for Windows, because there’s no need to do it on iOS, and I’m not updating this on the Mac yet, that’ll come later.

void switchDisplayMode(int mode)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)

  bool fullscreen = false;
  int desktopMode = -1;   // invalid
  
  // this lets me have a max size window
  float openGlW = GetSystemMetrics(SM_CXSCREEN);
  float openGlH = GetSystemMetrics(SM_CYSCREEN);
  float availableHt = openGlH - ADJUST_Y(14) - ADJUST_Y(24);
  float factor = availableHt / openGlH;
  float w = 1920.0*factor;
  float h = 1080.0*factor;

  CCLOG("openGL width = %f, height = %f", openGlW, openGlH);

  if (mode == kDesktopResFullscreen)
  {
    float ratio = openGlW / openGlH;
    float ultrawide = 21.0/9.0;
    CCLOG("ratio = %0.2f, ultrawide = %0.2f", ratio, ultrawide);

    fullscreen = true;

    w = 1920.0*factor;
    h = 1080.0*factor;
    desktopMode = DESKTOP_IPHONE5;

    if (ratio >= ultrawide)
    {
      h = openGlH*factor;
      w = 21.0*h/9.0;
      desktopMode = DESKTOP_IPHONEX;
      CCLOG("fullscreen, ultrawide");
    }
    else
      CCLOG("fullscreen, NOT ultrawide");
  }
  else if (mode == kDesktopRes169Small || mode == kDesktopRes169Large)
  {
    // smaller window
    if (mode == kDesktopRes169Small)
    {
      w = 1136.0*desktopFactor();
      h = 640.0*desktopFactor();
    }
    desktopMode = DESKTOP_IPHONE5;
  }
  else if (mode == kDesktopRes219Small || mode == kDesktopRes219Large)
  {
    // smaller window
    if (mode == kDesktopRes219Small)
    {
      w = 1218.0*desktopFactor();
      h = 562.0*desktopFactor();
    }
    else
    {
      h = openGlH*factor;
      w = 21.0*h/9.0;
    }
    desktopMode = DESKTOP_IPHONEX;
  }

  if (desktopMode != -1 || fullscreen)
  {
    desktopResolutionMode = desktopMode;
    auto director = Director::getInstance();
    auto glview = director->getOpenGLView();
    auto fileUtils = FileUtils::getInstance();

    director->purgeCachedData();
    // FontFNT::purgeCachedData();
    // FontAtlasCache::purgeCachedData();

    if (fullscreen)
    {
      dynamic_cast<GLViewImpl*>(cocos2d::Director::getInstance()->getOpenGLView())->setFullscreen();
    }
    else
    {
      dynamic_cast<GLViewImpl*>(cocos2d::Director::getInstance()->getOpenGLView())->setWindowed(w, h);
    }

    ResolutionPolicy resolutionPolicy = ResolutionPolicy::SHOW_ALL;
    resolutionPolicy = ResolutionPolicy::EXACT_FIT;

    // also fix the folders
    std::vector<std::string> resDirOrders;
    if (desktopMode == DESKTOP_IPHONE5)
    {
      resDirOrders.push_back("iphonehd5");
      resDirOrders.push_back("iphonehd");
      resDirOrders.push_back("iphone");

      glview->setDesignResolutionSize(1136, 640, resolutionPolicy);
      Director::getInstance()->setContentScaleFactor(1.0);
    }
    else
    {
      resDirOrders.push_back("iphonex");
      resDirOrders.push_back("iphonehd5");
      resDirOrders.push_back("iphonehd");
      resDirOrders.push_back("iphone");

      glview->setDesignResolutionSize(2436, 1125, resolutionPolicy);
      Director::getInstance()->setContentScaleFactor(1.0);
    }
    fileUtils->setSearchResolutionsOrder(resDirOrders);

    std::vector<std::string> searchPaths;

    searchPaths.push_back("audio");
    searchPaths.push_back("music");
    searchPaths.push_back("files");
    searchPaths.push_back("fonts");
    searchPaths.push_back("backgrounds");
    searchPaths.push_back("bmfonts");
    searchPaths.push_back("images_animation");
    searchPaths.push_back("images_players");
    searchPaths.push_back("images_ui");
    searchPaths.push_back("particles");
    searchPaths.push_back("schedules");
    searchPaths.push_back("player_names");
  
    // myLog("setSearchPaths");
    fileUtils->setSearchPaths(searchPaths);

    // and reload all the spriteframes, in case I changed format
    SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("common_anim.plist");
    SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("ui_8888_trimmed.plist");
    SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("whiteset.plist");
    SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("whiteset.plist");

    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("common_anim.plist");
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("colorset.plist", "dallas_color_jerseys.png");
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("whiteset.plist", "dallas_white_jerseys.png");
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("ui_8888_trimmed.plist");

    // loadTeamsAndBindNames(game.team_idx[HOME_TM], game.team_idx[VISITING_TM]);
    loadPlayerTextures(false);
  }
  
  // if (mode == kDesktopResFullscreen)
  //   addCloseButtonFullScreen();

#endif
}

So, some of this may make no sense even to me :slight_smile: , so let me try to explain:

  • I was having trouble with cached fonts, so I call director->purgeCachedData();
  • because I may have changed desktop resolution (IPHONE5 or IPHONEX), I go through setting my search paths and reload all my spritesheets

I hope it makes some sense. Good luck!

Thanks, @stevetranby, that looks great! That prompted me to look for an event for the user clicking on the “maximize” button so that I could use my fullscreen logic, but I haven’t been able to find it. Is that just part of EVENT_WINDOW_RESIZED?

Ideally, I’d love to be able to catch the user maximizing the window and also trying to close the window. I tried to add a callback for the window closing, but my callback was never called. I’m thinking that the window closes first.

(btw, I’m not sure how to get the code to display correctly formatted, sorry)

I had this:
void window_close_callback(GLFWwindow *window)
{
CCLOG(“in window_close_callback, ok_to_close is %d”, ok_to_close);
if (!ok_to_close)
glfwSetWindowShouldClose(window, GLFW_FALSE);
}

and set it like this:
auto window = dynamic_cast<GLViewImpl*>(glview)->getWindow();
if (window != nullptr)
glfwSetWindowCloseCallback(window, window_close_callback);

Perfect. This was the missing piece. It’s now working for everything apart from going from windowed mode to full screen. For some reason the director is not getting a new size in this instance - investigating.

Fixed! Full screen wasn’t going through all the code… !