Search Unity

Canvas Flow - UI Presentation & Storyboarding

Discussion in 'Assets and Asset Store' started by Pelican_7, May 26, 2018.

  1. mtpabis

    mtpabis

    Joined:
    Oct 18, 2012
    Posts:
    13
    Nice, very good points, thanks for the insights :)
    I bought the asset and will test it out in the near future.
     
    Pelican_7 likes this.
  2. 4sascha

    4sascha

    Joined:
    Mar 9, 2017
    Posts:
    51
    Hello,
    just another , does it works together question with....
    AnyUi
    We recently fell in love with it because of the ability to free choose geometry where ui canvas can be rendered on.
    Did you could test if it works together with CanvasFlow.
     
    Last edited: Sep 12, 2018
  3. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hi @4sascha. That's a really cool looking UI plugin. I haven't tested Canvas Flow with it yet. Reading the AnyUI documentation, it sounds promising as it only requires a world space canvas. Hopefully it can handle instantiated child canvases .

    I'm going to get AnyUI tomorrow (Friday) to test it with Canvas Flow and I'll report back here.

    -andy.
     
  4. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hey @4sascha,

    I've been testing AnyUI with Canvas Flow and so far it seems to be working well - it's a really cool plugin :)

    Here is a quick capture, in which I'm using the AnyUI terrain demo as a base and I have replaced their canvas with a Canvas Flow storyboard. You can see AnyUI is projecting the instantiated world-space canvases onto the object just fine. As you can also see, I tried using all the default transition animators too, which all work as expected.



    -andy.
     
  5. 4sascha

    4sascha

    Joined:
    Mar 9, 2017
    Posts:
    51
    Thats great. There are crazy things you could do with.
    Try staying compatibel with it.)
     
    Pelican_7 likes this.
  6. Meatloaf4

    Meatloaf4

    Joined:
    Jul 30, 2013
    Posts:
    183
    Just purchased the asset. So far it looks to be pretty nice :). Great job!

    Was curious is there anyway to set a new default transition?
     
  7. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Thanks @Meatloaf4! :)

    If I have understood correctly, no you can't currently change the default transition animator, i.e., the one that is used when no transition animator is set. You can of course create a new instance of the default transition animator (or any other animator) and modify it, but you must assign it as the custom animator on any transitions on which you wish to use it.

    Thanks for the suggestion - it would be nice to be able to specify a default animator. If you knew you were going to use it on the majority of transitions, it would save you from having to set it each time you create one in the storyboard.

    -andy.
     
  8. Meatloaf4

    Meatloaf4

    Joined:
    Jul 30, 2013
    Posts:
    183
    Thanks for the reply!

    Here's a little solution I coded up in the meantime :).

    Code (CSharp):
    1. public abstract class JumpCanvasController : CanvasController, ICanvasControllerTransitioningAnimatorVendor {
    2.     public CanvasControllerTransitioningAnimator defaultTransition;
    3.  
    4.     public new void PresentCanvasController<T>(bool animated = true, Action<T> configuration = null,
    5.         Action<T> completion = null, Vector3? canvasWorldPositionOverride = null, bool loadAsynchronously = true)
    6.         where T : CanvasController {
    7.        
    8.         Action<T> updatedConfig = (controller) => {
    9.             controller.transitioningAnimatorVendor = this;
    10.             configuration?.Invoke(controller);
    11.         };
    12.  
    13.         base.PresentCanvasController(animated, updatedConfig, completion, canvasWorldPositionOverride,
    14.             loadAsynchronously);
    15.     }
    16.  
    17.     public virtual ICanvasControllerTransitioningAnimator TransitioningAnimatorForContext(
    18.         CanvasControllerTransitionContext transitionContext) {
    19.  
    20.         // The presented canvas controller is the destination on downstream transitions (Present)
    21.         // and the source on upstream transitions (Dismiss).
    22.         CanvasController presentedCanvasController = (transitionContext.isUpstream)
    23.             ? transitionContext.sourceCanvasController
    24.             : transitionContext.destinationCanvasController;
    25.         return defaultTransition;
    26.     }
    27.    
    28. }
    29.  
     
    Pelican_7 likes this.
  9. Meatloaf4

    Meatloaf4

    Joined:
    Jul 30, 2013
    Posts:
    183
    Sorry one more thing. I find this really confusing and was wondering if you could sort it out for me.

    Code (CSharp):
    1.  
    2. protected override void CanvasWillAppear(){}
    3. protected override void CanvasDidAppear() {}
    4. protected override void CanvasWillDisappear() {}
    5. protected override void CanvasDidDisappear() {}
    6.  
    Why should I have to call
    IsBeingPresented || IsBeingDismissed
    in these methods? They seem pretty self explanatory. When i want to know when my canvas is going to appear/disappear or finished appearing/disappearing I call one of these.

    I'm just really confused as to why I can't just say something like this in my CanvasController.

    Code (CSharp):
    1.  
    2. protected override void CanvasDidAppear() {
    3.     //my canvas just appeared start coding here
    4. }
    5.  
    instead it seems i have to say this
    Code (CSharp):
    1.  
    2. protected override void CanvasDidAppear() {
    3.     if (IsBeingPresented) {
    4.         //my canvas just appeared start coding here
    5.     }
    6. }
    7.  
    Thanks so much for your time! Sorry, I feel like I must be missing something, I read the documentation pretty thoroughly but couldn't work this bit out
     
  10. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    I love the default animator solution above, nice one. :)

    Oh, no worries @Meatloaf4.

    There isn't actually anything wrong with your first statement:

    Code (CSharp):
    1. protected override void CanvasDidAppear() {
    2.     //my canvas just appeared start coding here
    3. }
    The reason for the IsBeingPresented and IsBeingDismissed methods is to give you a way to differentiate between when your canvas has appeared because it has been presented, or has appeared because another canvas controller has been dismissed. To give an example, imagine that screen A presents screen B. B receives its will/did appear callbacks and A receives its will/did disappear callbacks. Now, screen B is dismissed. Screen A will receive its will/did appear callbacks because it is about to appear again, and B will receive its will/did disappear callbacks.

    To try to give a concrete use case of when you might want to use these methods - imagine a level-complete screen where there is a fireworks particle effect that plays in CanvasDidAppear. If the user navigates to another screen (perhaps a high scores list) and then returns, the explosion would play again as the canvas has appeared again. Instead, you could use IsBeingPresented to check that we are appearing because of a presentation and only play this particle effect when the canvas appears AND is being presented.

    So your first statement is totally correct. It's perhaps just worth noting that a canvas can potentially appear multiple times throughout its lifetime. If you want your code to run every time the canvas appears, then go with your first statement. Otherwise, you might want to use IsBeingPresented and IsBeingDismissed.

    Looking at the documentation on this, I think it's a bit unclear so thanks for bringing it up! I'm wondering if the methods might need to be renamed to something like IsInvolvedInPresentation/IsInvolvedInDismissal too, but I'll have a look into it.

    -andy.
     
  11. Meatloaf4

    Meatloaf4

    Joined:
    Jul 30, 2013
    Posts:
    183
    Ah ok now I get it! Thanks for the breakdown.

    I do agree that I think these two properties might benefit from a renaming to make them a bit more clear.

    Another solution could be something like this

    Code (CSharp):
    1.  
    2. CanvasDidAppear(bool fromPresentation,  bool fromDismissal){ }
    3.  
    4. //Another possibility condensing into an enum instead?
    5. enum TransitionState { Presenting, Dismissing }
    6. CanvasDidAppear(TransitionState fromCanvas){
    7.     if(fromCanvas == TransitionState.Dismissing){
    8.     }
    9. }
    10.  
    Not sure if the two properties are useful in other methods, but if they aren't it might make sense to make them part of the method signature to give them a bit more context.

    Thanks in advance!

    P.S. The code above is just off the top of my head so take it with a grain of salt :)
     
    Pelican_7 likes this.
  12. Meatloaf4

    Meatloaf4

    Joined:
    Jul 30, 2013
    Posts:
    183
    Sorry I managed to stumble across one more question.

    If I want something occur on
    Update
    but only when the panel is visible it seems I might have to do something like this.

    Code (CSharp):
    1. void Update(){
    2.     if(Hidden){
    3.         return;
    4.     }
    5.     //code to run on update when panel visible here
    6. }
    7.  
    Is this correct or am I missing something?

    Thanks in advance!
     
  13. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    @Meatloaf4 Good point. A method enum argument could be cleaner as these methods likely aren't all that useful from elsewhere. Thanks!

    Yes, that's correct.
     
  14. Meatloaf4

    Meatloaf4

    Joined:
    Jul 30, 2013
    Posts:
    183
    Ah got it thanks!

    One other thing. While working with CanvasFlow I've noticed that getting access to an instantiated service from a menu that's a couple levels deep can be a bit cumbersome. You would have to pass down the service through parent CanvasControllers to get access to what you needed.

    I was thinking of how this might be solved and came up with the below. It's definitely not perfect but it does solve that issue in a decent manner I think.

    Base Class (My Implementation of CanvasController)
    Code (CSharp):
    1. public abstract class JumpCanvasController : CanvasController {
    2.     public static Action<CanvasController> OnPreconfigure;
    3.  
    4. public new void PresentCanvasController<T>(bool animated = true, Action<T> configuration = null,
    5.     Action<T> completion = null, Vector3? canvasWorldPositionOverride = null, bool loadAsynchronously = true)
    6.     where T : CanvasController {
    7.     Action<T> updatedConfig = (controller) => {
    8.         OnPreconfigure?.Invoke(controller);
    9.         controller.transitioningAnimatorVendor = this;
    10.         configuration?.Invoke(controller);
    11.     };
    12.     base.PresentCanvasController(animated, updatedConfig, completion, canvasWorldPositionOverride,
    13.         loadAsynchronously);
    14. }
    Base State

    Code (CSharp):
    1. public class MainState : State {
    2.     public PlayFabAuthorizationService playFabAuthorizationService;
    3.  
    4.     protected override void OnStateEnter() {
    5.         playFabAuthorizationService = new PlayFabAuthorizationService();
    6.         JumpCanvasController.OnPreconfigure += InitializeCanvases;
    7.         CanvasController.PresentInitialCanvasController<MainCanvasController>();
    8.     }
    9.  
    10.     private void InitializeCanvases(CanvasController presentedCanvas) {
    11.         (presentedCanvas as SettingsCanvasController)?.Initialize(playFabAuthorizationService);
    12.     }
    13. }
    Nested Class
    Code (CSharp):
    1. public class SettingsCanvasController : JumpCanvasController {
    2.     private PlayFabAuthorizationService playFabAuthorizationService;
    3.  
    4.     public void Initialize(PlayFabAuthorizationService playFabAuthorizationService) {
    5.         this.playFabAuthorizationService = playFabAuthorizationService;
    6.     }
    7.  
    8. }
    I would love any feedback on this implementation.
     
    Pelican_7 likes this.
  15. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    @Meatloaf4 Really interesting solution! This is something I had wondered about too - when not using storyboards you end up having to pass dependencies down the stack like you say.

    So you essentially have a global configuration action that is executed for all JumpCanvasControllers, allowing you to set it once? Clever.

    When working with storyboards, you are able to hook into all presentations and dismissals from a single place via the storyboard callbacks. For example, you could do something like:

    Code (CSharp):
    1. public class AuthorizationCanvasController : CanvasController
    2. {
    3.     private PlayFabAuthorizationService authorizationService;
    4.  
    5.     public void Initialize(PlayFabAuthorizationService authorizationService)
    6.     {
    7.         this.authorizationService = authorizationService;
    8.     }
    9. }
    10.  
    11. // This is a MonoBehaviour present in the scene (or could be a ScriptableObject)
    12. public class PassAuthorizationServiceToAuthorizationCanvasControllers : MonoBehaviour
    13. {
    14.     // Assigned in inspector to the authorization service.
    15.     public PlayFabAuthorizationService authorizationService;
    16.  
    17.     // Added to storyboard's StoryboardWillPresentInitialCanvasController event in inspector.
    18.     public void OnStoryboardWillPresentInitialCanvasController(StoryboardTransition transition)
    19.     {
    20.         HandleAuthorizationCanvasControllerPresentation(transition);
    21.     }
    22.  
    23.     // Added to storyboard's StoryboardWillPerformTransition event in inspector.
    24.     public void OnStoryboardWillPerformTransition(StoryboardTransition transition)
    25.     {
    26.         HandleAuthorizationCanvasControllerPresentation(transition);
    27.     }
    28.  
    29.     private void HandleAuthorizationCanvasControllerPresentation(StoryboardTransition transition)
    30.     {
    31.         // Is this a presentation?
    32.         if (transition.direction == StoryboardTransitionDirection.Downstream)
    33.         {
    34.             // Is the destination canvas controller an AuthorizationCanvasController (or derived)?
    35.             var authorizationCanvasController = transition.DestinationCanvasController<AuthorizationCanvasController>();
    36.             if (authorizationCanvasController != null)
    37.             {
    38.                 // Initialize the presented authorization canvas controller with the authorization service.
    39.                 authorizationCanvasController.Initialize(authorizationService);
    40.             }
    41.         }
    42.     }
    43. }
    44.  
    So, any AuthorizationCanvasController (including derived types) that gets presented will be initialized with the service.

    It has got me thinking... I wonder if your idea of a global config opportunity should be built-in. For example, there could be a static CanvasController event, such as OnCanvasWillBePresented. This would be called every time any canvas controller is presented, giving you a chance to check if it requires your service and initialize it if required. So taking your example, you could subscribe to this in your MainState and then you wouldn't need the preConfigAction in JumpCanvasController, perhaps like so:

    Code (CSharp):
    1. public class MainState : State
    2. {
    3.     public PlayFabAuthorizationService playFabAuthorizationService;
    4.  
    5.     protected override void OnStateEnter()
    6.     {
    7.         playFabAuthorizationService = new PlayFabAuthorizationService();
    8.         CanvasController.OnCanvasWillBePresented+= OnCanvasWillBePresented;
    9.         CanvasController.PresentInitialCanvasController<MainCanvasController>();
    10.     }
    11.  
    12.     private void OnCanvasWillBePresented(CanvasController presentedCanvas)
    13.     {
    14.         (presentedCanvas as SettingsCanvasController)?.Initialize(playFabAuthorizationService);
    15.     }
    16. }
    Likewise, any feedback is welcome!

    -andy.
     
  16. mmvlad

    mmvlad

    Joined:
    Dec 31, 2014
    Posts:
    98
    @Pelican_7 any possibility to get source code?
    ps: promise I won't write extra support tickets etc :)
    Just want to modify for my needs.
     
  17. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    My storyboard can no longer be edited. When I try to delete a canvas controller from the storyboard I get this error in the Unity console.


    NullReferenceException: Object reference not set to an instance of an object
    P7.CanvasFlow.UIStoryboardEditor.NodeViewForNode (P7.CanvasFlow.StoryboardNode node)
    P7.CanvasFlow.UIStoryboardEditor.DestroyNodeViewForNode (P7.CanvasFlow.StoryboardNode node)
    P7.CanvasFlow.StoryboardEditorData.StoryboardGraphWillDestroyNode (P7.CanvasFlow.StoryboardGraph graph, P7.CanvasFlow.StoryboardNode node)
    P7.CanvasFlow.StoryboardGraphEditorExtensions.DestroyNode (P7.CanvasFlow.StoryboardGraph graph, P7.CanvasFlow.StoryboardNode node)
    P7.CanvasFlow.UIStoryboardEditor.NodeViewDidSelectDelete (P7.CanvasFlow.UIEditorNodeView nodeView)
    P7.CanvasFlow.UIEditorNodeView.DidPressButtonView (P7.CanvasFlow.UIEditorButtonView buttonView)
    P7.CanvasFlow.UIEditorButtonView.UpdateState (Single deltaTime, UnityEngine.Event evt)
    P7.CanvasFlow.UIEditorButtonView.UpdateWithEvent (Single deltaTime, UnityEngine.Event evt)
    P7.CanvasFlow.UIEditorView.UpdateContentsWithEvent (Single deltaTime, UnityEngine.Event evt)
    P7.CanvasFlow.UIEditorView.UpdateContentsWithEvent (Single deltaTime, UnityEngine.Event evt)
    P7.CanvasFlow.UIEditorView.UpdateContentsWithEvent (Single deltaTime, UnityEngine.Event evt)
    P7.CanvasFlow.UIEditorView.UpdateContentsWithEvent (Single deltaTime, UnityEngine.Event evt)
    P7.CanvasFlow.UIEditorView.UpdateContentsWithEvent (Single deltaTime, UnityEngine.Event evt)
    P7.CanvasFlow.UIEditorView.UpdateContentsWithEvent (Single deltaTime, UnityEngine.Event evt)
    P7.CanvasFlow.UIEditorWindow.UpdateWindowWithEvent (Single deltaTime, UnityEngine.Event evt, Rect editorWindowRect)
    P7.CanvasFlow.UIStoryboardEditor.UpdateInContainerWindow (Single deltaTime, UnityEngine.Event evt, UnityEditor.EditorWindow containerWindow)
    P7.CanvasFlow.StoryboardEditorWindow.OnGUI ()
    System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:222)
    Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation.
    System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MonoMethod.cs:232)
    System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Reflection/MethodBase.cs:115)
    UnityEditor.HostView.Invoke (System.String methodName, System.Object obj) (at /Users/builduser/buildslave/unity/build/Editor/Mono/HostView.cs:295)
    UnityEditor.HostView.Invoke (System.String methodName) (at /Users/builduser/buildslave/unity/build/Editor/Mono/HostView.cs:288)
    UnityEditor.HostView.InvokeOnGUI (Rect onGUIPosition) (at /Users/builduser/buildslave/unity/build/Editor/Mono/HostView.cs:255)
     
  18. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    I'm afraid I'm not making source code available at this time @mmvlad.

    @TillmaniaLtd

    1. Were you using the latest version (v1.1.3) of Canvas Flow when this occurred? There was an issue that was resolved in v1.1.3 whereby a Unity build error could cause a similar error to occur.

    2. Are you able to send me your Unity project (andy@pelican7.com)? If so, I should be able to fix this storyboard for you.

    - andy.
     
  19. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    Hey,

    I updated to the latest version and remade the storyboard, it seems to be working now. I'm now trying to present a canvas controller from a scene that already contains a storyboard. When the canvas appears however it always appears over the first canvas controller, regardless of where the user is in the navigation stack. How do I make it appear over the currently presented canvas controller?
     
  20. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hey @TillmaniaLtd. Ok cool, glad it's working now.

    When a canvas controller is presented, its camera's depth value is set to be one higher than its presenter's. This ensures presented canvases are always rendered on top of their presenter.

    By presenting a canvas controller in a new hierarchy, its camera's depth value will be less than all but the first canvas of an existing hierarchy (your storyboard) because it's beginning the hierarchy's camera-depth count at zero.

    A quick & simple solution is to change the camera's depth value in the canvas controller's scene that you're wanting to always be on top. Set it to a value large enough that it will always be higher than the number of screens in the storyboard. When this canvas controller is presented as the initial canvas controller in a hierarchy, the hierarchy's camera-depth count will begin at this value. If I've understood your setup correctly, this will cause it to always be rendered on top of your storyboard's screens.

    Alternatively, you could set the camera-depth value at run-time to be one higher than the storyboard's current canvas controller, but you'd need to keep track of the storyboard's current canvas controller to do that.

    Lastly, you could always present the canvas controller from a canvas controller in the storyboard so that it stays within the same hierarchy. If you don't want to have a storyboard transition from every screen, you can present it in code directly with PresentCanvasController.

    -andy.

    (Where the camera's depth value is in a canvas controller. )
    Capture.PNG
     
  21. TillmaniaLtd

    TillmaniaLtd

    Joined:
    Jan 29, 2013
    Posts:
    65
    Awesome, changing the camera depth worked. Perfect :)
     
    Pelican_7 likes this.
  22. fariazz

    fariazz

    Joined:
    Nov 21, 2016
    Posts:
    55
    Hey there! Bought the product earlier and I'm already using it in my project.

    Is there any recommended way of creating a theme / default Canvas Controller? Currently using a prefab to make all canvases look the same, but was wondering if there is another way of doing it.
     
  23. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hi @fariazz! There isn't currently a way to modify the default Canvas Controller scene/script. Out of interest, what kind of objects do you have in your theme prefab?

    -andy.
     
  24. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    Hi,

    could you please add an option to allow us to zoom in/out in the Storyboard editor using mouse wheel without having to hold down the ALT key?

    I think it would also make sense to activate panning when holding down the middle mouse button - just like how it works in Unity's Scene editor.
     
  25. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hi @AndreasO. Sure, I agree. I've been using Unity's Shader Graph a lot recently and have been finding myself trying to use the middle-click pan and scroll-without-ALT in storyboards too. So, I'd like to bring it more in-line with Unity's standard controls. Thanks for the feedback. I've logged it to be implemented in the next version (v1.1.4).

    -andy.
     
  26. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    Cool! :)

    I've only worked one day with CanvasFlow but I already have some more feedback for you.

    • A debug mode that can be enabled which will print out every action like "Controller X performed transition Y", "Controller X will appear", "Controller X did appear". I think this would help a lot in debugging why something doesn't work as expected without having to manually spray Debug.Log()'s all over the place. Btw, the "debug console" of CanvasFlow did never say anything. I'm not sure why that is.
    • Visualization of the flow in the editor panel. The currently active controller could be outlined in light green. Deactivated controller(s) could slowly fade out from green to red to gray over time.
    • Right click on a transition (line) in the editor panel could delete it
    And two question:

    Q1)
    I want to do a fade-in (from black) at the start of my scene. It seems like controllers that are set to be the entry point don't use animations for the activation part. I worked around this by creating a dummy canvas controller and make it the entry point only to make it transition to the actual starting canvas controller. This allowed me to have a fade-in transition as needed.
    Is there a better way to achieve this?

    Q2)
    Is it possible to transition from one storyboard to another by code or do I have to use Unity's SceneManager and load a scene which contains another a GameObject with another storyboard configured in it?

    Cheers,
    Andreas
     
    Pelican_7 likes this.
  27. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Thanks for the feedback Andreas! I've logged it in Trello. What do you mean by "the "debug console" of CanvasFlow did never say anything. I'm not sure why that is."?

    Q1
    Yes, you can make the entry transition animated and use a transition animator (just like all other transitions). If you click the blue edit button on the Entry transition, then you can check "Animated" in the inspector. The default animators will all work when being used on entry and exit transitions.

    Q2
    Hmm, yes generally you'd have a single storyboard for your entire scene. You can't currently present a storyboard from a storyboard - i.e. so that they exist seamlessly within the same screen hierarchy. However, although not ideal, there isn't actually anything stopping you from presenting a second storyboard and having two storyboards presented at once. You would need to adjust the presented canvas controller's camera-depth so that it appears over the top of the current storyboard, which actually came up a few posts ago: https://forum.unity.com/threads/canvas-flow-ui-presentation-storyboarding.533196/page-2#post-3766924.

    -andy.
     
  28. Meatloaf4

    Meatloaf4

    Joined:
    Jul 30, 2013
    Posts:
    183
    Just had this bug appear

    Cannot present canvas controller 'LevelSelectCanvasController'. Canvas controller 'CharacterSelectCanvasController (CharacterSelectCanvasController)' is already in transition.


    This occurs because a user presses a button quickly that should load another CanvasController before it's transition is finished.

    To me it seems like there might be a more elegant way of handling this issue under the hood rather then me having to check this case for every new presentation. Perhaps the presentation are queued, or maybe something prevents events on the canvas before the transition is finished.

    Curious of your thoughts. Thanks in advance!
     
  29. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hey @Meatloaf4.

    This error-log is triggered, as you noted, when a transition is invoked whilst another transition is in progress, such as when repeatedly tapping a button. Internally, Canvas Flow blocks any subsequent transition requests whilst already in transition and so this situation is not a problem for Canvas Flow. As such, it is not an error and so this error-log is being removed in the next update.

    Whilst Canvas Flow simply ignores these transition requests (button presses) whilst in transition, this is something that is worth considering more generally for your UI. If you have selectables that you don't want to be interactable during transition, they'll need to be disabled somehow.

    I had this exact requirement in my current game project and my solution is to do it in the transition animator. I have a custom transition animator that fades in the canvas controller's Canvas Group (I have added a Canvas Group to all my CCs). But a handy feature of Canvas Groups is that they also have an interactable option that toggles all child selectable's interactable state. So, whilst in transition I toggle my Canvas Groups interactable flag, ensuring that no interactable elements can be pressed whilst in transition at all.

    Another idea would be to do something on the current EventSystem/InputModule itself, but that would likely have the effect of disabling ALL input whilst in transition, which may or may not be what you want.

    -andy.
     
    Meatloaf4 likes this.
  30. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    Great. Never mind about that debug log. I was confused. :D

    It works now. I tried it before but this time I figured out that I would also have to enable the "Use Scaled Time" setting.

    Ok, thanks ;)
     
    Pelican_7 likes this.
  31. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    I have another idea.

    You have this handy "open canvas controller scene" button. Could you also add a button to open the corresponding script file?

    It would also be helpful if we could open the Scene (via button or some menu) where the currently opened Storyboard is in. And a drop down menu with all available storyboards in the project could allow to quickly switch between them.
     
    Last edited: Nov 3, 2018
  32. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    @Pelican_7
    It seems to me like the fading doesn't work correctly in all situations. Here's what I try to do:

    1. Entry scene "AppEntry" with its own Storyboard is loaded as the starting scene
    2. CanvasFlow loads "intro".
      1. Screen fades from black to visible
      2. Code will call
        PerformTransitionWithIdentifier("next")
        after waiting for 3 seconds
      3. Screen fades from visible to black
      4. Transition is performed by CanvasFlow
    3. CanvasFlow loads "loading"
      1. Screen fades from black to visible
      2. Code will call
        SceneManager.LoadSceneAsync("game", LoadSceneMode.Single)
      3. Code will call
        PerformTransitionWithIdentifier("next")
      4. Screen fades from visible to black
    4. At this point "game" gets fully activated as well as its own Storyboard
      1. Screen popups up fully visible
      2. Screen fade from black to visible
    My problem is that last step in 4.1. It seems like the transition is not executed on the first frame of a scene. I probably can come up with several hacks but that's not the reason why I bought CanvasFlow... ;-)

    Could you please check this out? Let me know if you need more information.
     
    Last edited: Nov 3, 2018
  33. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    *sigh* ... it's me again. Trying to wrap my head around CanvasFlow ...

    What I want to do is the following:

    Storyboard "intro"
    1. fade from black ► show splash screen ► wait 3 seconds ► fade to black
    2. fade from black ► show logo screen ► wait 3 seconds ► fade to black
    3. fade from black ► show loading screen ► load target scene "main menu" asynchronously ► fade to black
    Storyboard "main menu"
    1. fade from black ► show main menu

    While in the intro storyboard, the canvases from step 1 and 2 seem to be not unloaded until I finally call DismissAllCanvasControllers() in step 3 after doing the async scene loading. It makes sense because technically those canvases are stacked on top of each other. Still, is there a way to unload canvas controllers when I advance to the next one?

    I don't understand why it is so hard to set this up. I checked out the Floaty Cube example and it works very good with loading/unloading and fading the canvases. Unfortunately there seems to be no example on how to do sequential canvas controller transitions where the previous one gets unloaded after a transition. I tried DismissCanvasController() in CanvasDidDisappear() but that doesn't seem to work. It almost looks like I need to create one storyboard per "screen" in my case which would make it really tedious to setup.

    Could someone please help me out on how to configure this in the best way with CanvasFlow?

    EDIT:
    So what seems to work is if I manually initiate an unload of the canvas controller's scene like so:

    Code (CSharp):
    1. protected override void CanvasDidDisappear() {
    2.     base.CanvasDidDisappear();
    3.     SceneManager.UnloadSceneAsync(sceneName);
    4. }
    I must be overlooking something here as this way of doing it doesn't feel very clean.
     
    Last edited: Nov 5, 2018
  34. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    Bug Report:
    Renaming scene asset files result in broken links in storyboards. Storyboard will still refer to old scene names which , of course, cannot be found anymore.
     
  35. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Thanks, they're nice ideas.

    What does this look like on screen? Are you seeing a single frame of the game scene & UI before the fade from black to transparent?

    I did notice that in step 3 you load the scene with LoadSceneMode.Single. You should use LoadSceneMode.Additive when loading scenes, otherwise all open scenes will be closed when loading the new scene. Could this be a cause of why you're getting a flicker at the midpoint (if that's what is happening)?

    What is the reason that you want to unload each screen after it presents the next? I would be very surprised if you need to "create one storyboard per "screen"" - it sounds like we need to back up a little and figure out your structure.

    So, I'm understanding the user flow you want to create is:

    splash screen ► logo screen ► loading screen ► main menu screen

    And in this structure, we have two scenes: intro and main menu. I'd create the following content:

    - An Intro storyboard which contains the UI screen flow - splash screen ► logo screen ► loading screen.
    - An Intro scene which contains the intro storyboard.
    - A Main Menu storyboard which contains the main menu screen.
    - A Main Menu scene which contains the main menu storyboard.

    And as in the Floaty Cube example, the loading screen will be responsible for:

    - Unloading the current scene (Intro in this case).
    - Loading the next scene (Main Menu in this case).
    - Dismissing its current hierarchy of screens (Loading, Logo, and Splash in this case).

    Code (CSharp):
    1. private IEnumerator LoadSceneRoutine(string sceneName)
    2.     {
    3.         Scene previousScene = SceneManager.GetActiveScene();
    4.  
    5.         // Unload the previous scene.
    6.         AsyncOperation loadOperation = SceneManager.UnloadSceneAsync(previousScene);
    7.         yield return loadOperation;
    8.  
    9.         // Load the new scene asynchronously.
    10.         loadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    11.         yield return loadOperation;
    12.  
    13.         // Make the newly loaded scene the active scene.
    14.         Scene loadedScene = SceneManager.GetSceneByName(sceneToLoad);
    15.         SceneManager.SetActiveScene(loadedScene);
    16.  
    17.         // Dismiss all canvas controllers in our hierarchy.
    18.         DismissAllCanvasControllers();
    19.     }
    (Note that the Main Menu Storyboard's entry transition will not need to be animated. The animation happens in dismissing our previous hierarchy.)

    Thanks for the report.

    I hope that helps. Let me know if I have misunderstood your issue at all and I'll try to help.
    -andy.
     
  36. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    Yes, the game scene is fully visible for a split second, then the fading from black to transparent begins. I did try both
    .Single
    and
    .Additive
    but that didn't seem to fix it. However, I checked out your Floaty Cube example in the meantime and this example showed me how to correctly do the scene loading/swapping where also the fading animations work as expected.

    The reason is that my intro scene contains other game objects than just the UI which should become invisible when a transition to another screen is executed.

    The UI is parented under "content" as suggested by your examples/docs and the other game objects, let's say a simple 3D plane game object, sits right at the root inside the "splash screen" scene.

    So while the storyboard is traversed (splash ► logo ► loading) the 3D plane will exist since the first screen and stays visible all the time until the loading screen initiates the scene swapping.

    Yes, this is correct except that I need to be able to dismiss/unload previous screens or at least be able to hide them. The best time for this would probably be when
    CanvasDidDisappear()
    is called. Hm, wait ... is this the solution I'm looking for? I could simply hide/deactivate the 3D plane in
    CanvasDidDisappear()
    , right?

    Yep, that's exactly the code I'm using now and it works great so far regarding the fading issue.

    Thanks for your quick reply. :)
     
  37. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Ahhh, I understand. So you also have world objects in the scene that you'd like to transition, too. There are a couple of ways to achieve this but usually when I need my UI and world objects to interact, I create a script to mediate between the two. This script can listen to a storyboard's callbacks and direct the scene's objects accordingly.

    For example, say we have a 3D model/character in the introduction scene that has an Animator component and we want to change this model's animator state each time we move to the next screen in the storyboard. We can create a script to do this, something like:

    Code (CSharp):
    1. public class IntroductionSceneDirector : MonoBehaviour
    2. {
    3.     public Animator introductionSceneAnimator;
    4.  
    5.     public void OnStoryboardWillPresentInitialCanvasController(StoryboardTransition transition)
    6.     {
    7.         // We are about to transition to the initial splash screen. Direct our scene accordingly.
    8.         // For example, here we just move our animator to the "Splash" state.
    9.         introductionSceneAnimator.SetTrigger("Splash");
    10.     }
    11.  
    12.     public void OnStoryboardWillPresentCanvasController(StoryboardTransition transition)
    13.     {
    14.         if ("Logo".Equals(transition.userIdentifier))
    15.         {
    16.             // We are about to transition to the logo screen. Direct our scene accordingly.
    17.             // For example, here we just move our animator to the "Logo" state.
    18.             introductionSceneAnimator.SetTrigger("Logo");
    19.         }
    20.     }
    21. }
    By placing this script in the introduction scene, we can assign the storyboard's callback actions to our methods, as well as assign the Animator reference. e.g:

    Capture.PNG

    Then each time the storyboard advances, our animator will change state.

    You could of course activate/deactivate game objects in these callbacks or do whatever else you need to do instead of the Animator example.

    Something that I like about doing it this way in my own projects is that it doesn't require any changes to the splash/logo/loading canvas controllers themselves, and therefore these screens have no dependency to the scene's objects. This means that we are free to use any of these screens elsewhere in the project and they wouldn't be expecting the presence of particular scene objects (in this example the Animator).

    -andy.
     
  38. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    Excellent! This is a very nice solution. Thanks for pointing me into this direction even with some sample code. :)
     
    Pelican_7 likes this.
  39. Hubi_C

    Hubi_C

    Joined:
    Mar 14, 2018
    Posts:
    11
    Hey @Pelican_7,

    I've been struggling to make Canvas Flow work with RenderTextures. Right now I've assigned the RenderTexture to the cameras of all Canvas Controllers. However, during the transitions, when a new CanvasController is opened or closed, the rendered image of the MainCanvasController is upside down. Once the new CanvasController is completely closed, the image is fine again.

    Do you have an idea what could cause this problem and how to fix it?
     
  40. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hi @Hubi_C. Hmm. I'm not 100% sure what could cause that. What Wrap Mode is your RenderTexture using and what Canvas Flow transition animator are you using? When a canvas controller is animated off screen, its canvas is animated to the edges of the camera rect. Thus, I'm wondering if it could be caused by the canvas being 'off-screen' and the RenderTexture mirroring this content.

    -andy.
     
  41. Hubi_C

    Hubi_C

    Joined:
    Mar 14, 2018
    Posts:
    11
    Hey, my RenderTexture is using the Clamp Wrap Mode and I'm using the ScaleTransitionAnimator. I've also tried it in the transitions example scene and the same problem occurs. Hm, yeah that could be the reason, since the problem only happens during the transition.

    Just for your reference:
    https://media.giphy.com/media/8cQrs2fScPWXsaUrjO/giphy.gif
     
    Last edited: Nov 19, 2018
  42. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Thanks for the gif. Ah, it's likely not related to the content being off-screen if its happening with the scale animator - that only scales up its content.

    Any chance you could send me a reproduction project? Or show me how you have your render textures setup? I think I could do with reproducing the issue to see if I can narrow down what the cause might be. I'm wondering if it's specific to the scale animator and is related to scaling content, or more generally related to rendering multiple canvasses into a render texture.

    -andy.
     
  43. Hubi_C

    Hubi_C

    Joined:
    Mar 14, 2018
    Posts:
    11
    Hey! I've sent you a pm with a link to the example project. Thanks a lot for the support!
     
    Last edited: Nov 19, 2018
    Pelican_7 likes this.
  44. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    Hi,

    would it be possible to make CanvasFlow load one canvas controller while it also unloads previous ones (might be specifiable as 'last' or as some list)? I tried to use CanvasFlow for a sequence of screens I want to step through and found out that I cannot do this unless I accept that every step further in into my sequence more and more canvas controllers will be loaded and stay in memory forever.

    What I want to do is this:

    • Main Storyboard
      • App Entry Canvas Controller
        • Splash Canvas Controller
          • Intro Canvas Controller
            • Title Canvas Controller
              • Loading Canvas Controller
                • Game Canvas Controller
    Well... every other canvas controller will stay when I have finally reached the Game Canvas Controller. This is not good as it eats up my RAM for no good reason.

    I don't see a good non-code way how to achieve this with CanvasFlow at the moment.

    My current idea for a workaround (but with having to write code) would be to use the "director" script method you mentioned earlier where I would react to the used user identifier and direction of the transition. This makes the whole process very, very clunky imho. While I was beginning to try this out I stopped and asked myself why I'd use CanvasFlow then in the first place when I have to code more or less 50% of what I want to do anyway. So probably (hopefully) I'm just on the wrong track and there's a much better solution to do this.

    I hope my explanation of what I'm trying to achieve here is clear enough. Any ideas?

    Cheers
     
  45. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hi @AndreasO,

    Yes, thanks for the clear explanation.

    I have a similar structure in my current game and the way I approach it is to separate the sections (menu, game etc.) into their own distinct scenes and storyboards. This allows you to dismiss the previous storyboard once you transition to the next and no longer need it. This is also how the included FloatyCube example is structured.

    So, in this case you can create one storyboard containing the menu screens and a second storyboard containing the game screens. Then you'd place the menu storyboard in your menu's scene and your game storyboard in your game's scene.

    Now to transition from your menu to your game you can have a loading canvas controller that when presented, additively loads a specified scene (e.g. your game scene) and then dismisses its entire canvas hierarchy (e.g. the menu storyboard). Likewise, to transition from your game back to your menu you present the same loading screen from your game storyboard, configure it to load the menu scene this time, and the loading screen will dismiss its entire canvas hierarchy (the game storyboard) when it's done. This currently does need to be created in code, however if you look at the FCLoadingCanvasController.cs file from the examples folder, this shows how to do it. This is the relevant code from that file:

    Code (CSharp):
    1. private IEnumerator LoadSceneRoutine(string sceneName)
    2. {
    3.     Scene previousScene = SceneManager.GetActiveScene();
    4.  
    5.     // Unload the previous scene.
    6.     AsyncOperation loadOperation = SceneManager.UnloadSceneAsync(previousScene);
    7.     yield return loadOperation;
    8.  
    9.     // Load the new scene asynchronously.
    10.     loadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    11.     yield return loadOperation;
    12.  
    13.     // Make the newly loaded scene the active scene.
    14.     Scene loadedScene = SceneManager.GetSceneByName(sceneToLoad);
    15.     SceneManager.SetActiveScene(loadedScene);
    16.  
    17.     // Dismiss all canvas controllers in our hierarchy.
    18.     DismissAllCanvasControllers();
    19. }
    EDIT: I think the insight that I didn't explain clearly above is that two separate storyboards are two separate hierarchies. So if within your menu storyboard you present the loading canvas controller, calling DismissAllCanvasControllers from this loading canvas controller will dismiss all canvas controllers in the current hierarchy - i.e. the menu storyboard. Likewise, when this same loading screen is presented in the game storyboard, the same DismissAllCanvasControllers method will dismiss the game storyboard.

    Ultimately I'd like to include some kind of 'scene-transition' or 'loading' canvas controller by default in Canvas Flow if I can find a way to make it generic and extensible enough to cover the majority of use-cases...

    Hope that helps.
    -andy.
     
    Last edited: Dec 7, 2018
  46. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    Ah, I never thought about reusing the loading canvas in several storyboards. That could make all the difference since I came from pretty much your described solution and changed everything into one big storyboard which became very limiting in the end.
    Thanks for your quick answer. Perfect timing for the weekend. :D
     
    Pelican_7 likes this.
  47. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    From the docs for Awake():
    If you override Awake, always call base.Awake() in your derived class's implementation.


    Interestingly, Start() does not have such requirement. I think this leads to hard to find errors.
    So I'd suggest to minimize user errors here by not requiring users having to call the base method at all.
    You could do this by renaming the internally used Awake() method in your CanvasFlow code to something like (private) InternalAwake() and implement an empty virtual method Awake() that can be overridden without harm. Then InternalAwake() will call the public Awake() method no matter if it was overridden or not.

    This change also wouldn't harm existing user code :)
     
  48. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    So far it appears to be working what you suggested by using multiple storyboards and a shared loading canvas controller. However, I still have troubles with using animated fade in/out transitions. It is quite hard to correctly coordinate them.

    As soon as the a new scene with another storyboard is loaded it will fire its transition animation (here: fade in). At this point the previous storyboard that is to be dismissed has just started the fade out animation. To compensate for this "delay" introduced by the fade out animation I created a second transition animator for all non-loading storyboards. This transition animator contains said delay in its animation curve by keeping the value at 0 for the time it needs to wait.

    I would really appreciate better support for scene loading/transitioning in CanvasFlow. It would be the icing on the cake. :)

    The last thing I still need to fix (or find a work around) is to also let non-loading storyboards fade out correctly again.

    Edit:
    I think I will write a custom TransitionAnimator to support delays as another property. It's not easy to understand when the delay is incorporated in the final animation curve and it also depends on the used duration value as well.

    Maybe this is something we could get in a later version out of the box. ;)
     
    Last edited: Dec 8, 2018
  49. Pelican_7

    Pelican_7

    Joined:
    Nov 25, 2014
    Posts:
    190
    Hmm, I believe the issue here is that Unity calls the Awake method via reflection. Therefore any user-defined Awake method in a derived class would have to call the base class's InternalAwake method manually - i.e. it's probably easier just to require a call to base.Awake(). Or have I misunderstood your suggestion here?

    The loading screen method should mean that the loading canvas is on screen when the next storyboard is loaded - i.e. the user will never see the next storyboard transition/fade in. Then when the next scene is loaded & ready, that's when the loading screen is dismissed and can fade out to reveal the next storyboard for a smooth transition.

    Could it be that the loaded scene is very small and therefore has loaded quickly enough that the loading screen dismisses before the new storyboard has finished animating in? For example, you could make your storyboard's entry transition not animated, and then fade out your loading canvas to reveal it.

    Does that make sense or am I explaining it badly?! :rolleyes: If I am, here is a very similar setup from one of my own games which I think illustrates it well. I'm transitioning from the menu scene with a menu storyboard to a game scene with a game storyboard and back again. Notice how the loading screen fades out to reveal the next storyboard and that second storyboard is already there. I've included Unity's Scene View too to show how the transition unfolds. https://drive.google.com/file/d/1yTTkxwhq9Iz9-4O7CkB2_d5zJyBiZsKp/view

    Thanks a lot for the suggestions by the way. This is an area I'd really like to find a way to offer a non-coded solution to if possible.

    -andy.
     
  50. AndreasO

    AndreasO

    Joined:
    Sep 25, 2012
    Posts:
    90
    You are probably right. That might pose a problem to what I suggested. Bummer.

    Hehe, you're welcome. Thanks for your great support here.

    That makes sense but it doesn't seem to work like that for me. I already add an extra second to my loading scene to avoid too quick switches between scenes.

    I think I will start from scratch and build an isolated test project. If it still doesn't work I could send it to you to inspect it if that is ok.

    Another issue I just noticed is that it seems to break CanvasFlow if I try to (additively) load additional scenes after I loaded another scene that contains the next storyboard.

    This is the error message I get:

    Could not find storyboardable in node: UIStoryboardNode - FCGameOverlayCanvasController (P7.CanvasFlow.StoryboardNode)


    This makes me wonder if there is some hidden mechanic in CanvasFlow I should know of when I am doing my own scene loading. Generally, I will need to load some more scenes when I reach my Game Canvas Controller. It seems strange to me that CanvasFlow needs to be the last scene to be loaded for some reason... I tried this in the FloatyCube example. All I changed was to also be able to specify an SceneToUnload property so I was free to not rely on the active scene for knowing what to unload in the Loading Scene. I then created a new empty scene and tried to load that "empty" scene while being in the Loading Scene.

    Modified
    FCLoadingCanvasController.cs
    :

    This works
    Code (CSharp):
    1.  
    2.    private IEnumerator LoadSceneRoutine(string sceneName)
    3.     {
    4.         // Unload the previous scene.
    5.         AsyncOperation loadOperation = SceneManager.UnloadSceneAsync(sceneToUnload);
    6.         yield return loadOperation;
    7.  
    8.         yield return SceneManager.LoadSceneAsync("SampleScene", LoadSceneMode.Additive);
    9.  
    10.         // Load the new scene asynchronously.
    11.         loadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    12.         yield return loadOperation;
    13.  
    14.         // Make the newly loaded scene the active scene.
    15. //        Scene loadedScene = SceneManager.GetSceneByName(sceneToLoad);
    16. //        SceneManager.SetActiveScene(loadedScene);
    17.  
    18.         // Dismiss all canvas controllers in our hierarchy.
    19.         DismissAllCanvasControllers();
    20.     }
    21.  
    This doesn't work (loading another scene after the new storyboard has been loaded)
    Code (CSharp):
    1. // Load the new scene asynchronously.
    2. loadOperation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    3. yield return loadOperation;
    4.  
    5. yield return SceneManager.LoadSceneAsync("SampleScene", LoadSceneMode.Additive);