更新首页homeScene涉及到的按钮,以及涉及到的所有分包或者活动的按钮 增加震动
This commit is contained in:
parent
b028946207
commit
2e0d1826f1
|
|
@ -20,6 +20,7 @@ import { initProvinceLocator } from "./module/Position/GetPosition";
|
|||
import LoadingCatAnimation from "./LoadingCatAnimation";
|
||||
import BlockAssetManager from "./module/Tool/BlockAssetManager";
|
||||
import MiniProgramBenefitsBridge from "./module/MiniProgramBenefitsBridge";
|
||||
import { onHomeButtonTouchEnd, vibrateHomeButton } from "./module/Tool/HomeButtonVibration";
|
||||
const { ccclass, property } = cc._decorator;
|
||||
|
||||
@ccclass
|
||||
|
|
@ -46,6 +47,7 @@ export default class JiaZai extends cc.Component {
|
|||
}
|
||||
// 新的主动操作取代尚未打开的按需弹窗;资源下载仍正常完成并写入缓存。
|
||||
this.homePopupOpenRequest++;
|
||||
if (vibrateHomeButton(event)) return;
|
||||
try {
|
||||
cc.fx.GameTool.setVibrate("light", 1);
|
||||
} catch (error) {
|
||||
|
|
@ -366,6 +368,14 @@ export default class JiaZai extends cc.Component {
|
|||
MiniGameSdk.API.showToast("当前设备内存不足,排行榜暂不可用");
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.node.on(cc.Node.EventType.TOUCH_END, onHomeButtonTouchEnd, this, true);
|
||||
}
|
||||
|
||||
onDisable() {
|
||||
this.node.off(cc.Node.EventType.TOUCH_END, onHomeButtonTouchEnd, this, true);
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
// 首页在进关前展示连胜;覆盖登录或缓存恢复的测试初始值。
|
||||
if (cc.fx.GameConfig.forceOldRainbowWinStreakC || cc.fx.GameConfig.forceRainbowWinStreakB) {
|
||||
|
|
|
|||
|
|
@ -1022,7 +1022,7 @@ export default class MapConroler extends cc.Component {
|
|||
const hand = child.getChildByName('hand');
|
||||
if (!hand) return;
|
||||
const animation = hand.getComponent(GuideHand) || hand.addComponent(GuideHand);
|
||||
animation.rippleMaterial = rippleSprite.sharedMaterials[0];
|
||||
animation.rippleMaterial = rippleSprite.getMaterial(0);
|
||||
});
|
||||
this.propGuideMask = guide;
|
||||
guide.parent = this.node.parent;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import LoadingCatAnimation from "../LoadingCatAnimation";
|
||||
import SevenDayGiftBootstrapApi from "../seven_day_gift/SevenDayGiftBootstrapApi";
|
||||
import { vibrateHomeButton } from "./Tool/HomeButtonVibration";
|
||||
|
||||
declare const wx: any;
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ export default class MiniProgramBenefitsBridge {
|
|||
private entryRed: cc.Node = null;
|
||||
private entryFrame: cc.SpriteFrame = null;
|
||||
private entryVisual: cc.Node = null;
|
||||
private onEntryTouch = (): void => { this.open(); };
|
||||
private onEntryTouch = (event: cc.Event): void => { vibrateHomeButton(event); this.open(); };
|
||||
private session: any = null;
|
||||
private rewardRequests = 0;
|
||||
private testMount = 0;
|
||||
|
|
|
|||
34
assets/Script/module/Tool/HomeButtonVibration.ts
Normal file
34
assets/Script/module/Tool/HomeButtonVibration.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
const handledEvents = new WeakSet<cc.Event>();
|
||||
|
||||
/** 返回是否由首页统一处理;旧入口在其他场景仍可保留原有行为。 */
|
||||
export function vibrateHomeButton(event?: cc.Event): boolean {
|
||||
if (!event) return false;
|
||||
const scene = cc.director.getScene();
|
||||
if (!scene || scene.name !== "HomeScene") return false;
|
||||
if (handledEvents.has(event)) return true;
|
||||
handledEvents.add(event);
|
||||
try {
|
||||
cc.fx.GameTool.setVibrate("light", 1);
|
||||
} catch (error) {
|
||||
console.warn("HomeScene 按钮轻振动调用失败:", error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 根节点捕获触摸,自动覆盖后来挂入首页的 Bundle 按钮。 */
|
||||
export function onHomeButtonTouchEnd(event: cc.Event.EventTouch): void {
|
||||
// 引擎可能复用事件对象,每次派发重新去重。
|
||||
handledEvents.delete(event);
|
||||
let target = event.target as cc.Node;
|
||||
while (target) {
|
||||
const button = target.getComponent(cc.Button);
|
||||
if (button) {
|
||||
if (button.interactable && button.enabledInHierarchy && (button as any)._pressed) {
|
||||
vibrateHomeButton(event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (target === event.currentTarget) return;
|
||||
target = target.parent;
|
||||
}
|
||||
}
|
||||
10
assets/Script/module/Tool/HomeButtonVibration.ts.meta
Normal file
10
assets/Script/module/Tool/HomeButtonVibration.ts.meta
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"ver": "1.1.0",
|
||||
"uuid": "b9f1f6e3-d1e1-4a3d-a1a6-f3089fd502dd",
|
||||
"importer": "typescript",
|
||||
"isPlugin": false,
|
||||
"loadPluginInWeb": true,
|
||||
"loadPluginInNative": true,
|
||||
"loadPluginInEditor": false,
|
||||
"subMetas": {}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import NumberToImage from "../../Script/NumberToImage";
|
||||
import { vibrateHomeButton } from "../../Script/module/Tool/HomeButtonVibration";
|
||||
|
||||
import CareerList2 from "./CareerList2";
|
||||
import CareerItem2 from "./CareerItem2";
|
||||
|
|
@ -12,6 +13,7 @@ export default class CareerManager2 extends cc.Component {
|
|||
if (!event) {
|
||||
return;
|
||||
}
|
||||
if (vibrateHomeButton(event)) return;
|
||||
try {
|
||||
cc.fx.GameTool.setVibrate("light", 1);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { vibrateHomeButton } from "../../Script/module/Tool/HomeButtonVibration";
|
||||
|
||||
const { ccclass, property } = cc._decorator;
|
||||
|
||||
type RewardType = "hammer" | "freeze" | "magic_wand" | "coin" | "infinite_health";
|
||||
|
|
@ -59,10 +61,11 @@ export default class JungleOver extends cc.Component {
|
|||
this.confirming = confirming;
|
||||
}
|
||||
|
||||
private onClose(): void {
|
||||
private onClose(event?: cc.Event): void {
|
||||
if (this.confirming) {
|
||||
return;
|
||||
}
|
||||
vibrateHomeButton(event);
|
||||
this.confirming = true;
|
||||
this.node.emit("jungle-over-close");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import MiniProgramBenefitsReward from "./MiniProgramBenefitsReward";
|
||||
import { vibrateHomeButton } from "../../Script/module/Tool/HomeButtonVibration";
|
||||
const { ccclass, property } = cc._decorator;
|
||||
const TASKS = ["favorite_entry", "desktop_entry"];
|
||||
|
||||
|
|
@ -37,18 +38,22 @@ export default class MiniProgramBenefitsPanel extends cc.Component {
|
|||
const key = index === 0 ? "list" : TASKS[index - 1];
|
||||
const view = this.views[key] = cc.instantiate(prefab); view.parent = this.pages;
|
||||
const content = view.getChildByName("Content") || view;
|
||||
content.getChildByName("Close").on(cc.Node.EventType.TOUCH_END, () => {
|
||||
content.getChildByName("Close").on(cc.Node.EventType.TOUCH_END, (event: cc.Event) => {
|
||||
if (this.closing || this.busy) return;
|
||||
vibrateHomeButton(event);
|
||||
if (key === "list") this.closeWithAnimation();
|
||||
else { this.page = "list"; this.message = ""; this.render(); }
|
||||
});
|
||||
if (key === "list") TASKS.forEach((task, i) => {
|
||||
const row = view.getChildByName("Task" + i);
|
||||
row.getChildByName("View").on(cc.Node.EventType.TOUCH_END, () => {
|
||||
row.getChildByName("View").on(cc.Node.EventType.TOUCH_END, (event: cc.Event) => {
|
||||
if (this.busy || this.closing) return;
|
||||
vibrateHomeButton(event);
|
||||
this.page = task; this.message = ""; this.render();
|
||||
});
|
||||
row.getChildByName("Claim").on(cc.Node.EventType.TOUCH_END, () => this.claim(task));
|
||||
row.getChildByName("Claim").on(cc.Node.EventType.TOUCH_END, (event: cc.Event) => {
|
||||
this.claim(task, event);
|
||||
});
|
||||
});
|
||||
});
|
||||
this.unsubscribe = host.subscribe(() => { if (!this.busy) this.render(); });
|
||||
|
|
@ -155,8 +160,9 @@ export default class MiniProgramBenefitsPanel extends cc.Component {
|
|||
cc.tween(overlay).delay(1.08).call(() => { ready = true; }).start();
|
||||
});
|
||||
}
|
||||
private async claim(task: string): Promise<void> {
|
||||
private async claim(task: string, event?: cc.Event): Promise<void> {
|
||||
if (this.closing || this.busy || this.claiming) return;
|
||||
vibrateHomeButton(event);
|
||||
const token = this.generation;
|
||||
this.claiming = true; this.busy = true; this.message = "正在领取…"; this.render();
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import SevenDayGiftRuntime from "./SevenDayGiftRuntime";
|
|||
import ActivityPopupAnimator, { ActivityPopupAnimationType } from "../Script/ActivityPopupAnimator";
|
||||
import RewardClaimEffect from "../reward_claim_effect/RewardClaimEffect";
|
||||
import SevenDayHitTest from "./SevenDayHitTest";
|
||||
import { vibrateHomeButton } from "../Script/module/Tool/HomeButtonVibration";
|
||||
|
||||
const { ccclass, property } = cc._decorator;
|
||||
const TOTAL_DAYS = 7;
|
||||
|
|
@ -170,7 +171,7 @@ export default class SevenDayGift extends cc.Component {
|
|||
while (hit && hit !== this.panel) {
|
||||
const index = this.cards.indexOf(hit);
|
||||
if (index >= 0) {
|
||||
this.onCardClicked(index + 1);
|
||||
this.onCardClicked(index + 1, event);
|
||||
return;
|
||||
}
|
||||
hit = hit.parent;
|
||||
|
|
@ -234,7 +235,7 @@ export default class SevenDayGift extends cc.Component {
|
|||
}
|
||||
|
||||
// 只允许领取当前可领天数,其他卡片点击无效。
|
||||
private onCardClicked(day: number) {
|
||||
private onCardClicked(day: number, event?: cc.Event) {
|
||||
const reward = this.findReward(day);
|
||||
if (reward && reward.claimed) {
|
||||
return;
|
||||
|
|
@ -244,6 +245,7 @@ export default class SevenDayGift extends cc.Component {
|
|||
}
|
||||
if (this.claiming || !this.claimHandler) return;
|
||||
|
||||
vibrateHomeButton(event);
|
||||
this.requestCurrentReward();
|
||||
}
|
||||
|
||||
|
|
|
|||
145
tools/test-home-button-vibration.cjs
Normal file
145
tools/test-home-button-vibration.cjs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const ts = require('typescript');
|
||||
|
||||
function evaluate(source, globals) {
|
||||
return vm.runInNewContext(ts.transpileModule(source, {
|
||||
compilerOptions: { target: ts.ScriptTarget.ES2017, module: ts.ModuleKind.CommonJS },
|
||||
}).outputText, globals);
|
||||
}
|
||||
|
||||
function method(file, name, globals) {
|
||||
const source = ts.createSourceFile(file, fs.readFileSync(file, 'utf8'), ts.ScriptTarget.Latest, true);
|
||||
let found;
|
||||
function visit(node) {
|
||||
if (ts.isMethodDeclaration(node) && node.name.getText(source) === name) found = node;
|
||||
else ts.forEachChild(node, visit);
|
||||
}
|
||||
visit(source);
|
||||
assert.ok(found, `${file}: ${name}`);
|
||||
return evaluate(`({ ${found.getText(source).replace(/^private\s+/, '')} }).${name};`, globals);
|
||||
}
|
||||
|
||||
function fixture() {
|
||||
const state = { scene: { name: 'HomeScene' }, pulses: [], warnings: [] };
|
||||
const cc = { Button: 'Button', Node: { EventType: { TOUCH_END: 'touchend' } },
|
||||
director: { getScene: () => state.scene }, fx: { GameConfig: { GM_INFO: { vibrateOpen: true } } } };
|
||||
const wx = { vibrateShort: options => state.pulses.push(options.type) };
|
||||
cc.fx.GameTool = { setVibrate: method('assets/Script/module/Tool/GameTool.ts', 'setVibrate', { cc, wx }) };
|
||||
const globals = { cc, exports: {}, console: { warn: (...args) => state.warnings.push(args) } };
|
||||
evaluate(fs.readFileSync('assets/Script/module/Tool/HomeButtonVibration.ts', 'utf8'), globals);
|
||||
Object.assign(globals, globals.exports);
|
||||
const node = (parent = null, button = null) => ({ parent, getComponent: () => button });
|
||||
const root = node();
|
||||
const button = { interactable: true, enabledInHierarchy: true, _pressed: true };
|
||||
const target = node(root, button);
|
||||
const event = { target, currentTarget: root };
|
||||
return { state, cc, wx, globals, node, root, button, event, ...globals.exports };
|
||||
}
|
||||
|
||||
test('existing and late-mounted bundle buttons vibrate once, including child hit targets', () => {
|
||||
const f = fixture();
|
||||
f.onHomeButtonTouchEnd(f.event);
|
||||
f.vibrateHomeButton(f.event);
|
||||
assert.deepEqual(f.state.pulses, ['light']);
|
||||
const popup = f.node(f.root);
|
||||
const dynamicButton = f.node(popup, { ...f.button });
|
||||
f.onHomeButtonTouchEnd({ target: f.node(dynamicButton), currentTarget: f.root });
|
||||
assert.deepEqual(f.state.pulses, ['light', 'light']);
|
||||
});
|
||||
|
||||
test('disabled buttons, cancelled presses and empty backgrounds do not vibrate', () => {
|
||||
for (const property of ['interactable', 'enabledInHierarchy', '_pressed']) {
|
||||
const f = fixture(); f.button[property] = false;
|
||||
f.onHomeButtonTouchEnd(f.event);
|
||||
assert.equal(f.state.pulses.length, 0, property);
|
||||
}
|
||||
const f = fixture();
|
||||
f.onHomeButtonTouchEnd({ target: f.node(f.root), currentTarget: f.root });
|
||||
assert.equal(f.state.pulses.length, 0);
|
||||
});
|
||||
|
||||
test('reused event objects vibrate on each new dispatch, but never twice per dispatch', () => {
|
||||
const f = fixture();
|
||||
for (let i = 0; i < 2; i++) {
|
||||
f.onHomeButtonTouchEnd(f.event);
|
||||
f.vibrateHomeButton(f.event);
|
||||
assert.equal(f.state.pulses.length, i + 1);
|
||||
}
|
||||
});
|
||||
|
||||
test('respects the vibration switch and leaves GameScene and other scenes untouched', () => {
|
||||
const f = fixture();
|
||||
f.cc.fx.GameConfig.GM_INFO.vibrateOpen = false;
|
||||
f.onHomeButtonTouchEnd(f.event);
|
||||
assert.equal(f.state.pulses.length, 0);
|
||||
f.cc.fx.GameConfig.GM_INFO.vibrateOpen = true;
|
||||
for (const scene of [{ name: 'GameScene' }, { name: 'LoadScene' }, null]) {
|
||||
f.state.scene = scene;
|
||||
f.onHomeButtonTouchEnd(f.event);
|
||||
assert.equal(f.vibrateHomeButton({ target: f.event.target }), false);
|
||||
assert.equal(f.state.pulses.length, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test('legacy Home and Career callbacks do not double vibrate or alter popup request cancellation', () => {
|
||||
const f = fixture();
|
||||
const homeClick = method('assets/Script/JiaZai.ts', 'vibrateButtonClick', f.globals);
|
||||
const careerClick = method('assets/career/script/CareerManager2.ts', 'vibrateButtonClick', f.globals);
|
||||
const home = { homePopupOpenRequest: 7 };
|
||||
f.onHomeButtonTouchEnd(f.event);
|
||||
homeClick.call(home, f.event);
|
||||
careerClick(f.event);
|
||||
assert.equal(home.homePopupOpenRequest, 8);
|
||||
assert.deepEqual(f.state.pulses, ['light']);
|
||||
homeClick.call(home);
|
||||
assert.equal(home.homePopupOpenRequest, 8);
|
||||
f.state.scene = { name: 'GameScene' };
|
||||
careerClick({});
|
||||
assert.deepEqual(f.state.pulses, ['light', 'light'], 'preserves the legacy callback outside Home');
|
||||
});
|
||||
|
||||
test('vibration API errors do not interrupt click handling or cause a duplicate attempt', () => {
|
||||
const f = fixture(); let attempts = 0;
|
||||
f.wx.vibrateShort = () => { attempts++; throw Error('unavailable'); };
|
||||
assert.doesNotThrow(() => f.onHomeButtonTouchEnd(f.event));
|
||||
assert.doesNotThrow(() => f.vibrateHomeButton(f.event));
|
||||
assert.equal(attempts, 1);
|
||||
assert.equal(f.state.warnings.length, 1);
|
||||
});
|
||||
|
||||
test('Home component registers capture on enable and removes the same listener on disable', () => {
|
||||
const f = fixture(); const calls = [];
|
||||
const home = { node: { on: (...args) => calls.push(['on', ...args]), off: (...args) => calls.push(['off', ...args]) } };
|
||||
method('assets/Script/JiaZai.ts', 'onEnable', f.globals).call(home);
|
||||
method('assets/Script/JiaZai.ts', 'onDisable', f.globals).call(home);
|
||||
assert.equal(calls[0][0], 'on'); assert.equal(calls[1][0], 'off');
|
||||
assert.deepEqual(calls[0].slice(1), ['touchend', f.onHomeButtonTouchEnd, home, true]);
|
||||
assert.deepEqual(calls[0].slice(1), calls[1].slice(1));
|
||||
});
|
||||
|
||||
test('touch-only Jungle close vibrates once and preserves confirmation guarding', () => {
|
||||
const f = fixture(); let closes = 0;
|
||||
const close = method('assets/jungle_treasure/script/JungleOver.ts', 'onClose', f.globals);
|
||||
const panel = { confirming: false, node: { emit: () => closes++ } };
|
||||
const event = { target: f.node(f.root), currentTarget: f.root };
|
||||
f.onHomeButtonTouchEnd(event);
|
||||
assert.equal(f.state.pulses.length, 0);
|
||||
close.call(panel, event); close.call(panel, event);
|
||||
assert.equal(closes, 1); assert.deepEqual(f.state.pulses, ['light']);
|
||||
});
|
||||
|
||||
test('seven-day cards only vibrate when the reward can be claimed', () => {
|
||||
const f = fixture(); let claims = 0;
|
||||
const click = method('assets/seven_day_gift/SevenDayGift.ts', 'onCardClicked', f.globals);
|
||||
const panel = { findReward: () => ({ claimed: false }), info: { canClaim: true, nextRewardDay: 2 },
|
||||
claiming: false, claimHandler() {}, requestCurrentReward: () => claims++ };
|
||||
click.call(panel, 1, {});
|
||||
assert.equal(f.state.pulses.length, 0);
|
||||
click.call(panel, 2, {});
|
||||
assert.equal(claims, 1); assert.deepEqual(f.state.pulses, ['light']);
|
||||
panel.claiming = true; click.call(panel, 2, {});
|
||||
assert.equal(claims, 1); assert.equal(f.state.pulses.length, 1);
|
||||
});
|
||||
|
|
@ -73,7 +73,9 @@ function inflate(data, id) {
|
|||
for (const ref of raw._components || []) {
|
||||
const component = data[ref.__id__];
|
||||
node.components[component.__type__] = { ...component, updateAlignment() {}, setState() {} };
|
||||
if (component.__type__ === 'cc.Sprite') node.components[component.__type__].sharedMaterials = component._materials;
|
||||
if (component.__type__ === 'cc.Sprite') {
|
||||
node.components[component.__type__].getMaterial = index => component._materials[index] || null;
|
||||
}
|
||||
}
|
||||
for (const ref of raw._children || []) inflate(data, ref.__id__).parent = node;
|
||||
return node;
|
||||
|
|
@ -144,6 +146,9 @@ for (const [level, name, amount] of [[8, 'destroyBtn', 'hammerAmount'], [11, 'ti
|
|||
map.showPropGuideMask(level);
|
||||
assert.equal(map.propGuideMask, guide);
|
||||
const activeButton = buttons.find(button => button.active);
|
||||
const rippleSprite = gameNode.getChildByName('Bottom').getChildByName('rippleShrink').getComponent('cc.Sprite');
|
||||
assert.ok(rippleSprite.getMaterial(0));
|
||||
assert.equal(activeButton.getChildByName('hand').getComponent('GuideHand').rippleMaterial, rippleSprite.getMaterial(0));
|
||||
assert.equal(activeButton.getComponent('cc.Button').clickEvents[0].customEventData, 'guide');
|
||||
activeButton.click();
|
||||
assert.equal(map.propGuideMask, null);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ function environment() {
|
|||
if(name==='../LoadingCatAnimation')return {default:{play:node=>{node.active=true;return true},stop:node=>{node.active=false}}};
|
||||
if(name==='./MiniProgramBenefitsReward')return {default:class {request(){throw Error('Unexpected network adapter call in UI test');}}};
|
||||
if(name.endsWith('/MiniProgramBenefitsBridge'))return {default:Bridge};
|
||||
if(name.endsWith('/HomeButtonVibration'))return {vibrateHomeButton() {}};
|
||||
throw new Error('Unexpected dependency: '+name);
|
||||
},cc,wx,window:{wx},console,setTimeout,clearTimeout,Promise,Set},{filename:file});
|
||||
return module.exports.default;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user