Hi, is there a way to redefine a variable type in a child class?
Example: I would like to have a generic StateMachine and one crafted for the needs of my character extending the generic one.
I have two base class: StateMachine and State
and CharacterStateMachine extending StateMachine and CharacterState extending State.
in the StateMachine class, i defined a variable current_state of type State
var current_state: State
In the CharacterStateMachine, is it possible to redefine the current_state variable type with one that extends its initial type State (CharacterState in this example)?
I feel this is a bit weird and I donât think this is possible. What would be a good way to handle this? am I thinking about this in a really weird way?
No, but you donât need to. You can just assign a CharacterState object to a variable of type State. Thatâs how inheritance works, and how it is intended to work.
It doesnât know anything about CharaterStates or PlayerStates. It is completely agnostic to whatever states it is switching between. In fact, itâs the exact same code as my Game State Machine.
My State Machine Plugin contains two files, and I use them for all my state machines. The only changes I make to derived classes is to make them actually implement functions beyond logging.
Itâs only irrelevant if you make it irrelevant. In which case, if it becomes an issue, you can either fix your architecture so that inheritance is a benefit, or you can use a function to run interference.
Create a setter and getter and override them. Make the State class @abstract, and while defining them to only take and return a State respectively, internally do type checking and casting.
I do not suggest this approach, but itâs available.
I wouldnât worry about it. As you use your state machine, youâll refine it.
Thank you, that gives me food for thought, I still have to process some information.
Thatâs probably the way I should build it but I wasnât sure I could keep it completely agnostic until the end.
I started learning State Machine with this serie of videos (there is a textual transcription here). I really donât have the experience to truly judge, but I felt some stuff was a bit âwrongâ and everything a bit too coupled. Now Iâm trying to get a better understanding on that topic.
And in the process, I am studying your own State Machine Plugin and reading some of your long posts about other topics, thanks for the resources!
It looks âcoupledâ because you forcibly separated things that belong together into too many pieces, just because someone said you should. Fragment less and you wonât have to worry about coupling.
Always try to minimize the number of classes while still modeling the problem domain adequately, and aim to keep your class inheritance tree as shallow as possible, ideally no inheritance at all.
I actually donât think of it that dogmatically (but saying âeverythingâ was a stretch, for sure, I have some specific points in mind). But as I said, I donât have the experience to truly judge, Iâm still assessing and and trying to find what could best fits my needs. Iâm currently not overwhelmed or lost in the code, but I felt I could easily be in the future building from there and had to find some adaptations.
It could be just my skills, though, I still have to look at it in more depth.
This is known as future proofing (aka speculative/premature generality). Itâs a waste of time and mental effort in most cases, especially if youâre not experienced. Your predictions are almost always guaranteed to be wrong, even if youâre an experienced developer.
Instead of succumbing to that, always implement the simplest solution for your actual known problem, as it exist right now.
This is SO true. I fell into this trap when I first came to Godot, and I see so many experienced developers do the same thing. One ends up creating things that Godot handles in other ways, and much more elegantly.
I talk about my process in this post:
And if you read it, youâll see where I refactor as I go. It prevents Future Proofing.
I always recommend following a tutorial exactly as it is presented, and then re-implementing it yourself afterwards. It helps reinforce the learning, prevents issues with things not working because you changed something you didnât realize was important, and makes the tutorial go faster. Keep what you want, discard the rest.
Thank you for the warning.
I had a previous project done with Unreal Engine that I feel has become too much of a mess. Itâs not hopeless and I think I can easily extract some elements or âcleanâ it with a lot of work, but it was hard to continue adding anything, I felt the foundations were not good enough.
Thatâs what I would like to avoid. Of course, I wasnât going make the same mistakes again, but I could easily make all new ones
Youâre right, Iâll be wary of that and try to find the right balance, refactoring as you go seems a good point.
No, thatâs one I havenât read, Iâll have a look, thank you.
Good, thank you for the recommandation
Is there a good method to get the information you ignore to ignore in order to prevent doing that?Are there other good information centralisation other than the documentation?
And would you recommand simply getting code from plugin and using it without understanding it in depth?
Iâm in the process of learning and trying to rebuild from scratch in order to able to adjust everything. Iâm a bit âafraidâ of building from systems that I donât know in depth, but maybe Iâm also losing my time there.
I guess I could use some basic features that way. Could State Machine be one of them?
Edit: Iâm realising with these last questions I might be in the same mindset of Future Proofing instead of just making things work.
Maybe as a small reassurance, @jul-a , even after 30 years of hacking (I refuse to call myself an expert on anything, except maybe trying out stuff), I still run into the future proofing trap.
And the premature optimization trap.
And the âmiscouplingâ pattern we noticed here: when things are close to eachother they communicate faster (shared memory space for instance).
Anyway, before I fall into the associative grandpa story telling trap again, Iâll leave you at it.
Just wanted to compliment you (all) on the great learning curve happening in this thread
Experience. You watch enough videos, and everyone does things slightly differently. After a while you find the way that works best for you. Over time, that will likely change.
The forums.
Depends on your goal. If a plugin does what you need, then yes. This is something we do all the time in programming - we use packages that other people make. Understand the things you want to understand, and then the things you need to understand will expand that knowledgebase.
Being afraid of failure will hold you back in life. Doesnât matter if your are programming or anything else. Failure is how we learn. The more you fail, the more successful you will be.
Yep. Thereâs plenty of plugins for that, and state machines are actually a pretty simple thing at their core. Hence why you can make them out of Enums.
gdscript doesnât support variable shadowing, method overloading, or covariant field types, so the closest you can get is a pseudo getter/setter;
extends StateMachine
class_name CharacterStateMachine
var character_state: CharacterState:
get:
return state as CharacterState
set(value):
state = value
in c# you can shadow it new keyword. But itâs kills inheritance chain, you should forward it to parent object
public class CharacterState : State {}
public partial class CharacterStateMachine: StateMachine
{
public new CharacterState State
{
get => (CharacterState)base.State;
set => base.State = value;
}
}
edit: as c# generics can be used
public partial class State: Resource {}
public partial class CharacterState : State {}
public abstract partial class StateMachine<T> : Node where T : State
{
public T State { get; set; }
}
public partial class CharacterStateMachine: StateMachine<CharacterState> {}
only downside you canât attach open generic objects to scene tree or decorate generic fields with export
Thank you! Not sure if it is reassuring though, as I understand Iâll never escape this trap
But I take this as a good reminder to remain vigilant
Thank you for your detailed answer. I took the time to study your plugins (from your game template) and rebuild them step by step. What I have now is mostly identical to your plugin, but it was a good example of something worth understanding in depth, that was very instructive. Iâll feel totally comfortable building from it, but I think I first needed to acquire the theoretical base knowledge on that subject.
It gived some nice good practices insights too. Again, thank you for the resources.
Small side question: is there a place to discuss about it?
@KerimCetinbas Thank you for your answer. I kind of move along with that issue. I initially expected to get autocompletion and error detection that ârelevantâ static typing provides. I think your suggestion might help with that, Iâll give it some thought.
There is a related manta called âYAGNIâ (âYou arenât going to need itâ). In general, think about if something is actually needed before implementing it. Donât get carried away implementing e.g. an entire linear algebra library if all you really need to do is add and subtract vectors. Godot does things like this because it is a general purpose game engine with contributions from thousands of people. None of us have that kind of manpower - no matter how tempting it is to emulate the expansive and general purpose nature of the platform weâre building on top of.
There is a long-standing mantra that âpremature optimization is the root of all evil.â These words ring with a lot of wisdom, but I think it is far too easy for beginners to misunderstand this as âperformance doesnât matter.â We should be thinking about things like the algorithms and data structures we plan on using before we get too carried away. There are multiple approaches at hand to solve most problems, and most of the time they have trade-offs.
This is demonstrated in something as simple as containers. For the problem of storing a collection of objects, Arrays, Lists, and Dictionaries do more-or-less the same thing, but they each have implications and trade-offs. Arrays provide the fastest random access and iteration, but the slowest insertion. Lists provide fast insertion at any position, but the speed of random access decreases linearly with size. Dictionaries have a larger memory footprint, but fall in the middle performance-wise.
It isnât the âevilâ sort of premature optimization to think about things like this. A lot of times, the data structures we choose for our core systems will have profound impacts on the rest of the design. This can be seen in a comparison between Godotâs tree-based scene architecture vs. Unity or Bevyâs array-based entity-component-systems. The architecture of everything else reflects the underlying data structures, and it is not the sort of thing that can be âoptimizedâ at the end of the development cycle. It would be nearly as difficult as trying to lose weight by replacing your skeleton.
The âevilâ kind of premature optimization is when you go around searching for problems where they do not exist. For instance, taking a perfectly functional procedure and making it twice as long and much harder to understand because you wanted to eliminate duplicate calls to ârelatively slowâ trigonometric functions. This generally applies to any functions which take an input and provide an output without side effects. If the output is correct, your work is done. The better your architecture, the more functions will fall in this category.
On the other hand, when creating an interface which is going to be used by many other pieces of code, it is definitely worth stopping for a moment and giving it the basic sniff test before building months of work on top of it. âDoes this make sense? Are there any obvious bottlenecks?â A little extra work up front can save a lot of effort re-designing it later. On the other hand, sometimes it is impossible to tell if something will work well unless you try. Sometimes your requirements can change in the middle of the project, too.
Ok, I just had really minor questions / suggestions while I was at it. Not sure that could request their own threads, but Iâll think about it if I have more.
Well, as I said, they were minor questions, and most already slipped out of my mindâŚ
I was wondering if there was a place where I can ask those if they come back to my mind as I dive in the code again.
In my project, Iâm getting my camera facing direction using this function, that keeps my code independant from whatever camera I use: get_viewport().get_camera_3d().global_basis.
I was wondering if there were any reason to NOT do it that way.
But now that Iâm reading your code again, Iâm understanding that youâre just doing it differently and rely on the camera to provide a specific âready to useâ facing direction (that one used here), not just the camera facing direction. Iâm processing that in my character, but I now understand that for some cameras, it might make sense to process it differently and then do it on the camera.
So ultimately, I havenât really any questions left here for nowâŚ