Announcement

Collapse
No announcement yet.

Managing complexity in Blueprints

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    Managing complexity in Blueprints

    Managing complexity in Blueprints
    As you build larger projects with Blueprints, it’s easy to end up with an overwhelming sea of nodes. However, we’ve built in a number of different encapsulation and code reuse mechanisms to help you battle the chaos.

    To encapsulate something is “to show or express the main idea or quality of (something) in a brief way”; in other words we can hide a complex sequence of nodes with a simple stand-in that conveys the same meaning or idea. You can still drill down and see how it works ‘under the hood’, but you don’t have to worry about the details when looking at the broader picture.

    Code reuse is frequently paired along with encapsulation, but basically it’s the approach of applying the same idea (set of nodes) in different situations without having to duplicate your work each time. It’s obviously more effort to duplicate the work each time, and any bugs or feature changes would have to be done in each copy independently.

    Here’s a quick comparison of the benefits and limitations of the different approaches:

    Functions Events Macros Collapsed Graphs
    Execution Paths One One Any Any
    Non-exec Outputs Any None Any Any
    Latent actions No Yes Yes Yes*
    Add components/timelines No Yes No Yes*
    Other events No N/A No Yes*
    * A collapsed graph inherits the limits of the graph it is in, e.g., a collapsed graph inside of a function still cannot contain latent actions or timelines.


    Collapsed Graphs
    You can make a collapsed graph from a selection of nodes by right-clicking on one of the nodes and selecting ‘Collapse Nodes’. Any wires to nodes outside of the selection set will become inputs or outputs to the new collapsed node. Once collapsed, you can hover over the node to see a preview of the nodes in the contained graph.


    [collapsed graph]

    Collapsed graphs can contain any kind of node that is allowed in their current context and have no limits on their inputs/outputs. Collapsed graphs may seem like the best pick of the bunch with no limitations on the nodes they contain, but they are limited to only encapsulation, not code reuse. If you copy-paste a collapsed graph, all of the nodes inside get duplicated too, making a new collapsed graph rather than referencing the original.

    Functions and Events
    You can create your own empty functions using the Add Function button on the ‘My Blueprints’ toolbar or turn a selection of nodes (if it meets the limitations) into a function using the ‘Collapse to Function’ option when right-clicking on a selected node. You can make new events using the Add Custom Event… action in the graph context menu. A function shows up as a separate graph in the My Blueprints list, while events go into an event graph which can contain many different events.

    Functions and events can both be called from other places in the blueprint just like calling a function defined in C++. Both of them define a single path of execution from an outside caller’s perspective, although they can have branching or loops internally. Events can also have their execution flow delayed by a latent action or even merge into the flow for another event.


    [Event declaration]


    [Event or function call]

    Functions are guaranteed to execute and return immediately by limiting what kinds of nodes can be placed in a function (latent actions, timelines, etc… are all prohibited). This allows them to return a value to a C++ caller, which isn’t possible when using events or macros.

    Functions and events are also how C++ can call into Blueprints. C++ can define a function as a BlueprintImplementableEvent which can be implemented in a Blueprint. These functions have no built-in behavior, and will do nothing if called without a blueprint implementation, but they can instead be declared as a BlueprintNativeEvent, and provided with a C++ definition as well. Although these UFUNCTION() keywords mention just events, they will turn into either events or functions in a blueprint when implemented in a Blueprint, depending on whether they have any return values or not.

    Macros
    You can create a new empty Macro using the Add Macro option on the ‘My Blueprints’ toolbar, or turn a selection of nodes into a macro using the ‘Collapse to Macro’ option when right-clicking on a selected node. You can also create Blueprint Macro Libraries, sharing macros across many blueprints. This makes them one of the most effective means of Blueprint code reuse. I'll go into more depth on macros and macro libraries in a future post.


    [macro instance]

    Macros can have arbitrary inputs and outputs, including execution wires. Macros can be called in the same way as functions or events from a blueprint, but they aren’t visible from C++ code. Like collapsed graphs, macros don’t really exist in a compiled blueprint; every instance gets expanded out into a unique set of nodes during compilation.

    Since you can have more than one instance of a macro, some things like custom events are prohibited inside of the macro, since there’d be ambiguity in which one (or in which order) to execute them. Timelines and Add Component nodes are currently also prohibited due to implementation details, but we’d like to allow them in the future.

    Comments
    Comments are basically a present to future-you from current-you; explaining what your intentions were or making a note of potential issues and additional work to be done.


    You can place a comment box around selected nodes by pressing C. By default these boxes move all contained nodes around with them, but you can change that behavior as well as the color in the Details panel. You can edit the comment just like any other editable title by double-clicking on it or pressing F2. You can also comment any other individual nodes using the field in the context menu for that node, which will show up as a bubble above the node.

    Finally, you can write a comment that will show up in the Content Browser tooltip for your Blueprint by ‘Blueprint Properties’ and editing the ‘Blueprint Description’ in the Details panel. This is really useful for gameplay Blueprints that don’t have a thumbnail preview, but it’s still good to add usage notes even for things like level props that have a thumbnail.

    -----

    Feel free to reply with questions or comments below or join me on Twitter at @joatski.
    Attached Files
    Last edited by Michael Noland; 05-05-2014, 02:14 PM.

    #2
    I would add blueprint interfaces, which can used to reduce the complexity too. You can write functions with it and add them to other blueprints.

    edit: its only for abstract function declaration, not for implementation as mentioned later in this thread
    Last edited by plucked; 05-08-2014, 05:35 AM.

    Comment


      #3
      Originally posted by plucked View Post
      I would add blueprint interfaces, which can used to reduce the complexity too. You can write functions with it and add them to other blueprints.
      Can you expand on this? I thought that blueprint interfaces could not have any blueprint networks in them, but only served as a way to have channels of communication between different blueprints?
      Trevor Lee

      Comment


        #4
        Heya,

        Hyperloop is correct, an interface is essentially a contract promising that you will implement a set of functions, but doesn't include an implementation of any of those functions. Each Blueprint might implement them completely differently (or not at all if it doesn't implement the interface, in which case the interface message calling it would just safely and silently fail).

        If you want to write code once and share it between several Blueprints, the best way right now is to use a Macro Library. I've got another blog post in the works on them that should go up soon, but at a high level they let you make macros that can be called from any other Blueprint based on the same parent class (typically Actor).

        [edit]Macros and macro libraries post is now up[/edit]

        Cheers,
        Michael Noland
        Last edited by Michael Noland; 05-08-2014, 12:23 AM. Reason: added link to other blog post

        Comment


          #5
          Oh my Bad for the misconception. I thought the interfaces can do both: abstract and normal implementation of functions.

          Comment


            #6
            This is crazy, you have made it absolutely impossible to make modular construction scripts. The only way to reference one blueprint from another is with the "Add child actor component" functionality, which gives you no control over the created component at all, no passing references or triggering events or even setting variables. Not to mention that child actors get removed and readded to the scene every time the construction script is ran, severely slowing the editor.

            The only alternative to adding child actors is macro libraries, and now it turns out that macros are severely limited in functionality, not being able to add components.

            Isn't the goal of a Component Entity system like we have here to allow for extreme composability? Why do blueprints have these weird limitations? Why can't a blueprint be a component?

            Comment


              #7
              I don't want to be rude, but I recommend you to study UE4 a bit more and behave yourself less agressive.
              Originally posted by tinco View Post
              The only way to reference one blueprint from another is with the "Add child actor component" functionality, which gives you no control over the created component at all
              You are doing this, like, super wrong.
              • If you want to reference particular instance of Blueprint - you should create Actor variable and assign instance of blueprint to this variable. You'll get access to variables, events, functions and etc.
              • If you want to reference base Blueprint itself - you should create Blueprint variable.
              • If you want to spawn instance of Blueprint - you should create Class variable, assign Blueprint class to it and then use "Spawn Actor from Class" node.

              Why do blueprints have these weird limitations? Why can't a blueprint be a component?
              Well, why car can't be a component of a car? It's a matter of commons sense and architecture. You can find everything you are looking for in variables, component system serves another purposes.
              Last edited by zeOrb; 05-18-2014, 06:31 AM.
              SuperGrid: Marketplace Page | Feedback Thread | Demo | Website
              Level design and prototyping for newbies

              Comment


                #8
                Originally posted by zeOrb View Post
                I don't want to be rude, but I recommend you to study UE4 a bit more and behave yourself less agressive.
                I apologize, it was late and I had wasted a couple of hours trying to make my thing work and it was the third time I discovered things could not work like I was hoping they could.

                Originally posted by zeOrb View Post
                You are doing this, like, super wrong.
                • If you want to reference particular instance of Blueprint - you should create Actor variable and assign instance of blueprint to this variable. You'll get access to variables, events, functions and etc.
                • If you want to reference base Blueprint itself - you should create Blueprint variable.
                • If you want to spawn instance of Blueprint - you should create Class variable, assign Blueprint class to it and then use "Spawn Actor from Class" node.
                Thanks for the tip, I tried this now and it certainly allows to have more control over the spawned actor and allows me to set properties, but it also runs into a limitation. Actors can not be spawned in a construction script. It inspired me to explore the child actor component again, and it seems I can cast the child actor to its class and then invoke its functions, so that's nice.

                Originally posted by zeOrb View Post
                Well, why car can't be a component of a car? It's a matter of commons sense and architecture. You can find everything you are looking for in variables, component system serves another purposes.
                This is not a matter of common sense, why can't a boat be a component of a boat? But in my case, why can't a window be a component of a house?

                Anyway, you're not being rude and I am sorry for setting an aggressive tone in the discussion, there's a bunch of counter-intuitive limitations in blueprint that I feel make it hard to use blueprint for extending Unreal Engine with more than just self contained actors.

                Comment


                  #9
                  Thanks for the post, I think this is a really interesting topic and one well worth discussing out in the open. Let me see if I can talk about one thing at a time:

                  Child Actor Components do not allow very convenient customization

                  This is true at the moment, but we'd like to fix it. Right now we are trying to work through some nasty bugs related to CAC - this is a feature that is being used more heavily by our new community, but we are making progress! Once we think its stable, we want to make them work like adding a component, where selecting the component/node shows you the actor properties in the details panel and lets you configure. Also 'Expose On Spawn' variables should create pins just like a Spawn Actor node. This will probably take some time, but we will have it up on the roadmap trello soon so you can track our progress.

                  Child Actors being destroyed and re-spawned slow things down

                  We haven't run into this too much yet, but it's certainly a valid criticism. The reason that we 'tear down' the components and re-run the construction is that it makes procedural content very robust. We had a prefab system in UE3 but it was very buggy and complicated, mostly because it tried to merge the graph of components saved in the level with the new graph of components from an updated prefab. In UE4, we felt robustness was the #1 priority, so went with a model where we could throw out all components and guarantee a 'clean slate' each time we run the construction. One option if you are seeing slowness is to turn off the 'run CS on drag' option the Blueprint Properties. That way the CS only runs when you finish moving, not every frame of a move. There are probably some good optimizations we can make to the whole 'rerun construction' process as well that we will hopefully work on at some point.

                  Cannot create a Blueprint of a Component

                  This is something which is theoretically totally possible (and some big teams are already doing), we just haven't had enough time to test it before wanting to enable it. I believe it is coming very soon though!
                  Lead Programmer - UE4 Animation/Physics/Audio Team - Epic Games
                  Twitter: @EpicJamesG

                  Comment


                    #10
                    Just an update, 4.2 introduces the new function library blueprint feature. I thought this would be the solution to my troubles, but unfortunately calling "Add ChildActorComponent" does not even work in a function library blueprint. So it's still impossible to have modular procedurally generated blueprints visible in the editor.

                    Comment


                      #11
                      Hi Tinco,

                      Have you tried just calling SpawnActor with an actor reference? You have to manually track the spawned actor and destroy it when your parent actor is destroyed, but otherwise it should work fine.

                      Cheers,
                      Michael Noland

                      Comment


                        #12
                        Ah, I thought it wouldn't work because of the no spawning actors in the construction script thing. I will try it tomorrow. Thanks for the hint

                        Comment


                          #13
                          Thank you very much for your explanation. That helps a lot.

                          One thing I was confused about is which is the best approach if I want to make some utility functions. For example, let's say I want to a function/macro to find the nearest instance of a given class with a certain property (e.g., find the nearest instance of the cat class with the hungry property set to true). I know how to do this with a macro (I think). Is there any downside in using a macro instead of a function? For example, will it significantly bloat my code or run slower?

                          Can I do this with a blueprint function? If so, would I just make a blueprint class which inherits from actor, and put the FindHungryCat function along with other utility functions in there?

                          Basically, I want to know if I should always use macros or if/when/how to write a blueprint function instead.

                          Thanks a lot.

                          Comment


                            #14
                            Originally posted by Xarol View Post
                            Thank you very much for your explanation. That helps a lot.

                            One thing I was confused about is which is the best approach if I want to make some utility functions. For example, let's say I want to a function/macro to find the nearest instance of a given class with a certain property (e.g., find the nearest instance of the cat class with the hungry property set to true). I know how to do this with a macro (I think). Is there any downside in using a macro instead of a function? For example, will it significantly bloat my code or run slower?

                            Can I do this with a blueprint function? If so, would I just make a blueprint class which inherits from actor, and put the FindHungryCat function along with other utility functions in there?

                            Basically, I want to know if I should always use macros or if/when/how to write a blueprint function instead.

                            Thanks a lot.
                            Sorry for the thread necro, but I was about to post this exact question (when to use macros vs. functions - in a best practices sense). There has been some concern on our team over some cyclical dependency/constant recompilation issues with blueprints, and both macro libraries and function libraries have been brought up as culprits. I'd love to get some official guidance on the benefits/pitfalls of heavy usage of macros and functions. Is there anything we should watch out for? How much is too much? etc.

                            Thanks!

                            Comment


                              #15
                              For most intents and purposes, there is no significant difference between them in terms of cost, so use whichever one fits the job better. I generally use functions for things that make sense as functions, and macros for things where I want multiple execution wires out, etc..., but there's not a hard and fast rule.

                              A function called several times ends up using less space in bytecode than a macro but has the overhead of the function call (although compared to the general overhead of Blueprints this isn't very significant). Functions also support recursion while macros do not. Local intermediate terms in a function are also only allocated when that function is called, as opposed to being allocated for the lifetime of the object as is the case for event graphs (since Macros can be used either in event graphs or functions, this isn't a strict win/loss for them, it depends entirely where they are used).

                              RE: cyclical dependencies, we've made huge improvements in 4.7 that should hopefully eliminate dependency issues and Blueprint COL corruption.

                              Cheers,
                              Michael Noland

                              Comment

                              Working...
                              X