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.
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…
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.
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!!!
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.
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!
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.
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.
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).
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
}
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.