This commit is contained in:
liuxiaobo 2025-07-01 00:00:42 +08:00
parent a81c5bd272
commit dec94497d2
19 changed files with 7676 additions and 0 deletions

View File

@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://docs.cocos.com/creator/manual/en/scripting/setup.html#custom-script-template

View File

@ -0,0 +1,99 @@
type EventCallback = (data?: any) => void
class CallbackAndContext {
public callback: EventCallback = null
public context: any = null
public constructor(callback: EventCallback, context: any) {
this.callback = callback
this.context = context
}
}
export class EventManager {
private static instance: EventManager = null
private listeners: { [eventName: string]: Array<CallbackAndContext> }
public static Instance(): EventManager {
if (!this.instance) {
this.instance = new EventManager()
}
return this.instance
}
public On(eventName: string, callback: EventCallback, context?: any): void {
if (!this.listeners[eventName]) {
this.listeners[eventName] = []
}
if (!callback) return
if (this.IsExsit(eventName, callback, context)) return
this.listeners[eventName].push(new CallbackAndContext(callback, context))
}
private IsExsit(eventName: string, callback: EventCallback, context?: any): boolean {
const listeners = this.listeners[eventName]
if (!listeners) return false
for (let i = listeners.length - 1; i >= 0; i--) {
const listener = listeners[i]
if (listener.callback === callback && (!context || listener.context === context)) {
return true
}
}
return false
}
// 取消事件监听
public Off(eventName: string, callback?: EventCallback, context?: any): void {
const listeners = this.listeners[eventName]
if (!listeners) return
// 如果没有提供回调,则移除该事件的所有监听
if (!callback) {
delete this.listeners[eventName]
return
}
for (let i = listeners.length - 1; i >= 0; i--) {
const listener = listeners[i]
if (listener.callback === callback && (!context || listener.context === context)) {
listeners.splice(i, 1)
}
}
if (listeners.length === 0) {
delete this.listeners[eventName]
}
}
// 触发事件
public Emit(eventName: string, data?: any): void {
const listener = this.listeners[eventName]
if (!listener) return
// 创建副本,防止回调中修改原始数组
const copy = [...listener]
copy.forEach((listener) => {
try {
listener.callback.call(listener.context, data)
} catch (error) {
console.error(`事件回调错误,事件名: ${eventName}:`, error)
}
})
}
// 一次性监听(触发后自动移除)
public Once(eventName: string, callback: EventCallback, context?: any): void {
const wrapper = (data: any) => {
callback.call(context, data)
this.Off(eventName, wrapper, context)
}
this.On(eventName, wrapper, context)
}
// 移除所有事件监听
public ClearAll(): void {
this.listeners = {}
}
// 获取某个事件的监听者数量
public ListenerCount(eventName: string): number {
return this.listeners[eventName]?.length || 0
}
}

6
assets/pkg/mvc/Model.ts Normal file
View File

@ -0,0 +1,6 @@
import { EventManager } from './EventManager'
export class Model {
protected eventMgr: EventManager = EventManager.Instance()
protected data: any = {}
}

View File

@ -0,0 +1,32 @@
import { EventManager } from './EventManager'
// 装饰器工厂函数
export function Ob(eventName?: string) {
return function (target: any, propertyKey: string) {
// 存储原始值的弱映射
const privateMap = new WeakMap()
// 定义getter/setter
Object.defineProperty(target, propertyKey, {
get: function () {
return privateMap.get(this)?.value
},
set: function (newValue) {
const oldValue = privateMap.get(this)?.value
if (oldValue == newValue) return
privateMap.set(this, { value: newValue })
// 触发事件
const eventMgr = EventManager.Instance()
const finalEventName = eventName || `${target.constructor.name.toLowerCase()}:${propertyKey}`
const data = { property: propertyKey, oldValue, newValue }
eventMgr.Emit(finalEventName, data)
// 触发通用事件
const commEventName = `${target.constructor.name.toLowerCase()}:property`
const commData = { property: propertyKey, oldValue, newValue }
eventMgr.Emit(commEventName, commData)
},
enumerable: true,
configurable: true,
})
}
}

View File

