欢迎访问

摆摊系统

摆摊系统完全指南 | 开发进度追踪

摆摊系统是《蛙噻!好摊》的核心玩法之一。作为一名取经路上的行者,你需要通过低买高卖来积攒盘缠。掌握市场规律、把握交易时机、与各路商人周旋,是成为一代商业传奇的必经之路。

摆摊流程

完整的摆摊经营流程包含以下六个步骤:

1

进货采购

在各地商人处或副本中购买商品。不同地区商品价格存在差异,是套利的基础。

2

定价策略

根据市场行情设定售价。定价过高滞销,过低利润微薄。

3

选择摊位

选择摆摊的位置。城镇中心人流量大但竞争激烈。

4

摆摊销售

开始摆摊,NPC顾客陆续前来询价。可随时调整价格或下架商品。

5

议价成交

与顾客进行价格谈判。成功议价可获得额外利润。

6

收摊结算

收摊后结算已售出商品的收益。未售出商品返还背包。

商品类型

类型特点例子风险
普通商品供应充足,价格波动小粮食、布匹、日常用品
稀有商品供应有限,利润空间高珍贵药材、精良装备中等
特产商品特定地区独有,跨区域贸易利润极高花果山仙桃、火焰山火晶

摊位管理

四条街道

街道主营商品特点人流量
东街工具工匠和冒险者的聚集地中等
南街综合商品最大的综合市场最高
西街道具炼金师和工匠的必去之地中等
北街装备战士和冒险者的天堂较高

摊位位置价值

位置类型人流量出售效率适合阶段
中心区域5星+50%中后期
街道中段4星+25%中期
街道边缘3星标准新手期

摊位自动前移机制

当某个摊位收摊后,该摊位后方的所有摊位会自动向前移动填补空位。只要你持续经营的时间够久,你的摊位会慢慢移动到更靠前的位置。

出摊费用

项目说明费率
出摊手续费上架商品时需支付的手续费商品市场价格的 1%
交易税费商品成功卖出时收取的税费成交金额的 5%

装饰系统

称号解锁装饰成交率加成离线收益加成
小商贩草创期6个+1%-
行商立业期6个+2%+1%
富商兴业期6个+3%+2%
巨贾旺业期6个+4%+3%
商业大亨鸿业期6个+5%+4%
大富翁传世期6个+6%+5%

摆摊系统开发流程

按照以下计划逐步完成开发,完成所有计划即开发成功。

0%

第一阶段:摊位管理模块

