跳转到内容

岛屿与陆地

用一份 JSON 配置文件定义的多边形岛屿,在运行时自动生成沙滩-悬崖-顶面三层地形、实心碰撞体、港口建筑锚点与小地图标记;船撞上扣血,AI 船自动绕行,刷怪与巡逻航点全部避开陆地。

陆地系统(Service/LandSystem/)是航海世界中的静态地形层。整个游戏世界是一片开阔海面,岛屿是海面上唯一的不可通行障碍物。每座岛由一个二维多边形(世界 XZ 平面坐标)定义,运行时由场景中的 LandManager.cs 读取 LandPolygons.json 后逐岛生成。

玩家通过以下方式接触这套系统:

  • 航行受阻:岛屿有实心碰撞体,船开不进岛的轮廓线之内。
  • 触礁扣血:以足够速度撞上岛屿,船上离撞击点最近的建筑直接掉血(见伤害与护甲)。
  • 靠港交易:每座城市岛在沙滩上有一个 Dock(码头)锚点,城市的交易触发距离、巡逻船出生点、驻防炮台布置全部以这个码头位置为基准(见城市与国家交易)。
  • 小地图:每座岛在小地图上显示棕色轮廓描边,城市岛额外显示青蓝色港口点和半径 80 米的港口安全区圆圈(见界面总览)。
  • AI 行为:所有 AI 船的漫游航点、刷怪位置、巡逻海域都会主动过滤陆地,反应式转向避让兜底。

岛屿是装饰性与功能性地形,不可登陆、不可破坏、不可建造。岛上的房屋、灯塔等只是按锚点摆放的视觉预制体,不接入建筑系统。

所有岛屿数据来自一份 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.csLandPolygonsLoader.cs):

  • 加载时机:场景加载时 LandManager(mvp_002 场景内的 GameObject)在 Awake 阶段一次性读取并生成全部岛屿,之后注册进 LandRegistry.cs 供全局查询。岛屿随场景销毁,不跨场景保留。
  • 容错:文件缺失或 JSON 格式错误时报错且不生成任何岛;单座岛多边形顶点少于 3 个时跳过该岛并警告。
  • 城市系统(CitySystemInitializer.cs)和巡逻船生成器(PatrolShipSpawner.cs)会等待 LandRegistry 中出现至少 1 座岛后才继续初始化,确保码头位置先于城市逻辑就绪。

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 米,散布在城市间航线上充当航行障碍。

每座岛的立体造型由尺寸档位决定,四档参数全部是代码常量(出处 IslandMeshBuilder.csGetSizeParams):

档位 沙滩宽 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.csIslandMeshBuilder.cs):多边形向内收缩时如果岛太小(收缩后面积近 0、边方向反转或自相交),内收结果作废。顶面生成失败时仅输出警告“mesh degenerate”,岛屿视觉与碰撞不完整——所以小岛不要配大档位(参考测试 IslandMeshBuilderTests.cs:边长 5 米的方形配 Big 档直接生成不出顶面)。中间环成功而内环失败时,悬崖退化为圆锥造型兜底。

锚点是岛屿边缘的预制体摆放点,用来放置码头、城市房屋、灯塔、瞭望塔等地标(出处 BuildingAnchor.csBuildingAnchorType.csIslandInstance.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.assetAssets/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.csIslandMeshBuilder.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.csApplyLandAvoidance):

触发条件:从船当前位置沿船头方向探查 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.csShipNavigationController.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
  • 城市与国家:城市通过 cityId 绑定到岛;Dock 锚点注册的码头位置是城市交易触发、巡逻船出生、驻防炮台布置的统一锚点。城市系统初始化会等待岛屿生成完毕。
  • 交易:玩家船进入以码头为圆心的触发半径即可开始交易。
  • 遭遇与刷怪:刷怪点采样经过陆地落点过滤,怪物不会刷在岛上。
  • 伤害与护甲 / 建筑:触礁伤害落在船上离撞击点最近的建筑上,走标准承伤管线。
  • 船只:AI 船的漫游、自动导航、反应式避让三层机制全部消费陆地数据;玩家船只受物理碰撞与触礁伤害约束。
  • 界面总览:小地图绘制岛屿轮廓、港口点与港口安全区。
  • 探索与情报:岛屿/城市的发现状态影响其在地图上的展示。
想调什么 改哪里 类型
增删岛屿、改轮廓/位置/大小 Assets/StreamingAssets/DataSets/land/LandPolygons.jsonpolygon 数组 JSON 配置,策划直接改
改岛屿高矮胖瘦观感(档位参数) IslandMeshBuilder.csGetSizeParams 代码常量,需程序改
给某岛换档位 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.csApplyLandAvoidance 代码常量
改巡逻海域离岸距离 PatrolZone.csSafetyMargin 代码常量
重新生成占位地标与注册表 Unity 菜单 Tools/LandSystem/Create Anchor Placeholders + Registry 编辑器工具,CreateLandPlaceholders.cs
重新生成三个陆地材质 Unity 菜单 Tools/LandSystem/Create Land Materials 编辑器工具,CreateLandMaterials.cs

编辑 JSON 时的硬约束:

  1. 多边形顶点至少 3 个,建议逆时针排列(顺时针会被自动翻转,但锚点外法线计算依赖质心判向,奇形怪状的凹多边形建议保持逆时针)。
  2. 顶点数必须少于 255(凸碰撞体的引擎上限,IslandInstance.cs 注释);自相交检测按“顶点少于 30”的规模设计(PolygonInset.cs 注释),当前地图单岛 6-11 个顶点。
  3. 岛不能配过大的档位:多边形内收 beachWidth+cliffWidth 后必须仍是有效多边形,否则顶面生成失败(Big 档至少需要约 28 米的内收余量)。
  4. 锚点 edge 索引必须在多边形边数范围内,越界仅警告跳过、不报错,容易漏看。
  • 凹岛碰撞是凸包近似:碰撞棱柱以 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(文件内注释有说明)。