KNOWLEDGE MODULE 02

Spawn 与 Creep 生命周期

从创建、出口方向、身体配置、房间 Energy 供能和多 Spawn 队列调度,到提前替换、续命、回收与断代恢复,管理 Creep 的完整生命周期。

专题内进度5 / 9查看完整学习顺序 →

Screeps 进阶开发

Screeps 多 Spawn 队列怎么设计:优先级、去重、Energy 预留与任务分配

为同一房间或帝国中的多个 Spawn 建立统一队列:用稳定 requestKey 去重,按紧急度和等待时间排序,为共享 Spawn 与 Extension Energy 做本地预留,避免多个模块重复生成 Creep,并在后续 tick 验证真实孵化结果。

难度
基础
适用阶段
创建、出口、身体、供能与队列
模块位置
5 / 9
前置知识
Screeps 房间 Energy 为什么一直充不满:energyAvailable、Extension 缺口与运输诊断

KNOWLEDGE GRAPH

把这篇文章连接到 API、错误码和工具

这里由现有 API Reference、Object Hub 与知识库顺序自动生成,用来继续排查和学习;它不替代正文中的具体前置条件与返回值说明。

NEXT TOPIC

继续这个知识模块

当前属于“Spawn 与 Creep 生命周期”第 5 / 9 篇。

下一篇:Screeps Creep 提前补员:用 ticksToLive、Spawn 时间和路程避免断代
VERIFICATION文档已核对 · 语法已检查 · Console待测试 · 主循环待验证查看验证详情
官方文档
已核对
JavaScript 语法
已检查
Screeps Console
待测试
真实主循环
待验证
最后核对
2026年8月6日
本文目录26 个主要章节
  1. 常见错误

当房间只有一个 Spawn、一个 Harvester 需求时,下面的代码可以工作:

if (harvesters.length < 2) {
  spawn.spawnCreep(
    [WORK, CARRY, MOVE],
    'Harvester' + Game.time,
    {
      memory: {
        role: 'harvester'
      }
    }
  );
}

当代码继续增长后,问题通常不是“不会调用 spawnCreep()”,而是 太多模块都在调用它

  • Harvester 管理器发现缺员;
  • Upgrader 管理器也发现缺员;
  • 远程房间管理器请求 Reserver;
  • 防御模块临时请求 Defender;
  • 提前补员模块为即将死亡的 Creep 请求替代者;
  • 房间内有两个或三个空闲 Spawn;
  • 多个请求共用同一批 Spawn 和 Extension Energy。

如果这些模块直接操作 Spawn,就会出现重复请求、优先级反转、名字冲突、同 tick Energy 争用和难以验证的失败。

更稳定的结构是:

业务模块只提交请求
→ 队列统一去重和排序
→ 调度器选择一个空闲 Spawn
→ 本地预留共享 Energy
→ 每个 Spawn 最多提交一次 spawnCreep()
→ 下一 tick 验证接受的名字是否真的进入孵化

快速答案

多 Spawn 调度器至少应完成六件事:

  1. 使用稳定的 requestKey 表示业务需求,而不是使用随机 Creep 名称去重;
  2. 同一个需求在队列中只能保留一份;
  3. 紧急恢复、防御和 Controller 安全请求优先于普通经济请求;
  4. 同房间的多个 Spawn 必须共享一份本地 Energy 预算;
  5. dryRun: true 只用于检查单个请求,不能代替同 tick 预算预留;
  6. spawnCreep() 返回 OK 后保存待验证记录,在后续 tick 检查真实状态。

为什么不能让每个角色模块直接调用 Spawn

下面的两个模块可能在同一个 tick 运行:

function runHarvesterDemand(room) {
  if (countRole(room, 'harvester') < 2) {
    return room.find(FIND_MY_SPAWNS)[0]
      .spawnCreep(
        [WORK, CARRY, MOVE],
        'harvester-' + Game.time
      );
  }
}

function runUpgraderDemand(room) {
  if (countRole(room, 'upgrader') < 1) {
    return room.find(FIND_MY_SPAWNS)[0]
      .spawnCreep(
        [WORK, CARRY, MOVE],
        'upgrader-' + Game.time
      );
  }
}

常见结果包括:

  • 两个模块都选择同一个 Spawn;
  • 后调用的请求覆盖或阻止前一个业务意图;
  • 低优先级 Upgrader 抢在应急 Harvester 前面;
  • 两个模块都认为自己应该创建同一类 Creep;
  • 日志只保留返回码,无法知道哪个需求最终完成。

