|
@@ -0,0 +1,287 @@
|
|
|
+import { _decorator, Component, Prefab, Sprite, SpriteFrame, instantiate, Node, Vec3, assetManager, AssetManager, resources, randomRangeInt, randomRange, UITransform, Vec2 } from "cc";
|
|
|
+import FindItem, { ItemState } from "./FindItem";
|
|
|
+import { LayerMgr } from "../../script/Manager/LayerMgr";
|
|
|
+import { CCFloat } from "cc";
|
|
|
+const { ccclass, property } = _decorator;
|
|
|
+
|
|
|
+// 关卡模式枚举
|
|
|
+export enum LevelMode {
|
|
|
+ EDIT, // 编辑模式
|
|
|
+ NORMAL // 正常选择模式
|
|
|
+}
|
|
|
+
|
|
|
+@ccclass('LevelContainer')
|
|
|
+export class LevelContainer extends Component {
|
|
|
+
|
|
|
+ @property({ type: Node, tooltip: '遮罩' })
|
|
|
+ mask: Node = null;
|
|
|
+
|
|
|
+ @property({ type: Sprite, tooltip: '背景图' })
|
|
|
+ lvlBg: Sprite = null;
|
|
|
+
|
|
|
+ @property({ type: Prefab, tooltip: '物品预制体' })
|
|
|
+ findItemPrefab: Prefab = null;
|
|
|
+
|
|
|
+ @property({ type: CCFloat, tooltip: '最小缩放比例' })
|
|
|
+ minScale: number = 0.1;
|
|
|
+
|
|
|
+ @property({ type: CCFloat, tooltip: '最大缩放比例' })
|
|
|
+ maxScale: number = 1.5;
|
|
|
+
|
|
|
+ @property({ type: Node, tooltip: '物品容器' })
|
|
|
+ itemContainer: Node = null;
|
|
|
+
|
|
|
+ _findItems: FindItem[] = [];
|
|
|
+ _currentMode: LevelMode = LevelMode.NORMAL;
|
|
|
+ _totalItems: number = 0;
|
|
|
+ _foundItems: number = 0;
|
|
|
+ _onLevelComplete: Function = null;
|
|
|
+
|
|
|
+ _bgPath: string = "";
|
|
|
+ _itemSpinePaths: string[] = [];
|
|
|
+ _itemcnt: number = 0;
|
|
|
+ _itemScales: number[] = [];
|
|
|
+
|
|
|
+ _defaultScales: number[] = [1.18, 1.16, 1.14, 0.3976, 0.3690, 0.3029,
|
|
|
+ 0.0667, 0.0766, 0.0531, 0.0716];
|
|
|
+
|
|
|
+ // start() {
|
|
|
+ // LayerMgr.instance.loadBundle("editor", () => {
|
|
|
+ // this.genLvlContent("image/bg1", ["prefab/spine/spine01"], 10);
|
|
|
+ // });
|
|
|
+ // }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 切换编辑模式
|
|
|
+ * @param isEdit 是否为编辑模式
|
|
|
+ */
|
|
|
+ swtichEditMode(isEdit: boolean) {
|
|
|
+ this._currentMode = isEdit ? LevelMode.EDIT : LevelMode.NORMAL;
|
|
|
+
|
|
|
+ // 更新所有物品的状态
|
|
|
+ this._findItems.forEach(item => {
|
|
|
+ if (isEdit) {
|
|
|
+ item.enterEditMode();
|
|
|
+ } else {
|
|
|
+ item.exitEditMode();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 重置找到的物品计数
|
|
|
+ if (!isEdit) {
|
|
|
+ this._foundItems = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成关卡内容
|
|
|
+ * @param bgPath 背景图路径
|
|
|
+ * @param Items 物品预制体路径数组
|
|
|
+ * @param itemcnt 物品数量
|
|
|
+ * @param itemScales 物品缩放数组,为空时随机缩放
|
|
|
+ */
|
|
|
+ public genLvlContent(bgPath: string, itemSpinePaths: string[], itemcnt: number, itemScales: number[] = null) {
|
|
|
+ // 清空现有物品
|
|
|
+ this.clearItems();
|
|
|
+ this._bgPath = bgPath;
|
|
|
+ this._totalItems = itemcnt;
|
|
|
+ this._itemSpinePaths = itemSpinePaths;
|
|
|
+ this._itemcnt = itemcnt;
|
|
|
+ this._itemScales = itemScales || this._defaultScales;
|
|
|
+
|
|
|
+ // 加载背景图
|
|
|
+ this.loadSprite(bgPath, (spriteFrame: SpriteFrame) => {
|
|
|
+ console.log("loadSprite", bgPath, spriteFrame);
|
|
|
+ if (this.lvlBg && spriteFrame) {
|
|
|
+ this.lvlBg.spriteFrame = spriteFrame;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 生成物品
|
|
|
+ for (let i = 0; i < itemcnt; i++) {
|
|
|
+ // 随机选择一个物品
|
|
|
+ var randomIndex = randomRangeInt(0, itemSpinePaths.length - 1);
|
|
|
+ this.createItem(itemSpinePaths[randomIndex], this._itemScales[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建物品
|
|
|
+ * @param itemPrefab 物品预制体
|
|
|
+ * @param scale 缩放值,null时随机缩放
|
|
|
+ */
|
|
|
+ private createItem(itemPrefabPath: string, scale: number | null) {
|
|
|
+ const itemNode = instantiate(this.findItemPrefab);
|
|
|
+ itemNode.parent = this.itemContainer || this.node;
|
|
|
+
|
|
|
+ const findItem = itemNode.getComponent(FindItem);
|
|
|
+ if (findItem) {
|
|
|
+ var size = this.mask.getComponent(UITransform).contentSize;
|
|
|
+ // 设置缩放
|
|
|
+ const finalScale = scale !== null ? scale : randomRange(this.minScale, this.maxScale);
|
|
|
+ // 设置随机位置, 不要出边框
|
|
|
+ var itemSize = itemNode.getComponent(UITransform).contentSize;
|
|
|
+ var w = itemSize.width * finalScale;
|
|
|
+ var h = itemSize.height * finalScale;
|
|
|
+
|
|
|
+ // 随机,并尽量不要重叠
|
|
|
+ let tryCount = 0;
|
|
|
+ let maxTry = 50;
|
|
|
+ let pos: Vec3;
|
|
|
+ let overlap = false;
|
|
|
+ do {
|
|
|
+ overlap = false;
|
|
|
+ const randomX = randomRange(-size.width / 2 + w / 2, size.width / 2 - w / 2);
|
|
|
+ const randomY = randomRange(-size.height / 2 + h / 2, size.height / 2 - h / 2);
|
|
|
+ pos = new Vec3(randomX, randomY, 0);
|
|
|
+
|
|
|
+ // 检查与已放置物品是否重叠
|
|
|
+ for (let other of this._findItems) {
|
|
|
+ let otherPos = other.node.getPosition();
|
|
|
+ let otherScale = other.node.scale.x;
|
|
|
+ let otherSize = other.node.getComponent(UITransform).contentSize;
|
|
|
+ let otherW = otherSize.width * otherScale;
|
|
|
+ let otherH = otherSize.height * otherScale;
|
|
|
+
|
|
|
+ // 简单的AABB重叠检测
|
|
|
+ if (
|
|
|
+ Math.abs(pos.x - otherPos.x) < (w + otherW) / 2 &&
|
|
|
+ Math.abs(pos.y - otherPos.y) < (h + otherH) / 2
|
|
|
+ ) {
|
|
|
+ overlap = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ tryCount++;
|
|
|
+ } while (overlap && tryCount < maxTry);
|
|
|
+
|
|
|
+ itemNode.setPosition(pos);
|
|
|
+
|
|
|
+
|
|
|
+ findItem.setData({
|
|
|
+ position: itemNode.getPosition(),
|
|
|
+ scale: finalScale
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加spine内容
|
|
|
+ this.loadPrefab(itemPrefabPath, (prefab: Prefab) => {
|
|
|
+ findItem.addSpineContentByPrefab(prefab);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 设置状态
|
|
|
+ findItem.setState(this._currentMode === LevelMode.EDIT ? ItemState.EDIT : ItemState.NORMAL);
|
|
|
+
|
|
|
+ // 添加到物品列表
|
|
|
+ this._findItems.push(findItem);
|
|
|
+
|
|
|
+ // 监听物品状态变化
|
|
|
+ this.setupItemListeners(findItem);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置物品监听器
|
|
|
+ * @param findItem 物品组件
|
|
|
+ */
|
|
|
+ private setupItemListeners(findItem: FindItem) {
|
|
|
+ // 监听物品点击事件
|
|
|
+ findItem.node.on("itemFound", () => {
|
|
|
+ if (this._currentMode === LevelMode.NORMAL) {
|
|
|
+ this._foundItems++;
|
|
|
+ console.log("itemFound", this._foundItems, this._totalItems);
|
|
|
+ // 检查是否所有物品都被找到
|
|
|
+ if (this._foundItems >= this._totalItems) {
|
|
|
+ this.onLevelComplete();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 关卡完成回调
|
|
|
+ */
|
|
|
+ private onLevelComplete() {
|
|
|
+ if (this._onLevelComplete) {
|
|
|
+ this._onLevelComplete();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 可以在这里添加完成动画或音效
|
|
|
+ console.log("关卡完成!所有物品都已找到");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清空所有物品
|
|
|
+ */
|
|
|
+ private clearItems() {
|
|
|
+ this._findItems.forEach(item => {
|
|
|
+ if (item && item.node) {
|
|
|
+ item.node.destroy();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this._findItems = [];
|
|
|
+ this._foundItems = 0;
|
|
|
+ this._totalItems = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置关卡完成回调
|
|
|
+ * @param callback 完成回调函数
|
|
|
+ */
|
|
|
+ public setLevelCompleteCallback(callback: Function) {
|
|
|
+ this._onLevelComplete = callback;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示提示(高亮未找到的物品)
|
|
|
+ */
|
|
|
+ public showHint() {
|
|
|
+ this._findItems.forEach(item => {
|
|
|
+ if (item.getState() === ItemState.NORMAL) {
|
|
|
+ item.showHint();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public reset() {
|
|
|
+ this.clearItems();
|
|
|
+ this.swtichEditMode(false);
|
|
|
+ this.genLvlContent(this._bgPath, this._itemSpinePaths, this._itemcnt, this._itemScales);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取关卡数据(用于保存)
|
|
|
+ */
|
|
|
+ public getLevelData() {
|
|
|
+ return {
|
|
|
+ items: this._findItems.map(item => item.getData()),
|
|
|
+ totalItems: this._totalItems
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载精灵图片
|
|
|
+ * @param path 图片路径
|
|
|
+ * @param callback 回调函数
|
|
|
+ */
|
|
|
+ loadSprite(path: string, callback: (spriteFrame: SpriteFrame) => void) {
|
|
|
+ LayerMgr.instance.loadSprite("editor", path, callback);
|
|
|
+ }
|
|
|
+
|
|
|
+ prefabMap: Map<string, Prefab> = new Map();
|
|
|
+ /**
|
|
|
+ * 加载预制体
|
|
|
+ * @param path 预制体路径
|
|
|
+ * @param callback 回调函数
|
|
|
+ */
|
|
|
+ loadPrefab(path: string, callback: (prefab: Prefab) => void) {
|
|
|
+ if (this.prefabMap.has(path)) {
|
|
|
+ callback(this.prefabMap.get(path));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ LayerMgr.instance.loadPrefab("editor", path, (prefab: Prefab) => {
|
|
|
+ this.prefabMap.set(path, prefab);
|
|
|
+ callback(prefab);
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|