// 地图编辑测试场景 // 防御性编程原则: // 1. 始终检查对象和方法是否存在 // 2. 检查参数类型和范围 // 3. 使用 try-catch 块捕获可能的异常 // 4. 为所有容器和对象使用明确的命名 // 5. 避免使用相同的键名,防止冲突 // 6. 始终检查纹理是否存在 // 7. 在使用方法前检查方法是否存在 // 8. 添加详细的日志和错误信息 // 9. 为复杂逻辑添加注释 // 10. 使用类型检查和断言 export default class MapEditTestScene extends Phaser.Scene { constructor() { super({ key: 'MapEditTestScene' }); this.tileSize = 32; // 瓦片大小 this.selectedTile = null; // 当前选中的瓦片 this.tileSet = []; // 瓦片集合 this.mapLayer = null; // 地图层 this.mapWidth = 50; // 地图宽度(瓦片数量) this.mapHeight = 50; // 地图高度(瓦片数量) this.isDragging = false; // 是否正在拖拽地图 this.lastDragPosition = { x: 0, y: 0 }; // 上次拖拽位置 this.zoomLevel = 1; // 缩放级别 } preload() { // 加载瓦片资源 this.loadTileAssets(); // 显示加载进度 this.createLoadingUI(); } create() { console.log('开始创建地图编辑场景'); // 物理世界和相机边界 const worldSize = this.mapWidth * this.tileSize; this.physics.world.setBounds(0, 0, worldSize, worldSize); this.cameras.main.setBounds(0, 0, worldSize, worldSize); // 设置背景色 this.cameras.main.setBackgroundColor('#222222'); // 修复:移除可能有问题的defaultFilterMode设置,使用资源加载完成后设置过滤模式 // 创建地图编辑区域 console.log('创建地图编辑区域'); this.createMapArea(); // 创建瓦片选择区域 console.log('创建瓦片选择区域'); this.createTileSelectionArea(); // 检查资源是否已加载 if (this.assetsLoaded) { console.log('资源已加载,调用updateTileSelection'); this.updateTileSelection(); } else { console.log('资源尚未加载,等待加载完成'); } // 创建控制面板 console.log('创建控制面板'); this.createControlPanel(); // 设置输入处理 console.log('设置输入处理'); this.setupInput(); // 显示提示信息 console.log('显示提示信息'); this.showInstructions(); console.log('地图编辑场景创建完成'); } /** * 加载瓦片资源 */ loadTileAssets() { console.log('开始加载瓦片资源'); // 加载地形瓦片 this.tileCount = 32; for (let i = 0; i < this.tileCount; i++) { // 使用唯一的键名,避免与其他场景冲突 const tileKey = `mapedit-tile-${i}`; const tilePath = `assets/PNG/Tiles/Tiles/tile_${String(i).padStart(4, '0')}.png`; console.log(`加载瓦片: ${tileKey} -> ${tilePath}`); // 修复:使用正确的方式加载瓦片,不使用setFilter链式调用 this.load.image(tileKey, tilePath); } // 加载进度回调 this.load.on('progress', (progress) => { console.log(`资源加载进度: ${Math.round(progress * 100)}%`); }); // 文件加载完成回调 this.load.on('filecomplete', (key, type, data) => { console.log(`文件加载完成: ${key} (${type})`); }); // 加载完成回调 this.load.on('complete', () => { console.log('所有资源加载完成!'); this.assetsLoaded = true; this.loadingText.setText('资源加载完成!'); // 修复:在资源加载完成后为每个纹理设置NEAREST过滤模式 for (let i = 0; i < this.tileCount; i++) { const tileKey = `mapedit-tile-${i}`; // 检查纹理是否存在 if (this.textures.exists(tileKey)) { // 获取纹理并设置过滤模式 const texture = this.textures.get(tileKey); // 设置所有帧的过滤模式为NEAREST texture.setFilter(Phaser.Textures.FilterMode.NEAREST); console.log(`设置纹理 ${tileKey} 的过滤模式为NEAREST`); } } // 初始化瓦片集合 this.tileSet = []; for (let i = 0; i < this.tileCount; i++) { this.tileSet.push(`mapedit-tile-${i}`); } console.log(`瓦片集合初始化完成,共 ${this.tileSet.length} 个瓦片键名`); // 只有在tileSelectionContainer初始化后才调用updateTileSelection if (this.tileSelectionContainer) { console.log('tileSelectionContainer已初始化,调用updateTileSelection'); this.updateTileSelection(); } else { console.log('tileSelectionContainer尚未初始化,等待create方法调用'); } }); } /** * 创建加载UI */ createLoadingUI() { // 加载文本 this.loadingText = this.add.text(10, 10, '加载瓦片资源...', { fontSize: '16px', fill: '#ffffff', fontFamily: 'Arial' }); this.loadingText.setScrollFactor(0, 0); this.loadingText.setDepth(100); // 加载进度条 this.progressBar = this.add.rectangle(10, 40, 300, 20, 0x555555); this.progressBar.setOrigin(0, 0); this.progressBar.setScrollFactor(0, 0); this.progressBar.setDepth(100); this.progressFill = this.add.rectangle(10, 40, 0, 20, 0x00ff00); this.progressFill.setOrigin(0, 0); this.progressFill.setScrollFactor(0, 0); this.progressFill.setDepth(100); // 更新进度 this.load.on('progress', (progress) => { this.progressFill.width = progress * 300; this.loadingText.setText(`加载瓦片资源... ${Math.round(progress * 100)}%`); }); } /** * 创建地图编辑区域 */ createMapArea() { // 创建地图容器 this.mapContainer = this.add.container(0, 0); this.mapContainer.setDepth(1); // 创建背景网格 this.createGrid(); // 创建地图层(用于放置瓦片) this.mapLayer = this.add.container(0, 0); this.mapContainer.add(this.mapLayer); // 初始化空地图 this.mapData = []; for (let y = 0; y < this.mapHeight; y++) { this.mapData[y] = []; for (let x = 0; x < this.mapWidth; x++) { this.mapData[y][x] = -1; // -1表示空瓦片 } } } /** * 创建背景网格 */ createGrid() { // 创建网格线 const gridGraphics = this.add.graphics(); gridGraphics.lineStyle(1, 0x444444, 0.5); // 绘制垂直线 for (let x = 0; x <= this.mapWidth; x++) { gridGraphics.moveTo(x * this.tileSize, 0); gridGraphics.lineTo(x * this.tileSize, this.mapHeight * this.tileSize); } // 绘制水平线 for (let y = 0; y <= this.mapHeight; y++) { gridGraphics.moveTo(0, y * this.tileSize); gridGraphics.lineTo(this.mapWidth * this.tileSize, y * this.tileSize); } this.mapContainer.add(gridGraphics); } /** * 创建瓦片选择区域 */ createTileSelectionArea() { // 瓦片选择区域背景 const tilePanel = this.add.rectangle(10, 70, 220, 400, 0x111111, 0.8); tilePanel.setOrigin(0, 0); tilePanel.setScrollFactor(0, 0); tilePanel.setDepth(10); // 瓦片选择区域标题 this.add.text(20, 80, '瓦片选择', { fontSize: '16px', fill: '#ffffff', fontFamily: 'Arial', fontWeight: 'bold' }).setScrollFactor(0, 0).setDepth(11); // 瓦片选择容器 - 注意:这是一个Container对象,可以使用removeAll方法 this.tileSelectionContainer = this.add.container(20, 110); this.tileSelectionContainer.setScrollFactor(0, 0); this.tileSelectionContainer.setDepth(11); // 瓦片选择网格 this.tileSelectionGrid = this.add.grid(0, 0, 200, 360, 64, 64, 0x333333, 0.5, 0x444444, 0.5); this.tileSelectionContainer.add(this.tileSelectionGrid); // 当前选中瓦片显示 - 改为使用Container容器,而不是Rectangle,这样可以使用removeAll方法 this.selectedTileContainer = this.add.container(20, 490); this.selectedTileContainer.setScrollFactor(0, 0); this.selectedTileContainer.setDepth(11); // 添加选中瓦片背景 const selectedTileBg = this.add.rectangle(0, 0, 64, 64, 0x333333); selectedTileBg.setOrigin(0, 0); selectedTileBg.setDepth(10); this.selectedTileContainer.add(selectedTileBg); this.add.text(20, 470, '当前选中瓦片:', { fontSize: '14px', fill: '#ffffff', fontFamily: 'Arial' }).setScrollFactor(0, 0).setDepth(11); } /** * 创建控制面板 */ createControlPanel() { // 控制面板背景 const controlPanel = this.add.rectangle(this.sys.game.config.width - 210, 10, 200, 150, 0x111111, 0.8); controlPanel.setOrigin(0, 0); controlPanel.setScrollFactor(0, 0); controlPanel.setDepth(10); // 控制面板标题 this.add.text(this.sys.game.config.width - 200, 20, '控制面板', { fontSize: '16px', fill: '#ffffff', fontFamily: 'Arial', fontWeight: 'bold' }).setScrollFactor(0, 0).setDepth(11); // 创建按钮 this.createButton(this.sys.game.config.width - 200, 50, 180, 30, '清空地图', () => { this.clearMap(); }); this.createButton(this.sys.game.config.width - 200, 90, 180, 30, '保存地图', () => { this.saveMap(); }); this.createButton(this.sys.game.config.width - 200, 130, 180, 30, '加载地图', () => { this.loadMap(); }); } /** * 创建按钮 */ createButton(x, y, width, height, text, callback) { // 按钮背景 const button = this.add.rectangle(x, y, width, height, 0x333333); button.setOrigin(0, 0); button.setScrollFactor(0, 0); button.setDepth(10); button.setInteractive({ useHandCursor: true }); // 按钮文本 const buttonText = this.add.text(x + 10, y + 5, text, { fontSize: '14px', fill: '#ffffff', fontFamily: 'Arial' }); buttonText.setScrollFactor(0, 0); buttonText.setDepth(11); // 按钮事件 button.on('pointerdown', callback); button.on('pointerover', () => { button.setFillStyle(0x555555); }); button.on('pointerout', () => { button.setFillStyle(0x333333); }); } /** * 更新瓦片选择显示 */ updateTileSelection() { console.log('开始更新瓦片选择显示'); console.log(`当前瓦片集合数量: ${this.tileSet.length}`); // 清空当前瓦片选择 console.log('清空当前瓦片选择'); this.tileSelectionContainer.removeAll(true); // 重新添加网格 console.log('重新添加瓦片选择网格'); this.tileSelectionGrid = this.add.grid(0, 0, 200, 360, 64, 64, 0x333333, 0.5, 0x444444, 0.5); this.tileSelectionContainer.add(this.tileSelectionGrid); // 添加瓦片到选择区域 console.log('开始添加瓦片到选择区域'); for (let i = 0; i < this.tileSet.length; i++) { const tileX = (i % 3) * 64; const tileY = Math.floor(i / 3) * 64; const tileKey = this.tileSet[i]; console.log(`添加瓦片 ${i}: ${tileKey} 到位置 (${tileX}, ${tileY})`); try { // 检查纹理是否存在 if (this.textures.exists(tileKey)) { console.log(`纹理 ${tileKey} 存在`); // 创建瓦片 const tile = this.add.image(tileX, tileY, tileKey); tile.setOrigin(0, 0); // 修复:根据瓦片实际大小调整缩放比例,确保在选择区域中显示正确大小 const selectionTileSize = 64; // 选择区域中每个瓦片的大小 tile.setScale(selectionTileSize / tile.width); tile.setInteractive({ useHandCursor: true }); this.tileSelectionContainer.add(tile); // 瓦片点击事件 tile.on('pointerdown', () => { console.log(`点击了瓦片 ${i}: ${tileKey}`); this.selectTile(i); }); tile.on('pointerover', () => { // 高亮显示 tile.setTint(0x888888); }); tile.on('pointerout', () => { // 取消高亮 tile.clearTint(); }); console.log(`成功添加瓦片 ${i}: ${tileKey}`); } else { console.error(`纹理 ${tileKey} 不存在!`); } } catch (error) { console.error(`加载瓦片${i}失败:`, error); } } console.log('瓦片选择区域已更新,瓦片数量:', this.tileSet.length); } /** * 选择瓦片 * @param {number} tileIndex - 瓦片索引 */ selectTile(tileIndex) { // 防御性编程:检查参数类型 if (typeof tileIndex !== 'number') { console.error('selectTile: tileIndex 必须是数字类型'); return; } this.selectedTile = tileIndex; console.log(`选择瓦片索引: ${tileIndex}`); // 更新选中瓦片显示 // 注意:使用Container对象的removeAll方法,而不是Rectangle对象 try { // 防御性编程:检查容器是否存在 if (this.selectedTileContainer && typeof this.selectedTileContainer.removeAll === 'function') { this.selectedTileContainer.removeAll(true); } else { console.error('selectTile: selectedTileContainer 不存在或没有 removeAll 方法'); return; } } catch (error) { console.error('selectTile: 清空选中瓦片容器失败:', error); return; } if (this.selectedTile !== null && this.selectedTile !== -1) { try { // 防御性编程:检查瓦片索引是否在有效范围内 if (this.selectedTile >= 0 && this.selectedTile < this.tileSet.length) { const tileKey = this.tileSet[this.selectedTile]; // 防御性编程:检查纹理是否存在 if (this.textures.exists(tileKey)) { // 创建瓦片 - 注意:添加到Container容器中,而不是直接添加到场景 const tile = this.add.image(0, 0, tileKey).setScale(2); tile.setOrigin(0, 0); this.selectedTileContainer.add(tile); // 添加选中边框 const border = this.add.rectangle(0, 0, 64, 64, 0x00ff00, 0); border.setOrigin(0, 0); border.setStrokeStyle(2, 0x00ff00, 1); this.selectedTileContainer.add(border); } else { console.error(`selectTile: 纹理 ${tileKey} 不存在`); } } else { console.error(`selectTile: 瓦片索引 ${this.selectedTile} 超出范围 (0-${this.tileSet.length-1})`); } } catch (error) { console.error('selectTile: 显示选中瓦片失败:', error); } } else if (this.selectedTile === -1) { // 显示删除模式 try { const deleteText = this.add.text(0, 20, '删除模式', { fontSize: '14px', fill: '#ff0000', fontFamily: 'Arial', fontWeight: 'bold' }); deleteText.setOrigin(0, 0); this.selectedTileContainer.add(deleteText); } catch (error) { console.error('selectTile: 显示删除模式失败:', error); } } } /** * 设置输入处理 */ setupInput() { // 初始化拖动绘制相关变量 this.isDrawing = false; this.lastDrawnTile = null; // 鼠标输入 this.input.on('pointerdown', (pointer) => { this.handleMouseDown(pointer); // 开始绘制 this.isDrawing = true; this.lastDrawnTile = null; }); this.input.on('pointerup', (pointer) => { this.handleMouseUp(pointer); // 停止绘制 this.isDrawing = false; this.lastDrawnTile = null; }); this.input.on('pointermove', (pointer) => { this.handleMouseMove(pointer); // 拖动绘制:如果鼠标按下且不是在拖拽地图,则绘制瓦片 if (this.isDrawing && !this.isDragging) { this.placeTile(pointer); } }); // 滚轮缩放 this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => { this.handleWheel(deltaY); }); // 键盘输入 this.setupKeyboardInput(); } /** * 设置键盘输入 */ setupKeyboardInput() { // 空格键返回启动场景 this.input.keyboard.on('keydown-SPACE', () => { this.scene.start('BootScene'); }); // 数字键1-9选择瓦片 for (let i = 0; i < 9; i++) { this.input.keyboard.on(`keydown-${i + 1}`, () => { if (i < this.tileSet.length) { this.selectTile(i); } }); } // D键删除瓦片 this.input.keyboard.on('keydown-D', () => { this.selectedTile = -1; // -1表示删除模式 }); // C键清空地图 this.input.keyboard.on('keydown-C', () => { this.clearMap(); }); // S键保存地图 this.input.keyboard.on('keydown-S', () => { this.saveMap(); }); // L键加载地图 this.input.keyboard.on('keydown-L', () => { this.loadMap(); }); } /** * 处理鼠标按下事件 */ handleMouseDown(pointer) { // 检查是否点击了瓦片选择区域 const tileSelectionRect = new Phaser.Geom.Rectangle(20, 110, 200, 360); if (Phaser.Geom.Rectangle.Contains(tileSelectionRect, pointer.x, pointer.y)) { return; // 点击了瓦片选择区域,不处理地图交互 } // 检查是否点击了控制面板 const controlPanelRect = new Phaser.Geom.Rectangle(this.sys.game.config.width - 210, 10, 200, 150); if (Phaser.Geom.Rectangle.Contains(controlPanelRect, pointer.x, pointer.y)) { return; // 点击了控制面板,不处理地图交互 } // 检查是否点击了当前选中瓦片显示区域 const selectedTileRect = new Phaser.Geom.Rectangle(20, 490, 64, 64); if (Phaser.Geom.Rectangle.Contains(selectedTileRect, pointer.x, pointer.y)) { return; // 点击了选中瓦片显示区域,不处理地图交互 } // 如果点击了地图区域 if (this.selectedTile !== null) { // 放置或删除瓦片 this.placeTile(pointer); } else { // 开始拖拽地图 this.isDragging = true; this.lastDragPosition.x = pointer.x; this.lastDragPosition.y = pointer.y; } } /** * 处理鼠标释放事件 */ handleMouseUp(pointer) { this.isDragging = false; } /** * 处理鼠标移动事件 */ handleMouseMove(pointer) { if (this.isDragging) { // 计算拖拽距离 const deltaX = pointer.x - this.lastDragPosition.x; const deltaY = pointer.y - this.lastDragPosition.y; // 移动相机 this.cameras.main.scrollX -= deltaX / this.zoomLevel; this.cameras.main.scrollY -= deltaY / this.zoomLevel; // 更新上次拖拽位置 this.lastDragPosition.x = pointer.x; this.lastDragPosition.y = pointer.y; } } /** * 处理滚轮事件 */ handleWheel(deltaY) { // 放大 if (deltaY < 0 && this.zoomLevel < 2) { this.zoomLevel += 0.1; } // 缩小 else if (deltaY > 0 && this.zoomLevel > 0.5) { this.zoomLevel -= 0.1; } // 更新相机缩放 this.cameras.main.setZoom(this.zoomLevel); } /** * 放置瓦片 */ placeTile(pointer) { // 将屏幕坐标转换为世界坐标 const worldPoint = this.cameras.main.getWorldPoint(pointer.x, pointer.y); // 计算瓦片坐标 const tileX = Math.floor(worldPoint.x / this.tileSize); const tileY = Math.floor(worldPoint.y / this.tileSize); // 检查瓦片坐标是否在地图范围内 if (tileX < 0 || tileX >= this.mapWidth || tileY < 0 || tileY >= this.mapHeight) { return; } // 检查是否已经绘制过这个位置,避免重复绘制 if (this.lastDrawnTile && this.lastDrawnTile.x === tileX && this.lastDrawnTile.y === tileY) { return; } // 删除瓦片模式 if (this.selectedTile === -1) { this.removeTile(tileX, tileY); } else { // 放置瓦片 const tileKey = this.tileSet[this.selectedTile]; // 检查该位置是否已有瓦片 const existingTile = this.getTileAt(tileX, tileY); if (existingTile) { // 删除现有瓦片 existingTile.destroy(); } // 创建新瓦片 - 修复:使用正确的缩放比例,确保瓦片填满整个图块 try { const tile = this.add.image(tileX * this.tileSize, tileY * this.tileSize, tileKey); tile.setOrigin(0, 0); // 修复:根据瓦片实际大小调整缩放比例,确保填满32x32图块 tile.setScale(this.tileSize / tile.width); tile.setDepth(2); this.mapLayer.add(tile); // 更新地图数据 this.mapData[tileY][tileX] = this.selectedTile; } catch (error) { console.error('放置瓦片失败:', error); } } // 记录最后绘制的瓦片位置 this.lastDrawnTile = { x: tileX, y: tileY }; } /** * 删除瓦片 */ removeTile(tileX, tileY) { // 检查该位置是否已有瓦片 const existingTile = this.getTileAt(tileX, tileY); if (existingTile) { // 删除现有瓦片 existingTile.destroy(); // 更新地图数据 this.mapData[tileY][tileX] = -1; } } /** * 获取指定位置的瓦片 */ getTileAt(tileX, tileY) { const children = this.mapLayer.getAll(); for (const child of children) { const childTileX = Math.floor(child.x / this.tileSize); const childTileY = Math.floor(child.y / this.tileSize); if (childTileX === tileX && childTileY === tileY) { return child; } } return null; } /** * 清空地图 */ clearMap() { // 删除所有瓦片 this.mapLayer.removeAll(true); // 重置地图数据 for (let y = 0; y < this.mapHeight; y++) { for (let x = 0; x < this.mapWidth; x++) { this.mapData[y][x] = -1; } } console.log('地图已清空'); } /** * 保存地图 */ saveMap() { // 将地图数据转换为JSON const mapJson = JSON.stringify(this.mapData); // 创建下载链接 const blob = new Blob([mapJson], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'map.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); console.log('地图已保存'); } /** * 加载地图 */ loadMap() { // 创建文件选择器 const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => { try { const mapData = JSON.parse(event.target.result); this.loadMapData(mapData); } catch (error) { console.error('加载地图失败:', error); } }; reader.readAsText(file); } }; input.click(); } /** * 加载地图数据 */ loadMapData(mapData) { // 清空当前地图 this.clearMap(); // 更新地图数据 this.mapData = mapData; // 重新绘制地图 for (let y = 0; y < this.mapHeight; y++) { for (let x = 0; x < this.mapWidth; x++) { const tileIndex = this.mapData[y][x]; if (tileIndex !== -1 && tileIndex < this.tileSet.length) { const tileKey = this.tileSet[tileIndex]; try { const tile = this.add.image(x * this.tileSize, y * this.tileSize, tileKey); tile.setOrigin(0, 0); tile.setDepth(2); this.mapLayer.add(tile); } catch (error) { console.error(`加载地图瓦片(${x},${y})失败:`, error); } } } } console.log('地图已加载'); } /** * 显示提示信息 */ showInstructions() { const instructions = [ '地图编辑测试场景', '点击瓦片选择区域选择瓦片', '在地图上点击放置瓦片', '选择瓦片后按D键进入删除模式', '鼠标拖拽移动地图', '鼠标滚轮缩放地图', '按C键清空地图', '按S键保存地图', '按L键加载地图', '按空格键返回启动场景' ]; for (let i = 0; i < instructions.length; i++) { this.add.text(this.sys.game.config.width - 300, 180 + i * 20, instructions[i], { fontSize: '12px', fill: '#ffffff', fontFamily: 'Arial' }).setScrollFactor(0, 0).setDepth(11); } } update() { // 每帧更新 } }