业务模块应只描述“需要什么”,不应决定“由哪个 Spawn 在这个 tick 创建”。

请求对象需要哪些字段

function createSpawnRequest(input) {
  return {
    requestKey: input.requestKey,
    roomName: input.roomName,
    role: input.role,
    body: [...input.body],
    priority: input.priority,
    createdAt: input.createdAt,
    expiresAt: input.expiresAt,
    memory: {
      ...input.memory,
      role: input.role
    },
    directions: Array.isArray(input.directions)
      ? [...input.directions]
      : undefined,
    preferredSpawnNames:
      Array.isArray(input.preferredSpawnNames)
        ? [...input.preferredSpawnNames]
        : []
  };
}

字段职责:

字段 用途
requestKey 一个稳定业务需求的唯一标识
roomName 从哪个房间的 Spawn 与 Energy 池创建
role 角色分类和日志字段
body 已经确定的身体数组
priority 调度优先级,数值越大越先执行
createdAt 同优先级下的等待顺序
expiresAt 请求失效时间,防止旧需求长期残留
memory 创建后写入 Creep Memory 的初始数据
directions 可选出生方向
preferredSpawnNames 可选 Spawn 偏好,不是强制唯一目标

requestKey 应描述需求身份,例如:

E51S44:local:harvester:source-0
E51S44:controller:upgrader:slot-1
E51S44:defense:melee:wave-12345
E51S44:remote:E52S44:reserver

不要使用:

harvester-Game.time
Math.random()
当前 Creep 名称

这些值每 tick 都可能变化,无法用于去重。

先验证请求结构

function calculateBodyCost(body) {
  if (!Array.isArray(body)) {
    return null;
  }

  let cost = 0;

  for (const part of body) {
    const partCost = BODYPART_COST[part];

    if (!Number.isFinite(partCost)) {
      return null;
    }

    cost += partCost;
  }

  return cost;
}

function validateSpawnRequest(request) {
  if (
    !request
    || typeof request.requestKey !== 'string'
    || request.requestKey.trim() === ''
  ) {
    return {
      ok: false,
      reason: 'invalid-request-key'
    };
  }

  if (
    typeof request.roomName !== 'string'
    || !Game.rooms[request.roomName]
  ) {
    return {
      ok: false,
      reason: 'room-not-visible'
    };
  }

  if (
    !Array.isArray(request.body)
    || request.body.length === 0
    || request.body.length > MAX_CREEP_SIZE
  ) {
    return {
      ok: false,
      reason: 'invalid-body-length'
    };
  }

  const cost = calculateBodyCost(request.body);

  if (!Number.isInteger(cost) || cost <= 0) {
    return {
      ok: false,
      reason: 'invalid-body-parts'
    };
  }

  if (!Number.isFinite(request.priority)) {
    return {
      ok: false,
      reason: 'invalid-priority'
    };
  }

  if (
    !Number.isInteger(request.createdAt)
    || !Number.isInteger(request.expiresAt)
    || request.expiresAt < request.createdAt
  ) {
    return {
      ok: false,
      reason: 'invalid-time-window'
    };
  }

  return {
    ok: true,
    cost
  };
}

验证应在队列入口执行,避免一个错误请求阻止整个调度循环。

requestKey 去重

同一个业务需求可能被多个模块观察到,也可能在每个 tick 重复提交。队列应保留一个确定版本:

function deduplicateRequests(requests) {
  const byKey = new Map();

  for (const request of requests) {
    const previous = byKey.get(request.requestKey);

    if (!previous) {
      byKey.set(request.requestKey, request);
      continue;
    }

    const shouldReplace =
      request.priority > previous.priority
      || (
        request.priority === previous.priority
        && request.createdAt < previous.createdAt
      );

    if (shouldReplace) {
      byKey.set(request.requestKey, request);
    }
  }

  return [...byKey.values()];
}

如果同一个 requestKey 出现不同身体或不同角色,不应静默随机选择。生产代码可另外记录配置冲突:

function requestFingerprint(request) {
  return JSON.stringify({
    roomName: request.roomName,
    role: request.role,
    body: request.body,
    memory: request.memory,
    directions: request.directions ?? null
  });
}

相同 key、不同 fingerprint 表示两个模块对同一需求定义不一致,需要修复上游配置。