预计耗时:3-4小时 难度:中等
  • 任务1.1:创建摊位数据结构

    定义摊位类和街道数据

    class Stall { constructor(id, street, index) { this.id = id; // 摊位ID this.street = street; // 所属街道:east/south/west/north this.index = index; // 在街道中的位置序号 this.owner = null; // 拥有者ID this.status = 'vacant'; // vacant/occupied/locked this.listedItems = []; // 上架商品列表 this.decorations = []; // 装饰列表 } } // 街道配置 const STREETS = { east: { name: '东街', specialty: 'tool', stallCount: 200 }, south: { name: '南街', specialty: 'misc', stallCount: 200 }, west: { name: '西街', specialty: 'potion', stallCount: 200 }, north: { name: '北街', specialty: 'equipment', stallCount: 200 } };
  • 任务1.2:实现摊位管理类 StallManager

    管理摊位的选择、占用、撤离

    class StallManager { constructor() { this.stalls = []; this.initStalls(); } initStalls() { let id = 1; for (let street of Object.keys(STREETS)) { for (let i = 0; i < STREETS[street].stallCount; i++) { this.stalls.push(new Stall(`stall_${id++}`, street, i)); } } } // 获取空闲摊位 getVacantStalls(street) { return this.stalls.filter(s => s.street === street && s.status === 'vacant'); } // 占用摊位 occupyStall(stallId, playerId) { const stall = this.stalls.find(s => s.id === stallId); if (stall && stall.status === 'vacant') { stall.status = 'occupied'; stall.owner = playerId; return true; } return false; } // 撤离摊位 vacateStall(stallId) { const stall = this.stalls.find(s => s.id === stallId); if (stall) { stall.status = 'vacant'; stall.owner = null; stall.listedItems = []; return true; } return false; } }
  • 任务1.3:实现摊位自动对齐

    收摊后后方摊位自动前移

    class StallManager { // ... 其他方法 // 摊位自动前移对齐 autoAlignStalls(street) { const streetStalls = this.stalls .filter(s => s.street === street) .sort((a, b) => a.index - b.index); let currentIndex = 0; streetStalls.forEach(stall => { if (stall.status === 'occupied') { stall.index = currentIndex++; } }); } // 收摊时调用 vacateAndAlign(stallId) { const stall = this.stalls.find(s => s.id === stallId); if (!stall) return false; const street = stall.street; const result = this.vacateStall(stallId); if (result) { this.autoAlignStalls(street); } return result; } }

验证方法

创建摊位管理器,测试占用和撤离功能,验证自动对齐是否正确执行。

第二阶段:商品交易模块

预计耗时:4-5小时 难度:中等
  • 任务2.1:定义商品数据结构

    商品类和商品类型配置

    class Item { constructor(id, name, type, rarity, basePrice) { this.id = id; this.name = name; this.type = type; // tool/armor/consumable/material this.rarity = rarity; // common/rare/epic/legendary this.basePrice = basePrice; this.currentPrice = basePrice; } } // 商品类型配置 const ITEM_TYPES = { tool: { name: '工具', streetBonus: { east: 1.2 } }, armor: { name: '防具', streetBonus: { north: 1.2 } }, consumable: { name: '消耗品', streetBonus: { west: 1.2 } }, material: { name: '材料', streetBonus: {} } }; // 上架商品记录 class ListedItem { constructor(item, price, quantity) { this.itemId = item.id; this.item = item; this.price = price; // 玩家设定的售价 this.quantity = quantity; this.listedAt = Date.now(); this.soldAt = null; } }
  • 任务2.2:实现交易系统类 TradingSystem

    商品上架、下架、成交

    class TradingSystem { constructor() { this.listings = new Map(); // stallId -> ListedItem[] } // 上架商品 listItem(stallId, item, price, quantity, player) { // 检查玩家库存 if (!player.hasItem(item.id, quantity)) { return { success: false, message: '商品数量不足' }; } // 计算手续费 const fee = Math.floor(item.basePrice * 0.01 * quantity); if (player.money < fee) { return { success: false, message: '余额不足支付手续费' }; } // 扣除商品和手续费 player.removeItem(item.id, quantity); player.money -= fee; // 添加上架记录 const listing = new ListedItem(item, price, quantity); if (!this.listings.has(stallId)) { this.listings.set(stallId, []); } this.listings.get(stallId).push(listing); return { success: true, fee: fee, listing: listing }; } // 下架商品 delistItem(stallId, listingIndex, player) { const listings = this.listings.get(stallId); if (!listings || !listings[listingIndex]) { return { success: false, message: '商品不存在' }; } const listing = listings.splice(listingIndex, 1)[0]; player.addItem(listing.item, listing.quantity); return { success: true, item: listing }; } }
  • 任务2.3:实现交易成交逻辑

    NPC购买商品和收益结算

    class TradingSystem { // ... 其他方法 // NPC购买商品 sellItem(stallId, listingIndex, quantity) { const listings = this.listings.get(stallId); if (!listings || !listings[listingIndex]) { return { success: false, message: '商品不存在' }; } const listing = listings[listingIndex]; const sellQty = Math.min(quantity, listing.quantity); // 计算收益(扣除5%税费) const grossEarnings = listing.price * sellQty; const tax = Math.floor(grossEarnings * 0.05); const netEarnings = grossEarnings - tax; // 更新上架数量 listing.quantity -= sellQty; if (listing.quantity <= 0) { listings.splice(listingIndex, 1); } return { success: true, quantity: sellQty, grossEarnings: grossEarnings, tax: tax, netEarnings: netEarnings }; } // 获取摊位总收益 getTotalEarnings(stallId) { const listings = this.listings.get(stallId) || []; let total = 0; listings.forEach(l => { if (l.soldAt) { total += l.netEarnings || 0; } }); return total; } }

验证方法

测试商品上架、下架、NPC购买流程,验证手续费和税费计算正确。

第三阶段:价格机制模块

预计耗时:3-4小时 难度:中等
  • 任务3.1:实现市场价格计算

    根据多种因素计算商品市场价

    class PriceSystem { constructor() { this.basePrices = new Map(); // itemId -> basePrice this.priceModifiers = new Map(); // itemId -> modifier } // 计算市场价格 calculateMarketPrice(itemId, location, gameTime) { const basePrice = this.basePrices.get(itemId) || 100; let price = basePrice; // 位置加成 const locationBonus = this.getLocationBonus(location); price *= locationBonus; // 时段加成 const timeBonus = this.getTimeBonus(gameTime); price *= timeBonus; // 季节加成 const seasonBonus = this.getSeasonBonus(gameTime, itemId); price *= seasonBonus; // 供需波动 const demandModifier = this.priceModifiers.get(itemId) || 1.0; price *= demandModifier; return Math.floor(price); } getLocationBonus(location) { const bonuses = { center: 1.2, middle: 1.0, edge: 0.9 }; return bonuses[location] || 1.0; } getTimeBonus(gameTime) { const hour = gameTime.hour; if (hour >= 8 && hour < 12) return 1.1; // 上午高峰 if (hour >= 17 && hour < 20) return 1.15; // 傍晚高峰 if (hour >= 23 || hour < 5) return 0.8; // 深夜 return 1.0; } getSeasonBonus(gameTime, itemId) { // 根据季节和商品类型返回加成 return 1.0; } }
  • 任务3.2:实现定价反馈系统

    根据定价与市场价对比给出反馈

    class PriceSystem { // ... 其他方法 // 获取定价反馈 getPriceFeedback(listedPrice, marketPrice) { const ratio = listedPrice / marketPrice; if (ratio < 0.8) { return { type: 'very_low', message: '定价过低,商品快速售出', sellProbability: 0.95, avgSellTime: 15 * 60 * 1000 // 15分钟 }; } else if (ratio < 0.95) { return { type: 'low', message: '定价略低,成交较快', sellProbability: 0.8, avgSellTime: 30 * 60 * 1000 // 30分钟 }; } else if (ratio <= 1.1) { return { type: 'fair', message: '定价合理,正常成交', sellProbability: 0.6, avgSellTime: 2 * 60 * 60 * 1000 // 2小时 }; } else if (ratio <= 1.3) { return { type: 'high', message: '定价偏高,NPC议价中', sellProbability: 0.3, avgSellTime: 5 * 60 * 60 * 1000, // 5小时 bargainChance: 0.7 }; } else { return { type: 'very_high', message: '定价过高,无人问津', sellProbability: 0.05, avgSellTime: 10 * 60 * 60 * 1000, // 10小时 bargainChance: 0.3 }; } } }
  • 任务3.3:实现议价系统

    NPC议价和玩家回应

    class BargainingSystem { // NPC发起议价 initiateBargain(listing, marketPrice) { const ratio = listing.price / marketPrice; // 议价幅度(定价越高,议价幅度越大) const bargainRate = Math.min(0.3, (ratio - 1) * 0.5); const bargainPrice = Math.floor(listing.price * (1 - bargainRate)); return { npcId: this.generateNpcId(), originalPrice: listing.price, bargainPrice: bargainPrice, deadline: Date.now() + 5 * 60 * 1000, // 5分钟内回应 message: this.generateBargainMessage(bargainRate) }; } // 玩家接受议价 acceptBargain(bargain, listing) { listing.price = bargain.bargainPrice; return { success: true, finalPrice: bargain.bargainPrice }; } // 玩家拒绝议价 rejectBargain(bargain, listing) { // 降低成交概率 return { success: true, sellProbabilityModifier: 0.5, message: 'NPC离开,成交概率降低' }; } generateBargainMessage(rate) { if (rate < 0.1) return '老板,能便宜一点点吗?'; if (rate < 0.2) return '这价格有点贵啊,少点呗?'; return '老板,这也太贵了,便宜点我就买了!'; } }

验证方法

测试不同定价策略的反馈,验证议价系统是否正常工作。

第四阶段:装饰系统模块

预计耗时:2-3小时 难度:简单
  • 任务4.1:定义装饰数据结构

    装饰类型和效果配置

    const DECORATION_TIERS = { startup: { name: '草创期', count: 6 }, growth: { name: '立业期', count: 6 }, develop: { name: '兴业期', count: 6 }, prosper: { name: '旺业期', count: 6 }, flourish: { name: '鸿业期', count: 6 }, legacy: { name: '传世期', count: 6 } }; const DECORATION_TYPES = { signboard: { name: '招牌类', effect: 'dealRate' }, display: { name: '陈列类', effect: 'dealRate' }, atmosphere: { name: '氛围类', effect: 'offlineRate' }, fengshui: { name: '风水类', effect: 'taxReduction' }, statue: { name: '神像类', effect: 'all' } }; class Decoration { constructor(id, name, tier, type, effects) { this.id = id; this.name = name; this.tier = tier; // startup/growth/develop/prosper/flourish/legacy this.type = type; // signboard/display/atmosphere/fengshui/statue this.effects = effects; // { dealRate: 0.01, offlineRate: 0.01, taxReduction: 0.005 } this.unlocked = false; } }
  • 任务4.2:实现装饰管理类

    装饰解锁和效果计算

    class DecorationSystem { constructor() { this.decorations = []; this.playerTitle = '小商贩'; // 玩家当前称号 this.activeDecorations = []; // 当前激活的装饰 } // 称号加成 getTitleBonus() { const titleBonuses = { '小商贩': { dealRate: 0.01, offlineRate: 0, taxReduction: 0 }, '行商': { dealRate: 0.02, offlineRate: 0.01, taxReduction: 0 }, '富商': { dealRate: 0.03, offlineRate: 0.02, taxReduction: 0.01 }, '巨贾': { dealRate: 0.04, offlineRate: 0.03, taxReduction: 0.02 }, '商业大亨': { dealRate: 0.05, offlineRate: 0.04, taxReduction: 0.03 }, '大富翁': { dealRate: 0.06, offlineRate: 0.05, taxReduction: 0.03 } }; return titleBonuses[this.playerTitle] || titleBonuses['小商贩']; } // 装饰加成 getDecorationBonus() { let total = { dealRate: 0, offlineRate: 0, taxReduction: 0 }; this.activeDecorations.forEach(decId => { const dec = this.decorations.find(d => d.id === decId); if (dec) { total.dealRate += dec.effects.dealRate || 0; total.offlineRate += dec.effects.offlineRate || 0; total.taxReduction += dec.effects.taxReduction || 0; } }); return total; } // 获取总加成 getTotalBonus() { const titleBonus = this.getTitleBonus(); const decBonus = this.getDecorationBonus(); return { dealRate: titleBonus.dealRate + decBonus.dealRate, offlineRate: titleBonus.offlineRate + decBonus.offlineRate, taxReduction: titleBonus.taxReduction + decBonus.taxReduction }; } }
  • 任务4.3:实现称号解锁

    消耗凭证解锁称号

    class DecorationSystem { // ... 其他方法 // 解锁称号 unlockTitle(title, player) { const requirements = { '小商贩': { startup: 1000 }, '行商': { growth: 1500 }, '富商': { develop: 2000 }, '巨贾': { prosper: 2500 }, '商业大亨': { flourish: 3000 }, '大富翁': { legacy: 5000 } }; const req = requirements[title]; if (!req) return { success: false, message: '称号不存在' }; // 检查凭证是否足够 for (let [tier, amount] of Object.entries(req)) { if ((player.vouchers[tier] || 0) < amount) { return { success: false, message: `${DECORATION_TIERS[tier].name}凭证不足` }; } } // 扣除凭证 for (let [tier, amount] of Object.entries(req)) { player.vouchers[tier] -= amount; } // 更新称号 this.playerTitle = title; // 解锁对应装饰 this.unlockDecorationsByTier(Object.keys(req)[0]); return { success: true, title: title }; } unlockDecorationsByTier(tier) { this.decorations.forEach(dec => { if (dec.tier === tier) { dec.unlocked = true; } }); } }

验证方法

测试称号解锁流程,验证装饰效果正确叠加。

第五阶段:离线收益模块

预计耗时:2-3小时 难度:中等
  • 任务5.1:保存离线状态

    记录玩家离线时的摊位状态

    function saveOfflineState(player, stallManager) { const state = { offlineTime: Date.now(), activeStalls: [] }; // 保存所有活跃摊位 stallManager.stalls.forEach(stall => { if (stall.owner === player.id && stall.status === 'occupied') { state.activeStalls.push({ stallId: stall.id, street: stall.street, index: stall.index, listedItems: stall.listedItems.map(item => ({ itemId: item.itemId, price: item.price, quantity: item.quantity })) }); } }); localStorage.setItem('offlineState', JSON.stringify(state)); return state; } function loadOfflineState() { const saved = localStorage.getItem('offlineState'); return saved ? JSON.parse(saved) : null; }
  • 任务5.2:计算离线收益

    根据离线时长和摊位状态计算收益

    function calculateOfflineEarnings(offlineState, decorationSystem) { const offlineMs = Math.min( Date.now() - offlineState.offlineTime, 24 * 60 * 60 * 1000 // 最长24小时 ); const offlineHours = offlineMs / (60 * 60 * 1000); // 离线效率 const efficiencyTiers = [ { maxHours: 4, efficiency: 1.0 }, { maxHours: 8, efficiency: 0.8 }, { maxHours: 12, efficiency: 0.6 }, { maxHours: 24, efficiency: 0.4 } ]; let efficiency = 0.4; for (let tier of efficiencyTiers) { if (offlineHours <= tier.maxHours) { efficiency = tier.efficiency; break; } } // 装饰加成 const bonus = decorationSystem.getTotalBonus(); // 计算每个摊位收益 let totalEarnings = 0; const stallEarnings = []; offlineState.activeStalls.forEach(stall => { let stallEarning = 0; stall.listedItems.forEach(item => { // 预估销售数量 const avgSellTime = 2 * 60 * 60 * 1000; // 平均2小时卖出 const expectedSales = Math.min( item.quantity, Math.floor(offlineMs / avgSellTime) ); // 位置加成 const posBonus = stall.index < 50 ? 1.5 : stall.index < 100 ? 1.25 : 1.0; // 计算收益 const itemEarnings = expectedSales * item.price * 0.95 * posBonus; stallEarning += itemEarnings; }); stallEarning *= efficiency * (1 + bonus.offlineRate); stallEarnings.push({ stallId: stall.stallId, earnings: Math.floor(stallEarning) }); totalEarnings += stallEarning; }); return { totalEarnings: Math.floor(totalEarnings), stallEarnings: stallEarnings, offlineHours: offlineHours, efficiency: efficiency }; }
  • 任务5.3:显示离线结算界面

    玩家上线时展示离线收益

    function showOfflineSummary(earnings, player) { if (earnings.offlineHours < 0.1) return; // 少于6分钟不显示 const modal = document.createElement('div'); modal.className = 'offline-summary-modal'; modal.innerHTML = ` `; document.body.appendChild(modal); // 领取收益 window.claimOfflineEarnings = function() { player.money += earnings.totalEarnings; modal.remove(); savePlayerData(player); }; }

验证方法

模拟离线场景,验证收益计算正确,结算界面正常显示。

第六阶段:UI界面模块

预计耗时:3-4小时 难度:中等
  • 任务6.1:实现摊位选择界面

    可视化展示街道和摊位

    // HTML结构 <div class="stall-selector"> <div class="street-tabs"> <button class="street-tab" data-street="east">东街</button> <button class="street-tab" data-street="south">南街</button> <button class="street-tab" data-street="west">西街</button> <button class="street-tab" data-street="north">北街</button> </div> <div class="stall-grid" id="stallGrid"></div> </div> // 渲染摊位网格 function renderStallGrid(street, stallManager) { const grid = document.getElementById('stallGrid'); const stalls = stallManager.stalls.filter(s => s.street === street); grid.innerHTML = stalls.map(stall => ` <div class="stall-cell ${stall.status}" data-id="${stall.id}" onclick="selectStall('${stall.id}')"> <span class="stall-index">${stall.index + 1}</span> ${stall.status === 'occupied' ? '<span class="occupied-mark">占</span>' : ''} </div> `).join(''); } // CSS样式 .stall-grid { display: grid; grid-template-columns: repeat(20, 1fr); gap: 2px; } .stall-cell { aspect-ratio: 1; background: #e8f5e9; border: 1px solid #c8e6c9; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 10px; } .stall-cell.occupied { background: #ffcdd2; border-color: #ef9a9a; cursor: not-allowed; } .stall-cell.selected { border: 2px solid #2196f3; }
  • 任务6.2:实现商品上架界面

    从背包选择商品上架

    function showListingModal(player, selectedStall) { const modal = document.createElement('div'); modal.className = 'listing-modal'; modal.innerHTML = ` <div class="modal-content"> <h2>上架商品</h2> <div class="inventory-list"> ${player.inventory.map(item => ` <div class="inventory-item" data-id="${item.id}"> <span class="item-name">${item.name}</span> <span class="item-qty">x${item.quantity}</span> <input type="number" class="list-price" placeholder="售价"> <input type="number" class="list-qty" placeholder="数量" max="${item.quantity}"> </div> `).join('')} </div> <div class="modal-actions"> <button onclick="confirmListing()">确认上架</button> <button onclick="closeModal()">取消</button> </div> </div> `; document.body.appendChild(modal); }
  • 任务6.3:实现摊位信息面板

    显示当前摊位状态和收益

    // HTML结构 <div class="stall-info-panel"> <h3>摊位信息</h3> <div class="info-row"> <span>位置:</span> <span id="stallLocation">南街 第15号</span> </div> <div class="info-row"> <span>状态:</span> <span id="stallStatus">营业中</span> </div> <h4>上架商品</h4> <div class="listed-items" id="listedItems"></div> <h4>今日收益</h4> <div class="earnings-summary"> <span>已售:<span id="soldCount">5</span>件</span> <span>收益:<span id="todayEarnings">1500</span>文</span> </div> <div class="panel-actions"> <button onclick="endStall()">收摊</button> </div> </div>

验证方法

测试摊位选择、商品上架、信息面板显示是否正常。

完成清单

完成以下所有任务即表示摆摊系统开发成功:

阶段任务数状态
第一阶段:摊位管理模块3待完成
第二阶段:商品交易模块3待完成
第三阶段:价格机制模块3待完成
第四阶段:装饰系统模块3待完成
第五阶段:离线收益模块3待完成
第六阶段:UI界面模块3待完成
总计180/18 完成

开发提示

  • 建议按顺序完成各阶段,后续阶段依赖前面的基础
  • 每完成一个任务,在代码中标记为已完成
  • 摊位自动对齐是核心机制,需要仔细测试
  • 价格机制需要与游戏时间系统配合

摆摊系统模拟器

输入参数,模拟计算摆摊的各项费用和收益。

参数设置

计算结果

上架数量--
设定售价--
市场参考价--
定价偏离度--
出摊手续费(1%)--
交易税费(5%)--
总支出--
预估成交数--
预估时间--
总预估收益--
建议:点击"计算"获取建议

快速场景