7. 世界探索系统
2026/4/14大约 8 分钟
世界探索系统
一个好玩的游戏,世界本身就要有吸引力。如果到处都是一样的草地,玩家走两步就不想探索了。我们需要设计不同的"生物群系"——每个区域有独特的地形、资源、帕鲁和危险。
本章你将学到
- 生物群系设计(草原、沙漠、雪山、火山、沼泽)
- 地下城程序化生成(随机迷宫)
- 传送点系统
- 稀有资源分布
- 世界事件系统
世界地图结构
生物群系设计
每个生物群系就像一个"主题区域"——有独特的地貌、颜色、天气、资源和帕鲁。
五大群系对比
| 群系 | 地貌 | 天气 | 特有资源 | 特有帕鲁 | 危险等级 |
|---|---|---|---|---|---|
| 草原 | 绿色平原、小丘陵 | 晴天为主 | 木材、石材、野果 | 草属性帕鲁、普通帕鲁 | 低 |
| 沙漠 | 沙丘、仙人掌 | 沙暴、高温 | 铁矿、沙石 | 沙属性帕鲁、蜥蜴类 | 中 |
| 雪山 | 白色雪地、冰川 | 暴风雪、低温 | 冰晶、雪莲 | 冰属性帕鲁、雪狼 | 高 |
| 火山 | 熔岩、火山岩 | 高温、火山灰 | 水晶、稀有矿石 | 火属性帕鲁、龙类 | 极高 |
| 沼泽 | 泥地、枯树 | 毒雾、阴雨 | 草药、毒液 | 毒属性帕鲁、蛇类 | 中 |
群系环境效果
不同群系对玩家有不同的环境影响,需要对应的装备来应对:
| 群系 | 环境效果 | 应对方式 |
|---|---|---|
| 沙漠 | 高温:体力消耗加倍 | 携带水袋、穿轻薄装备 |
| 雪山 | 低温:持续扣血 | 穿保暖装备、带火把 |
| 火山 | 极高温:持续扣血+减速 | 穿隔热装备、带冷却药水 |
| 沼泽 | 毒雾:持续中毒 | 戴防毒面具、带解毒药 |
地下城程序化生成
地下城是隐藏在地下的迷宫——每次进入都不一样,有随机的房间、走廊、宝箱和 Boss。这叫"程序化生成"——用算法自动创建迷宫,而不是手工设计每一个关卡。
地下城生成流程
地下城生成代码
C#
// DungeonGenerator.cs
// 地下城程序化生成器
using Godot;
using System.Collections.Generic;
public partial class DungeonGenerator : Node3D
{
[Export] public int MinRooms = 5;
[Export] public int MaxRooms = 10;
[Export] public int RoomMinSize = 8;
[Export] public int RoomMaxSize = 16;
[Export] public int CorridorWidth = 3;
[Export] public PackedScene WallScene { get; set; }
[Export] public PackedScene FloorScene { get; set; }
private List<Rect2I> _rooms = new();
private RandomNumberGenerator _rng = new();
public override void _Ready()
{
_rng.Randomize();
Generate();
}
public void Generate()
{
_rooms.Clear();
GenerateRooms();
ConnectRooms();
PlaceContents();
}
// 生成房间
private void GenerateRooms()
{
int attempts = 0;
int maxAttempts = 100;
while (_rooms.Count < MaxRooms && attempts < maxAttempts)
{
int w = _rng.RandiRange(RoomMinSize, RoomMaxSize);
int h = _rng.RandiRange(RoomMinSize, RoomMaxSize);
int x = _rng.RandiRange(0, 100 - w);
int y = _rng.RandiRange(0, 100 - h);
var newRoom = new Rect2I(x, y, w, h);
// 检查是否和已有房间重叠
bool overlaps = false;
foreach (var room in _rooms)
{
if (newRoom.GrowIndividual(2, 2, 2, 2).Intersects(room))
{
overlaps = true;
break;
}
}
if (!overlaps)
{
_rooms.Add(newRoom);
BuildRoom(newRoom);
}
attempts++;
}
}
// 构建单个房间的墙壁和地板
private void BuildRoom(Rect2I room)
{
// 放置地板
for (int x = room.Position.X; x < room.End.X; x++)
{
for (int y = room.Position.Y; y < room.End.Y; y++)
{
if (FloorScene != null)
{
var floor = FloorScene.Instantiate<Node3D>();
floor.Position = new Vector3(x * 2, 0, y * 2);
AddChild(floor);
}
}
}
// 放置墙壁(房间边缘)
// TODO: 在房间四周放置墙壁
}
// 用走廊连接相邻房间
private void ConnectRooms()
{
for (int i = 1; i < _rooms.Count; i++)
{
var roomA = _rooms[i - 1];
var roomB = _rooms[i];
// 计算两个房间的中心点
var centerA = roomA.Position + roomA.Size / 2;
var centerB = roomB.Position + roomB.Size / 2;
// L型走廊:先水平再垂直
BuildCorridor(centerA, new Vector2I(centerB.X, centerA.Y));
BuildCorridor(new Vector2I(centerB.X, centerA.Y), centerB);
}
}
// 构建走廊
private void BuildCorridor(Vector2I from, Vector2I to)
{
int minX = Mathf.Min(from.X, to.X);
int maxX = Mathf.Max(from.X, to.X);
int minY = Mathf.Min(from.Y, to.Y);
int maxY = Mathf.Max(from.Y, to.Y);
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
if (FloorScene != null)
{
var floor = FloorScene.Instantiate<Node3D>();
floor.Position = new Vector3(x * 2, 0, y * 2);
AddChild(floor);
}
}
}
}
// 放置内容(敌人、宝箱、Boss)
private void PlaceContents()
{
if (_rooms.Count < 2) return;
// 第一个房间:入口(安全)
GD.Print($"入口房间: {_rooms[0]}");
// 中间的房间:随机放敌人和宝箱
for (int i = 1; i < _rooms.Count - 1; i++)
{
GD.Print($"战斗房间 {_rooms[i]}:放置敌人和宝箱");
// TODO: 实例化敌人和宝箱
}
// 最后一个房间:Boss
GD.Print($"Boss房间: {_rooms[_rooms.Count - 1]}");
// TODO: 实例化 Boss
}
}GDScript
# dungeon_generator.gd
# 地下城程序化生成器
extends Node3D
@export var min_rooms: int = 5
@export var max_rooms: int = 10
@export var room_min_size: int = 8
@export var room_max_size: int = 16
@export var corridor_width: int = 3
@export var wall_scene: PackedScene
@export var floor_scene: PackedScene
var _rooms: Array = []
func _ready() -> void:
generate()
func generate() -> void:
_rooms.clear()
_generate_rooms()
_connect_rooms()
_place_contents()
## 生成房间
func _generate_rooms() -> void:
var attempts := 0
var max_attempts := 100
while _rooms.size() < max_rooms and attempts < max_attempts:
var w := randi_range(room_min_size, room_max_size)
var h := randi_range(room_min_size, room_max_size)
var x := randi_range(0, 100 - w)
var y := randi_range(0, 100 - h)
var new_room := Rect2i(x, y, w, h)
# 检查是否和已有房间重叠
var overlaps := false
for room in _rooms:
if new_room.grow_individual(2, 2, 2, 2).intersects(room):
overlaps = true
break
if not overlaps:
_rooms.append(new_room)
_build_room(new_room)
attempts += 1
## 构建单个房间
func _build_room(room: Rect2i) -> void:
# 放置地板
for rx in range(room.position.x, room.end.x):
for ry in range(room.position.y, room.end.y):
if floor_scene != null:
var floor := floor_scene.instantiate() as Node3D
floor.position = Vector3(rx * 2, 0, ry * 2)
add_child(floor)
## 连接房间
func _connect_rooms() -> void:
for i in range(1, _rooms.size()):
var room_a: Rect2i = _rooms[i - 1]
var room_b: Rect2i = _rooms[i]
var center_a := room_a.position + room_a.size / 2
var center_b := room_b.position + room_b.size / 2
# L型走廊
_build_corridor(center_a, Vector2i(center_b.x, center_a.y))
_build_corridor(Vector2i(center_b.x, center_a.y), center_b)
## 构建走廊
func _build_corridor(from: Vector2i, to: Vector2i) -> void:
var min_x := mini(from.x, to.x)
var max_x := maxi(from.x, to.x)
var min_y := mini(from.y, to.y)
var max_y := maxi(from.y, to.y)
for x in range(min_x, max_x + 1):
for y in range(min_y, max_y + 1):
if floor_scene != null:
var floor := floor_scene.instantiate() as Node3D
floor.position = Vector3(x * 2, 0, y * 2)
add_child(floor)
## 放置内容
func _place_contents() -> void:
if _rooms.size() < 2:
return
# 第一个房间:入口
print("入口房间: %s" % str(_rooms[0]))
# 中间房间:敌人+宝箱
for i in range(1, _rooms.size() - 1):
print("战斗房间 %s:放置敌人和宝箱" % str(_rooms[i]))
# 最后房间:Boss
print("Boss房间: %s" % str(_rooms[-1]))传送点系统
世界这么大,每次都靠走路太累了。传送点让玩家在已经去过的地方之间快速移动。
传送点设计
- 传送点是地图上的发光柱子,靠近后激活
- 激活后可以在大地图界面选择传送到任意已激活的传送点
- 传送有 3 秒的施法时间(期间不能移动,被打断取消)
- 每个群系至少 2~3 个传送点
传送点分布
| 群系 | 传送点数量 | 位置 |
|---|---|---|
| 草原 | 3 | 出生点、基地附近、草原边缘 |
| 沙漠 | 2 | 沙漠入口、绿洲 |
| 雪山 | 2 | 山脚营地、山顶 |
| 火山 | 2 | 火山入口、火山口 |
| 沼泽 | 2 | 沼泽边缘、沼泽中心 |
稀有资源分布
不同群系有不同的稀有资源,鼓励玩家去探索每个角落。
世界事件系统
世界不是静态的——随机发生的事件让每次游玩都有新鲜感。
随机事件类型
| 事件 | 触发条件 | 效果 | 持续时间 |
|---|---|---|---|
| 暴风雨 | 随机(草原) | 视野下降、移速降低 | 5 分钟 |
| 沙暴 | 随机(沙漠) | 视野极差、体力消耗加倍 | 3 分钟 |
| 极光 | 夜间(雪山) | 无负面效果,获得经验加成 | 整夜 |
| Boss 刷新 | 每 30 分钟 | 指定区域出现 Boss 帕鲁 | 直到被击败 |
| 流浪商人 | 随机 | 出现一个可以交易的 NPC | 10 分钟 |
| 资源丰收 | 随机 | 某个群系的资源产出翻倍 | 10 分钟 |
| 帕鲁潮 | 夜间随机 | 大量帕鲁涌向某个区域 | 5 分钟 |
常见问题
Q:生物群系之间的过渡怎么处理?
用"渐变带"——在两个群系之间做一个 20~30 米宽的过渡区域。比如草原和沙漠之间,草的颜色从绿色渐渐变成黄色,地面从泥土变成沙子。这样不会显得突兀。
Q:地下城怎么保证不迷路?
两个方法:一是在入口给玩家一个"地下城地图"道具,使用后显示已探索的区域;二是在走廊里放发光的路标,指向出口方向。
Q:传送点会不会让探索变得太容易?
不会,因为传送点需要先"走到那个地方"才能激活。你只能传送到你已经去过的地方。这鼓励了探索,同时减少了重复跑路的烦恼。
Q:世界事件会不会影响游戏平衡?
大部分事件只是增加氛围和变化,不影响核心平衡。唯一需要注意的是 Boss 刷新事件——确保 Boss 出现时有足够的提示,让玩家可以选择避开或挑战。
下一步
世界探索做好了,接下来实现 游戏UI——让玩家能方便地管理帕鲁、物品和地图。
