Announcement

Collapse
No announcement yet.

Lacking essential coding utilities in the Blueprint Editor.

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

    #16
    Originally posted by EntrpriseCustomr View Post
    Ledii

    5 & 7 sound interesting. Any chance you'd offer show-don't-tell anim-gif / short-youtube-clip examples?
    For #7
    Let's say you want to have a more generic player request for "asking for confirmation".
    This is a common feature that may occur many places in your games depending on what you are trying to make.
    And making bindings to several callbacks is a little tedious if you want to do it without long spaghetti.

    This is what my Widget looks like:
    Click image for larger version

Name:	callbacks3.PNG
Views:	119
Size:	17.9 KB
ID:	1782132

    This is a function library to hook up the tedious pieces of nodes:
    Click image for larger version

Name:	callbacks2.PNG
Views:	109
Size:	109.0 KB
ID:	1782133

    And finally, clean reusable node with callback variables:
    Click image for larger version

Name:	callbacks1.PNG
Views:	108
Size:	102.5 KB
ID:	1782134

    Comment


      #17
      Originally posted by EntrpriseCustomr View Post
      Ledii

      5 & 7 sound interesting. Any chance you'd offer show-don't-tell anim-gif / short-youtube-clip examples?
      For #5
      In our team at work, we dont really have the luxary of spending any budget on testing as of right now.
      So the only reason we would want to use interfaces would be in the case that we would want to experiment with different implementations of a mechanic.

      Let's say we want to make some sort of a save system. Maybe we want this cloud based so that the user can share their saves accross devices.
      In which case I would like to work with an internal SaveGame object first. Because that is the most simple case to handle.
      So I would love to make UnrealSaveGame and CloudServiceSaveGame objects. Which both implement a shared interface.
      I know that most common loading & saving APIs will supply you with getters & setters for common types like boolean, integers, floats and strings.
      So I want to plan ahead for that in my system. So I create a interface with those getters & setters, right?
      Click image for larger version  Name:	save data.PNG Views:	0 Size:	136.7 KB ID:	1782157

      Well, I also need to be able to keep streaming levels persistent after the get unloaded and reloaded again.
      To handle the states I decided to look into a state system to bind saved variables to conditions.
      (There is lots of blueprints to this, but I will summarize the core of it here!)

      Step 1, we make a state machine, and say what we want each state to initialize in the blueprint.
      We also make a key string that will be used for the conditions later.
      Click image for larger version  Name:	state machine.PNG Views:	0 Size:	108.6 KB ID:	1782158

      Step 2, we create states and feed them which result they should return.
      Click image for larger version  Name:	state machine 2.PNG Views:	0 Size:	61.3 KB ID:	1782159

      Step 3, we create conditions that is required to evaluate the particular state true.
      (In the picture we want the condition to be "The boolean value stored in the Key of the SaveGameObject" should be "True".
      Click image for larger version  Name:	state machine 3.PNG Views:	0 Size:	63.2 KB ID:	1782160

      When you make a condition, it will also want to bind to an event dispatcher that will fire whenever the Key is set.
      (This is the part where I discover i can't do it with interfaces, without event dispatchers)
      The condition will then make a internal evaluation on weather it changed the result or not (to prevent firing again if its the same result).
      If it happens to be a change, it will send a event dispatcher back to the StateMachine to run a full check.
      Last edited by Ledii; 06-29-2020, 08:07 AM.

      Comment


        #18
        Originally posted by Ledii View Post

        Yes absolutly, having consistent options is important
        Might I suggest this for the "Open Level" node as well?
        Sure, added this as well
        https://github.com/EpicGames/UnrealEngine/pull/7120

        Comment


          #19
          Nice, a PR; now we only gotta wait for at least 6 months
          | Savior | USQLite | FSM | Object Pool | Sound Occlusion | Property Transfer | Magic Nodes | MORE |

          Comment


            #20
            Technology solutions to people problems seldom work. At best, they can help reinforce an actual people-solution.
            (Yes, I claim that private/protected in the C++/Java style is a design mistake in software development, as is implementation inheritance. We have to live with these mistakes, but when we can avoid using them, we're better off!)
            If you really want private in blueprints, today, with no additional effort needed, use a naming convention!
            If you have a team who can't read and follow a naming convention, and keeps writing obviously broken code without testing it, fix your team!

            Comment


              #21
              Originally posted by jwatte View Post
              Technology solutions to people problems seldom work. At best, they can help reinforce an actual people-solution.
              My point was that fixing technology is still much easier for the most part than fixing people.
              But I agree that fixing people would be the more "proper" solution if its a possibility.

              Originally posted by jwatte View Post
              (Yes, I claim that private/protected in the C++/Java style is a design mistake in software development, as is implementation inheritance. We have to live with these mistakes, but when we can avoid using them, we're better off!)
              When it comes to inheritance, it's absolutly not a mistake!
              Inheritance is one of the most powerful programming techinques out there.
              If you think that is a mistake, I would argue you should look into learning what good you can use it for.

              And private/protected is a also very essential part of that.
              Without it, you would start crying when you were trying to access the one method you want to use, and find 1000 of simularly named methods available on the dropdown list.
              It's meant to be a clean-up feature. And working in a dirty environment is not good.

              Comment


                #22
                I would argue you should look into learning what good you can use it for.
                Note that I said "implementation inheritance."

                Having worked on actual C++ compilers and having shipped public SDKs based on C++ in the 1990s all the way to today, I know very well how implementation inheritance is used, and in almost every case where it's used, there exists a simpler to work with, safer to execute, and more performant way of achiving the same goals using a function library, or using interfaces-only, or using type classes, or using type traits, or using delegation.

                Anytime you have to ask yourself the question "Do I need to call the superclass function or not? What happens if I do? What happens if I don't?" then you're running into one of the biggest problems of implementation inheritance. But there are others (having spent years of my life mitigating the Fragile Base Class problem in practice, for example.)

                Go learn some Rust, and some Go, and some Haskell, and ship a real project in each of those languages, and then go back to the Java-style deep implementation inheritance chains, and you'll realize that most of the implementation-based OO was just GOOP getting in the way of the underlying code that wants to get out.

                Comment


                  #23
                  Originally posted by jwatte View Post
                  Note that I said "implementation inheritance."
                  I have tried looking up some info on "implementation inheritance" but I can't figure out exactly what you're reffering to.
                  I can describe the type of inheritance I know of, which I do like very much...

                  abstract class Animal has a method called Decicion. This runs based on a random timer.
                  class Tiger inherits from Animal, and can choose how it wants to implement its Decicion method.

                  (If this is not what you meant, could you please provide me a example where I can see the problems you reffer to?)
                  Last edited by Ledii; 07-01-2020, 04:37 PM.

                  Comment


                    #24
                    Implementation inheritance in C++ is when you have a class that has both:
                    - non-abstract functions
                    - virtual functions

                    Code:
                    struct Vector3 { ... };
                    This is a value object, with no virtual functions, and can work fine as a container of plain data. (Maybe even use unique pointers, shared pointers, and such.)

                    Code:
                    class IMyInterface {
                      public:
                        virtual void MyFunction() = 0;
                      protected:
                        virtual ~MyFunction() {} // avoid compiler warnings
                    };
                    This is an interface class. (C++ doesn't have "interfaces" per se, but these PABC Pure Abstract Base Classes will serve)

                    Those are both fine.

                    This is a bad API:

                    Code:
                    class MyThing {
                    public:
                      MyThing(lots of parameters);
                      ~MyThing();
                      virtual void DoSomeThing();
                      virtual void DoSomeOtherThing();
                    };
                    Why is this bad?

                    Well, someone who overrides DoSomeThing doesn't know whether they're intended to call the superclass function or not. It's also not part of the specification whether DoSomeOtherThing() will call DoSomeThing() or not.

                    There are more subtle design problems that end up happening as the inheritance chain gets deeper, and the pressure you feel towards creating a "god object" base class that has all the functionality comes from the problems of implementation inheritance.

                    A better alternative is to use the object simply as a collection of API functions (like a module) and use specific hooks/callbacks where necessary (this is delegation):

                    Code:
                    class MyThing {
                    public:
                      MyThing(lots of parameters);
                      ~MyThing();
                      void DoSomeThing();
                      void DoSomeOtherThing();
                      void AddDelegate(ISomeThingHook *hook);
                      void RemoveDelegate(ISomeThingHook *hook);
                    };
                    
                    class ISomeThingHook {
                    public:
                      // if returning false, abort the thing. state is the current number of geegaws.
                      virtual bool OnDoingSomeThing(int state) = 0;
                    };
                    This more cleanly separates the "implementation of my module on my data" of the base class, from "well defined variation points" in the hooks/delegates.

                    Still, this ends up with a fair bit of pointer chasing. If you can get away with POD structs and free functions, you'll be better off, and your software will miss cache less often, too! And if you define your interfaces in terms of "large contiguous vectors of data" rather than "one thing at a time," you end up parallelizing better, which works well both for SIMD, and for threading, and for GPU ports, depending on what specifically your code is doing.

                    There are other languages where these ways of designing are less cumbersome and more built-in, leading to less boilerplate. Dropping all subtlety, it approximately ends up as: "Haskell good; Java bad."

                    Comment


                      #25
                      Originally posted by jwatte View Post
                      Well, someone who overrides DoSomeThing doesn't know whether they're intended to call the superclass function or not.
                      That is quite simple, You run it if you want the default implementation at some point and also something in addition to that.
                      If the defualt implementation is bad for what you're trying to achieve then you need to re-implement something that works in your favor.
                      If either of those are really a problem, then its all a fault of the class designers that did not make the base class generic enough.

                      This is a somewhat naked skeleton of a case I would argue is very good.
                      Code:
                      class Enemy {
                      public:
                         int startingHealth = 100;
                      protected:
                         int health = startingHealth;
                      
                      public:
                         virtual void OnHit(Weapon weapon, BodyPart bodyPart) {
                            //Most common enemies take more damage from the head
                            if (bodyPart == BodyPart::Head) {
                               TakeDamage(weapon.damage * criticalModifier);
                            }
                            else {
                               TakeDamage(weapon.damage);
                            }
                         }
                      protected:
                         virtual void TakeDamage(int amount) {
                            //Do default health calculation
                            health -= amount;
                            updateEventForUI();
                      
                            //Death check
                            if (health <= 0) {
                               Die();
                               health = 0;
                            }
                         }
                      };
                      
                      class ShieldEnemy : Enemy {
                      public:
                         int startingShield = 100;
                      protected:
                         int shield = startingShield;
                      
                         virtual override void TakeDamage(int amount) {
                            //Need to consume shield first
                            shield -= amount;
                      
                            if (shield < 0) {
                               int overflowAmount = abs(shield);
                               shield = 0;
                               Enemy::TakeDamage(overflowAmount);
                            }
                            else {
                               updateEventForUI();
                            }
                         }
                      };

                      Originally posted by jwatte View Post
                      It's also not part of the specification whether DoSomeOtherThing() will call DoSomeThing() or not.
                      It absolutely should be. A base behaviour should be well documented. You should know exactly when each virtual call should appear.
                      If I in the example above only have shields on some of my body parts, I would have to account for that with a override to the OnHit method.


                      Originally posted by jwatte View Post
                      There are more subtle design problems that end up happening as the inheritance chain gets deeper, and the pressure you feel towards creating a "god object" base class that has all the functionality comes from the problems of implementation inheritance.
                      Having very deep chains could be a problem.
                      Though with with a good understanding of the base class as agreed upon by the designers in the documentation.
                      You should be able to make a decicion on weather you already have a child class that behaves like you want or not.
                      Ideally, you should never have to go any deeper than 3 levels! (2 Most of the time)


                      Originally posted by jwatte View Post
                      A better alternative is to use the object simply as a collection of API functions (like a module) and use specific hooks/callbacks where necessary (this is delegation):
                      Callbacks are indeed quite useful. Though they should not replace inheritance entirely.
                      They are useful for decoupling code, like HealthUI in my example.
                      We want to be able to test the enemy behaviour without needing a specific UI to be there.
                      We only care to tell the UI that something has happend, and it should update.
                      How ever, children should in most cases still use the default parent behaviour.
                      Unless you implement something custom.


                      Programming is all about making hard problems simple to understand. Being able to break that up into base and child functionality is an important skill for that.
                      If you do a poor job at generalizing what a base class should do, you get bad code that is hard to work with, that much is true.
                      But don't blame that on the language and utilities.
                      Yes, bad designs is possible because of it. But good designs are also possible because of it.
                      If we should keep restricting ourselves like this, we would not be able to allow anything for anyone!

                      Comment


                        #26
                        That is quite simple, You run it if you want the default implementation
                        But how do I, as a programmer, reading the headers, know whether I do that or not?
                        Many APIs *require* that you call the superclass for *some* functions (Android comes to mind.) But if I get it wrong, there's really nothing telling me so, until I get some weird runtime failure under hard-to-pin-down circumstances.
                        I have to trust the documentation, if it exists, or I have to read the source and trust that I don't miss any subtlety in the inherited code. This adds significant cognitive overhead.

                        What if there were mechanisms in programming languages that simply didn't let me do the wrong thing?
                        Turns out, such programming constructs exist, and they're not based on implementation inheritance. They can, sort-of, be expressed in C++, although in a very clunky way, using templates and concepts. They can't even be expressed in languages like Java and C# and Objective-C.

                        Programming is all about making hard problems simple to understand
                        I don't feel like we're having the same conversation here.
                        Ship a couple of systems using other paradigms, and compare.
                        It'll be pretty obvious that OO with implementation inheritance ended up being a 20-year detour in the industry's path towards robust software development.

                        Comment


                          #27
                          Originally posted by jwatte View Post
                          But how do I, as a programmer, reading the headers, know whether I do that or not?
                          I have to trust the documentation, if it exists, or I have to read the source and trust that I don't miss any subtlety in the inherited code.
                          Yes. The people who should need to work on it should have access to documentation which is at the very least semi-up-to-date.
                          Full source code so that they can check if anything diverts from the documentation etc.
                          If you want to work on a system. That system is going to work in a specific way.
                          You can't expect to be able to write stuff for that system without knowing full well how it works.

                          I have seen so many times that people end us jamming wrenches in my cogwheels.
                          Just because it made the classes functionality pause. Then they come to me when its time to make all the cracked wheels turn again.
                          With proper hiding of things that should not be messed with, this could not have happened.

                          If it seems hard to implement what you try to do, without having access to all the underlaying variables and methods that go into a system.
                          it't more likely than not, the wrong use case for that system.


                          Originally posted by jwatte View Post
                          Ship a couple of systems using other paradigms, and compare.
                          It'll be pretty obvious that OO with implementation inheritance ended up being a 20-year detour in the industry's path towards robust software development.
                          Could be that the story is different for other languages and engines.
                          But Unreal is the only relevant topic at the moment for me.
                          Why am I seeing such a great improvement in how easy it is to develop after I started using these then I wonder?

                          Comment


                            #28
                            Originally posted by Ledii View Post
                            If you want to work on a system. That system is going to work in a specific way.
                            You can't expect to be able to write stuff for that system without knowing full well how it works.
                            This is how it works in a normal software development environment. You often just get headers to compile against and absolutely zero knowledge of the inner workings of the software you're working with.

                            Comment


                              #29
                              Originally posted by ambershee View Post
                              This is how it works in a normal software development environment. You often just get headers to compile against and absolutely zero knowledge of the inner workings of the software you're working with.
                              Yeah, I have come to understand that things are not the same for software development and Unreal.
                              Which is why I hope we could lay this discussion on ice.
                              Because by now, I'm kind of exhausted from it.

                              This was supposed to be a simple feature request to make blueprint more consistently the same as c++.
                              I don't wanna argue weather the guys who invented c++ made the rights calls or not.
                              You work with what you have, and you make it work consistent.

                              Comment

                              Working...
                              X