@ -0,0 +1,35 @@
export class ObProperty<T> {
private value:T; // 值
private onChanges:((newValue:T, oldValue:T)=>void)[]=[];
constructor(initValue:T){
this.value = initValue;
}
public get Value():T{ return this.value;}
public set Value(newValue:T) {
if(newValue != this.value){
const oldValue = this.value;
this.value = newValue;
this.onChanges.forEach(callback=>callback(newValue, oldValue));
}
}
// 注册变更回调
public RegisterOnChange(callback:(newValue:T, oldValue:T)=>void):void{
this.onChanges.push(callback);
}
// 移除变更回调
public UnregisterOnChange(callback:(newValue:T, oldValue:T)=>void):void {
const idx = this.onChanges.indexOf(callback);
if (idx !== -1){
this.onChanges.splice(idx, 1);
}
}
}

File diff suppressed because it is too large Load Diff

552
assets/scenes/main.scene Normal file
View File

@ -0,0 +1,552 @@
[
{
"__type__": "cc.SceneAsset",
"_name": "main",
"_objFlags": 0,
"__editorExtras__": {},
"_native": "",
"scene": {
"__id__": 1
}
},
{
"__type__": "cc.Scene",
"_name": "main",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": null,
"_children": [
{
"__id__": 2
}
],
"_active": true,
"_components": [],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"autoReleaseAssets": false,
"_globals": {
"__id__": 11
},
"_id": "b4d997b6-dfae-4f49-bd9a-7758657287ae"
},
{
"__type__": "cc.Node",
"_name": "Canvas",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 1
},
"_children": [
{
"__id__": 3
},
{
"__id__": 5
}
],
"_active": true,
"_components": [
{
"__id__": 8
},
{
"__id__": 9
},
{
"__id__": 10
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 640,
"y": 360.00000000000006,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "beI88Z2HpFELqR4T5EMHpg"
},
{
"__type__": "cc.Node",
"_name": "Camera",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 4
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 1000
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 1073741824,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "ebFwiq8gBFaYpqYbdoDODe"
},
{
"__type__": "cc.Camera",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 3
},
"_enabled": true,
"__prefab": null,
"_projection": 0,
"_priority": 0,
"_fov": 45,
"_fovAxis": 0,
"_orthoHeight": 414.53807106598987,
"_near": 0,
"_far": 2000,
"_color": {
"__type__": "cc.Color",
"r": 0,
"g": 0,
"b": 0,
"a": 255
},
"_depth": 1,
"_stencil": 0,
"_clearFlags": 7,
"_rect": {
"__type__": "cc.Rect",
"x": 0,
"y": 0,
"width": 1,
"height": 1
},
"_aperture": 19,
"_shutter": 7,
"_iso": 0,
"_screenScale": 1,
"_visibility": 1108344832,
"_targetTexture": null,
"_postProcess": null,
"_usePostProcess": false,
"_cameraType": -1,
"_trackingType": 0,
"_id": "63WIch3o5BEYRlXzTT0oWc"
},
{
"__type__": "cc.Node",
"_name": "LoginPanel",
"_objFlags": 0,
"__editorExtras__": {},
"_parent": {
"__id__": 2
},
"_children": [],
"_active": true,
"_components": [
{
"__id__": 6
},
{
"__id__": 7
}
],
"_prefab": null,
"_lpos": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_lrot": {
"__type__": "cc.Quat",
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"_lscale": {
"__type__": "cc.Vec3",
"x": 1,
"y": 1,
"z": 1
},
"_mobility": 0,
"_layer": 33554432,
"_euler": {
"__type__": "cc.Vec3",
"x": 0,
"y": 0,
"z": 0
},
"_id": "ceKLLLNdFJJZHvPReMV47t"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 5
},
"_enabled": true,
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 1280,
"height": 720
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": "55Wwv5/nFGzpwhjoCwHqDp"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 5
},
"_enabled": true,
"__prefab": null,
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 0,
"_bottom": 0,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 100,
"_originalHeight": 100,
"_alignMode": 2,
"_lockFlags": 0,
"_id": "58VGFS6qRA0oDNxuq2ABP2"
},
{
"__type__": "cc.UITransform",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": null,
"_contentSize": {
"__type__": "cc.Size",
"width": 1280,
"height": 720
},
"_anchorPoint": {
"__type__": "cc.Vec2",
"x": 0.5,
"y": 0.5
},
"_id": "d6rUX5yfhMlKoWX2bSbawx"
},
{
"__type__": "cc.Canvas",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": null,
"_cameraComponent": {
"__id__": 4
},
"_alignCanvasWithScreen": true,
"_id": "12O/ljcVlEqLmVm3U2gEOQ"
},
{
"__type__": "cc.Widget",
"_name": "",
"_objFlags": 0,
"__editorExtras__": {},
"node": {
"__id__": 2
},
"_enabled": true,
"__prefab": null,
"_alignFlags": 45,
"_target": null,
"_left": 0,
"_right": 0,
"_top": 5.684341886080802e-14,
"_bottom": 5.684341886080802e-14,
"_horizontalCenter": 0,
"_verticalCenter": 0,
"_isAbsLeft": true,
"_isAbsRight": true,
"_isAbsTop": true,
"_isAbsBottom": true,
"_isAbsHorizontalCenter": true,
"_isAbsVerticalCenter": true,
"_originalWidth": 0,
"_originalHeight": 0,
"_alignMode": 2,
"_lockFlags": 0,
"_id": "c5V1EV8IpMtrIvY1OE9t2u"
},
{
"__type__": "cc.SceneGlobals",
"ambient": {
"__id__": 12
},
"shadows": {
"__id__": 13
},
"_skybox": {
"__id__": 14
},
"fog": {
"__id__": 15
},
"octree": {
"__id__": 16
},
"skin": {
"__id__": 17
},
"lightProbeInfo": {
"__id__": 18
},
"postSettings": {
"__id__": 19
},
"bakedWithStationaryMainLight": false,
"bakedWithHighpLightmap": false
},
{
"__type__": "cc.AmbientInfo",
"_skyColorHDR": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0.520833125
},
"_skyColor": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0.520833125
},
"_skyIllumHDR": 20000,
"_skyIllum": 20000,
"_groundAlbedoHDR": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0
},
"_groundAlbedo": {
"__type__": "cc.Vec4",
"x": 0,
"y": 0,
"z": 0,
"w": 0
},
"_skyColorLDR": {
"__type__": "cc.Vec4",
"x": 0.2,
"y": 0.5,
"z": 0.8,
"w": 1
},
"_skyIllumLDR": 20000,
"_groundAlbedoLDR": {
"__type__": "cc.Vec4",
"x": 0.2,
"y": 0.2,
"z": 0.2,
"w": 1
}
},
{
"__type__": "cc.ShadowsInfo",
"_enabled": false,
"_type": 0,
"_normal": {
"__type__": "cc.Vec3",
"x": 0,
"y": 1,
"z": 0
},
"_distance": 0,
"_planeBias": 1,
"_shadowColor": {
"__type__": "cc.Color",
"r": 76,
"g": 76,
"b": 76,
"a": 255
},
"_maxReceived": 4,
"_size": {
"__type__": "cc.Vec2",
"x": 512,
"y": 512
}
},
{
"__type__": "cc.SkyboxInfo",
"_envLightingType": 0,
"_envmapHDR": null,
"_envmap": null,
"_envmapLDR": null,
"_diffuseMapHDR": null,
"_diffuseMapLDR": null,
"_enabled": false,
"_useHDR": true,
"_editableMaterial": null,
"_reflectionHDR": null,
"_reflectionLDR": null,
"_rotationAngle": 0
},
{
"__type__": "cc.FogInfo",
"_type": 0,
"_fogColor": {
"__type__": "cc.Color",
"r": 200,
"g": 200,
"b": 200,
"a": 255
},
"_enabled": false,
"_fogDensity": 0.3,
"_fogStart": 0.5,
"_fogEnd": 300,
"_fogAtten": 5,
"_fogTop": 1.5,
"_fogRange": 1.2,
"_accurate": false
},
{
"__type__": "cc.OctreeInfo",
"_enabled": false,
"_minPos": {
"__type__": "cc.Vec3",
"x": -1024,
"y": -1024,
"z": -1024
},
"_maxPos": {
"__type__": "cc.Vec3",
"x": 1024,
"y": 1024,
"z": 1024
},
"_depth": 8
},
{
"__type__": "cc.SkinInfo",
"_enabled": false,
"_blurRadius": 0.01,
"_sssIntensity": 3
},
{
"__type__": "cc.LightProbeInfo",
"_giScale": 1,
"_giSamples": 1024,
"_bounces": 2,
"_reduceRinging": 0,
"_showProbe": true,
"_showWireframe": true,
"_showConvex": false,
"_data": null,
"_lightProbeSphereVolume": 1
},
{
"__type__": "cc.PostSettingsInfo",
"_toneMappingType": 0
}
]

