3. 地图与分路系统
2026/4/15大约 5 分钟
3. 地图与分路系统:搭建三条路线的战场
3.1 地图设计原理
MOBA 地图的核心原则是对称——蓝色方和红色方的地形完全镜像,保证公平。我们的地图包含以下要素:
| 要素 | 数量 | 说明 |
|---|---|---|
| 基地 | 2 个 | 蓝方和红方各一个 |
| 分路 | 3 条 | 上路、中路、下路 |
| 防御塔 | 每方 6 座 | 每路 2 座 + 高地塔 |
| 野怪营地 | 4 个 | 每方 2 个 |
| Boss 怪物 | 1 个 | 地图中央河道区域 |
| 商店 | 2 个 | 各方基地附近 |
地图尺寸规划
地图总大小:100 × 80 单位
上路线长度:约 90 单位(斜线)
中路线长度:约 70 单位(直线)
下路线长度:约 90 单位(斜线)
基地大小:10 × 10 单位
防御塔碰撞半径:1.0 单位
防御塔攻击范围:8.0 单位3.2 创建地面
首先创建地图的地面:
C#
// TerrainGenerator.cs
using Godot;
public partial class TerrainGenerator : Node3D
{
public override void _Ready()
{
CreateGround();
CreateRiver();
}
private void CreateGround()
{
// 创建主地面
var ground = new MeshInstance3D();
var planeMesh = new PlaneMesh();
planeMesh.Size = new Vector2(100, 80);
ground.Mesh = planeMesh;
// 给地面添加绿色草地材质
var material = new StandardMaterial3D();
material.AlbedoColor = new Color(0.3f, 0.6f, 0.2f); // 草绿色
ground.MaterialOverride = material;
ground.Position = new Vector3(0, 0, 0);
AddChild(ground);
// 添加碰撞形状(让角色不会掉下去)
var staticBody = new StaticBody3D();
var collisionShape = new CollisionShape3D();
var boxShape = new BoxShape3D();
boxShape.Size = new Vector3(100, 0.1f, 80);
collisionShape.Shape = boxShape;
staticBody.AddChild(collisionShape);
AddChild(staticBody);
}
private void CreateRiver()
{
// 河道穿过地图中间
var river = new MeshInstance3D();
var riverMesh = new PlaneMesh();
riverMesh.Size = new Vector2(12, 80);
river.Mesh = riverMesh;
var riverMaterial = new StandardMaterial3D();
riverMaterial.AlbedoColor = new Color(0.2f, 0.4f, 0.8f, 0.7f);
riverMaterial.Transparency = BaseMaterial3D.TransparencyEnum.Alpha;
river.MaterialOverride = riverMaterial;
river.Position = new Vector3(0, 0.01f, 0);
AddChild(river);
}
}GDScript
# terrain_generator.gd
extends Node3D
func _ready():
create_ground()
create_river()
func create_ground():
# 创建主地面
var ground = MeshInstance3D.new()
var plane_mesh = PlaneMesh.new()
plane_mesh.size = Vector2(100, 80)
ground.mesh = plane_mesh
# 给地面添加绿色草地材质
var material = StandardMaterial3D.new()
material.albedo_color = Color(0.3, 0.6, 0.2) # 草绿色
ground.material_override = material
ground.position = Vector3(0, 0, 0)
add_child(ground)
# 添加碰撞形状
var static_body = StaticBody3D.new()
var collision_shape = CollisionShape3D.new()
var box_shape = BoxShape3D.new()
box_shape.size = Vector3(100, 0.1, 80)
collision_shape.shape = box_shape
static_body.add_child(collision_shape)
add_child(static_body)
func create_river():
# 河道穿过地图中间
var river = MeshInstance3D.new()
var river_mesh = PlaneMesh.new()
river_mesh.size = Vector2(12, 80)
river.mesh = river_mesh
var river_material = StandardMaterial3D.new()
river_material.albedo_color = Color(0.2, 0.4, 0.8, 0.7)
river_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
river.material_override = river_material
river.position = Vector3(0, 0.01, 0)
add_child(river)3.3 设置导航区域
小兵和 AI 英雄需要自动寻路。我们使用 Godot 的 NavigationRegion3D 来定义可行走区域:
- 在场景树中添加
NavigationRegion3D节点 - 使用导航网格(NavigationMesh)定义可行走区域
- 烘焙导航网格
导航网格烘焙步骤
- 选中
NavigationRegion3D节点 - 在检查器中点击 NavigationMesh 属性
- 调整参数:
Agent Radius = 0.5,Cell Size = 0.25 - 点击右上角的 烘焙 按钮
3.4 创建三条分路
每条路由一系列路径点(waypoint)组成,小兵沿着这些路径点前进:
C#
// LanePath.cs
using Godot;
using System.Collections.Generic;
[GlobalClass]
public partial class LanePath : Resource
{
[Export] public string LaneName { get; set; } = ""; // "top", "mid", "bot"
[Export] public Vector3[] BlueWaypoints { get; set; } // 蓝方路径点
[Export] public Vector3[] RedWaypoints { get; set; } // 红方路径点
}
// LaneManager.cs - 管理三条分路
public partial class LaneManager : Node
{
private Dictionary<string, LanePath> _lanes = new();
public override void _Ready()
{
CreateTopLane();
CreateMidLane();
CreateBotLane();
}
private void CreateMidLane()
{
// 中路:从蓝方基地到红方基地,直线
var midPath = new LanePath();
midPath.LaneName = "mid";
midPath.BlueWaypoints = new Vector3[]
{
new(-40, 0, 0), // 蓝方基地门口
new(-25, 0, 0), // 第一座塔附近
new(-10, 0, 0), // 中间区域
new(10, 0, 0), // 河道
new(25, 0, 0), // 对方第一座塔附近
new(40, 0, 0), // 红方基地门口
};
// 红方路径是蓝方的镜像(反序)
midPath.RedWaypoints = ReversePath(midPath.BlueWaypoints);
_lanes["mid"] = midPath;
}
private Vector3[] ReversePath(Vector3[] path)
{
var result = new Vector3[path.Length];
for (int i = 0; i < path.Length; i++)
{
result[i] = path[path.Length - 1 - i];
}
return result;
}
// CreateTopLane() 和 CreateBotLane() 类似,只是路径点不同
// 上路沿地图上方走斜线,下路沿地图下方走斜线
public LanePath GetLane(string laneName) => _lanes.GetValueOrDefault(laneName);
}GDScript
# lane_path.gd
class_name LanePath
extends Resource
@export var lane_name: String = "" # "top", "mid", "bot"
@export var blue_waypoints: Array[Vector3] = [] # 蓝方路径点
@export var red_waypoints: Array[Vector3] = [] # 红方路径点# lane_manager.gd
extends Node
var lanes: Dictionary = {}
func _ready():
create_top_lane()
create_mid_lane()
create_bot_lane()
func create_mid_lane():
# 中路:从蓝方基地到红方基地,直线
var mid_path = LanePath.new()
mid_path.lane_name = "mid"
mid_path.blue_waypoints = [
Vector3(-40, 0, 0), # 蓝方基地门口
Vector3(-25, 0, 0), # 第一座塔附近
Vector3(-10, 0, 0), # 中间区域
Vector3(10, 0, 0), # 河道
Vector3(25, 0, 0), # 对方第一座塔附近
Vector3(40, 0, 0), # 红方基地门口
]
# 红方路径是蓝方的镜像(反序)
mid_path.red_waypoints = reverse_path(mid_path.blue_waypoints)
lanes["mid"] = mid_path
func reverse_path(path: Array[Vector3]) -> Array[Vector3]:
var result: Array[Vector3] = []
for i in range(path.size() - 1, -1, -1):
result.append(path[i])
return result
# create_top_lane() 和 create_bot_lane() 类似,只是路径点不同
func get_lane(lane_name: String) -> LanePath:
return lanes.get(lane_name):::
3.5 创建野区
野区是路和路之间的区域,里面有可以打的野怪:
野区布局示意:
上路
[野怪营A] [野怪营B]
| |
蓝方基地──中路──红方基地
| |
[野怪营C] [Boss] [野怪营D]
| |
下路野怪营地在地图上的坐标:
| 营地 | 位置 | 刷怪类型 |
|---|---|---|
| A 营地 | (-20, 0, -15) | 小野怪 × 3 |
| B 营地 | (20, 0, -15) | 小野怪 × 3 |
| C 营地 | (-20, 0, 15) | 小野怪 × 3 |
| D 营地 | (20, 0, 15) | 小野怪 × 3 |
| Boss | (0, 0, 5) | Boss 怪物 × 1 |
3.6 草丛系统
草丛是 MOBA 游戏的经典机制——进入草丛后,敌方看不到你:
C#
// Bush.cs
using Godot;
public partial class Bush : Area3D
{
public override void _Ready()
{
BodyEntered += OnBodyEntered;
BodyExited += OnBodyExited;
}
private void OnBodyEntered(Node3D body)
{
if (body.IsInGroup("heroes"))
{
// 英雄进入草丛:隐藏其视野信息
if (body.HasMethod("SetInBush"))
body.Call("SetInBush", true);
}
}
private void OnBodyExited(Node3D body)
{
if (body.IsInGroup("heroes"))
{
// 英雄离开草丛:恢复可见
if (body.HasMethod("SetInBush"))
body.Call("SetInBush", false);
}
}
}GDScript
# bush.gd
extends Area3D
func _ready():
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
func _on_body_entered(body: Node3D):
if body.is_in_group("heroes"):
# 英雄进入草丛:隐藏其视野信息
if body.has_method("set_in_bush"):
body.set_in_bush(true)
func _on_body_exited(body: Node3D):
if body.is_in_group("heroes"):
# 英雄离开草丛:恢复可见
if body.has_method("set_in_bush"):
body.set_in_bush(false)章节导航
| 上一章 | 下一章 |
|---|---|
| ← 2. 项目搭建与场景配置 | 4. 英雄角色系统 → |