设计清楚的优先级

优先级不是越多越好。建议先使用少量层级:

const SPAWN_PRIORITY = Object.freeze({
  EMERGENCY_RECOVERY: 1000,
  ACTIVE_DEFENSE: 900,
  CONTROLLER_SAFETY: 800,
  ESSENTIAL_ECONOMY: 700,
  REPLACEMENT: 600,
  NORMAL_ECONOMY: 400,
  REMOTE_EXPANSION: 200,
  OPTIONAL: 100
});

一个稳定排序器:

function compareSpawnRequests(left, right) {
  return (
    right.priority - left.priority
    || left.createdAt - right.createdAt
    || left.requestKey.localeCompare(
      right.requestKey
    )
  );
}

function sortSpawnRequests(requests) {
  return [...requests].sort(
    compareSpawnRequests
  );
}

排序规则依次是:

  1. 优先级高的先;
  2. 同优先级等待更久的先;
  3. 最后使用稳定 key,避免对象遍历顺序影响结果。

防止低优先级请求长期饿死

只有固定优先级时,普通 Builder 可能长期排不到。可以加入有上限的等待加权:

function getEffectivePriority(
  request,
  now,
  options = {}
) {
  const waitStep = options.waitStep ?? 50;
  const maxBonus = options.maxBonus ?? 100;
  const waitedTicks = Math.max(
    0,
    now - request.createdAt
  );
  const waitBonus = Math.min(
    maxBonus,
    Math.floor(waitedTicks / waitStep)
  );

  return request.priority + waitBonus;
}

紧急恢复与主动防御通常不应被普通等待加权追平,因此 maxBonus 必须有上限。

只让调度器选择空闲 Spawn

function getAvailableSpawns(room) {
  return room.find(FIND_MY_SPAWNS)
    .filter(spawn =>
      spawn.my
      && spawn.isActive()
      && !spawn.spawning
    )
    .sort((left, right) =>
      left.name.localeCompare(right.name)
    );
}

每个 Spawn 在一个 tick 最多接收一个创建请求。不要在同一个调度循环中重复使用已经分配过的 Spawn。

Spawn 偏好不能破坏可用性

function rankSpawnsForRequest(
  request,
  spawns
) {
  const preferred = new Set(
    request.preferredSpawnNames ?? []
  );

  return [...spawns].sort((left, right) => {
    const leftRank = preferred.has(left.name)
      ? 0
      : 1;
    const rightRank = preferred.has(right.name)
      ? 0
      : 1;

    return (
      leftRank - rightRank
      || left.name.localeCompare(right.name)
    );
  });
}

偏好只改变顺序。除非业务确实要求指定出口或指定房间,不应因为首选 Spawn 忙碌就让紧急请求完全停止。

为什么多个 Spawn 需要共享本地 Energy 预算

同一个房间的 Spawn 和 Extension 构成共享创建 Energy 池。官方文档说明,同一房间的 Extension 可以被多个 Spawn 使用。

同时,Screeps 主循环读取的是 tick 开始时的状态,动作命令会在之后统一结算。由此可以推断:如果两个空闲 Spawn 在同一 tick 分别执行 dryRun: true,它们可能都基于同一份初始 room.energyAvailable 返回 OK。这不能证明两次真实创建都具备独立 Energy。

因此调度器需要自己的预算:

function createRoomEnergyBudget(room) {
  return {
    roomName: room.name,
    observedAt: Game.time,
    observedEnergy: room.energyAvailable,
    reservedEnergy: 0
  };
}

function getBudgetRemaining(budget) {
  return Math.max(
    0,
    budget.observedEnergy
      - budget.reservedEnergy
  );
}

function reserveSpawnEnergy(
  budget,
  amount
) {
  if (
    !Number.isInteger(amount)
    || amount <= 0
    || amount > getBudgetRemaining(budget)
  ) {
    return false;
  }

  budget.reservedEnergy += amount;
  return true;
}

这是一种本地调度保护,不是服务器锁。它的作用是让本次主循环不会对同一份观测 Energy 重复承诺。

dryRun: true 应该怎么用

function dryRunSpawnRequest(
  spawn,
  request,
  name
) {
  return spawn.spawnCreep(
    request.body,
    name,
    {
      memory: request.memory,
      directions: request.directions,
      dryRun: true
    }
  );
}

