Visitor: How I Mastered the Toughest Programming Pattern

  Рет қаралды 29,723

git-amend

git-amend

Күн бұрын

Unity C# Architecture: This is how I finally understood the Visitor Programming Pattern - Building a Power Up System in Unity.
🔔 Subscribe for more Unity Tutorials / @git-amend
#unity3d #gamedev #indiedev
▬ Contents of this video ▬▬▬▬▬▬▬▬▬▬
0:00 Classic Visitor
5:58 Intrusive Visitor
7:13 Reflective Visitor
9:43 GetOrAdd Extension Method
Extension Methods and Utils
github.com/adammyhre/3D-Platf...
Assets Shown In This Video (Affiliate Links)
Odin: assetstore.unity.com/publishe...
Dungeon Mason Tiny Hero Duo: (FREE): assetstore.unity.com/packages...
Chromisu: Handpainted Forest MEGA Pack assetstore.unity.com/packages...
SineVFX: Better Crystals assetstore.unity.com/packages...
VFX Trees: assetstore.unity.com/packages...
Kronnect Cloud Shadows: assetstore.unity.com/packages...
Kronnect Beautify: assetstore.unity.com/packages...
Kyeoms Hyper Casual FX 2: assetstore.unity.com/packages...
MalberS Animations: Forest Golems: assetstore.unity.com/packages...
Follow me!
linktr.ee/gitamend

