新增mvvm示例
This commit is contained in:
9
extensions/cocos/cocos-ecs/assets/resources/prefabs.meta
Normal file
9
extensions/cocos/cocos-ecs/assets/resources/prefabs.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "2bf3ded8-4054-4d8f-a367-c76b21eaf538",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"ver": "1.1.50",
|
||||
"importer": "prefab",
|
||||
"imported": true,
|
||||
"uuid": "51a6e245-2983-4258-be9f-9e21378f7f9f",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"syncNodeName": "Panel_Node"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,22 +7,22 @@ const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass('BehaviorTreeComponent')
|
||||
export class BehaviorTreeComponent extends Component {
|
||||
|
||||
|
||||
@property
|
||||
behaviorTreeFile: string = '';
|
||||
|
||||
|
||||
@property
|
||||
autoStart: boolean = true;
|
||||
|
||||
|
||||
@property
|
||||
debugMode: boolean = false;
|
||||
|
||||
|
||||
@property
|
||||
showStatusUI: boolean = true;
|
||||
|
||||
|
||||
@property(Prefab)
|
||||
statusUIPrefab: Prefab | null = null;
|
||||
|
||||
|
||||
private behaviorTree: BehaviorTree<any> | null = null;
|
||||
private statusUI: MinerStatusUI | null = null;
|
||||
private blackboard: Blackboard | null = null;
|
||||
@@ -30,28 +30,28 @@ export class BehaviorTreeComponent extends Component {
|
||||
private eventRegistry: EventRegistry | null = null;
|
||||
private isLoaded: boolean = false;
|
||||
private isRunning: boolean = false;
|
||||
|
||||
|
||||
private actionStates: Map<string, {
|
||||
isExecuting: boolean;
|
||||
startTime: number;
|
||||
duration: number;
|
||||
}> = new Map();
|
||||
|
||||
|
||||
start() {
|
||||
if (this.autoStart && this.behaviorTreeFile) {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
|
||||
if (this.showStatusUI) {
|
||||
this.createStatusUI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async initialize() {
|
||||
if (!this.behaviorTreeFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await this.loadBehaviorTree();
|
||||
this.isLoaded = true;
|
||||
@@ -60,7 +60,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
// 静默处理
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async loadBehaviorTree(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let jsonPath = this.behaviorTreeFile;
|
||||
@@ -69,7 +69,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const treeData = asset.json as BehaviorTreeJSONConfig;
|
||||
this.buildBehaviorTree(treeData);
|
||||
@@ -80,22 +80,22 @@ export class BehaviorTreeComponent extends Component {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private buildBehaviorTree(treeData: BehaviorTreeJSONConfig) {
|
||||
this.eventRegistry = new EventRegistry();
|
||||
this.setupEventHandlers();
|
||||
|
||||
|
||||
const baseContext = {
|
||||
node: this.node,
|
||||
component: this,
|
||||
eventRegistry: this.eventRegistry
|
||||
};
|
||||
|
||||
|
||||
const result = BehaviorTreeBuilder.fromBehaviorTreeConfig(treeData, baseContext);
|
||||
this.behaviorTree = result.tree;
|
||||
this.blackboard = result.blackboard;
|
||||
this.context = result.context;
|
||||
|
||||
|
||||
this.initializeBlackboard();
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
|
||||
private initializeBlackboard() {
|
||||
if (!this.blackboard) return;
|
||||
|
||||
|
||||
this.blackboard.setValue('stamina', 100);
|
||||
this.blackboard.setValue('staminaPercentage', 1.0);
|
||||
this.blackboard.setValue('isLowStamina', false);
|
||||
@@ -140,7 +140,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
this.createSimpleStatusUI();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const uiNode = instantiate(this.statusUIPrefab);
|
||||
const canvas = this.node.scene?.getChildByName('Canvas');
|
||||
if (canvas) {
|
||||
@@ -151,22 +151,22 @@ export class BehaviorTreeComponent extends Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private createSimpleStatusUI() {
|
||||
this.statusUI = StatusUIManager.createStatusUIForMiner(this.node);
|
||||
}
|
||||
|
||||
|
||||
private updateStatusUI() {
|
||||
if (!this.statusUI || !this.blackboard) return;
|
||||
|
||||
|
||||
const stamina = this.blackboard.getValue('stamina') || 0;
|
||||
const maxStamina = this.blackboard.getValue('maxStamina') || 100;
|
||||
const hasOre = this.blackboard.getValue('hasOre') || false;
|
||||
const isResting = this.blackboard.getValue('isResting') || false;
|
||||
|
||||
|
||||
// 更新体力
|
||||
this.statusUI.updateStamina(stamina, maxStamina);
|
||||
|
||||
|
||||
// 更新状态文本
|
||||
let status = '';
|
||||
if (isResting) {
|
||||
@@ -177,30 +177,30 @@ export class BehaviorTreeComponent extends Component {
|
||||
status = '⛏️挖矿中';
|
||||
}
|
||||
this.statusUI.updateStatus(status);
|
||||
|
||||
|
||||
// 获取仓库矿石总数
|
||||
const gameManager = this.node.parent?.getComponent('SimpleMinerDemo');
|
||||
const warehouseTotal = (gameManager as any)?.getTotalOresCollected() || 0;
|
||||
|
||||
|
||||
// 更新矿石数量显示
|
||||
this.statusUI.updateOreCount(hasOre, warehouseTotal);
|
||||
|
||||
|
||||
// 更新动作进度
|
||||
this.updateActionProgressUI();
|
||||
}
|
||||
|
||||
|
||||
private updateActionProgressUI() {
|
||||
if (!this.statusUI) return;
|
||||
|
||||
|
||||
let actionName = '';
|
||||
let progress = 0;
|
||||
|
||||
|
||||
// 检查当前正在执行的动作
|
||||
for (const [key, state] of this.actionStates.entries()) {
|
||||
if (state.isExecuting) {
|
||||
const elapsed = Date.now() - state.startTime;
|
||||
progress = Math.min(elapsed / state.duration, 1.0);
|
||||
|
||||
|
||||
switch (key) {
|
||||
case 'mine-gold-ore':
|
||||
actionName = '⛏️ 挖掘中';
|
||||
@@ -217,7 +217,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
break; // 只显示第一个正在执行的动作
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果没有正在执行的动作,清空进度显示
|
||||
this.statusUI.updateActionProgress(actionName, progress);
|
||||
}
|
||||
@@ -244,9 +244,9 @@ export class BehaviorTreeComponent extends Component {
|
||||
// 检查是否已经在家了
|
||||
const homePos = blackboard.getValue('homePosition') || this.node.worldPosition;
|
||||
const distance = Vec3.distance(this.node.worldPosition, homePos);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (distance > 1.0) {
|
||||
// 还没到家,继续移动
|
||||
this.moveToPosition(homePos, 2.0);
|
||||
@@ -257,7 +257,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
blackboard.setValue('isResting', true);
|
||||
const actionKey = 'go-home-rest';
|
||||
const currentTime = Date.now();
|
||||
|
||||
|
||||
// 初始化休息状态
|
||||
if (!this.actionStates.has(actionKey)) {
|
||||
this.actionStates.set(actionKey, {
|
||||
@@ -274,20 +274,20 @@ export class BehaviorTreeComponent extends Component {
|
||||
if (elapsed >= actionState.duration) {
|
||||
const currentStamina = blackboard.getValue('stamina');
|
||||
const newStamina = Math.min(100, currentStamina + 10);
|
||||
|
||||
|
||||
blackboard.setValue('stamina', newStamina);
|
||||
blackboard.setValue('staminaPercentage', newStamina / 100);
|
||||
|
||||
|
||||
if (newStamina >= 80) {
|
||||
blackboard.setValue('isResting', false);
|
||||
blackboard.setValue('isLowStamina', false);
|
||||
this.actionStates.delete(actionKey);
|
||||
return 'success';
|
||||
}
|
||||
|
||||
|
||||
actionState.startTime = currentTime;
|
||||
}
|
||||
|
||||
|
||||
return 'running';
|
||||
}
|
||||
}
|
||||
@@ -303,7 +303,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
const hasOre = blackboard.getValue('hasOre');
|
||||
const isLowStamina = blackboard.getValue('isLowStamina');
|
||||
const isResting = blackboard.getValue('isResting');
|
||||
|
||||
|
||||
if (hasOre || isLowStamina || isResting) {
|
||||
return 'failure';
|
||||
}
|
||||
@@ -314,7 +314,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
|
||||
let nearestMine = goldMines[0];
|
||||
let minDistance = Vec3.distance(this.node.worldPosition, nearestMine.worldPosition);
|
||||
|
||||
|
||||
for (const mine of goldMines) {
|
||||
const distance = Vec3.distance(this.node.worldPosition, mine.worldPosition);
|
||||
if (distance < minDistance) {
|
||||
@@ -329,7 +329,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
} else {
|
||||
const actionKey = 'mine-gold-ore';
|
||||
const currentTime = Date.now();
|
||||
|
||||
|
||||
if (!this.actionStates.has(actionKey)) {
|
||||
this.actionStates.set(actionKey, {
|
||||
isExecuting: true,
|
||||
@@ -345,16 +345,16 @@ export class BehaviorTreeComponent extends Component {
|
||||
if (elapsed >= actionState.duration) {
|
||||
const currentStamina = blackboard.getValue('stamina');
|
||||
const newStamina = Math.max(0, currentStamina - 15);
|
||||
|
||||
|
||||
blackboard.setValue('stamina', newStamina);
|
||||
blackboard.setValue('staminaPercentage', newStamina / 100);
|
||||
blackboard.setValue('hasOre', true);
|
||||
blackboard.setValue('isLowStamina', newStamina < 20);
|
||||
|
||||
|
||||
this.actionStates.delete(actionKey);
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
|
||||
return 'running';
|
||||
}
|
||||
}
|
||||
@@ -379,14 +379,14 @@ export class BehaviorTreeComponent extends Component {
|
||||
if (!warehouse) return 'failure';
|
||||
|
||||
const distance = Vec3.distance(this.node.worldPosition, warehouse.worldPosition);
|
||||
|
||||
|
||||
if (distance > 2.0) {
|
||||
this.moveToPosition(warehouse.worldPosition, 2.0);
|
||||
return 'running';
|
||||
} else {
|
||||
const actionKey = 'store-ore';
|
||||
const currentTime = Date.now();
|
||||
|
||||
|
||||
if (!this.actionStates.has(actionKey)) {
|
||||
this.actionStates.set(actionKey, {
|
||||
isExecuting: true,
|
||||
@@ -405,7 +405,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
this.actionStates.delete(actionKey);
|
||||
return 'success';
|
||||
}
|
||||
|
||||
|
||||
return 'running';
|
||||
}
|
||||
}
|
||||
@@ -418,41 +418,41 @@ export class BehaviorTreeComponent extends Component {
|
||||
tween(this.node).stop();
|
||||
tween(this.node).to(duration, { worldPosition: targetPos }).start();
|
||||
}
|
||||
|
||||
|
||||
update(deltaTime: number) {
|
||||
if (this.behaviorTree && this.isRunning) {
|
||||
this.behaviorTree.tick(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
if (this.showStatusUI) {
|
||||
this.updateStatusUI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取黑板
|
||||
*/
|
||||
getBlackboard(): Blackboard | null {
|
||||
return this.blackboard;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取行为树
|
||||
*/
|
||||
getBehaviorTree(): BehaviorTree<any> | null {
|
||||
return this.behaviorTree;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 暂停行为树
|
||||
*/
|
||||
pause() {
|
||||
this.isRunning = false;
|
||||
if (this.debugMode) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 恢复行为树
|
||||
*/
|
||||
@@ -460,11 +460,11 @@ export class BehaviorTreeComponent extends Component {
|
||||
if (this.isLoaded) {
|
||||
this.isRunning = true;
|
||||
if (this.debugMode) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 停止行为树
|
||||
*/
|
||||
@@ -474,10 +474,10 @@ export class BehaviorTreeComponent extends Component {
|
||||
this.behaviorTree.reset();
|
||||
}
|
||||
if (this.debugMode) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重新加载行为树
|
||||
*/
|
||||
@@ -485,7 +485,7 @@ export class BehaviorTreeComponent extends Component {
|
||||
this.stop();
|
||||
await this.initialize();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重置行为树状态
|
||||
*/
|
||||
@@ -494,22 +494,22 @@ export class BehaviorTreeComponent extends Component {
|
||||
this.behaviorTree.reset();
|
||||
}
|
||||
if (this.debugMode) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onDestroy() {
|
||||
this.stop();
|
||||
if (this.eventRegistry) {
|
||||
this.eventRegistry.clear();
|
||||
}
|
||||
|
||||
|
||||
// 清理UI
|
||||
if (this.statusUI) {
|
||||
this.statusUI.node.destroy();
|
||||
this.statusUI = null;
|
||||
}
|
||||
|
||||
|
||||
this.behaviorTree = null;
|
||||
this.blackboard = null;
|
||||
this.context = null;
|
||||
|
||||
9
extensions/cocos/cocos-ecs/assets/scripts/mvvm/game.meta
Normal file
9
extensions/cocos/cocos-ecs/assets/scripts/mvvm/game.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "21b8d75a-82be-4b5a-8ecf-765558907857",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ViewModel, observable, computed, command } from '@esengine/mvvm-ui-framework';
|
||||
|
||||
/**
|
||||
* 游戏状态视图模型
|
||||
*/
|
||||
export class GameStateViewModel extends ViewModel {
|
||||
|
||||
public get name(): string {
|
||||
return 'GameStateViewModel';
|
||||
}
|
||||
|
||||
@observable
|
||||
currentLevel: number = 1;
|
||||
|
||||
@observable
|
||||
health: number = 100;
|
||||
|
||||
@observable
|
||||
mana: number = 50;
|
||||
|
||||
@observable
|
||||
experience: number = 0;
|
||||
|
||||
@computed(['health'])
|
||||
get healthPercent(): number {
|
||||
return (this.health / 100) * 100;
|
||||
}
|
||||
|
||||
@computed(['experience', 'currentLevel'])
|
||||
get experienceToNextLevel(): number {
|
||||
return (this.currentLevel * 100) - this.experience;
|
||||
}
|
||||
|
||||
@command()
|
||||
public levelUp(): void {
|
||||
this.currentLevel += 1;
|
||||
this.health = 100;
|
||||
this.mana += 10;
|
||||
}
|
||||
|
||||
@command()
|
||||
public takeDamage(damage: number): void {
|
||||
this.health = Math.max(0, this.health - damage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "8fa14e6f-46cd-4cb4-9ac1-0a1919e260a5",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
extensions/cocos/cocos-ecs/assets/scripts/mvvm/shop.meta
Normal file
9
extensions/cocos/cocos-ecs/assets/scripts/mvvm/shop.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "2c09b280-bf73-4d95-9b1f-d4d915442980",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ViewModel, observable, computed, command } from '@esengine/mvvm-ui-framework';
|
||||
|
||||
/**
|
||||
* 商店视图模型
|
||||
*/
|
||||
export class ShopViewModel extends ViewModel {
|
||||
|
||||
public get name(): string {
|
||||
return 'ShopViewModel';
|
||||
}
|
||||
|
||||
@observable
|
||||
selectedCategory: string = 'weapons';
|
||||
|
||||
@observable
|
||||
playerGold: number = 1000;
|
||||
|
||||
@observable
|
||||
cartItems: any[] = [];
|
||||
|
||||
@computed(['cartItems'])
|
||||
get totalPrice(): number {
|
||||
return this.cartItems.reduce((total, item) => total + item.price, 0);
|
||||
}
|
||||
|
||||
@computed(['playerGold', 'totalPrice'])
|
||||
get canPurchase(): boolean {
|
||||
return this.playerGold >= this.totalPrice;
|
||||
}
|
||||
|
||||
@command()
|
||||
public addToCart(item: any): void {
|
||||
this.cartItems.push(item);
|
||||
}
|
||||
|
||||
@command('canPurchase')
|
||||
public purchase(): void {
|
||||
this.playerGold -= this.totalPrice;
|
||||
this.cartItems = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "148fe394-03cd-45a1-9bc0-5cb24440d8db",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
extensions/cocos/cocos-ecs/assets/scripts/mvvm/user.meta
Normal file
9
extensions/cocos/cocos-ecs/assets/scripts/mvvm/user.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "62abfd02-b9f5-41d2-9822-2c777af21e27",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ViewModel, observable, computed, command } from '@esengine/mvvm-ui-framework';
|
||||
|
||||
/**
|
||||
* 用户配置文件视图模型
|
||||
*/
|
||||
export class UserProfileViewModel extends ViewModel {
|
||||
|
||||
public get name(): string {
|
||||
return 'UserProfileViewModel';
|
||||
}
|
||||
|
||||
@observable
|
||||
avatar: string = '';
|
||||
|
||||
@observable
|
||||
nickname: string = '';
|
||||
|
||||
@observable
|
||||
email: string = '';
|
||||
|
||||
@observable
|
||||
phone: string = '';
|
||||
|
||||
@computed(['nickname', 'email'])
|
||||
get displayInfo(): string {
|
||||
return `${this.nickname} (${this.email})`;
|
||||
}
|
||||
|
||||
@command()
|
||||
public updateProfile(): void {
|
||||
console.log('更新用户配置文件');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "05648650-5963-4847-8789-7bdc6ea7f43c",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Submodule extensions/cocos/cocos-ecs/extensions/behaviour-tree updated: 06eb76889b...141a36f92d
Submodule extensions/cocos/cocos-ecs/extensions/mvvm-designer updated: f030e47991...3a63efa72c
8
extensions/cocos/cocos-ecs/package-lock.json
generated
8
extensions/cocos/cocos-ecs/package-lock.json
generated
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "cocos-ecs",
|
||||
"dependencies": {
|
||||
"@esengine/ai": "^2.0.17",
|
||||
"@esengine/ai": "^2.0.19",
|
||||
"@esengine/cocos-nexus": "^1.0.1",
|
||||
"@esengine/ecs-framework": "^2.1.23",
|
||||
"@esengine/mvvm-ui-framework": "^1.0.3"
|
||||
@@ -38,9 +38,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@esengine/ai": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@esengine/ai/-/ai-2.0.17.tgz",
|
||||
"integrity": "sha512-ek19BGzW4VmkZsCr6N0SDJthzU6q+FTjb5wMwc3dEU1PdDRxPeSaD5wwhPfnTkgMXuGkncnM3NGuboz915LWyg==",
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@esengine/ai/-/ai-2.0.19.tgz",
|
||||
"integrity": "sha512-vK+5jtOYv4LeLX9IbhfC04qzaXfZOYdGyXsZ52Bxiv3KCgzMnP2cKQfMXLvkhgkMKQ8m16PaiHyaDFoo8BKk4w==",
|
||||
"dependencies": {
|
||||
"@esengine/ecs-framework": "^2.1.20"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"version": "3.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@esengine/ai": "^2.0.17",
|
||||
"@esengine/ai": "^2.0.19",
|
||||
"@esengine/cocos-nexus": "^1.0.1",
|
||||
"@esengine/ecs-framework": "^2.1.23",
|
||||
"@esengine/mvvm-ui-framework": "^1.0.3"
|
||||
|
||||
Reference in New Issue
Block a user