You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
335 lines
11 KiB
335 lines
11 KiB
|
3 weeks ago
|
//************************
|
||
|
|
// Create By 流云
|
||
|
|
// Time: 2025.01.15
|
||
|
|
// Desc: 设置图片资源组件
|
||
|
|
//************************
|
||
|
|
import { size, Size, Texture2D, UITransform, Vec2 } from 'cc';
|
||
|
|
import { AssetManager } from 'cc';
|
||
|
|
import { ImageAsset } from 'cc';
|
||
|
|
import { _decorator, assetManager, Component, Node, Sprite, SpriteFrame } from 'cc';
|
||
|
|
import MTools from '../tools/MTools';
|
||
|
|
import { BundleLifecycle } from '../module/res/BundleLifecycle';
|
||
|
|
const { ccclass, property, executeInEditMode } = _decorator;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 设置图片资源组件
|
||
|
|
*/
|
||
|
|
@ccclass('SpriteLoad')
|
||
|
|
//@executeInEditMode
|
||
|
|
export class SpriteLoad extends Component {
|
||
|
|
|
||
|
|
@property
|
||
|
|
private bundleName: string = "icons";
|
||
|
|
|
||
|
|
@property({ type: Node })
|
||
|
|
private SpriteNode: Node = null;
|
||
|
|
|
||
|
|
@property
|
||
|
|
private AdaptSize: boolean = false;
|
||
|
|
|
||
|
|
spComp: Sprite = null;
|
||
|
|
path: string = "";
|
||
|
|
|
||
|
|
private _loadedRes = null;
|
||
|
|
private _initSize: Size = null;
|
||
|
|
private _curentPath = "";
|
||
|
|
/** 本地/Bundle 加载世代:节点回池或再次 setSprite 时递增,作废过期异步回调 */
|
||
|
|
private _localGen = 0;
|
||
|
|
/** 远程加载世代,错开列表复用时的过期回调 */
|
||
|
|
private _remoteGen = 0;
|
||
|
|
/** 当前远程加载结束回调 */
|
||
|
|
private _remoteCb: ((ok: boolean) => void) | null = null;
|
||
|
|
/** 远程图地址;onEnable 按此重拉 */
|
||
|
|
private _remoteUrl = '';
|
||
|
|
|
||
|
|
onLoad() {
|
||
|
|
this._initSize = this.node.getComponent(UITransform).contentSize.clone();
|
||
|
|
if (this.SpriteNode) {
|
||
|
|
this.spComp = this.SpriteNode.getComponent(Sprite);
|
||
|
|
} else {
|
||
|
|
this.SpriteNode = this.node;
|
||
|
|
this.spComp = this.getComponent(Sprite);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected onEnable(): void {
|
||
|
|
if (this.spComp?.isValid) this.spComp.enabled = true;
|
||
|
|
if (this._remoteUrl) {
|
||
|
|
this._startRemote(this._remoteUrl, null);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this.path != "") {
|
||
|
|
this.loadSprite();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected onDisable(): void {
|
||
|
|
// 临时隐藏只释放引用,不清 path,否则再显示时 onEnable 无法重载
|
||
|
|
this._localGen++;
|
||
|
|
this.releaseLoaded();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected onDestroy(): void {
|
||
|
|
this._localGen++;
|
||
|
|
this.clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 释放当前已加载 SpriteFrame,保留 path。
|
||
|
|
* 须先摘掉 Sprite 引用再 decRef:否则最后一引用销毁后 uv 已空,Batcher 仍 updateUVs 会读 null[0]。
|
||
|
|
*/
|
||
|
|
private releaseLoaded() {
|
||
|
|
const sp = this.spComp;
|
||
|
|
if (sp?.isValid) {
|
||
|
|
sp.enabled = false;
|
||
|
|
if (sp.spriteFrame) sp.spriteFrame = null;
|
||
|
|
}
|
||
|
|
if (this._loadedRes) {
|
||
|
|
this._loadedRes.decRef();
|
||
|
|
this._loadedRes = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 清除图片(含 path,用于销毁或主动清空)
|
||
|
|
*/
|
||
|
|
clear() {
|
||
|
|
this.releaseLoaded();
|
||
|
|
this._curentPath = "";
|
||
|
|
this.path = "";
|
||
|
|
this._remoteUrl = "";
|
||
|
|
this._remoteGen++;
|
||
|
|
this._remoteCb = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 设置图片
|
||
|
|
* @param path 图片路径(传空则清除当前的图片)
|
||
|
|
* @param bundleName 资源包名
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
setSprite(path: string, bundleName: string = "", callback = null) {
|
||
|
|
this._remoteUrl = '';
|
||
|
|
this._remoteGen++;
|
||
|
|
this._localGen++;
|
||
|
|
this.ensureSprite();
|
||
|
|
if (this.spComp == null) {
|
||
|
|
console.error('[SpriteLoad] setSprite 缺少 Sprite', this.node?.name, path);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this._initSize == null) {
|
||
|
|
this._initSize = this.node.getComponent(UITransform).contentSize.clone();
|
||
|
|
}
|
||
|
|
if (bundleName != "") {
|
||
|
|
this.bundleName = bundleName;
|
||
|
|
}
|
||
|
|
this.path = path;
|
||
|
|
|
||
|
|
let name = path.split("/").pop();
|
||
|
|
|
||
|
|
if (this.bundleName == "" || this.path == "") {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// this.spComp.spriteFrame = gg.res.getRes(name, this.path, this.bundleName, SpriteFrame);
|
||
|
|
// return;
|
||
|
|
|
||
|
|
this.path += "/spriteFrame";
|
||
|
|
|
||
|
|
if (this._curentPath != "" && this._curentPath == this.path && this._loadedRes && this.spComp.spriteFrame) return;
|
||
|
|
|
||
|
|
//this.clear();
|
||
|
|
// this.scheduleOnce(() => {
|
||
|
|
// this.loadSprite(callback);
|
||
|
|
// }, 0.1);
|
||
|
|
this.loadSprite(callback);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 加载远程图片
|
||
|
|
* @param url 远程 url
|
||
|
|
* @param callback 结束回调,成功 true
|
||
|
|
*/
|
||
|
|
loadRemote(url: string, callback: ((ok: boolean) => void) | null = null): void {
|
||
|
|
this.path = '';
|
||
|
|
this._curentPath = '';
|
||
|
|
this._remoteUrl = url;
|
||
|
|
this._startRemote(url, callback);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 发起远程加载(不改 _remoteUrl,供 onEnable 重拉)。
|
||
|
|
* @param url 地址
|
||
|
|
* @param callback 结束回调
|
||
|
|
*/
|
||
|
|
private _startRemote(url: string, callback: ((ok: boolean) => void) | null): void {
|
||
|
|
this.ensureSprite();
|
||
|
|
if (this.spComp == null) {
|
||
|
|
console.error('[SpriteLoad] loadRemote 缺少 Sprite', this.node?.name, url);
|
||
|
|
if (callback) callback(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this._remoteCb = callback;
|
||
|
|
const gen = ++this._remoteGen;
|
||
|
|
if (this.hasImageExtension(url)) {
|
||
|
|
this.loadTexture(url, gen);
|
||
|
|
} else {
|
||
|
|
this.loadTextureExt(url, gen);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 父节点未激活时 onLoad 未跑,补齐 Sprite */
|
||
|
|
private ensureSprite(): void {
|
||
|
|
if (this.spComp != null) return;
|
||
|
|
if (this.SpriteNode) {
|
||
|
|
this.spComp = this.SpriteNode.getComponent(Sprite);
|
||
|
|
} else {
|
||
|
|
this.SpriteNode = this.node;
|
||
|
|
this.spComp = this.getComponent(Sprite);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private loadSprite(callback = null) {
|
||
|
|
const gen = ++this._localGen;
|
||
|
|
this.loadBundle((bundle: AssetManager.Bundle | null) => {
|
||
|
|
if (gen !== this._localGen || !this.isValid) {
|
||
|
|
if (callback) callback(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!bundle) {
|
||
|
|
console.error('[SpriteLoad] bundle 不可用', this.bundleName, this.path);
|
||
|
|
if (callback) callback(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let res = bundle.get(this.path, SpriteFrame);
|
||
|
|
if (res) {
|
||
|
|
if (!res.isValid) {
|
||
|
|
console.error('[SpriteLoad] 缓存 SpriteFrame 已失效', this.bundleName, this.path);
|
||
|
|
if (callback) callback(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this.node && this.node.isValid && this.spComp) {
|
||
|
|
if (this._loadedRes === res && this.spComp.spriteFrame === res) {
|
||
|
|
this.adoptSize();
|
||
|
|
if (callback) callback(true);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.releaseLoaded();
|
||
|
|
this.spComp.enabled = true;
|
||
|
|
this.spComp.spriteFrame = res;
|
||
|
|
this._curentPath = this.path;
|
||
|
|
this._loadedRes = res;
|
||
|
|
res.addRef();
|
||
|
|
}
|
||
|
|
this.adoptSize();
|
||
|
|
if (callback) callback(true);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
bundle.load(this.path, SpriteFrame, (err, sp) => {
|
||
|
|
// 回池/再次 setSprite 后的过期回调不得写回,否则 decRef 后 Batcher 仍读到空纹理
|
||
|
|
if (gen !== this._localGen || !this.isValid) {
|
||
|
|
if (callback) callback(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (err || !sp) {
|
||
|
|
console.error('[SpriteLoad] sprite 加载失败', this.bundleName, this.path, String(err));
|
||
|
|
if (callback) callback(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (this.node && this.node.isValid && this.spComp) {
|
||
|
|
this.releaseLoaded();
|
||
|
|
this.spComp.enabled = true;
|
||
|
|
this.spComp.spriteFrame = sp;
|
||
|
|
this._curentPath = this.path;
|
||
|
|
this._loadedRes = sp;
|
||
|
|
this._loadedRes.addRef();
|
||
|
|
}
|
||
|
|
if (callback) {
|
||
|
|
callback(true);
|
||
|
|
}
|
||
|
|
this.adoptSize();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private loadBundle(callback: (bundle: AssetManager.Bundle | null) => void) {
|
||
|
|
if (!this.bundleName) {
|
||
|
|
console.error('[SpriteLoad] bundleName 为空', this.node?.name);
|
||
|
|
callback(null);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
void BundleLifecycle.getInstance().ensureLoaded(this.bundleName).then((bundle) => {
|
||
|
|
if (!bundle) {
|
||
|
|
console.error('[SpriteLoad] bundle 加载失败', this.bundleName);
|
||
|
|
}
|
||
|
|
callback(bundle);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private loadTexture(url: string, gen: number): void {
|
||
|
|
assetManager.loadRemote<ImageAsset>(url, (err, imageAsset) => {
|
||
|
|
this.applyRemote(gen, err, imageAsset, url);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 远程 url 无后缀时须指定类型 */
|
||
|
|
private loadTextureExt(url: string, gen: number, ext = '.png'): void {
|
||
|
|
assetManager.loadRemote<ImageAsset>(url, { ext }, (err, imageAsset) => {
|
||
|
|
this.applyRemote(gen, err, imageAsset, url);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 远程图回写 Sprite,忽略过期请求 */
|
||
|
|
private applyRemote(gen: number, err: Error | null, imageAsset: ImageAsset, url: string): void {
|
||
|
|
if (gen !== this._remoteGen) return;
|
||
|
|
const cb = this._remoteCb;
|
||
|
|
this._remoteCb = null;
|
||
|
|
if (err || !imageAsset) {
|
||
|
|
console.error('[SpriteLoad] 远程图加载失败', url, String(err));
|
||
|
|
if (cb) cb(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!this.node?.isValid || !this.spComp?.isValid) {
|
||
|
|
console.error('[SpriteLoad] 远程图回调时节点已销毁', url);
|
||
|
|
if (cb) cb(false);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.releaseLoaded();
|
||
|
|
const spriteFrame = new SpriteFrame();
|
||
|
|
const texture = new Texture2D();
|
||
|
|
texture.image = imageAsset;
|
||
|
|
spriteFrame.texture = texture;
|
||
|
|
spriteFrame.addRef();
|
||
|
|
this._loadedRes = spriteFrame;
|
||
|
|
this.spComp.enabled = true;
|
||
|
|
this.spComp.spriteFrame = spriteFrame;
|
||
|
|
if (cb) cb(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 判断图片 url 路径是否带后缀(忽略 query)
|
||
|
|
* @param url 远程地址
|
||
|
|
*/
|
||
|
|
private hasImageExtension(url: string): boolean {
|
||
|
|
const path = url.split('?')[0].split('#')[0];
|
||
|
|
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private adoptSize() {
|
||
|
|
if (this.spComp == null) return;
|
||
|
|
if (this.spComp.spriteFrame == null) return;
|
||
|
|
let spriteFrame = this.spComp.spriteFrame;
|
||
|
|
let width = spriteFrame.originalSize.width;
|
||
|
|
let height = spriteFrame.originalSize.height;
|
||
|
|
if (this.AdaptSize) {
|
||
|
|
let s = MTools.adaptSize(this._initSize.width, this._initSize.height, width, height);
|
||
|
|
this.node.getComponent(UITransform).contentSize = size(s.x, s.y);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|