Sometimes the link to a component breaks. Is it the component?

Hi all!

I use Cocos Creator version 3.8.6.

I have my own IconView component in my project, it is located in the following path:

assets/scripts/coreView/IconView.ts

I don’t know exactly under what conditions, but sometimes the link to this component becomes invalid. I just start the game and get an error that I’m trying to call a function on an object that is not assigned.

This doesn’t happen often, but it’s still unpleasant. For example, I’m working with some part of the UI, placing completely different things, assigning links, saving the prefab (usually I then close the prefab), launching the scene and getting an error (although I didn’t touch the IconView links manually). As far as I understand, this only happens if I open the prefab. I don’t think I’ve ever had links get messed up like that in prefabs that I haven’t opened recently. Afterwards, I have to open the prefab and assign the links again (in the inspector it shows as if I didn’t set anything, and not a missing object).

To be honest, I’m already tired of this. I noticed this since version 3.8.3.

I have many components in my project, but I only got this error with three of my components. Two of them are gone - only IconView remains.

What’s wrong with him?

import { Color, Component, Enum, Size, Sprite, SpriteFrame, UITransform, _decorator } from "cc";
const { ccclass, property } = _decorator;

export enum IconFillingMethod {
	filling,																	// May go out of area
	fitting																		// Will be included in the area
}
Enum(IconFillingMethod);

@ccclass('IconView')
export class IconView extends Component {
	@property({ type: UITransform })
    private areaUITransform: UITransform;
	@property({ type: UITransform })
    private iconUITransform: UITransform;
	@property({ type: Sprite })
    private icon: Sprite;
	@property({ type: IconFillingMethod })
    private fillingMethod: IconFillingMethod = IconFillingMethod.fitting;

	public SetIcon(sprite: SpriteFrame): void {
		this.icon.spriteFrame = sprite;
		this.UpdateSize();
	}

	public SetIconWithOriginalSize(sprite: SpriteFrame, scale: number = 1): void {
		this.areaUITransform.setContentSize(sprite.rect.width * scale, sprite.rect.height * scale);
		this.icon.spriteFrame = sprite;
		this.UpdateSize();
	}

	public GetIcon(): SpriteFrame {
		return this.icon.spriteFrame;
	}

	public GetIconSize(): Readonly<Size> {
		return this.iconUITransform.contentSize;
	}

	public SetIconColor(color: Readonly<Color>): void {
		this.icon.color = color;
	}

	public GetIconColor(): Readonly<Color> {
		return this.icon.color;
	}

	public SetGrayscale(isGrayscale: boolean): void {
		this.icon.grayscale = isGrayscale;
	}

	private UpdateSize(): void {
		let areaWidth = this.areaUITransform.contentSize.width;
		let areaHeight = this.areaUITransform.contentSize.height;
		let originalSpriteWidth = this.icon.spriteFrame.rect.width;
		let originalSpriteHeight = this.icon.spriteFrame.rect.height;

		switch(this.fillingMethod) {
			case IconFillingMethod.filling:
				this.CalcProportionalRectFillingSize(areaWidth, areaHeight, originalSpriteWidth, originalSpriteHeight);
				break;
			case IconFillingMethod.fitting:
				this.CalcProportionalRectFittingSize(areaWidth, areaHeight, originalSpriteWidth, originalSpriteHeight);
				break;
		}
	}

	private CalcProportionalRectFillingSize(fillableWidth: number, fillableHeight: number, spriteWidth: number, spriteHeight: number): void {
		let width = 0;
		let height = 0;
		
		let aspectRatio = spriteWidth / spriteHeight;
		
		width = fillableWidth;
		height = width / aspectRatio;
		
		if (height < fillableHeight) {
			height = fillableHeight;
			width = height * aspectRatio;
		}
		
		this.iconUITransform.setContentSize(width, height);
	}

	private CalcProportionalRectFittingSize(fillableWidth: number, fillableHeight: number, spriteWidth: number, spriteHeight: number): void {
		let width = 0;
		let height = 0;
		
		let aspectRatio = spriteWidth / spriteHeight;
		
		width = fillableWidth;
		height = width / aspectRatio;
		
		if (height > fillableHeight) {
			height = fillableHeight;
			width = height * aspectRatio;
		}
		
		this.iconUITransform.setContentSize(width, height);
	}
}

Important note. The object to which the IconView is attached is a prefab for me. It is located in another prefab, where I also specify it as a link. For example:

PauseWindow
|-...
|-TimerIcon
|-...

PauseWindow - a prefab that needs a reference to the IconView (I point it to the TimerIcon prefab).

TimerIcon - a prefab of IconView.

And so, sometimes, when I open the PauseWindow prefab, do something in it (but don’t touch either the TimerIcon or the link to it in PauseWindow), save and close the prefab, then when I launch the game in the browser I discover that the connection is broken.