Search Unity

Question Best Practice for Conditional Code when Targeting Multiple Platforms

Discussion in 'Scripting' started by baakta, Feb 28, 2023.

  1. baakta

    baakta

    Joined:
    May 22, 2019
    Posts:
    14
    I'm working on an application that will be shipped to both Android (Meta Quest) and WebGL platforms. While much of the functionality is shared, some is platform specific. For example, in the WebGL build, I'm looking to the system keyboard for text input, whereas in the Android build, a virtual, world-space keyboard is used. As I see it, I can use assembly directives to gate platform-specific functionality, or I could use if statements to gate behavior, as I'm already tracking platform with a singleton instance.

    Example - I could use if statements...

    Code (CSharp):
    1.  
    2. if (PlatformManager.INSTANCE.platform == PlatformManager.Platform.Desktop)
    3.         {
    4.             // Get system keyboards input stream.
    5.         }
    6.         else
    7.         {
    8.             if (virtualKeyboard != null)
    9.             {
    10.                 virtualKeyboard.gameObject.SetActive(true);
    11.             }
    12.         }
    13.  
    or I could use assembly directives...

    Code (CSharp):
    1.  
    2. #if !UNITY_WEBGL
    3.         if (virtualKeyboard!= null)
    4.         {
    5.             virtualKeyboard.gameObject.SetActive(true);
    6.         }
    7. #else
    8.             // Get system keyboards input stream.
    9. #endif
    10.  
    Should I only use assembly directives to gate content that's otherwise un-buildable for the given platform? Anyone have best practices in this domain? Optimization or readability concerns?

    Thanks in advance for any insights!
     
    TzuriTeshuba likes this.
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    I think you already know your answer. But, the second is, to me, the better choice. While including code for other platforms isn't always an issue, sometimes it does create problems. but, the end result is, why add code to a build that is never going to be used? Sure, it's most likely minor and not going to really have much impact, but there isn't any reason to do so.

    Plus, if you aren't on the correct platform in Unity for the particular code, it greys out that code. Readability is certainly much better than having to deal with a set of if statements for your target platform.

    I use the second method for iOS/Android usually.
     
    baakta and TzuriTeshuba like this.
  3. TzuriTeshuba

    TzuriTeshuba

    Joined:
    Aug 6, 2019
    Posts:
    185
    this isnt necesarily applicable for your immediate case, but many devices can run on, lets says, android. for example Quest and Pico, and even a smartphone version of your game. so assembly directives may not necesarily be enough. A good guiding principle i would say is to abstract the logic away, by whatever method/s get the per-device functionality that you need, and only work with an interface to the functionality. so your "ifs" and assembly directives are hidden to most of your program at the "PlatformHandlerFactory" and "PlatformHandler" levels. The PlatformHandlerFactory should get you a IPlatformHandler instance (i.e. QuestPlatformHandler, WebGLPlatformHandler, etc).

    disclaimer: i do not have much experience with this particular matter, so i may be missing some nuance, but this is something i will be dealing with soon and this was my anticipated approach. And looking forward to others input on the matter.
     
    SisusCo and baakta like this.
  4. dogmachris

    dogmachris

    Joined:
    Sep 15, 2014
    Posts:
    1,375
    Use assembly directives for code that can't be compiled on a platform and use if statements for code that can be compiled but behaves differently on different platforms. But don't overuse either of them or your code will be a mess. ;)
     
    baakta and TzuriTeshuba like this.
  5. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    The main pain point for me when using preprocessor directives tends to be that the code inside of them does not compile in the editor either unless I'm targeting a particular platform. So for example all UNITY_ANDROID code is completely disabled when I'm targeting the iOS platform.

    This can make it awkward to work on code, as you don't get any help from the compiler unless you temporarily tinker with the preprocessor directives.

    It can also cause issues whenever you rename a class or a variable, as that name might not get properly updated for any inactive code wrapped inside preprocessor directives.

    Player player;
    #if UNITY_IOS
    player = GetPlayerOnIOS();
    #elif UNITY_ANDROID
    target = GetPlayerOnAndroid(); // the 'target' variable was renamed to 'player', but it didn't update for the inactive code
    #else
    player = GetPlayerDefault();
    #endif

    The less intertwined your code for all the different platforms is, the easier time you will have working with them. The less variables you share between code for the different platforms, the less chances there are that code on one platform will break when something is changed for another one.

    This is why using interfaces / inheritance to handle branching on a higher level is much better than having a lot of if-else branches at the bottom.

    InputManager.ActiveInputProvider <- AndroidInputProvider / iOSInputProvider / KeyboardAndMouseInputProvider
     
    baakta and TzuriTeshuba like this.