岛屿与陆地
This content is not available in your language yet.
用一份 JSON 配置文件定义的多边形岛屿,在运行时自动生成沙滩-悬崖-顶面三层地形、实心碰撞体、港口建筑锚点与小地图标记;船撞上扣血,AI 船自动绕行,刷怪与巡逻航点全部避开陆地。
陆地系统(Service/LandSystem/)是航海世界中的静态地形层。整个游戏世界是一片开阔海面,岛屿是海面上唯一的不可通行障碍物。每座岛由一个二维多边形(世界 XZ 平面坐标)定义,运行时由场景中的 LandManager.cs 读取 LandPolygons.json 后逐岛生成。
玩家通过以下方式接触这套系统:
- 航行受阻:岛屿有实心碰撞体,船开不进岛的轮廓线之内。
- 触礁扣血:以足够速度撞上岛屿,船上离撞击点最近的建筑直接掉血(见伤害与护甲)。
- 靠港交易:每座城市岛在沙滩上有一个 Dock(码头)锚点,城市的交易触发距离、巡逻船出生点、驻防炮台布置全部以这个码头位置为基准(见城市与国家、交易)。
- 小地图:每座岛在小地图上显示棕色轮廓描边,城市岛额外显示青蓝色港口点和半径 80 米的港口安全区圆圈(见界面总览)。
- AI 行为:所有 AI 船的漫游航点、刷怪位置、巡逻海域都会主动过滤陆地,反应式转向避让兜底。
岛屿是装饰性与功能性地形,不可登陆、不可破坏、不可建造。岛上的房屋、灯塔等只是按锚点摆放的视觉预制体,不接入建筑系统。
岛屿数据:LandPolygons.json
Section titled “岛屿数据:LandPolygons.json”所有岛屿数据来自一份 JSON 配置文件,路径 Assets/StreamingAssets/DataSets/land/LandPolygons.json,策划可直接编辑(默认路径写在 LandManager 的场景 Inspector 字段 _jsonRelativePath 中,当前值 DataSets/land/LandPolygons.json)。
每座岛的字段(出处 IslandData.cs):
| 字段 | 含义 | 取值规则 |
|---|---|---|
id |
岛屿唯一标识 | 字符串,如 main_001_madrid |
size |
尺寸分档 | Big / Medium / Small / Reef 四档(IslandSize.cs) |
polygon |
岛屿轮廓多边形 | 顶点数组,每个顶点为 [x, z] 世界坐标(单位:米)。建议逆时针排列;顺时针输入会被自动翻转(IslandMeshBuilder.cs) |
harborEdge |
港口起始边索引 | 边 i 指 polygon[i] → polygon[i+1] 这条边 |
harborWidth |
港口连续边数 | 从 harborEdge 起连续多少条边作为沙滩(默认 1;填 0 表示全岛无沙滩) |
cityId |
关联城市 | 如 city_001;留空表示无人岛/礁石 |
anchors |
建筑锚点列表 | 见下文“建筑锚点” |
加载规则(LandManager.cs、LandPolygonsLoader.cs):
- 加载时机:场景加载时
LandManager(mvp_002 场景内的 GameObject)在 Awake 阶段一次性读取并生成全部岛屿,之后注册进LandRegistry.cs供全局查询。岛屿随场景销毁,不跨场景保留。 - 容错:文件缺失或 JSON 格式错误时报错且不生成任何岛;单座岛多边形顶点少于 3 个时跳过该岛并警告。
- 城市系统(
CitySystemInitializer.cs)和巡逻船生成器(PatrolShipSpawner.cs)会等待LandRegistry中出现至少 1 座岛后才继续初始化,确保码头位置先于城市逻辑就绪。
当前地图全量内容(11 座岛)
Section titled “当前地图全量内容(11 座岛)”7 座城市岛 + 4 座无人礁石,全部城市岛的 harborWidth 均为 3(出处 LandPolygons.json):
| id | 尺寸 | 城市 | 顶点数 | 港口边 | 锚点 |
|---|---|---|---|---|---|
main_001_madrid |
Big | city_001 | 10 | 边 5 起连续 3 条 | Dock(边6, t=0.5)、CityHouse(边5, t=0.5)、Lighthouse(边7, t=0.5)、Watchtower(边5, t=0.2) |
main_002_seville |
Big | city_002 | 10 | 边 9 起连续 3 条 | Dock(边0)、CityHouse(边9)、Watchtower(边1),t 均 0.5 |
main_003_plymouth |
Medium | city_003 | 9 | 边 4 起连续 3 条 | Dock(边5)、CityHouse(边4)、Lighthouse(边6),t 均 0.5 |
main_004_portsmouth |
Big | city_004 | 11 | 边 5 起连续 3 条 | Dock(边6)、CityHouse(边5)、Watchtower(边7),t 均 0.5 |
main_005_freeport |
Big | city_005 | 10 | 边 5 起连续 3 条 | Dock(边6)、CityHouse(边5)、Lighthouse(边7),t 均 0.5 |
main_006_le_havre |
Medium | city_006 | 9 | 边 5 起连续 3 条 | Dock(边6)、CityHouse(边5)、Lighthouse(边7),t 均 0.5 |
main_007_marseille |
Medium | city_007 | 9 | 边 5 起连续 3 条 | Dock(边6)、CityHouse(边5)、Lighthouse(边7),t 均 0.5 |
reef_madrid_seville |
Reef | 无 | 6 | 无(harborWidth=0) | 无 |
reef_plymouth_portsmouth |
Reef | 无 | 6 | 无 | 无 |
reef_seville_le_havre |
Reef | 无 | 6 | 无 | 无 |
reef_portsmouth_marseille |
Reef | 无 | 6 | 无 | 无 |
Big 城市岛直径约 400-500 米,Medium 城市岛约 330 米;礁石直径约 60-70 米,散布在城市间航线上充当航行障碍。
尺寸分档与岛屿剖面
Section titled “尺寸分档与岛屿剖面”每座岛的立体造型由尺寸档位决定,四档参数全部是代码常量(出处 IslandMeshBuilder.cs 的 GetSizeParams):
| 档位 | 沙滩宽 beachWidth | 悬崖带宽 cliffWidth | 沙滩顶高 beachHeight | 顶面高 topHeight |
|---|---|---|---|---|
| Big | 18 米 | 10 米 | 0.6 米 | 8 米 |
| Medium | 12 米 | 7 米 | 0.5 米 | 5 米 |
| Small | 6 米 | 4 米 | 0.3 米 | 3 米 |
| Reef | 0 米(无沙滩) | 1 米 | 0 米 | 1.5 米 |
岛屿剖面从外向内分三层(外轮廓 → 内收 beachWidth → 再内收 cliffWidth):
- 港口边(
harborEdge起连续harborWidth条边):水线(y=0)→ 缓坡沙滩升到 beachHeight → 上半段悬崖升到 topHeight。这是视觉上的“可登陆海滩”,码头等锚点站在这里。 - 非港口边:水线直接一面陡崖拉到 topHeight,没有沙滩。
- 顶面:内收 beachWidth+cliffWidth 后的多边形,平铺草地,高 topHeight。
三层使用三种材质:沙滩 LandSand.mat、悬崖 LandRock.mat、顶面 LandGrass.mat(颜色与光滑度在材质资产里改,材质引用在场景中 LandManager 的 Inspector 槽位上换)。
退化保护(PolygonInset.cs、IslandMeshBuilder.cs):多边形向内收缩时如果岛太小(收缩后面积近 0、边方向反转或自相交),内收结果作废。顶面生成失败时仅输出警告“mesh degenerate”,岛屿视觉与碰撞不完整——所以小岛不要配大档位(参考测试 IslandMeshBuilderTests.cs:边长 5 米的方形配 Big 档直接生成不出顶面)。中间环成功而内环失败时,悬崖退化为圆锥造型兜底。
建筑锚点 BuildingAnchor
Section titled “建筑锚点 BuildingAnchor”锚点是岛屿边缘的预制体摆放点,用来放置码头、城市房屋、灯塔、瞭望塔等地标(出处 BuildingAnchor.cs、BuildingAnchorType.cs、IslandInstance.cs)。
每个锚点三个字段:
| 字段 | 含义 |
|---|---|
edge |
落在哪条多边形边上(索引超界则警告并跳过该锚点) |
t |
沿这条边的相对位置,0=边起点,1=边终点,默认 0.5(自动夹紧到 0..1) |
type |
四种类型之一:Dock(码头)、CityHouse(城市房屋)、Lighthouse(灯塔)、Watchtower(瞭望塔) |
定位规则:先取边上 t 位置的点,再沿“朝岛内”方向内移,所有锚点统一面朝大海(朝向 = 该边的外法线方向):
- 锚点所在边是港口(沙滩)边 → 内移 beachWidth,站在沙滩内侧顶部(悬崖根部),高度 = beachHeight。
- 锚点所在边是悬崖边 → 内移 beachWidth + cliffWidth + 2 米,站在岛顶面边缘,高度 = topHeight(额外 2 米为代码常量,
IslandInstance.cs)。
预制体注册:锚点类型到预制体的映射由 ScriptableObject 资产 BuildingAnchorPrefabRegistry.asset(Assets/Res/Data/LandSystem/)维护,策划在 Inspector 里给每种类型换 prefab 即可换外观(出处 BuildingAnchorPrefabRegistry.cs)。类型缺 prefab 时该锚点警告跳过。当前注册的是编辑器菜单 Tools/LandSystem/Create Anchor Placeholders + Registry 生成的占位体(出处 CreateLandPlaceholders.cs):
| 类型 | 占位体 | 尺寸(米) | 颜色 |
|---|---|---|---|
| Dock | 立方体 | 4 × 2 × 6 | 棕色 |
| CityHouse | 立方体 | 3 × 4 × 3 | 米白 |
| Lighthouse | 圆柱 | 2 × 8 × 2 | 白色 |
| Watchtower | 立方体 | 2 × 6 × 2 | 深灰 |
Dock 的特殊作用:带 cityId 的岛生成 Dock 锚点时,会把码头位置注册进全局 HarborRegistry.cs(cityId → 码头 Transform)。下游系统全部以它为锚:
- 城市交易触发距离判定以码头为圆心(
CityTradingTrigger.cs,见交易); - 巡逻船在码头位置出生、面朝大海(
PatrolShipSpawner.cs); - 城市驻防海上炮台以码头为锚点、半径 50 米、朝海 ±80 度扇形等分布置(
CitySystemInitializer.cs,见城市与国家); - 小地图在码头位置画港口点与安全区圆。
岛被销毁时自动注销对应城市的码头注册。
物理碰撞与触礁伤害 LandDamageDealer
Section titled “物理碰撞与触礁伤害 LandDamageDealer”碰撞体结构(IslandInstance.cs、IslandMeshBuilder.cs):沙滩/悬崖/顶面三层视觉网格全部不带碰撞体(薄壳网格会被高速船穿透)。物理阻挡由一个隐藏的实心多棱柱承担:以外轮廓多边形为截面,从水下 y=-10 米拉到 topHeight,凸体(convex)处理,挂在 ShipBuilding 物理层——项目碰撞矩阵只允许 ShipBuilding 层互撞,船体建筑恰好也在这层,所以船一定撞得上岛、其余物体(炮弹走射线检测)不受影响。碰撞边界就是岛的外轮廓水线,船永远开不上沙滩。
触礁伤害(LandDamageDealer.cs,挂在碰撞棱柱上):
触发条件:船与岛发生碰撞,且相对撞击速度 ≥ 1 米/秒 伤害公式:伤害 = max(1, round(10 × 撞击速度 × 0.1)) = max(1, round(撞击速度))
即伤害值约等于撞击速度的数值(米/秒四舍五入),10 米/秒撞上去掉 10 点血。低于 1 米/秒的轻触不掉血。
伤害归属阵营 "Land",经 ShipDamageHelper.cs 的统一管线结算:从碰撞体解析出被撞船 → 找船上离撞击点最近的可受击建筑 → 对它结算伤害(之后走正常的护甲/血量规则,见伤害与护甲;建筑承伤见建筑)。每次成功撞击在控制台输出一条日志。
三个伤害参数(基础伤害 10、最低触发速度 1 米/秒、阵营 “Land”)虽然声明为序列化字段,但 LandDamageDealer 组件是运行时动态挂载的,没有任何 prefab 或场景实例可改——实际生效的永远是代码默认值,调整需要改 LandDamageDealer.cs。
航行避障 LandSteeringAvoidance(反应层)
Section titled “航行避障 LandSteeringAvoidance(反应层)”所有用 SteeringController.cs 驾驶的 AI 船,每次转向计算的最后一步叠加陆地避让(出处 LandSteeringAvoidance.cs + SteeringController.cs 的 ApplyLandAvoidance):
触发条件:从船当前位置沿船头方向探查 30 米,探查点落在任一岛多边形内 规则:找该岛离探查点最近的一条边,取其指向岛外的法线方向,乘权重 1.5 作为侧推向量;侧推向量投影到船的左右轴上修正转向输入 结果:船向远离岛的一侧打舵,同时油门立即压到原值的 30%(防止探查为正时仍全速冲滩)
探查距离 30 米、权重 1.5、减速系数 0.3 全部是 SteeringController.cs 调用处的代码常量。没探查到陆地时此层零开销。这是船-船避让之外的第二层防线,与玩家手动驾驶无关(玩家船想撞岛随时可以撞)。
漫游过滤 LandWanderFilter(意图层)
Section titled “漫游过滤 LandWanderFilter(意图层)”LandWanderFilter.cs 提供两个纯查询,供所有“选点/选路”逻辑在生成意图阶段就排除陆地,从源头避免 AI 朝岛里走:
| 查询 | 判定 |
|---|---|
| 落点过滤 IsRejected | 点的 XZ 坐标落在任一岛多边形内 → 拒绝 |
| 线段过滤 IsSegmentRejected | 线段任一端点入岛,或线段与任一岛的任一条边相交 → 拒绝 |
使用方与各自规则:
- AI 漫游航点(
WanderBehavior.cs):每次评估当前航点,若航点入岛或当前位置到航点的直线穿岛,立即重选。重选时随机采样候选点,候选必须同时通过落点与线段过滤(外加船间避让区检查),最多尝试 10 次;10 次全失败则退而求其次选“离障碍最远”的候选。到达判定阈值默认 5 米。 - 玩家船自动导航(
RoutePlanner.cs、ShipNavigationController.cs):起点到目标直线不穿岛则直航;穿岛则在所有岛屿顶点中找一个中转点(顶点沿离岛方向外推 16 米,代码常量DetourPushOut,兼作贴边判定保险与航行安全距离),要求“起点→中转”“中转→目标”两段都不穿岛,取总路程最短者;找不到任何可行中转则导航失败。航行途中状态机也用线段过滤持续校验航线。 - 遭遇刷怪(
DirectionalSpawnDirector.cs):怪物/敌船刷新位置采样时,落点入岛直接重试(见遭遇与刷怪)。 - 巡逻海域(
PatrolZone.cs,城市系统):以码头为圆心把海面切成 8 米见方的网格,剔除所有“在岛内或距任一岛边不足 12 米”的格子(12 米约等于船的包围半径,代码常量SafetyMargin),再从码头侧做连通性洪泛,只保留与码头同侧连通的海面——保证巡逻船的出生点和航点永远在能开回港口的开阔水域,不会被岛隔在对岸飞地。
| 项目 | 数值 | 单位 | 来源 |
|---|---|---|---|
| 触礁伤害公式 | 伤害 = max(1, round(10 × 撞击速度 × 0.1)) | 点 | 代码常量,LandDamageDealer.cs |
| 触礁最低触发速度 | 1 | 米/秒 | 代码常量(序列化字段无实例可改),LandDamageDealer.cs |
| 触礁伤害阵营 | “Land” | — | 代码常量,LandDamageDealer.cs |
| Big 档:沙滩宽 / 悬崖宽 / 沙滩高 / 顶面高 | 18 / 10 / 0.6 / 8 | 米 | 代码常量,IslandMeshBuilder.cs |
| Medium 档:同上 | 12 / 7 / 0.5 / 5 | 米 | 代码常量,IslandMeshBuilder.cs |
| Small 档:同上 | 6 / 4 / 0.3 / 3 | 米 | 代码常量,IslandMeshBuilder.cs |
| Reef 档:同上 | 0 / 1 / 0 / 1.5 | 米 | 代码常量,IslandMeshBuilder.cs |
| 碰撞棱柱底部深度 | -10 | 米(水下) | 代码常量,IslandMeshBuilder.cs |
| 悬崖边锚点额外内移 | 2 | 米 | 代码常量,IslandInstance.cs |
| 避障探查距离 | 30 | 米 | 代码常量,SteeringController.cs |
| 避障侧推权重 | 1.5 | 倍 | 代码常量,SteeringController.cs |
| 避障触发后油门 | 30% | 百分比 | 代码常量,SteeringController.cs |
| 漫游航点重选上限 | 10 | 次 | 代码常量,WanderBehavior.cs |
| 漫游航点到达阈值 | 5(默认) | 米 | 代码常量(构造参数默认值),WanderBehavior.cs |
| 导航绕岛中转点外推 | 16 | 米 | 代码常量,RoutePlanner.cs |
| 巡逻海域网格尺寸 | 8 | 米/格 | 代码常量,PatrolZone.cs |
| 巡逻海域离岸安全距离 | 12 | 米 | 代码常量,PatrolZone.cs |
| 驻防炮台布置:半径 / 扇形 | 50 / ±80 | 米 / 度 | 代码常量,CitySystemInitializer.cs |
| 小地图港口安全区半径 | 80 | 米 | 代码常量,HarborSafeZoneConstants.cs |
| 小地图陆地描边颜色 | RGBA(0.6, 0.5, 0.3, 0.8) | — | 代码常量,LandManager.cs |
| 小地图港口点颜色 | RGBA(0.3, 0.8, 1, 1) | — | 代码常量,LandManager.cs |
| 岛屿总数(当前地图) | 11(7 城市岛 + 4 礁石) | 座 | JSON 配置,LandPolygons.json |
| 城市岛港口宽度 | 全部 3 条边 | 条 | JSON 配置,LandPolygons.json |
| 锚点 t 默认值 | 0.5(边中点) | 0..1 | JSON 配置默认,BuildingAnchor.cs |
| harborWidth 默认值 | 1 | 条 | JSON 配置默认,IslandData.cs |
与其他系统的交互
Section titled “与其他系统的交互”- 城市与国家:城市通过
cityId绑定到岛;Dock 锚点注册的码头位置是城市交易触发、巡逻船出生、驻防炮台布置的统一锚点。城市系统初始化会等待岛屿生成完毕。 - 交易:玩家船进入以码头为圆心的触发半径即可开始交易。
- 遭遇与刷怪:刷怪点采样经过陆地落点过滤,怪物不会刷在岛上。
- 伤害与护甲 / 建筑:触礁伤害落在船上离撞击点最近的建筑上,走标准承伤管线。
- 船只:AI 船的漫游、自动导航、反应式避让三层机制全部消费陆地数据;玩家船只受物理碰撞与触礁伤害约束。
- 界面总览:小地图绘制岛屿轮廓、港口点与港口安全区。
- 探索与情报:岛屿/城市的发现状态影响其在地图上的展示。
配置与调参指南
Section titled “配置与调参指南”| 想调什么 | 改哪里 | 类型 |
|---|---|---|
| 增删岛屿、改轮廓/位置/大小 | Assets/StreamingAssets/DataSets/land/LandPolygons.json 的 polygon 数组 |
JSON 配置,策划直接改 |
| 改岛屿高矮胖瘦观感(档位参数) | IslandMeshBuilder.cs 的 GetSizeParams |
代码常量,需程序改 |
| 给某岛换档位 | JSON 中该岛 size 字段(Big/Medium/Small/Reef) |
JSON 配置 |
| 改港口(沙滩)位置与宽度 | JSON 中 harborEdge / harborWidth |
JSON 配置 |
| 增删/挪动码头、灯塔等地标 | JSON 中 anchors 数组(edge + t + type) |
JSON 配置 |
| 换地标外观 | Assets/Res/Data/LandSystem/BuildingAnchorPrefabRegistry.asset 各类型的 prefab 槽位 |
Inspector 序列化字段 |
| 改沙滩/悬崖/草地颜色 | Assets/Res/Data/LandSystem/Materials/ 下三个材质,或在 mvp_002 场景 LandManager 上换材质引用 |
Inspector 序列化字段 |
| 改触礁伤害/触发速度 | LandDamageDealer.cs 字段默认值 |
代码常量(运行时挂载,无实例可调) |
| 改 AI 避障灵敏度(探查距离/侧推/减速) | SteeringController.cs 的 ApplyLandAvoidance |
代码常量 |
| 改巡逻海域离岸距离 | PatrolZone.cs 的 SafetyMargin |
代码常量 |
| 重新生成占位地标与注册表 | Unity 菜单 Tools/LandSystem/Create Anchor Placeholders + Registry |
编辑器工具,CreateLandPlaceholders.cs |
| 重新生成三个陆地材质 | Unity 菜单 Tools/LandSystem/Create Land Materials |
编辑器工具,CreateLandMaterials.cs |
编辑 JSON 时的硬约束:
- 多边形顶点至少 3 个,建议逆时针排列(顺时针会被自动翻转,但锚点外法线计算依赖质心判向,奇形怪状的凹多边形建议保持逆时针)。
- 顶点数必须少于 255(凸碰撞体的引擎上限,
IslandInstance.cs注释);自相交检测按“顶点少于 30”的规模设计(PolygonInset.cs注释),当前地图单岛 6-11 个顶点。 - 岛不能配过大的档位:多边形内收 beachWidth+cliffWidth 后必须仍是有效多边形,否则顶面生成失败(Big 档至少需要约 28 米的内收余量)。
- 锚点
edge索引必须在多边形边数范围内,越界仅警告跳过、不报错,容易漏看。
已知限制与注意事项
Section titled “已知限制与注意事项”- 凹岛碰撞是凸包近似:碰撞棱柱以 convex 模式工作,凹形岛(如有内湾)的实际物理边界是其凸包——船可能被“看不见的墙”挡在湾口之外。当前地图所有岛都接近凸形,问题未暴露;设计深水湾地形前需先改碰撞方案。
LandDamageDealer.cs的类注释已过时:注释写“挂在岛 cliff / beach mesh GO 上”,实际只挂在隐藏的碰撞棱柱(Hull)上,沙滩/悬崖视觉网格没有任何碰撞体。- 触礁参数名义可配、实际不可配:
LandDamageDealer三个序列化字段因组件由代码动态挂载,Inspector 无处可调,等同代码常量。 - 岛屿高度无逐岛覆盖:高度、沙滩宽等全部由四档常量决定,JSON 无法逐岛微调;同档位岛屿剖面完全一致。
- Small 档已定义但当前地图未使用:四档中只有 Big / Medium / Reef 出现在
LandPolygons.json中。 - 自动导航只支持单中转点:
RoutePlanner.cs绕岛仅找一个中转顶点,目标藏在多岛迷宫后时会规划失败(返回不可达),不会多段绕行。 - 锚点建筑是纯摆设:Dock/CityHouse/Lighthouse/Watchtower 占位体没有血量、不可交互、不接入建筑系统;Dock 的功能价值仅在于注册码头坐标。
- 贴边判定不可靠:点恰好落在多边形边界上时的内外判定无保证,下游全部用外推距离规避(导航中转点外推 16 米、巡逻海域离岸 12 米)。
- 岛屿生成与小地图的时序竞态:岛屿生成末尾立即挂小地图标记,若 MiniMapManager 尚未就绪可能产生一次性报错(PlayMode 测试
LandManagerScenarioTests.cs对此类背景错误显式忽略),不影响岛屿功能。 - 设计文档与实现一致:实现与
Docs/superpowers/plans/2026-04-26-land-area-system.md(陆地系统 P0 计划)规划的结构相符;该计划中独立的LandMiniMapMarker.cs桥接文件后来被裁撤,小地图标记逻辑内联进了LandManager.cs(文件内注释有说明)。