View File

@ -0,0 +1,6 @@
export class PrefabNames {
private static login: string = 'prefabs/LoginPanel'
public static get Login() {
return this.login
}
}

43
assets/scripts/main.ts Normal file
View File

@ -0,0 +1,43 @@
import { _decorator, Component, UITransform, Vec3 } from 'cc'
import { prefabOp } from './prefabOp'
import { PrefabNames } from './constant'
const { ccclass, property } = _decorator
import { sys, native } from 'cc'
@ccclass('main')
export class main extends Component {
private prefab: prefabOp = new prefabOp()
async start() {
let assetsPath = ''
if (sys.isNative) {
// 原生平台
assetsPath = native.fileUtils.getWritablePath() + '../assets'
} else {
// Web 平台
assetsPath = window.location.href.replace(/\/[^/]*$/, '') + '/assets'
}
console.log('Final Assets Path:', assetsPath)
const instance = await this.prefab.LoadAsync(PrefabNames.Login)
this.node.addChild(instance)
// 获取父节点和预制体的尺寸
const parentTransform = this.node.getComponent(UITransform)
const instanceTransform = instance.getComponent(UITransform)
if (parentTransform && instanceTransform) {
// 计算左下角坐标(考虑锚点)
const x = -parentTransform.width * (0.5 - parentTransform.anchorX) + instanceTransform.width * (0.5 - instanceTransform.anchorX)
const y = -parentTransform.height * (0.5 - parentTransform.anchorY) + instanceTransform.height * (0.5 - instanceTransform.anchorY)
console.log('Final Assets Path:', x, ' ', y)
instance.setPosition(new Vec3(x, y, 0))
instance.active = true
}
}
protected onDestroy(): void {
this.prefab.Release(PrefabNames.Login)
}
update(deltaTime: number) {}
}