dryRun 适合检查:

  • 身体数组是否合法;
  • 名称是否冲突;
  • Spawn 是否可用;
  • 当前观测 Energy 是否足够;
  • 参数是否有效。

它不适合证明:

  • 同房间其他 Spawn 没有使用相同 Energy;
  • 真实创建意图一定会在服务器结算时完成;
  • 下一 tick 一定能看到目标 Creep 正在孵化。

调度顺序应是:

本地预算足够
→ dryRun 返回 OK
→ 提交真实 spawnCreep()
→ 真实调用返回 OK
→ 本地预留 Energy
→ 保存待验证记录

只有真实调用返回 OK 后才预留预算。如果真实调用失败,预算不应扣减。

生成稳定且唯一的 Creep 名称

function sanitizeNamePart(value) {
  return String(value)
    .replace(/[^A-Za-z0-9_-]/g, '-')
    .slice(0, 24);
}

function buildCreepName(
  request,
  sequence
) {
  return [
    sanitizeNamePart(request.role),
    sanitizeNamePart(request.roomName),
    Game.time,
    sequence
  ].join('-').slice(0, 100);
}

名称用于游戏对象身份,requestKey 用于业务需求身份。两者不要混为一谈。

提交一个请求

function submitSpawnRequest(
  spawn,
  request,
  name,
  budget
) {
  const validation = validateSpawnRequest(
    request
  );

  if (!validation.ok) {
    return {
      status: 'invalid-request',
      reason: validation.reason
    };
  }

  if (validation.cost > getBudgetRemaining(budget)) {
    return {
      status: 'local-budget-insufficient',
      cost: validation.cost,
      remaining: getBudgetRemaining(budget)
    };
  }

  const dryRunResult = dryRunSpawnRequest(
    spawn,
    request,
    name
  );

  if (dryRunResult !== OK) {
    return {
      status: 'dry-run-rejected',
      result: dryRunResult
    };
  }

  const result = spawn.spawnCreep(
    request.body,
    name,
    {
      memory: request.memory,
      directions: request.directions
    }
  );

  if (result !== OK) {
    return {
      status: 'submission-rejected',
      result
    };
  }

  if (!reserveSpawnEnergy(
    budget,
    validation.cost
  )) {
    return {
      status: 'budget-invariant-failed',
      result,
      cost: validation.cost
    };
  }

  return {
    status: 'submitted-locally',
    result,
    requestKey: request.requestKey,
    spawnName: spawn.name,
    creepName: name,
    cost: validation.cost,
    submittedAt: Game.time
  };
}

submitted-locally 是准确状态。不要立即写成:

spawned
created
running

保存待验证记录

function savePendingSpawn(outcome) {
  if (outcome.status !== 'submitted-locally') {
    return false;
  }

  Memory.spawnScheduler ??= {
    pending: {},
    completed: {}
  };
  Memory.spawnScheduler.pending[
    outcome.requestKey
  ] = {
    requestKey: outcome.requestKey,
    spawnName: outcome.spawnName,
    creepName: outcome.creepName,
    cost: outcome.cost,
    submittedAt: outcome.submittedAt,
    lastCheckedAt: null
  };

  return true;
}

待验证记录还可以防止同一个需求在下一 tick 又进入队列。

后续 tick 验证真实状态

function verifyPendingSpawn(pending) {
  const spawn = Game.spawns[
    pending.spawnName
  ] ?? null;
  const creep = Game.creeps[
    pending.creepName
  ] ?? null;

  if (
    spawn?.spawning?.name
      === pending.creepName
  ) {
    return {
      status: 'spawning-observed',
      requestKey: pending.requestKey,
      creepName: pending.creepName,
      remainingTime:
        spawn.spawning.remainingTime
    };
  }

  if (creep) {
    return {
      status: creep.spawning
        ? 'spawning-creep-observed'
        : 'creep-released',
      requestKey: pending.requestKey,
      creepName: pending.creepName
    };
  }

  return {
    status: 'not-observed-yet',
    requestKey: pending.requestKey,
    creepName: pending.creepName
  };
}

not-observed-yet 不是立即重试的理由。应给服务器结算和下一 tick 状态一个明确窗口:

function shouldRetryPending(
  pending,
  now,
  timeoutTicks = 2
) {
  return now - pending.submittedAt
    >= timeoutTicks;
}

如果超时后名字仍不存在,再根据日志、返回码和当前需求决定是否重新排队。

队列不能只看当前 Creep 数量

下面的条件会造成重复请求:

