Initializing something at its declaration:
Executes immediately as the object is constructed - both at runtime and in the editor, so you can assign sensible defaults when you add a new component, even without executing
Awake() in edit mode.
Limits you to expressions that don't reference other members of the object. (Since, by definition, the initializers run before the object is safely initialized!)
Supplying values in
Runs after the object is constructed, but still runs to completion before the object is returned from a call to
AddComponent(), and before
Start() or other messages can fire.
Start(), it's guaranteed to run only once for the lifetime of an object (whereas
OnEnable() will re-run every time the component is disabled and re-enabled). But unlike either, it will run even if the script is instantiated in an already-disabled state.
Allows you to vary the initialization of some fields based on the values of other fields or other context related to the parent game object, use intermediate temporary variables to compute the right initial values, or perform more complicated algorithms than you can easily fit in an initializer expression.
Does not execute in edit mode unless you add extra attributes to force it to do so
You also have another option,
Executes only in the editor, when you add a new component or reset the component in the Inspector. Does not run in the built executable (so make sure any results you want to carry over are in public or serialized fields that will be saved).
Can run complex computations like
Awake(), but since it's still doing all its work before build time, the only runtime cost is deserializing the saved results.
Or a related method
Executes only in the editor, after
Reset(), and whenever the developer makes a change to the component via the Inspector.
Lets you re-visit your initial values in light of new information, enforce custom constraints on configuration parameters, or log warnings and errors in case of mis-configuration.
So, boiling it down:
Does the initial value not depend on any other aspects of the object, and fit concisely in the space of the declaration?
-> Just initialize it when you declare it. No extra method to write, and it's obvious to a reader at a glance.
Does the value require more computation or relation between aspects of the object, but depend only on things we know at edit time?
OnValidate() to do the work ahead of time, so you can just deserialize the answer at runtime.
Does the value need to be revisited every time we disable / re-enable the component?
This can be handy if you're editing code while your game is running in the editor: all your objects will be disabled, serialized, swapped to the newly-compiled code, deserialized, and re-enabled, so you can use this to do any fix-ups to ensure the game can continue smoothly with the new code.
Does the value need to be initialized just once, before
Update(), but not necessarily before other scripts try to invoke our other member methods?
Do we need the value initialized, just once, but before anyone can possibly call any of our other methods?
The reason I recommend everything else before
Awake() is that if you have multiple scripts whose initialization depend on one another, you can run into annoying bugs where in editor the scripts each get awakened in the desired order, but in a build the depender gets awoken before the dependee. If most of your dependent initialization is happening in
Start(), you can fix this bug by moving just the dependee's setup to
Awake(). But if everything's in
Awake() already, then you have to try to kludge around it with script execution order controls or lazy/on-demand initialization.