fix boss tank cross brick

This commit is contained in:
jakciehan
2026-06-07 22:08:00 +08:00
parent c3a4aa8f15
commit e4140f073f
29 changed files with 2689 additions and 1240 deletions
+2 -2
View File
@@ -262,7 +262,7 @@ class CollisionManager {
* @private
*/
_isPositionValid(tank, x, y) {
const hs = tank.halfSize;
const hs = tank.colliderHalfSize;
const left = x - hs;
const top = y - hs;
const right = x + hs;
@@ -279,7 +279,7 @@ class CollisionManager {
}
// Terrain collision check
if (this._map.rectCollidesWithTerrain(left, top, tank.size, tank.size)) {
if (this._map.rectCollidesWithTerrain(left, top, tank.colliderSize, tank.colliderSize)) {
return false;
}
+33 -10
View File
@@ -46,6 +46,10 @@ class NetworkManager {
// Generate a unique player ID
this._playerId = this._generatePlayerId();
// Connection mutex: queue of pending connect() callers
/** @type {Array<{resolve: Function, timeoutMs: number}>} */
this._connectQueue = [];
}
/**
@@ -56,8 +60,15 @@ class NetworkManager {
*/
connect(serverUrl, timeoutMs = 10000) {
return new Promise((resolve) => {
if (this._connected || this._connecting) {
resolve(this._connected);
// If already connected, resolve immediately
if (this._connected) {
resolve(true);
return;
}
// If another connect() is in progress, queue this one and wait
if (this._connecting) {
this._connectQueue.push({ resolve, timeoutMs });
return;
}
@@ -85,6 +96,13 @@ class NetworkManager {
console.warn('[NetworkManager] connect() failed:', reason || 'unknown');
}
resolve(ok);
// Resolve all queued connect() calls with the same result
const queue = this._connectQueue;
this._connectQueue = [];
for (const pending of queue) {
pending.resolve(ok);
}
};
// Connection timeout guard (e.g. DNS/TLS hang on cellular).
@@ -96,7 +114,6 @@ class NetworkManager {
this._ws = wx.connectSocket({
url: serverUrl,
header: { 'content-type': 'application/json' },
// Surface wx.connectSocket API-level failures (invalid url / domain not whitelisted / etc.)
success: (res) => { console.log('[NetworkManager] wx.connectSocket invoked:', res && res.errMsg); },
fail: (err) => {
console.error('[NetworkManager] wx.connectSocket API failed:', JSON.stringify(err));
@@ -119,16 +136,13 @@ class NetworkManager {
});
this._ws.onError((err) => {
// Log as much context as possible; wx error objects vary across platforms.
console.error('[NetworkManager] WebSocket error:',
(err && (err.errMsg || err.message)) || err,
'url=', serverUrl);
this._emit('error', err);
// If the error arrives before we ever got onOpen, treat it as a connect failure.
if (!this._connected) {
finish(false, `onError before open: ${err && (err.errMsg || err.message)}`);
} else {
// Runtime error on an established connection — let onClose handle reconnection.
this._connecting = false;
}
});
@@ -144,13 +158,11 @@ class NetworkManager {
this._stopHeartbeat();
this._emit('disconnected', { code, reason });
// If onClose arrives before onOpen, this is a connect failure.
if (!wasConnected) {
finish(false, `onClose before open: code=${code} reason=${reason}`);
return;
}
// Auto-reconnect only for drops on an already-established connection.
if (this._shouldReconnect && this._reconnectAttempts < this._maxReconnectAttempts) {
this._attemptReconnect();
}
@@ -183,6 +195,13 @@ class NetworkManager {
this._connecting = false;
this._roomId = null;
this._playerSlot = 0;
// Clear connect queue
const queue = this._connectQueue;
this._connectQueue = [];
for (const pending of queue) {
pending.resolve(false);
}
}
/**
@@ -272,10 +291,12 @@ class NetworkManager {
/**
* Create a new team for 3v3 mode.
* @param {string} [battleMode='3v3'] - Battle mode ('1v1', '2v2', '3v3').
*/
createTeam() {
createTeam(battleMode = '3v3') {
this.send(NET_MSG.CREATE_TEAM, {
playerId: this._playerId,
battleMode,
});
}
@@ -350,10 +371,12 @@ class NetworkManager {
/**
* Start solo matchmaking for 3v3.
* @param {string} [battleMode='3v3'] - Battle mode ('1v1', '2v2', '3v3').
*/
soloMatch() {
soloMatch(battleMode = '3v3') {
this.send(NET_MSG.SOLO_MATCH, {
playerId: this._playerId,
battleMode,
});
}
+142 -2
View File
@@ -11,6 +11,8 @@ class ShareManager {
imageUrl: '',
query: '',
};
// Cached temp file path from last canvas capture
this._cachedImageUrl = '';
// Register share menu and callback ONCE at startup.
// The callback reads this._shareContent dynamically so it always
@@ -28,7 +30,7 @@ class ShareManager {
console.log('[ShareManager] onShareAppMessage callback, query:', this._shareContent.query);
return {
title: this._shareContent.title || '坦克大战 - 一起来战斗吧!',
imageUrl: this._shareContent.imageUrl || '',
imageUrl: this._shareContent.imageUrl || this._cachedImageUrl || '',
query: this._shareContent.query || '',
};
});
@@ -39,6 +41,137 @@ class ShareManager {
}
}
/**
* Generate a share image from the current game canvas.
* Outputs a 5:4 portrait-ratio image so WeChat's share card shows
* the full screen without cropping.
*
* Strategy:
* 1. Try offscreen canvas redraw (ideal — contain-fit into 5:4)
* 2. Fallback: direct capture with portrait dest dimensions
*
* @param {function} [callback] - Called with (tempFilePath) on success.
*/
generateShareImage(callback) {
var self = this;
try {
const srcCanvas = GameGlobal && GameGlobal.canvas;
if (!srcCanvas || typeof wx === 'undefined' || !wx.canvasToTempFilePath) {
if (callback) callback();
return;
}
const DPR = GameGlobal.DEVICE_PIXEL_RATIO || 1;
const SHARE_W = 500;
const SHARE_H = 625; // 5:4 portrait for WeChat share card
const srcW = srcCanvas.width / DPR;
const srcH = srcCanvas.height / DPR;
// --- Try offscreen canvas approach first ---
var offCanvas = null;
try {
offCanvas = wx.createCanvas && wx.createCanvas();
} catch (e2) {
offCanvas = null;
}
if (offCanvas) {
try {
offCanvas.width = SHARE_W;
offCanvas.height = SHARE_H;
const offCtx = offCanvas.getContext('2d');
if (offCtx) {
// Dark background
offCtx.fillStyle = '#0a0e1a';
offCtx.fillRect(0, 0, SHARE_W, SHARE_H);
// Contain-fit source into portrait frame
const srcRatio = srcW / srcH;
const dstRatio = SHARE_W / SHARE_H;
var drawW, drawH, dx, dy;
if (srcRatio > dstRatio) {
drawW = SHARE_W;
drawH = SHARE_W / srcRatio;
dx = 0;
dy = (SHARE_H - drawH) / 2;
} else {
drawH = SHARE_H;
drawW = SHARE_H * srcRatio;
dx = (SHARE_W - drawW) / 2;
dy = 0;
}
offCtx.drawImage(srcCanvas, dx, dy, drawW, drawH);
// Export offscreen canvas
wx.canvasToTempFilePath({
canvas: offCanvas,
width: SHARE_W,
height: SHARE_H,
destWidth: SHARE_W,
destHeight: SHARE_H,
fileType: 'png',
quality: 0.92,
success: function(res) {
if (res.tempFilePath) {
console.log('[ShareManager] Share image (offscreen):', res.tempFilePath);
self._cachedImageUrl = res.tempFilePath;
if (callback) callback(res.tempFilePath);
} else {
self._fallbackDirectCapture(srcCanvas, srcW, srcH, SHARE_W, SHARE_H, callback);
}
},
fail: function() {
self._fallbackDirectCapture(srcCanvas, srcW, srcH, SHARE_W, SHARE_H, callback);
},
});
return; // done via offscreen path
}
} catch (e3) {
console.warn('[ShareManager] Offscreen canvas draw failed:', e3);
}
}
// --- Fallback: direct canvas capture with portrait output ---
this._fallbackDirectCapture(srcCanvas, srcW, srcH, SHARE_W, SHARE_H, callback);
} catch (e) {
console.warn('[ShareManager] generateShareImage error:', e);
if (callback) callback();
}
}
/**
* Fallback: export main canvas directly with portrait dest dimensions.
*/
_fallbackDirectCapture(canvas, srcW, srcH, outW, outH, callback) {
try {
wx.canvasToTempFilePath({
canvas: canvas,
width: srcW,
height: srcH,
destWidth: outW,
destHeight: outH,
fileType: 'png',
quality: 0.92,
success: function(res) {
if (res.tempFilePath) {
console.log('[ShareManager] Share image (direct):', res.tempFilePath);
this._cachedImageUrl = res.tempFilePath;
}
if (callback) callback(res && res.tempFilePath ? res.tempFilePath : undefined);
}.bind(this),
fail: function(err) {
console.warn('[ShareManager] Direct canvasToTempFilePath failed:', err);
if (callback) callback();
},
});
} catch (e) {
console.warn('[ShareManager] _fallbackDirectCapture error:', e);
if (callback) callback();
}
}
/**
* Update open data for friend ranking.
* @param {number} score
@@ -90,6 +223,10 @@ class ShareManager {
setShareContent(opts) {
this._shareContent = opts || {};
console.log('[ShareManager] Share content updated:', JSON.stringify(this._shareContent));
// Auto-generate canvas screenshot as share image if none provided
if (!this._shareContent.imageUrl && !this._cachedImageUrl) {
this.generateShareImage();
}
// Re-register callback to ensure WeChat picks up the new content
this._refreshShareCallback();
}
@@ -106,6 +243,9 @@ class ShareManager {
// Update passive share callback (right-corner ··· menu fallback)
this.setShareContent(data);
// Ensure we have a share image — generate synchronously-style via cache
const imageUrl = data.imageUrl || this._cachedImageUrl || '';
// Directly invoke wx.shareAppMessage() to open the friend-picker panel.
// This is permitted because triggerShare is called from a touchstart handler.
try {
@@ -113,7 +253,7 @@ class ShareManager {
console.log('[ShareManager] Calling wx.shareAppMessage with query:', data.query);
wx.shareAppMessage({
title: data.title || '',
imageUrl: data.imageUrl || '',
imageUrl: imageUrl,
query: data.query || '',
});
}