if (countRole(room, 'harvester') < 2) {
  enqueueHarvester();
}

因为正在孵化和已提交待验证的 Harvester 可能尚未进入你统计的“可工作 Creep”集合。

更安全的缺口计算:

function countCoveredDemand(
  room,
  role,
  pendingRecords
) {
  const existing = room.find(FIND_MY_CREEPS)
    .filter(creep =>
      creep.memory.role === role
    ).length;
  const spawning = room.find(FIND_MY_SPAWNS)
    .filter(spawn => {
      const name = spawn.spawning?.name;
      const creep = name
        ? Game.creeps[name]
        : null;

      return creep?.memory?.role === role;
    }).length;
  const pending = pendingRecords.filter(record =>
    record.roomName === room.name
    && record.role === role
  ).length;

  return existing + spawning + pending;
}

实际项目中要避免“正在孵化 Creep 同时被 existing 和 spawning 重复统计”。可以按 Creep 名称建立 Set,而不是简单相加。

完整的房间调度器

function scheduleRoomSpawns(
  room,
  rawRequests
) {
  const now = Game.time;
  const requests = sortSpawnRequests(
    deduplicateRequests(rawRequests)
      .filter(request =>
        request.roomName === room.name
        && request.expiresAt >= now
      )
  );
  const availableSpawns = getAvailableSpawns(
    room
  );
  const budget = createRoomEnergyBudget(room);
  const assignedSpawnNames = new Set();
  const outcomes = [];
  let sequence = 0;

  for (const request of requests) {
    const candidates = rankSpawnsForRequest(
      request,
      availableSpawns.filter(spawn =>
        !assignedSpawnNames.has(spawn.name)
      )
    );
    const spawn = candidates[0];

    if (!spawn) {
      outcomes.push({
        status: 'no-available-spawn',
        requestKey: request.requestKey
      });
      continue;
    }

    sequence += 1;
    const name = buildCreepName(
      request,
      sequence
    );
    const outcome = submitSpawnRequest(
      spawn,
      request,
      name,
      budget
    );

    outcomes.push(outcome);

    if (outcome.status === 'submitted-locally') {
      assignedSpawnNames.add(spawn.name);
      savePendingSpawn(outcome);
    }
  }

  return {
    roomName: room.name,
    observedEnergy: budget.observedEnergy,
    reservedEnergy: budget.reservedEnergy,
    remainingEnergy:
      getBudgetRemaining(budget),
    outcomes
  };
}

该实现故意不在一次失败后把所有请求都丢弃:

  • 某个身体不合法,不影响后续请求;
  • 某个名称冲突,可以记录并继续;
  • 某个请求超过预算,较小的后续请求是否允许执行,应由你的策略决定;
  • 没有空闲 Spawn 时,请求应保留到下一 tick,而不是重复创建新 key。

大请求是否应该阻塞小请求

假设队列是:

优先级 700:1500 Energy 的 Harvester
优先级 600:300 Energy 的 Transporter
当前 Energy:800

两种策略都合理,但必须明确:

严格优先级

高优先级请求 Energy 不足时,本房间停止创建,等待它满足。

优点:不会让关键大身体长期被小请求消耗 Energy。

缺点:Spawn 可能闲置。

可跳过策略

暂时跳过大请求,执行能负担的小请求。

优点:提高 Spawn 利用率。

缺点:大请求可能长期饥饿。

可以给请求增加:

blocking: true

当高优先级阻塞请求无法满足预算时,停止处理更低优先级请求。

紧急恢复需要特殊身体降级

当房间没有 Harvester 时,等待 1200 Energy 的完整身体可能导致永久停摆。紧急请求应提供可运行的最低身体:

function chooseEmergencyBody(
  room,
  fullBody,
  fallbackBody
) {
  const fullCost = calculateBodyCost(
    fullBody
  );
  const fallbackCost = calculateBodyCost(
    fallbackBody
  );

  if (room.energyAvailable >= fullCost) {
    return [...fullBody];
  }

  if (
    Number.isInteger(fallbackCost)
    && room.energyAvailable >= fallbackCost
  ) {
    return [...fallbackBody];
  }

  return null;
}

这应与 房间断代后如何生成应急 Harvester 配合使用。

队列存 Memory 还是每 tick 重建

每 tick 重建

适合由当前房间状态直接推导的需求:

  • 本地 Harvester 数量;
  • Upgrader 插槽;
  • Controller 降级风险;
  • 当前防御需求。