Пікірлер: 89
@git-amend
@git-amend 8 ай бұрын
Hi Everyone! Hope you find this explanation of the Visitor pattern useful - it can help to implement your own Visitor and try it out; you'll see it's not too much code! Links in description to Extension methods and more!
@dan.ec90
@dan.ec90 8 ай бұрын
Thank you so much for making this channel! I've been developing in Unity for a decade now and after discovering git-amend I found out I was thirsty for more advanced tutorials and I didn't even know. Please, never stop!
@git-amend
@git-amend 8 ай бұрын
Thank you, I really appreciate that! Lots more topics to come!
@cit0110
@cit0110 2 ай бұрын
learned the visitor design pattern in my compilers class, i couldnt wrap my head around it until we finally got to the code gen part. my dopamine spiked so high when i realized what was going on and was able to implement it haha
@git-amend
@git-amend 2 ай бұрын
Haha nice!
@GettingRektGaming
@GettingRektGaming 8 ай бұрын
This channel is so good. I always run into issues when it comes to "mutators" and buffs/debuffs systems. Thats when my technical debt either starts, or comes to collect because of my previous sins.
@git-amend
@git-amend 8 ай бұрын
Haha I know the feeling!
@trustytea3136
@trustytea3136 8 ай бұрын
I was recommend to this KZbin channel and I’ve got to say, you’ve quickly become one of my favorite coding KZbin channels to watch, great stuff!
@git-amend
@git-amend 8 ай бұрын
Wow, thanks! I'm really happy to hear that!
@feildpaint
@feildpaint 7 ай бұрын
Wow that null coalescing operator tidbit at the end was super helpful! Just started using them on a new project because they looked fancy haha. Thanks for the great video!
@git-amend
@git-amend 7 ай бұрын
Glad it helped!
@BooleanIndecisive
@BooleanIndecisive 8 ай бұрын
Nice walkthrough showing how to apply a general pattern to Unity!
@git-amend
@git-amend 8 ай бұрын
Thank you!
@Sindrijo
@Sindrijo 8 ай бұрын
To avoid reflection you could just add a type parameter to the visitor interface method: public interface IVisitor { void Visit(T visitable) where T : Component, IVisitable } public interface IVisitable { void Accept(IVisitor); } public MyComponent : MonoBehaviour, IVisitable { public void Accept(IVisitor visitor) { visitor.Visit(this); // Type param is inferred to be MyComponent } } public MyVisitor : MonoBehaviour, IVisitor { public void Visit(T visitable) where T : Component { // use pattern matching etc. if(visitable is MyComponent myComponent) { // do stuff } } } Now you could actually also create a concrete interface that visits only a specific type of component, and then register those visitor on an aggregating visitor. public interface IVisitor where T : Component, IVisitable { void Visit(T component); } public MyAdvancedVisitor : MonoBehaviour, IVisitor { private Dictionary visitors = new Dictionary(); public void RegisterVisitor(IVisitor visitor) { visitors[typeof(T)] = visitor; } public void Visit(T visitable) where T : Component { // this is a bit naive, you could easily add support for inheritance // e.g. a IVisitor should be callable with a DerivedType if(!visitors.TryGetValue(typeof(T), out var boxedVisitor) { return; } if(!boxedVisitor is IVisitor concreteVisitor) { return; // or throw or log error whatever } concreteVisitor.Visit(visitable); } }
@git-amend
@git-amend 8 ай бұрын
I like the generic interface approach as well. Thanks for the detailed breakdown!
@PetrVejchoda
@PetrVejchoda 7 ай бұрын
Yeah, the reflection scared me. Generics is much better approach.
@KhanhLe-in7yi
@KhanhLe-in7yi 6 ай бұрын
RegisterVisitor(IVisitor visitor) where T : Component, IVisitable how to use this
@bxb5625
@bxb5625 2 ай бұрын
Looks nice, but I would have tried smthg like IVisitor where T IVisitable. This would allow you to combine multiple IVisitor like MyComponent : Monobehaviour, IVisitor, IVisitor . Making maybe clearer what visitable your visitor target. Only thing is how would it behave if we had both component on our object, I m thinking about this at 10:30 pm in bed XD, I might say not useable things 😭
@r1pfake521
@r1pfake521 6 күн бұрын
Now you defeated one of the main purpose of the visitor pattern in modern programming languages, that the uploader also missed, compile time error checks if you add a new visitable type later and forgot to add visitor implementation for the new type. With your implementation you would get a runtime error at best.
@Krahkarq
@Krahkarq 7 ай бұрын
Great video & very happy I've found your channel!
@git-amend
@git-amend 7 ай бұрын
Awesome! Thanks!
@ragerungames
@ragerungames 8 ай бұрын
I love your channel man! Newly discovered! :)
@git-amend
@git-amend 8 ай бұрын
Right on! Glad to hear it!
@user-cx4ex4ds9t
@user-cx4ex4ds9t 4 ай бұрын
God bless your channel! One of the best I'v seen on KZbin so far. Please keep making videos like this one, they're really helpful
@git-amend
@git-amend 4 ай бұрын
Thank you! Will do!
@Scott_Stone
@Scott_Stone 7 ай бұрын
This video has been on my recommendation for a week. Before the algorithm changes, I only saw such things only with the biggest channels.
@git-amend
@git-amend 7 ай бұрын
Interesting... well I'm glad to hear that, hope you like the video!
@FidosWideWorld
@FidosWideWorld 7 ай бұрын
Brain got bigger, stuff has been learned, you've earned a subscriber. Keep chadding ö7
@git-amend
@git-amend 7 ай бұрын
Haha thanks!
@quixadhal
@quixadhal 7 ай бұрын
It's funny how part of "object oriented" programming seems to be giving formal names to things we just used to DO in the old days. This is effectively making local dispatch tables with function pointers and varargs argument lists... but fancied up so it's pretty and the compiler can do a little bit more checking that you're using it correctly.
@deathtidegame
@deathtidegame 8 ай бұрын
I never knew this was a pattern or that it had a specific name when I implemented a buff/debuff system for my game using classes that "attach" themselves to objects (such as a health system or movement system) and interact with them (reduce max health, increase/decrease move speed etc).
@git-amend
@git-amend 8 ай бұрын
Nice! That’s awesome!
@marmont8005
@marmont8005 Ай бұрын
I was just thinking about the best way to incorporate collision logic into my game while minimizing the coupling between game objects. I'm currently using a collision manager that only checks if the game objects are touching. I came across the Visitor pattern by chance and discovered your channel. I am in the process of implementing it and it sounds very interesting.
@marmont8005
@marmont8005 Ай бұрын
but I just realized that with a visitorpattern you have a higher cohesion between the individual game objects, because to handle collisions you still need the class of the object to handle this. My conventional approach is to use a centralized collision manager to reduce the coupling between game objects.
@git-amend
@git-amend Ай бұрын
The visitor is really more about performing some logic on the object being visited than it is about collision detection. In many cases that does mean they might collide before the visitor 'Visits' the visitable object, and sometimes they would need to know what class is being visited so that the correct operation can be performed. But if you work with abstractions, this can be minimized. It really comes down to the design of your game. In future videos we use the visitor pattern more frequently, so you might get some other ideas. Glad you found the channel!
@cheesymcnuggets
@cheesymcnuggets 7 ай бұрын
Wow i really need to do more research on general programing, all this just went through one ear and out the other without me processing any of it. I've managed just fine only worrying about what I need to know but I think it would be worth knowing more than I need to if I want to a smoother experience.
@git-amend
@git-amend 7 ай бұрын
Good to hear! There is always more to learn!
@crazyfox55
@crazyfox55 8 ай бұрын
The reflection visitor is a really interesting idea that I have not come across yet. However, I think to avoid writing lots of visit functions it would be better to use a base visitor class that has empty functions for each visitable. The visitor interface will still need to grow as more visitable objects are added. I'm glad to see you're posting about this pattern, everyone on my team was fearful of this pattern and stayed out of the codebase where it was heavily used. But I had to dive into it to add features and fix bugs, it was very challenging at the beginning. I think if you went into more detail about how to debug this pattern that would be great. I think navigating the indirection within the IDE was my big breakthrough.
@git-amend
@git-amend 8 ай бұрын
The base class is a great solution. Another option I've been thinking about would be to keep a base class as you mention, but instead of one interface, the base class could implement several smaller interfaces instead. As long as there was a clear separation of concerns, that might work well. I'll keep debugging in mind, I'm sure I'll be using the Visitor in other videos in the future. Thanks for your comment!
@LimitedInput
@LimitedInput 8 ай бұрын
Amazing
@git-amend
@git-amend 8 ай бұрын
Thank you! Cheers!
@sahilmishra2945
@sahilmishra2945 8 ай бұрын
that null coalescing bit is cool to hear about, these videos are great do you get that examples from a textbook?
@git-amend
@git-amend 8 ай бұрын
Thanks! I don't recall exactly where I first saw the Classic Visitor used in this way, but I think it was in a book - I'll try to remember, it's been a while. I think the PowerUp is a common paradigm for learning the Visitor. The Reflective and Intrusive versions of Visitor come from enterprise software engineering.
@Director414
@Director414 7 ай бұрын
Great video!☺️ Many thanks for sharing. I dont really get the basic premise i guess, what problem are we trying to solve here? How does a scrappy, noobish-way, of doing this look like? I just wanna wrap my head around the actual problem we are solving.. Cheers! ☺️
@git-amend
@git-amend 7 ай бұрын
The Visitor pattern solves the problem of managing interactions between game elements in a scalable way. Without this pattern, you might directly hardcode interactions in each object's script, which becomes messy and hard to manage as your game grows. The Visitor pattern abstracts these interactions: pickups 'visit' objects, triggering appropriate responses (like health increase) defined by the object. A noobish version of this might look something like: public class Hero : MonoBehaviour { public healthComponent; public manaComponent; ... void OnTriggerEnter(Collider other) { if (other.gameObject.CompareTag("HealthPickup")) { health.addHealth(20); Destroy(other.gameObject); // Remove the pickup object } if (other.gameObject.CompareTag("ManaPickup")) { mana.addMana(20); Destroy(other.gameObject); } ... and so on } } Here is another good resouce which describes the Vistor pattern in a more abstract way: refactoring.guru/design-patterns/visitor
@friedcrumpets
@friedcrumpets 8 ай бұрын
WOW; you need more views
@git-amend
@git-amend 8 ай бұрын
Thanks!
@Jose-kr1li
@Jose-kr1li 7 ай бұрын
Hello, I have a doubt with this specific implementation. When the PuckUp class detects a collision, it takes the first IVisitable component that the object that is colliding with has. In this case is the class Hero, but in the same gameObject we have two more components that extents from IVisitable (HealthComponent and ManaComponent), so we would have to take care of the order of the components if we wanted to take Hero in order to visit both HealtComponent and ManaComponent right?
@git-amend
@git-amend 7 ай бұрын
Good question. The order of components retrieved when using GetComponents is always the order they are in the inspector, so in this example Hero will always be the first one retrieved. You could strictly enforce that by introducing another interface or putting the components onto children if you wanted to.
@Cloud-Yo
@Cloud-Yo 4 ай бұрын
Idk, this one was fairly easy for me to get. Credit is obviously due to the quality of the explanation on your video. I think of it as a modular way of adding methods to a Visitable class and its sort of what I was looking for :) Unrelated question: what package (if any) are you using to display the related code in your console when displaying the debug statements. seems useful.
@git-amend
@git-amend 4 ай бұрын
Thanks for the comment - I think the asset you are looking for is Editor Console Pro, I've been using it for years now, it's great! assetstore.unity.com/packages/tools/utilities/editor-console-pro-11889
@Cloud-Yo
@Cloud-Yo 4 ай бұрын
@@git-amend Awesome, thanks! turns out I had it in my assets already. Must have snuck in on one of the Humble Bundles I bought 👍
@pierredalaya444
@pierredalaya444 8 ай бұрын
I'm confused, could you explain what is good about this pattern ? In the classic and intrusive, you create dependencies between your scriptable objects and your components, and in the reflective, well; it uses reflection. I am failing to see any benefits to this one. Would be great to have more insight about pros
@git-amend
@git-amend 8 ай бұрын
The Visitor pattern, by its nature, will introduce some level of dependency between the Visitor and the object being Visited. However, the separation of concerns and the ability to add new operations without modifying existing code can outweigh this disadvantage in many scenarios. Separation of Concerns: The Visitor pattern helps to separate concerns by keeping the operations separate from the component. Adding New Behavior: It makes it easier to add new behaviors to existing object structures without modifying the objects themselves. Single Responsibility Principle: Each visitor is typically responsible for a single operation.
@pierredalaya444
@pierredalaya444 8 ай бұрын
I see, thanks for clarifying. I would need to try it myself, but I am not a fan of adding a new type for a simple ability that just modifies an int. Note: you can get the same benefits without the dependencies by using SO architecture. @@git-amend
@EskemaGames
@EskemaGames 7 ай бұрын
Not bad implementation, but too coupled and uses reflection, which you don't need at all. You already have all the code in there to make it useful, you need more abstraction to make the pattern shine
@malawann
@malawann 7 ай бұрын
Can you explain how to support the removal of buffs in this system? Which means to restore the health value to the state before the buff was added.
@git-amend
@git-amend 7 ай бұрын
Removable or temporary buffs is a bit too complex to describe in comments, but is a good subject for a future video. There are many ways you could accomplish this - one idea might be to allow the Visitor to add a Decorator which affects a particular component and has a limited lifespan. If you just wanted to restore the state of a component to a previous state, check out my video on the Memento pattern.
@malawann
@malawann 7 ай бұрын
@@git-amendThanks for answering. Can't wait for the future video.
@StephenBuergler
@StephenBuergler 8 ай бұрын
As a java dev there were always three C# features that I thought were really nice.. linq query syntax, structs, and dynamic variables. I thought that C# people never really needed to use the visitor pattern because they can use dynamic variables instead. Is that true?
@git-amend
@git-amend 8 ай бұрын
Yes, I know what you mean - and it would be great if we could use dynamic. However, the dynamic keyword in C# is not supported by Unity's IL2CPP scripting backend because it requires Just-In-Time (JIT) compilation, which IL2CPP, being an Ahead-Of-Time (AOT) compiler, doesn't allow. Check out: docs.unity3d.com/2023.3/Documentation/Manual/ScriptingRestrictions.html
@niuage
@niuage 7 ай бұрын
This channel is criminally underrated…
@git-amend
@git-amend 7 ай бұрын
Starting to grow a bit now!
@niuage
@niuage 7 ай бұрын
@@git-amend Cool :) Yea I bet if you keep it up it's only gonna go up faster and faster :)
@melpeslier
@melpeslier 7 ай бұрын
Maybe you could have explained the visitor pattern and what you were diving in before
@evggg
@evggg 5 ай бұрын
What if Pickup object in OnTriggerEnter receives another component with IVisitable interface. Not our Hero, but HealthComponent directly?
@git-amend
@git-amend 5 ай бұрын
Good question. The order of components retrieved when using GetComponents is always the order they are in the inspector, so in this example Hero will always be the first one retrieved. You could strictly enforce that by introducing another interface or putting the components onto children if you wanted to. If you had it setup so the HealthComponent is the one retrieved, it will only visit that component.
@ethanwasme4307
@ethanwasme4307 8 ай бұрын
unity devs are spoilt 😢😢
@nanaschi
@nanaschi 8 ай бұрын
Maybe next time MVP/ MVC patterns could be explained? It would be great!
@git-amend
@git-amend 8 ай бұрын
Great idea, I'll put that on the to-do list. I'm actually building a new feature using MVC at work this week.
@nanaschi
@nanaschi 8 ай бұрын
@@git-amend Cool to hear it coincided with your current workflow. From my perspective, I'd like to see examples of real game scenarios of using these patterns rather than just UI IMHO
@clamhammer2463
@clamhammer2463 7 ай бұрын
@@nanaschi The mvc pattern would be overkill for just a UI. that is only 1 component of the 3 withing MVC, v=view
@user-ju3vb6xc4u
@user-ju3vb6xc4u 2 ай бұрын
I love your channel I just don't understand what problem the Visitor pattern is trying to solve.
@git-amend
@git-amend 2 ай бұрын
The main purpose of the Visitor is extending the functionality of a class hierarchy without modifying the classes themselves. Maybe checkout my more recent video about Stats and Modifiers for another example.
@captainnoyaux
@captainnoyaux 7 ай бұрын
what do you use for the voice ? Seems like AI but it sounds OK
@git-amend
@git-amend 7 ай бұрын
That's my own voice, I don't use a voiceover.
@captainnoyaux
@captainnoyaux 7 ай бұрын
@@git-amend whatttt ? XD it sounds like AI to me, my bad !
@ranejeb
@ranejeb 7 ай бұрын
What's the reasoning behind using reflective visitor? I can imagine some real commercial projects to use visitor that would have logic complex enough that you wouldn't really want to have one class to contain all Visit(HealthComponent), Visit(ManaComponent), etc. Why can't we use generic types instead to both 1) get read of slow reflection 2) handle one particular case of visiting at a time in a separate class
@git-amend
@git-amend 7 ай бұрын
The flexibility of a reflective visitor is useful in systems where new component types might be added frequently or where the range of component types is extensive. However, if performance is critical and the types you're dealing with are known and relatively stable, using generic types might be the better option. The choice between these two approaches often boils down to a trade-off between flexibility and performance.
@r1pfake521
@r1pfake521 6 күн бұрын
The "reflectiv visitor" implementation defeats the whole purpose of the visitor pattern and it isn't even a real visitor pattern implementation anymore. In a real commercial project the visitor pattern is use if you want ALL the logic in one visitor class, you want to implement all these visit methods for every type in one visitor class, that's the only reason to use the pattern. His example shows a wrong use case of the pattern which lead him to belive that the "reflectiv visitor" was a good idea, but it isn't.
@r1pfake521
@r1pfake521 6 күн бұрын
Btw the visitor pattern is use very rarely, not because it is too complex or anything but because modern programming languages have so many features like pattern matching and other stuff that you rarely need a actual visitor pattern aynmore.
@lucasrgoes
@lucasrgoes 8 ай бұрын
You could simplify the reflection by just doing a switch on the type: public void Visit(IVisitable visitable) { switch (visitable) { case HealthComponent health: Visit(health); break; case ManaComponent mana: Visit(mana); break; } }
@r1pfake521
@r1pfake521 6 күн бұрын
His example is bad and the reflection visitor isn't even a real visitor pattern implementation. Your example shows a use case for the real visitor pattern. So you can do a switch with the type checks, but what if you create a new component and forgot to add a case and visit method for it? There would be no compiler error or anything it would be skipped or you get a runtime error, depending on how you implement your switch. But with the correct implementation of the visitor pattern (a new visit method per type) you are forced to implement both, the correct visit method and to call it in the accept method of the newly added type, otherwise you would get a compile time error, this is the only benefit of using a (real) visitor pattern instead of a swich based type check. This is also a reason why it is rarely used anymore, because in most cases a switch type check, like yours, is "good enough" for 99% cases.
@civo4457
@civo4457 7 ай бұрын
a visitor...
@JacobNax
@JacobNax 7 ай бұрын
Toughest?? Have you tried DOTS my friend?
@Gurem
@Gurem 7 ай бұрын
isnt this just a Interactible system. Thank god its not something that'll have me refactoring Edit: spoke too soon.
@Sweenus987
@Sweenus987 7 ай бұрын
Interesting pattern but feels a little dirty for some reason
Learn to Build an Advanced Event Bus | Unity Architecture
13:27
Tom & Jerry !! 😂😂
00:59
Tibo InShape
Рет қаралды 58 МЛН
⬅️🤔➡️
00:31
Celine Dept
Рет қаралды 51 МЛН
He sees meat everywhere 😄🥩
00:11
AngLova
Рет қаралды 9 МЛН
20 Advanced Coding Tips For Big Unity Projects
22:23
Tesseract
Рет қаралды 161 М.
The 6 Design Patterns game devs need?
24:20
Jason Weimann
Рет қаралды 361 М.
Learning C# In A Week... Otherwise I Fail University
9:04
8 Design Patterns | Prime Reacts
22:10
ThePrimeTime
Рет қаралды 388 М.
Why Stairs Suck in Games... and why they don't have to
11:24
Nick Maltbie
Рет қаралды 1,5 МЛН
How to do MORE with the Observer Pattern
13:09
git-amend
Рет қаралды 9 М.
SIMPLE Tip For Better Unity Game Architecture
12:57
git-amend
Рет қаралды 26 М.
Change Behaviors with the Strategy Pattern - Unity and C#
8:07
One Wheel Studio
Рет қаралды 38 М.