View File

@ -0,0 +1,50 @@
import { _decorator, Node, resources, Prefab, instantiate } from 'cc'
const { ccclass } = _decorator
import { assetManager, Asset } from 'cc'
function loadResource<T extends Asset>(path: string, type: new () => T): Promise<T> {
return new Promise((resolve, reject) => {
assetManager.resources.load(path, type, (err: Error | null, asset: T) => {
err ? reject(err) : resolve(asset)
})
})
}
@ccclass('prefabOp')
export class prefabOp {
// 加载预制体 parent:父节点
public Load(path: string): Node {
let instance: Node = null
resources.load(path, Prefab, (err, prefab) => {
if (err) {
console.error(`加载预制体${path}失败:`, err)
return
}
try {
instance = instantiate(prefab)
} catch (err) {
console.error(`初始化预制体${path}失败:`, err)
}
})
return instance
}
// 同步加载版本
public async LoadAsync(path: string): Promise<Node> {
try {
const prefab = await loadResource<Prefab>(path, Prefab)
const instance = instantiate(prefab)
return instance
} catch (err) {
console.error(`加载预制体${path}失败:`, err)
}
}
// 释放预制体资源
public Release(path: string) {
resources.release(path, Prefab)
}
update(deltaTime: number) {}
}

View File

@ -0,0 +1,3 @@
{
"__type__": "cc.SpriteAtlas",
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

4424
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,5 +3,12 @@
"uuid": "76b48d58-b2ad-4a8f-9f01-f49e3d3f5d64",
"creator": {
"version": "3.8.6"
},
"devDependencies": {
"@types/jest": "^30.0.0",
"jest": "^30.0.3"
},
"scripts": {
"test": "jest"
}
}