181404010226 2 settimane fa
parent
commit
07e561e5de

+ 187 - 0
assets/scripts/ButtonEffectsManager.ts

@@ -0,0 +1,187 @@
+import { _decorator, Component, Node, Button, Vec3, Color, UIOpacity, Enum } from 'cc';
+const { ccclass, property } = _decorator;
+
+/**
+ * Button effect types
+ */
+enum ButtonEffectType {
+    MOVE_DOWN = 0,
+    DARKEN = 1
+}
+
+@ccclass('ButtonEffectsManager')
+export class ButtonEffectsManager extends Component {
+    @property({
+        type: [Button],
+        tooltip: 'The buttons to apply effects to'
+    })
+    buttons: Button[] = [];
+
+    @property({
+        type: Enum(ButtonEffectType),
+        tooltip: 'Effect type: 0 for move down, 1 for darken'
+    })
+    effectType: ButtonEffectType = ButtonEffectType.MOVE_DOWN;
+
+    @property({
+        tooltip: 'How much to move the button down (in pixels)',
+        visible: function(this: ButtonEffectsManager) {
+            return this.effectType === ButtonEffectType.MOVE_DOWN;
+        }
+    })
+    moveDistance: number = 5;
+
+    @property({
+        tooltip: 'How much to darken the button (0-1)',
+        range: [0, 1, 0.05],
+        slide: true,
+        visible: function(this: ButtonEffectsManager) {
+            return this.effectType === ButtonEffectType.DARKEN;
+        }
+    })
+    darkenAmount: number = 0.2;
+
+    private originalPositions: Map<string, Vec3> = new Map();
+    private originalOpacities: Map<string, number> = new Map();
+
+    start() {
+        // Register all buttons
+        this.registerButtons();
+    }
+
+    /**
+     * Register all buttons in the buttons array
+     */
+    registerButtons() {
+        this.buttons.forEach(button => {
+            if (button) {
+                this.registerButton(button);
+            }
+        });
+    }
+
+    /**
+     * Register a single button with press effects
+     * @param button The button to register
+     */
+    registerButton(button: Button) {
+        const buttonNode = button.node;
+        const buttonId = buttonNode.uuid;
+
+        // Store original position for move effect
+        if (this.effectType === ButtonEffectType.MOVE_DOWN) {
+            const originalPos = buttonNode.position.clone();
+            this.originalPositions.set(buttonId, originalPos);
+        } 
+        // Store original opacity for darken effect
+        else if (this.effectType === ButtonEffectType.DARKEN) {
+            const uiOpacity = buttonNode.getComponent(UIOpacity);
+            if (uiOpacity) {
+                this.originalOpacities.set(buttonId, uiOpacity.opacity);
+            } else {
+                // Add UIOpacity if it doesn't exist
+                const newUIOpacity = buttonNode.addComponent(UIOpacity);
+                this.originalOpacities.set(buttonId, newUIOpacity.opacity);
+            }
+        }
+
+        // Add button events
+        button.node.on(Node.EventType.TOUCH_START, () => this.onButtonPressed(buttonId), this);
+        button.node.on(Node.EventType.TOUCH_END, () => this.onButtonReleased(buttonId), this);
+        button.node.on(Node.EventType.TOUCH_CANCEL, () => this.onButtonReleased(buttonId), this);
+    }
+
+    /**
+     * Handle button press
+     * @param buttonId The unique ID of the button
+     */
+    onButtonPressed(buttonId: string) {
+        const button = this.getButtonById(buttonId);
+        if (!button) return;
+
+        if (this.effectType === ButtonEffectType.MOVE_DOWN) {
+            const originalPos = this.originalPositions.get(buttonId);
+            if (originalPos) {
+                const newPos = originalPos.clone();
+                newPos.y -= this.moveDistance;
+                button.node.position = newPos;
+            }
+        } else if (this.effectType === ButtonEffectType.DARKEN) {
+            const uiOpacity = button.node.getComponent(UIOpacity);
+            if (uiOpacity) {
+                const originalOpacity = this.originalOpacities.get(buttonId) || 255;
+                uiOpacity.opacity = originalOpacity * (1 - this.darkenAmount);
+            }
+        }
+    }
+
+    /**
+     * Handle button release
+     * @param buttonId The unique ID of the button
+     */
+    onButtonReleased(buttonId: string) {
+        const button = this.getButtonById(buttonId);
+        if (!button) return;
+
+        if (this.effectType === ButtonEffectType.MOVE_DOWN) {
+            const originalPos = this.originalPositions.get(buttonId);
+            if (originalPos) {
+                button.node.position = originalPos.clone();
+            }
+        } else if (this.effectType === ButtonEffectType.DARKEN) {
+            const uiOpacity = button.node.getComponent(UIOpacity);
+            if (uiOpacity) {
+                const originalOpacity = this.originalOpacities.get(buttonId) || 255;
+                uiOpacity.opacity = originalOpacity;
+            }
+        }
+    }
+
+    /**
+     * Find a button by its ID
+     * @param buttonId The unique ID of the button
+     * @returns The button component or null if not found
+     */
+    private getButtonById(buttonId: string): Button | null {
+        for (const button of this.buttons) {
+            if (button && button.node.uuid === buttonId) {
+                return button;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Manually add a button to the manager
+     * @param button The button to add
+     */
+    public addButton(button: Button) {
+        if (!this.buttons.some(btn => btn === button)) {
+            this.buttons.push(button);
+            this.registerButton(button);
+        }
+    }
+
+    /**
+     * Remove a button from the manager
+     * @param button The button to remove
+     */
+    public removeButton(button: Button) {
+        const index = this.buttons.indexOf(button);
+        if (index !== -1) {
+            const buttonId = button.node.uuid;
+            
+            // Remove event listeners
+            button.node.off(Node.EventType.TOUCH_START, () => this.onButtonPressed(buttonId), this);
+            button.node.off(Node.EventType.TOUCH_END, () => this.onButtonReleased(buttonId), this);
+            button.node.off(Node.EventType.TOUCH_CANCEL, () => this.onButtonReleased(buttonId), this);
+            
+            // Remove button from array
+            this.buttons.splice(index, 1);
+            
+            // Clean up stored data
+            this.originalPositions.delete(buttonId);
+            this.originalOpacities.delete(buttonId);
+        }
+    }
+} 

+ 9 - 0
assets/scripts/ButtonEffectsManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "64557dd0-27e3-4300-9227-d8beafbc24bc",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 117 - 0
assets/scripts/DialogueManager.ts

@@ -0,0 +1,117 @@
+import { _decorator, Component, Node, Label, Tween, tween, Vec3, UITransform } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('DialogueManager')
+export class DialogueManager extends Component {
+    @property({
+        type: Node,
+        tooltip: '对话框节点'
+    })
+    dialogueNode: Node = null;
+
+    @property({
+        type: Label,
+        tooltip: '显示对话内容的文本标签'
+    })
+    dialogueLabel: Label = null;
+
+    @property({
+        tooltip: '对话框弹出动画持续时间(秒)'
+    })
+    popupDuration: number = 0.3;
+
+    @property({
+        tooltip: '对话框缩放前的初始大小(比例)'
+    })
+    initialScale: number = 0.5;
+
+    @property({
+        tooltip: '对话框最终大小(比例)'
+    })
+    targetScale: number = 1.0;
+
+    private currentDialogue: string = '';
+    private dialogueAnimation: Tween<Node> = null;
+
+    start() {
+        // 确保对话框初始隐藏
+        if (this.dialogueNode) {
+            this.dialogueNode.active = false;
+        }
+    }
+
+    /**
+     * 显示新对话
+     * @param text 对话内容,如果为空则重复当前对话
+     */
+    public showDialogue(text: string = ''): void {
+        // 如果传入的文本为空,则使用当前对话
+        const dialogueText = text || this.currentDialogue;
+        
+        // 如果对话为空,则不执行任何操作
+        if (!dialogueText) {
+            return;
+        }
+        
+        // 保存当前对话文本
+        this.currentDialogue = dialogueText;
+        
+        // 设置对话内容
+        if (this.dialogueLabel) {
+            this.dialogueLabel.string = dialogueText;
+        }
+        
+        // 显示对话框并播放弹出动画
+        this.playPopupAnimation();
+    }
+
+    /**
+     * 播放对话框弹出动画
+     */
+    private playPopupAnimation(): void {
+        if (!this.dialogueNode) {
+            return;
+        }
+
+        // 停止正在播放的动画(如果有)
+        if (this.dialogueAnimation) {
+            this.dialogueAnimation.stop();
+        }
+
+        // 显示对话框
+        this.dialogueNode.active = true;
+        
+        // 设置初始缩放
+        this.dialogueNode.setScale(new Vec3(this.initialScale, this.initialScale, 1));
+        
+        // 创建弹出动画
+        this.dialogueAnimation = tween(this.dialogueNode)
+            .to(this.popupDuration, { scale: new Vec3(this.targetScale, this.targetScale, 1) }, {
+                easing: 'elasticOut'
+            })
+            .start();
+    }
+
+    /**
+     * 隐藏对话框
+     */
+    public hideDialogue(): void {
+        if (this.dialogueNode) {
+            // 停止正在播放的动画(如果有)
+            if (this.dialogueAnimation) {
+                this.dialogueAnimation.stop();
+            }
+            
+            // 隐藏对话框并重置缩放
+            this.dialogueNode.active = false;
+            this.dialogueNode.setScale(new Vec3(this.initialScale, this.initialScale, 1));
+        }
+    }
+
+    /**
+     * 判断对话框是否正在显示
+     */
+    public isDialogueShowing(): boolean {
+        return this.dialogueNode ? this.dialogueNode.active : false;
+    }
+} 

+ 9 - 0
assets/scripts/DialogueManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "eee9917b-d463-4c04-9077-f044fa5b47ef",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 209 - 0
assets/scripts/QuestionAnswerManager.ts

@@ -0,0 +1,209 @@
+import { _decorator, Component, Node, Button, Label } from 'cc';
+import { DialogueManager } from './DialogueManager';
+const { ccclass, property } = _decorator;
+
+/**
+ * 问答对接口
+ */
+interface QuestionAnswer {
+    question: string;  // 问题文本
+    answer: string;    // 回答文本
+}
+
+@ccclass('QuestionAnswerManager')
+export class QuestionAnswerManager extends Component {
+    @property({
+        type: DialogueManager,
+        tooltip: '对话管理器引用'
+    })
+    dialogueManager: DialogueManager = null;
+
+    @property({
+        type: [Button],
+        tooltip: '问题按钮数组',
+        readonly: true
+    })
+    questionButtons: Button[] = [];
+
+    @property({
+        type: [Node],
+        tooltip: '问题文本节点数组(Label组件所在的节点)',
+        readonly: true
+    })
+    questionLabels: Node[] = [];
+
+    // 问答对数组
+    private questionAnswerPairs: QuestionAnswer[] = [];
+    
+    // 当前显示的问题索引
+    private currentQuestionIndices: number[] = [0, 1, 2];
+
+    start() {
+        // 初始化默认问答对
+        this.initDefaultQuestionAnswers();
+        
+        // 注册按钮事件
+        this.registerButtons();
+        
+        // 更新问题按钮文本
+        this.updateQuestionButtonTexts();
+    }
+
+    /**
+     * 初始化默认问答对
+     */
+    private initDefaultQuestionAnswers(): void {
+        // 添加一些默认的问答对
+        this.questionAnswerPairs = [
+            {
+                question: "询问为何不在名单内?",
+                answer: "我没见过你的资料。你确定你应该在这个区域吗?"
+            },
+            {
+                question: "询问通行证?",
+                answer: "你需要有效的通行证才能进入这个区域。没有通行证是不允许的。"
+            },
+            {
+                question: "询问外貌?",
+                answer: "我对你的外貌没有任何评价。我只负责确认身份。"
+            },
+            {
+                question: "你是谁?",
+                answer: "我是这个区域的安全管理员,负责身份验证和访问控制。"
+            },
+            {
+                question: "这是什么地方?",
+                answer: "这是一个受限制的区域,需要特殊许可才能进入。"
+            },
+            {
+                question: "我可以离开吗?",
+                answer: "如果你没有通行证,建议你尽快离开,否则可能会有麻烦。"
+            }
+        ];
+    }
+
+    /**
+     * 注册所有问题按钮
+     */
+    private registerButtons(): void {
+        // 注册每个按钮的点击事件
+        for (let i = 0; i < this.questionButtons.length; i++) {
+            const buttonIndex = i;
+            if (this.questionButtons[i]) {
+                this.questionButtons[i].node.on(Button.EventType.CLICK, () => {
+                    this.onQuestionButtonClicked(buttonIndex);
+                }, this);
+            }
+        }
+    }
+
+    /**
+     * 更新问题按钮文本
+     */
+    private updateQuestionButtonTexts(): void {
+        // 为每个按钮设置对应的问题文本
+        for (let i = 0; i < Math.min(this.questionLabels.length, this.currentQuestionIndices.length); i++) {
+            const questionIndex = this.currentQuestionIndices[i];
+            const labelNode = this.questionLabels[i];
+            
+            if (labelNode && questionIndex >= 0 && questionIndex < this.questionAnswerPairs.length) {
+                // 获取Label组件并设置文本
+                const label = labelNode.getComponent(Label);
+                if (label) {
+                    label.string = this.questionAnswerPairs[questionIndex].question;
+                }
+            }
+        }
+    }
+
+    /**
+     * 问题按钮点击回调
+     * @param buttonIndex 按钮索引
+     */
+    private onQuestionButtonClicked(buttonIndex: number): void {
+        if (buttonIndex < 0 || buttonIndex >= this.currentQuestionIndices.length) {
+            return;
+        }
+
+        const questionIndex = this.currentQuestionIndices[buttonIndex];
+        if (questionIndex < 0 || questionIndex >= this.questionAnswerPairs.length) {
+            return;
+        }
+
+        // 获取问答对
+        const qa = this.questionAnswerPairs[questionIndex];
+        
+        // 使用对话管理器显示回答
+        if (this.dialogueManager && qa) {
+            this.dialogueManager.showDialogue(qa.answer);
+        }
+    }
+
+    /**
+     * 添加新的问答对
+     * @param questionAnswers 要添加的问答对数组
+     */
+    public addQuestionAnswers(questionAnswers: QuestionAnswer[]): void {
+        if (!questionAnswers || questionAnswers.length === 0) {
+            return;
+        }
+
+        // 添加新的问答对
+        this.questionAnswerPairs = [...this.questionAnswerPairs, ...questionAnswers];
+        
+        // 更新按钮文本(如果需要)
+        this.updateQuestionButtonTexts();
+    }
+
+    /**
+     * 设置问题按钮显示的问题索引
+     * @param indices 问题索引数组,长度应与按钮数量相同
+     */
+    public setQuestionIndices(indices: number[]): void {
+        // 验证索引有效性
+        const validIndices = indices.filter(index => 
+            index >= 0 && index < this.questionAnswerPairs.length
+        );
+
+        // 更新当前显示的问题索引
+        this.currentQuestionIndices = validIndices.slice(0, this.questionButtons.length);
+        
+        // 如果索引数量少于按钮数量,使用默认索引填充
+        while (this.currentQuestionIndices.length < this.questionButtons.length) {
+            const defaultIndex = this.currentQuestionIndices.length % this.questionAnswerPairs.length;
+            this.currentQuestionIndices.push(defaultIndex);
+        }
+        
+        // 更新按钮文本
+        this.updateQuestionButtonTexts();
+    }
+
+    /**
+     * 获取当前所有问答对
+     * @returns 所有问答对数组
+     */
+    public getAllQuestionAnswers(): QuestionAnswer[] {
+        return [...this.questionAnswerPairs];
+    }
+
+    /**
+     * 清除所有问答对并重置为默认值
+     */
+    public resetToDefault(): void {
+        this.initDefaultQuestionAnswers();
+        this.currentQuestionIndices = [0, 1, 2];
+        this.updateQuestionButtonTexts();
+    }
+
+    /**
+     * 更新特定索引的问答对
+     * @param index 要更新的问答对索引
+     * @param newQA 新的问答对
+     */
+    public updateQuestionAnswer(index: number, newQA: QuestionAnswer): void {
+        if (index >= 0 && index < this.questionAnswerPairs.length && newQA) {
+            this.questionAnswerPairs[index] = newQA;
+            this.updateQuestionButtonTexts();
+        }
+    }
+} 

+ 9 - 0
assets/scripts/QuestionAnswerManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "4a62f153-68f7-40db-8ea5-02a9596df011",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}