Unity: // @serializeField applies to all fields of class

Heyo,

when adding // @serializeField to a field all other private fields of the class get translated to c# as well and decorated with [SerializeField].

It even adds the attribute to the commented-out fields where translation went wrong, e.g.:

[UnityEngine.SerializeField]
// private MyClass | null @_myClassInstance; → Could not resolve C# type
[UnityEngine.SerializeField]
private bool @foo;
...

which then results in error CS0579: Duplicate 'UnityEngine.SerializeField' attribute.

Side note: Please consider unifying the command naming, just add aliases. I always have trouble recalling which is which and there’s no intellisense helping me :face_with_peeking_eye:

const exportNextClassCommand = "@generate-component";
const dontExportNextClassCommand = "@dont-generate-component";
const serializeCommand = "@serializeField";
const dontSerializeCommand = "@nonSerialized";

Hi krizzl0r, thanks for the detailed report and feedback!

The issue you’re seeing—where using // @serializeField ends up tagging all private fields (including commented‐out ones) with [SerializeField] so that you get a duplicate attribute error in the generated C#—seems to be caused by the parser not properly filtering out fields that shouldn’t be serialized. In other words, the translation process is applying the decoration to every field it finds (even those we’d expect to be skipped, like commented out ones), which is why you’re seeing error CS0579.

A couple of workarounds you might consider are:

‱ Explicitly mark fields you do not want serialized with the exclusion command (// @nonSerialized) so they’re filtered out during translation.
‱ Remove or temporarily delete commented-out code from the file if it’s not meant to be processed.

Regarding your side note about unifying the command names (like @generate-component, @dont-generate-component, @serializeField, and @nonSerialized), this feedback is very valuable. We realize that remembering which command to use can be a challenge, and adding aliases or enhanced intellisense support would indeed help streamline the workflow. This is something we’ll look into for future updates.

If you’re not already on the latest Needle Engine version, please consider updating or even creating a minimal reproducible example on StackBlitz via Needle-engine - Vite Template (duplicated) - StackBlitz. Also, if you’re using Unity integration, using “Copy Project Info Into Clipboard” and filing a bug report might speed up resolution.

Hope this helps, and thanks again for your input! Let me know if you have further questions or need additional details.

1 replies left - Become PRO for more replies.
thread_qioStzacoBDvmCtCYdqmA8lt
Reply to this post directly if you need further help (bottom right button)

Could you share your ts code where this happens? Thanks

Default case

export class ComponentCompilerBug extends Behaviour {
    @serializable(Object3D)
    publicObject?: Object3D;

    @serializable(Object3D)
    private _privateObject?: Object3D;

    private _privateBool: boolean = true;
}
public partial class ComponentCompilerBug : UnityEngine.MonoBehaviour
{
	public UnityEngine.GameObject @publicObject;
}

As expected only the public field is translated. (One could argue _privateObject should be translated too as it is decorated with @serializable() though)

Failed case with decorator

export class ComponentCompilerBug extends Behaviour {
    @serializable(Object3D)
    publicObject?: Object3D;

    // @serializeField
    @serializable(Object3D)
    private _privateObject?: Object3D;

    private _privateBool: boolean = true;
}
public partial class ComponentCompilerBug : UnityEngine.MonoBehaviour
{
	public UnityEngine.GameObject @publicObject;
	[UnityEngine.SerializeField]
	private UnityEngine.GameObject @_privateObject;
	[UnityEngine.SerializeField]
	private bool @_privateBool = true;
}

Now every field is translated even though only _privateObject has the decorator.

Bonus special case
export class HasNoCSharpCounterpart {}

export class ComponentCompilerBug extends Behaviour {
    @serializable(Object3D)
    publicObject?: Object3D;
    
    // @serializeField
    @serializable(Object3D)
    private _privateObject?: Object3D;

    private _privateBool: boolean = true;

    private _privateRef: HasNoCSharpCounterpart | null = null;
}
public partial class ComponentCompilerBug : UnityEngine.MonoBehaviour
{
	public UnityEngine.GameObject @publicObject;
	[UnityEngine.SerializeField]
	private UnityEngine.GameObject @_privateObject;
	[UnityEngine.SerializeField]
	private bool @_privateBool = true;
	[UnityEngine.SerializeField]
	// private HasNoCSharpCounterpart | null @_privateRef; → Could not resolve C# type
}

Just a side effect of this bug: Here it adds the attribute on a automatically commented out field which leads to compiler errors.

Thanks for the details- will look at it

Hi, the issue will be fixed with the next update (4.5.8) of the Unity package (which will update the compiler package to 1.12). This will include some of your other suggestions e.g. // @type object will serialize non-component classes

I’ve added your examples to the compiler tests, thanks for that.

Yay, good job! Unified aliases for the decorators as well? :face_with_peeking_eye:

By that you mean the annotations using inconsistent casing, right?
e.g @dont-generate-component and @serializeField

Yep right.

Just verified and it now works like a charm, thanks.

1 Like