优点是不会残留旧请求。缺点是必须正确计算 pending 和 spawning 覆盖。

持久队列

适合跨 tick 工作流:

  • 远程房间建设计划;
  • 手动 Console 请求;
  • 一次性 Claim/Reserve 任务;
  • 需要保留创建时间和超时信息的任务。

持久队列必须有版本、过期时间和迁移策略。

可以混合使用:

本 tick 派生请求
+ Memory 中未过期的一次性请求
→ 统一验证、去重和排序

限频记录调度结果

function summarizeSchedule(result) {
  return {
    type: 'spawn-schedule-summary',
    tick: Game.time,
    roomName: result.roomName,
    observedEnergy: result.observedEnergy,
    reservedEnergy: result.reservedEnergy,
    remainingEnergy: result.remainingEnergy,
    submitted: result.outcomes.filter(item =>
      item.status === 'submitted-locally'
    ).map(item => ({
      requestKey: item.requestKey,
      spawnName: item.spawnName,
      creepName: item.creepName,
      cost: item.cost
    })),
    rejected: result.outcomes.filter(item =>
      item.status !== 'submitted-locally'
    ).map(item => ({
      requestKey: item.requestKey ?? null,
      status: item.status,
      result: item.result ?? null
    }))
  };
}

不要把整个 Room、Spawn 或请求对象写入长期 Memory。保存 key、名字、状态、tick、成本和返回码即可。

常见错误

多个模块直接调用 spawnCreep()

无法形成统一优先级,也容易重复请求。

使用 Creep 名称作为需求 key

名称是一次创建对象身份,不是长期需求身份。

每 tick 重新创建随机 requestKey

去重机制会失效。

只依赖 dryRun: true

多个 Spawn 可能基于同一 tick 初始 Energy 分别通过检查。必须维护本地共享预算。

OK 后立即删除请求并标记完成

OK 表示本地命令被接受,不是后续世界状态已经验证。

不统计 pending 和 spawning

相同角色会被重复排队。

同一个 Spawn 在循环中被分配两次

必须使用 assignedSpawnNames 或从候选数组中移除。

永远严格优先级

低优先级任务可能长期饿死;需要等待加权或显式阻塞策略。

永远跳过大请求

关键大身体可能永远攒不到 Energy。

队列没有过期时间

远程任务取消后,旧请求仍可能在几十个 tick 后突然执行。

排查清单

  1. 列出所有调用 spawnCreep() 的模块。
  2. 将真实调用收敛到一个调度入口。
  3. 为每个需求定义稳定 requestKey
  4. 检查同 key 是否出现不同 fingerprint。
  5. 明确紧急、防御、关键经济和普通任务优先级。
  6. 把正在孵化和待验证记录计入需求覆盖。
  7. 每个房间创建一份共享 Energy 预算。
  8. 每个 Spawn 每 tick 最多分配一次。
  9. 真实调用返回 OK 后再本地预留 Energy。
  10. 下一 tick 验证指定名字是否进入孵化或已经出生。
  11. 给一次性请求设置 expiresAt
  12. 记录预算、分配结果和拒绝原因。

验证状态与适用边界

仓库会检查本文 JavaScript 代码块语法,并用离线用例验证:

  • requestKey 去重;
  • 稳定优先级排序;
  • 等待加权上限;
  • 同房间 Energy 预留;
  • 每个 Spawn 只分配一次;
  • OK 后保存 pending;
  • 后续 tick 的 spawning、released 和 timeout 状态。

这些测试不能模拟官方服务器真实的同 tick 意图结算顺序、多个 Spawn 对同一组 Extension 的实际扣能、Power Spawn 效果、敌对房间状态或真实 CPU 成本。因此 consoleTestedliveTested 保持为 false

本文适用于:

  • 一个房间有两个或三个 Spawn;
  • 多个角色管理器同时请求 Creep;
  • 需要应急恢复和普通生产共享队列;
  • 提前补员、远程任务和防御任务同时存在;
  • 需要可解释的创建优先级与日志;
  • 需要避免同 tick 重复承诺共享 Energy。

本文不替代:

  • 身体动态生成算法;
  • Spawn 出口方向和阻塞诊断;
  • Extension 供能诊断;
  • 角色本身的运行逻辑;
  • 真实 shard 多 tick 验证。

相关站内内容

官方资料

返回目录