Why PrimeTween doesn't use extension methods? #3
Replies: 3 comments 5 replies
-
With some things I completely agree, but I think some aspects are misrepresented. You have an example: enemy.DOKill(); For a person seeing this piece of code for the first time and having no previous experience with DOTween I agree that it is misleading. Then you show this: Tween.StopAll(onTarget: enemy); This is somewhat better than DOTween in terms of naming, but you did a little manipulation - no one writes argument names. In the real world this would look like this: Tween.StopAll(enemy); And this is also not good at describing what this method does. Why My take on this: PrimeTween.StopAllOnTarget(enemy); // or KillAllOnTarget Or even like this: enemy.AsTweenable().StopAllTweens(); // or KillAllTwins... just having fun at this point :) Extension methods can hurt readability.Tween.PositionY(transform, startValue: 10, endValue: 20, duration: 1); I can't remember the single time I needed to specify the startValue, that's why I didn't even know about existence of Builder Pattern in disguise.transform.DOMove(targetPos, duration)
.SetEase(Ease.OutQuad)
.SetLoops(cycles, LoopType.Yoyo)
.SetDelay(delay)
.SetUpdate(useUnscaledTime)
.SetLink(transform.gameObject)
.Play(); The Tween.Position(transform, targetPos, duration, Ease.OutQuad, cycles, CycleMode.Yoyo, delay, 0, useUnscaledTime); The above code looks pretty and convenient when you have properly named variables. Tween.Position(transform, new Vector3(0,1,0), 0.2f, Ease.OutQuad, 4, CycleMode.Yoyo, 0.3f, 0, true); Wish you luck in understanding this mess XD Now look at this: transform.DOMove(new Vector3(0,1,0), 0.2f)
.WithEase(Ease.OutQuad)
.WithLoop(4, LoopType.Yoyo)
.WithDelay(0.3f)
.WithUnscaledTime(true)
.WithLink(transform.gameObject)
.Play(); This approach is easier to read when magic values are used. And it's super easy to write and iterate, there is no requirement on argument order, and you can add a new "argument" anywhere you want in that chain. Code completion is also easier, all you have to do is type And a bit of my personal preference(or should I say personal frustration) - methods with too many arguments. I use Unity for more than 10 years, and every time I type |
Beta Was this translation helpful? Give feedback.
-
Static methods are faster because they doesn't have null comparison under the hood |
Beta Was this translation helpful? Give feedback.
-
Don't you think this is a long piece of code? I think that when a method is exposed with more than four variables, the readability and usability of the method is greatly reduced. In fact, in my opinion, dotween's builder mode is more readable and easier to use.
This is wrong. In fact, I remember unity having a lot of classes that use extension methods. I can't remember which ones, but I'm pretty sure the VisualElement in uitoolkit does. DOTween's extension method works well, but it is crucial that his methods maintain a uniform naming style, that is, the DO beginning. If you're afraid of the trickery of the extension method, I think you can also use a uniform naming style so that the first thing you see is that this is the PrimeTween method. I share STARasGAMES 'sentiments for the most part, and I hope you'll reconsider the usability of extension methods. |
Beta Was this translation helpful? Give feedback.
-
Why PrimeTween doesn't use extension methods?
What's an extension method, anyway?
Extension methods in C# is a syntactic sugar feature that allows one to 'add' new functionality to other classes without modifying their source code.
In reality, an extension method is just a static method that accepts the type you're 'extending' as a first parameter. But for the user of your extension method, it looks like a regular method of that type. Cool, huh? But are there any downsides?
I'll review the potential downsides of extension methods on the example of DOTween, but this article can be applied to extension methods in general.
Extension methods deceive.
The first downside comes directly from the advantage - extension methods 'pretend' that an object has the functionality when in reality it doesn't. When you type
transform.
, your IDE will suggest all third-party extension methods alongside built-in Unity methods. You may think you're using a native Unity method when in reality you may be using a third-party dependency.Let's assume you're making a shooter game and you see this code. Can you tell at the first glance what the code will do?
Will the code kill the enemy? Will it destroy its GameObject? Or maybe the enemy will kill the player? It turns out, it's a DOTween's extension method that will stop all tweens and delays running on the enemy's MonoBehaviour. Unexcepted, right?
With PrimeTween, you can always be confident in what the code actually does:
Extension methods hide complexity.
Let's consider an example:
Which line is more 'significant'? Or simply put, which method call do 'more stuff'? At first glance, both method calls have the same significance, they look similar and they should move the transform in some way.
In reality,
Translate()
will immediately move the object, whileDOMove()
does a lot of things under the hood: it will create an animation that will change the position every frame for the next second. The look of theDOMove()
method doesn't convey its complexity in comparison toTranslate()
.With PrimeTween, the
Tween.Position()
clearly indicates we're using an animation library:Extension methods can hurt readability.
Let's consider how extension methods can impact readability. The next piece of code reads naturally, like this: animate position from 10 to 20 in 1 second.
While this reads awkwardly: animate position to 20 in 1 second from 10.
Builder Pattern in disguise.
A lot of times, extension methods are used as a Builder pattern. Builder pattern has a lot of criticism on its own, but I will touch only on the aspect of how it affects API design.
In DOTween, when you type
tween.
in your IDE, you'll see a bunch of different methods that can be split into two different categories: 1) creation methods and 2) manipulation methods. All of these methods are in the same bucket, and only by studying the documentation and examples, you can understand to which group every one of them belongs.Here is an example. All of these methods don't make sense after the tween has started, they serve only as creation methods to start a tween:
In PrimeTween, all the above code is as simple as passing parameters to a method. And when you type
tween.
, you'll see only a small subset of manipulation methods.Or you can use TweenSettings if you don't like a long chain of parameters or wish to improve readability. You can also pass the TweenSettings structure to other methods, serialise it in Inspector, or share it between different tween types.
Unity doesn't use extension methods.
I wanted to make PrimeTween feel 'native' in Unity. I've never encountered an extension method in UnityEngine or any official UPM package. For example,
Mathf
class orVector3.Dot/Cross/Distance()
could've been implemented as extension methods, but they are not.Or
Physics.Raycast()
could've been an extension method. Imagine how 'cool' it would be to write something like this:But this is not the case. Unity uses good-old static
Physics.Raycast()
method that simply accepts parameters. And there is a reason for it - Unity is building a tool for millions of developers and not a project to show cool syntactic sugar tricks.Performance impact.
I put the performance in last place because users' code readability and maintainability are far more important in my opinion. The performance of extension methods in themselves is never a problem, because they are essentially static methods. But when extension methods are used as the Builder Pattern to apply parameters, this has a measurable impact on performance.
For example, in DOTween, starting an animation with all parameters adds an extra 4.47 ms (per 100,000 iterations) in comparison to the simplest animation. While in PrimeTween, passing all parameters to a static method adds just 0.96 ms. It will unlikely be noticeable in a real project, but it's a nice bonus to all previous benefits.
"But I still miss DOTween's extension methods".
We've been all using DOTween for many years and have an addiction to its extension methods, I get that.
PrimeTween comes with a simple interface adapter so you can use your favorite extension methods and get all the performance benefits of PrimeTween. PrimeTween doesn't conflict with DOTween and they can be used side-by-side in one project. After enabling the adapter, just type
using PrimeTween;
instead ofusing DG.Tweening;
at the top of your script and you're good to go.Conclusion.
Should you ditch extension methods altogether? Absolutely not. For example, LINQ is a good example of extension methods used correctly.
But when you write your own custom extension methods, consider how much value they add, taking into account all aforementioned readability costs. Typically, you write the code once, then you read it hundreds of times while you work on a project. So when in doubt, prefer code readability over the number of characters you have to type or how 'cool' the code may look.
Have something to add? Or disagree? You're welcome in the comments. Help me make PrimeTween better for the community.
Beta Was this translation helpful? Give feedback.
All reactions