2. 项目搭建
2. 坦克大战——项目搭建
简介
项目搭建就像盖房子前先搭脚手架。在写具体的游戏功能之前,我们需要先把项目的基本结构搭好——创建文件夹、写好管理器代码、设置好场景骨架。这就好比你要做一顿大餐,先得把厨房收拾好、把食材分类放好、把锅碗瓢盆摆到该放的位置。
为什么项目搭建很重要?
想象一下,如果你的工具都乱扔在桌上,要找一把螺丝刀得翻半天。代码也是一样——如果文件乱放、没有统一的管理方式,后面功能越写越多,你就会迷失在自己的代码里。
一个好的项目结构应该做到:
- 一看就知道文件在哪儿:想改坦克的代码,去
tanks/文件夹找就行 - 代码不互相纠缠:坦克的代码不会混进地图的代码里
- 方便扩展:以后要加新功能,知道该往哪里加
项目目录结构
打开 Godot 编辑器,在项目面板中创建以下文件夹结构:
res://
├── scenes/ # 场景文件夹
│ ├── main.tscn # 主场景(游戏入口)
│ ├── game.tscn # 游戏场景(战场)
│ ├── menus/ # 菜单场景
│ │ ├── main_menu.tscn # 主菜单
│ │ └── stage_select.tscn # 关卡选择
│ ├── tanks/ # 坦克场景
│ │ ├── player.tscn # 玩家坦克
│ │ └── enemy.tscn # 敌人坦克
│ ├── bullets/ # 子弹场景
│ │ └── bullet.tscn # 子弹
│ ├── effects/ # 特效场景
│ │ ├── explosion.tscn # 爆炸效果
│ │ └── spawn_effect.tscn # 出生特效
│ └── items/ # 道具场景
│ ├── star.tscn # 星星(升级)
│ ├── bomb.tscn # 炸弹(清屏)
│ └── shovel.tscn # 铲子(加固基地)
│
├── scripts/ # 脚本文件夹
│ ├── managers/ # 管理器脚本
│ │ ├── game_manager.gd # 游戏总管理器
│ │ ├── score_manager.gd # 分数管理器
│ │ ├── enemy_spawner.gd # 敌人生成器
│ │ └── audio_manager.gd # 音效管理器
│ ├── tanks/ # 坦克脚本
│ │ ├── tank_base.gd # 坦克基类
│ │ ├── player_tank.gd # 玩家坦克
│ │ └── enemy_tank.gd # 敌人坦克
│ ├── bullets/ # 子弹脚本
│ │ └── bullet.gd # 子弹
│ ├── map/ # 地图脚本
│ │ ├── game_map.gd # 地图逻辑
│ │ └── tile_data.gd # 地形数据
│ └── ui/ # UI脚本
│ ├── hud.gd # 游戏界面
│ └── map_editor.gd # 地图编辑器
│
├── assets/ # 资源文件夹
│ ├── sprites/ # 图片资源
│ │ ├── tanks/ # 坦克图片
│ │ ├── tiles/ # 地形图片
│ │ ├── items/ # 道具图片
│ │ └── ui/ # UI图片
│ ├── audio/ # 音频资源
│ │ ├── sfx/ # 音效
│ │ └── music/ # 背景音乐
│ └── fonts/ # 字体
│
├── data/ # 数据文件夹
│ ├── levels/ # 关卡数据
│ │ ├── level_1.json # 第1关地图数据
│ │ ├── level_2.json # 第2关地图数据
│ │ └── ...
│ └── custom_levels/ # 玩家自定义关卡
│
└── autoload/ # 自动加载脚本
├── game_manager.gd # 游戏管理器(全局)
└── audio_manager.gd # 音效管理器(全局)创建项目
第一步:新建 Godot 项目
- 打开 Godot 4.x 编辑器
- 点击 "新建项目"(New Project)
- 项目名称填:
TankBattle - 选择一个你喜欢的文件夹路径
- 渲染器选择 "兼容"(Compatibility)——因为我们是2D游戏,兼容模式足够了
- 点击 "创建并编辑"
第二步:设置项目参数
点击菜单 项目 → 项目设置,进行以下配置:
| 设置项 | 值 | 说明 |
|---|---|---|
| 显示 → 窗口 → 宽度 | 624 | 游戏画面宽度(13格 x 48像素) |
| 显示 → 窗口 → 高度 | 624 | 游戏画面高度(13格 x 48像素) |
| 显示 → 窗口 → 拉伸模式 | canvas_items | 窗口缩放时画面跟着缩放 |
| 显示 → 窗口 → 拉伸方面 | keep | 保持画面比例不变形 |
| 输入映射 → move_up | W / 上箭头 | 向上移动 |
| 输入映射 → move_down | S / 下箭头 | 向下移动 |
| 输入映射 → move_left | A / 左箭头 | 向左移动 |
| 输入映射 → move_right | D / 右箭头 | 向右移动 |
| 输入映射 → shoot | Space / J | 发射子弹 |
| 图层名称 → Tanks | 2 | 坦克所在的物理层 |
| 图层名称 → Bullets | 3 | 子弹所在的物理层 |
| 图层名称 → Walls | 4 | 墙壁所在的物理层 |
编写游戏管理器
游戏管理器(GameManager)是整个游戏的"总指挥"。你可以把它想象成一个交响乐团的指挥——所有乐器(坦克、子弹、地图、UI)都听它指挥。
自动加载配置
GameManager 需要在整个游戏中随时可用,所以把它设为 自动加载(Autoload):
- 在 Godot 中,点击菜单 项目 → 项目设置 → 自动加载(Autoload)
- 脚本名称填:
GameManager - 路径选择:
res://autoload/game_manager.gd - 点击 添加
这样 GameManager 就可以在任何脚本中直接使用了,不需要 get_node() 去查找。
GameManager 代码
// GameManager.cs - 游戏总管理器
using Godot;
public partial class GameManager : Node
{
// ========== 单例模式 ==========
// 单例就像一个学校里只有一个校长,所有人要找校长都找同一个人
public static GameManager Instance { get; private set; }
// ========== 游戏常量 ==========
// 常量就像不会变的规则,一旦定好就不能改
public const int MAP_COLS = 13; // 地图列数
public const int MAP_ROWS = 13; // 地图行数
public const int TILE_SIZE = 48; // 每格像素大小
public const int PLAYER_START_LIVES = 3; // 玩家初始生命数
public const int TOTAL_ENEMIES = 20; // 每关总敌人数
public const int MAX_ON_SCREEN = 4; // 同时在场最大敌人数
// ========== 方向枚举 ==========
public enum Direction
{
Up, // 上
Down, // 下
Left, // 左
Right // 右
}
// ========== 游戏状态 ==========
public enum GameState
{
MainMenu, // 主菜单
StageSelect, // 关卡选择
Playing, // 游戏进行中
StageClear, // 关卡通过
GameOver, // 游戏结束
Paused // 暂停
}
// ========== 游戏状态变量 ==========
private GameState _currentState = GameState.MainMenu;
public GameState CurrentState
{
get => _currentState;
set
{
_currentState = value;
EmitSignal(SignalName.StateChanged, (int)value);
}
}
// 当前关卡编号
public int CurrentStage { get; set; } = 1;
// 总共有多少关
public int TotalStages { get; set; } = 5;
// 玩家剩余生命
public int PlayerLives { get; set; } = PLAYER_START_LIVES;
// 当前分数
public int CurrentScore { get; set; } = 0;
// 最高分
public int HighScore { get; set; } = 0;
// ========== 信号定义 ==========
// 信号就像广播站,发出通知让所有"收听者"知道发生了什么
[Signal] public delegate void StateChangedEventHandler(int newState);
[Signal] public delegate void ScoreChangedEventHandler(int newScore);
[Signal] public delegate void LivesChangedEventHandler(int newLives);
[Signal] public delegate void StageChangedEventHandler(int newStage);
// ========== 生命周期 ==========
public override void _EnterTree()
{
// 确保只有一个 GameManager 实例
if (Instance != null && Instance != this)
{
QueueFree(); // 销毁多余的实例
return;
}
Instance = this;
}
public override void _Ready()
{
// 加载最高分
LoadHighScore();
GD.Print("GameManager 初始化完成");
}
// ========== 游戏控制方法 ==========
// 开始新游戏
public void StartNewGame()
{
CurrentScore = 0;
CurrentStage = 1;
PlayerLives = PLAYER_START_LIVES;
CurrentState = GameState.Playing;
}
// 开始指定关卡
public void StartStage(int stage)
{
CurrentStage = Mathf.Clamp(stage, 1, TotalStages);
CurrentState = GameState.Playing;
EmitSignal(SignalName.StageChanged, CurrentStage);
}
// 玩家死亡
public void PlayerDied()
{
PlayerLives--;
EmitSignal(SignalName.LivesChanged, PlayerLives);
if (PlayerLives <= 0)
{
CurrentState = GameState.GameOver;
}
}
// 关卡通过
public void StageClear()
{
if (CurrentStage >= TotalStages)
{
CurrentState = GameState.GameOver;
}
else
{
CurrentState = GameState.StageClear;
}
}
// 进入下一关
public void NextStage()
{
StartStage(CurrentStage + 1);
}
// 暂停/恢复游戏
public void TogglePause()
{
if (CurrentState == GameState.Playing)
{
CurrentState = GameState.Paused;
GetTree().Paused = true;
}
else if (CurrentState == GameState.Paused)
{
CurrentState = GameState.Playing;
GetTree().Paused = false;
}
}
// 加分
public void AddScore(int points)
{
CurrentScore += points;
EmitSignal(SignalName.ScoreChanged, CurrentScore);
if (CurrentScore > HighScore)
{
HighScore = CurrentScore;
SaveHighScore();
}
}
// ========== 存档方法 ==========
// 保存最高分
private void SaveHighScore()
{
var data = new Godot.Collections.Dictionary
{
{ "high_score", HighScore }
};
using var file = FileAccess.Open("user://save_data.json", FileAccess.ModeFlags.Write);
if (file != null)
{
file.StoreString(Json.Stringify(data));
}
}
// 加载最高分
private void LoadHighScore()
{
if (!FileAccess.FileExists("user://save_data.json")) return;
using var file = FileAccess.Open("user://save_data.json", FileAccess.ModeFlags.Read);
if (file == null) return;
var json = new Json();
if (json.Parse(file.GetAsText()) == Error.Ok)
{
var data = json.Data.AsGodotDictionary();
if (data.ContainsKey("high_score"))
{
HighScore = (int)data["high_score"];
}
}
}
}# game_manager.gd - 游戏总管理器
extends Node
# ========== 单例模式 ==========
# 单例就像一个学校里只有一个校长,所有人要找校长都找同一个人
var instance: Node = null
# ========== 游戏常量 ==========
# 常量就像不会变的规则,一旦定好就不能改
const MAP_COLS: int = 13 # 地图列数
const MAP_ROWS: int = 13 # 地图行数
const TILE_SIZE: int = 48 # 每格像素大小
const PLAYER_START_LIVES: int = 3 # 玩家初始生命数
const TOTAL_ENEMIES: int = 20 # 每关总敌人数
const MAX_ON_SCREEN: int = 4 # 同时在场最大敌人数
# ========== 方向枚举 ==========
enum Direction { UP, DOWN, LEFT, RIGHT }
# ========== 游戏状态枚举 ==========
enum GameState {
MAIN_MENU, # 主菜单
STAGE_SELECT, # 关卡选择
PLAYING, # 游戏进行中
STAGE_CLEAR, # 关卡通过
GAME_OVER, # 游戏结束
PAUSED # 暂停
}
# ========== 游戏状态变量 ==========
var current_state: int = GameState.MAIN_MENU:
set(value):
current_state = value
state_changed.emit(value)
# 当前关卡编号
var current_stage: int = 1
# 总共有多少关
var total_stages: int = 5
# 玩家剩余生命
var player_lives: int = PLAYER_START_LIVES
# 当前分数
var current_score: int = 0
# 最高分
var high_score: int = 0
# ========== 信号定义 ==========
# 信号就像广播站,发出通知让所有"收听者"知道发生了什么
signal state_changed(new_state: int)
signal score_changed(new_score: int)
signal lives_changed(new_lives: int)
signal stage_changed(new_stage: int)
# ========== 生命周期 ==========
func _enter_tree() -> void:
# 确保只有一个 GameManager 实例
if instance != null and instance != self:
queue_free() # 销毁多余的实例
return
instance = self
func _ready() -> void:
# 加载最高分
load_high_score()
print("GameManager 初始化完成")
# ========== 游戏控制方法 ==========
## 开始新游戏
func start_new_game() -> void:
current_score = 0
current_stage = 1
player_lives = PLAYER_START_LIVES
current_state = GameState.PLAYING
## 开始指定关卡
func start_stage(stage: int) -> void:
current_stage = clampi(stage, 1, total_stages)
current_state = GameState.PLAYING
stage_changed.emit(current_stage)
## 玩家死亡
func player_died() -> void:
player_lives -= 1
lives_changed.emit(player_lives)
if player_lives <= 0:
current_state = GameState.GAME_OVER
## 关卡通过
func stage_clear() -> void:
if current_stage >= total_stages:
current_state = GameState.GAME_OVER
else:
current_state = GameState.STAGE_CLEAR
## 进入下一关
func next_stage() -> void:
start_stage(current_stage + 1)
## 暂停/恢复游戏
func toggle_pause() -> void:
if current_state == GameState.PLAYING:
current_state = GameState.PAUSED
get_tree().paused = true
elif current_state == GameState.PAUSED:
current_state = GameState.PLAYING
get_tree().paused = false
## 加分
func add_score(points: int) -> void:
current_score += points
score_changed.emit(current_score)
if current_score > high_score:
high_score = current_score
save_high_score()
# ========== 存档方法 ==========
## 保存最高分
func save_high_score() -> void:
var data = {"high_score": high_score}
var file = FileAccess.open("user://save_data.json", FileAccess.WRITE)
if file != null:
file.store_string(JSON.stringify(data))
## 加载最高分
func load_high_score() -> void:
if not FileAccess.file_exists("user://save_data.json"):
return
var file = FileAccess.open("user://save_data.json", FileAccess.READ)
if file == null:
return
var json = JSON.new()
if json.parse(file.get_as_text()) == OK:
var data = json.data
if "high_score" in data:
high_score = data.high_score创建主场景
主场景(Main Scene)是整个游戏的入口,就像一栋大楼的大门。它负责加载不同的界面(菜单、游戏、关卡选择等)。
场景结构
在 Godot 中创建 scenes/main.tscn,节点结构如下:
Main (Control) # 根节点,覆盖整个屏幕
├── MainMenu (Control) # 主菜单界面
│ ├── TitleLabel (Label) # 游戏标题
│ ├── StartButton (Button) # 开始游戏按钮
│ ├── StageSelectButton (Button) # 关卡选择按钮
│ └── QuitButton (Button) # 退出按钮
│
├── GameContainer (Control) # 游戏场景容器
│ └── (游戏场景会动态加载到这里)
│
├── StageClearPanel (Panel) # 关卡通过面板
│ ├── ClearLabel (Label) # "Stage Clear!" 文字
│ └── NextButton (Button) # 下一关按钮
│
└── GameOverPanel (Panel) # 游戏结束面板
├── GameOverLabel (Label) # "Game Over" 文字
├── ScoreLabel (Label) # 最终得分
└── RetryButton (Button) # 重新开始按钮主场景脚本
// Main.cs - 主场景控制器
using Godot;
public partial class Main : Control
{
// ========== 节点引用 ==========
[Export] public Control MainMenu { get; set; }
[Export] public Control GameContainer { get; set; }
[Export] public Control StageClearPanel { get; set; }
[Export] public Control GameOverPanel { get; set; }
// 当前加载的游戏场景
private Node _currentGameScene;
public override void _Ready()
{
// 显示主菜单,隐藏其他面板
ShowPanel("main_menu");
// 连接 GameManager 的信号
GameManager.Instance.StateChanged += OnStateChanged;
}
// 显示指定的面板
private void ShowPanel(string panelName)
{
MainMenu.Visible = (panelName == "main_menu");
GameContainer.Visible = (panelName == "game");
StageClearPanel.Visible = (panelName == "stage_clear");
GameOverPanel.Visible = (panelName == "game_over");
}
// 游戏状态变化时的回调
private void OnStateChanged(int newState)
{
var state = (GameManager.GameState)newState;
switch (state)
{
case GameManager.GameState.Playing:
LoadGameScene();
ShowPanel("game");
break;
case GameManager.GameState.StageClear:
ShowPanel("stage_clear");
break;
case GameManager.GameState.GameOver:
ShowPanel("game_over");
break;
case GameManager.GameState.MainMenu:
UnloadGameScene();
ShowPanel("main_menu");
break;
}
}
// 加载游戏场景
private void LoadGameScene()
{
UnloadGameScene(); // 先卸载旧的
var scene = GD.Load<PackedScene>("res://scenes/game.tscn");
_currentGameScene = scene.Instantiate();
GameContainer.AddChild(_currentGameScene);
}
// 卸载游戏场景
private void UnloadGameScene()
{
if (_currentGameScene != null)
{
_currentGameScene.QueueFree();
_currentGameScene = null;
}
}
// ========== 按钮回调 ==========
// 开始游戏按钮
private void OnStartButtonPressed()
{
GameManager.Instance.StartNewGame();
}
// 关卡选择按钮
private void OnStageSelectButtonPressed()
{
GameManager.Instance.CurrentState = GameManager.GameState.StageSelect;
}
// 下一关按钮
private void OnNextButtonPressed()
{
GameManager.Instance.NextStage();
}
// 重新开始按钮
private void OnRetryButtonPressed()
{
GameManager.Instance.StartNewGame();
}
// 退出按钮
private void OnQuitButtonPressed()
{
GetTree().Quit();
}
}# main.gd - 主场景控制器
extends Control
# ========== 节点引用 ==========
@export var main_menu: Control
@export var game_container: Control
@export var stage_clear_panel: Control
@export var game_over_panel: Control
# 当前加载的游戏场景
var current_game_scene: Node = null
func _ready() -> void:
# 显示主菜单,隐藏其他面板
show_panel("main_menu")
# 连接 GameManager 的信号
GameManager.state_changed.connect(on_state_changed)
## 显示指定的面板
func show_panel(panel_name: String) -> void:
main_menu.visible = (panel_name == "main_menu")
game_container.visible = (panel_name == "game")
stage_clear_panel.visible = (panel_name == "stage_clear")
game_over_panel.visible = (panel_name == "game_over")
## 游戏状态变化时的回调
func on_state_changed(new_state: int) -> void:
match new_state:
GameManager.GameState.PLAYING:
load_game_scene()
show_panel("game")
GameManager.GameState.STAGE_CLEAR:
show_panel("stage_clear")
GameManager.GameState.GAME_OVER:
show_panel("game_over")
GameManager.GameState.MAIN_MENU:
unload_game_scene()
show_panel("main_menu")
## 加载游戏场景
func load_game_scene() -> void:
unload_game_scene() # 先卸载旧的
var scene = load("res://scenes/game.tscn")
current_game_scene = scene.instantiate()
game_container.add_child(current_game_scene)
## 卸载游戏场景
func unload_game_scene() -> void:
if current_game_scene != null:
current_game_scene.queue_free()
current_game_scene = null
# ========== 按钮回调 ==========
## 开始游戏按钮
func _on_start_button_pressed() -> void:
GameManager.start_new_game()
## 关卡选择按钮
func _on_stage_select_button_pressed() -> void:
GameManager.current_state = GameManager.GameState.STAGE_SELECT
## 下一关按钮
func _on_next_button_pressed() -> void:
GameManager.next_stage()
## 重新开始按钮
func _on_retry_button_pressed() -> void:
GameManager.start_new_game()
## 退出按钮
func _on_quit_button_pressed() -> void:
get_tree().quit()创建游戏场景
游戏场景(Game Scene)是真正"打坦克"的地方,包含地图、坦克、子弹等所有游戏元素。
场景结构
创建 scenes/game.tscn,节点结构如下:
Game (Node2D) # 游戏场景根节点
├── GameMap (TileMapLayer) # 地图(地形层)
├── WallsContainer (Node2D) # 墙壁容器(可破坏墙壁)
├── TanksContainer (Node2D) # 坦克容器
│ ├── Player (CharacterBody2D) # 玩家坦克(从场景实例化)
│ └── Enemies (Node2D) # 敌人容器
├── BulletsContainer (Node2D) # 子弹容器
├── ItemsContainer (Node2D) # 道具容器
├── EffectsContainer (Node2D) # 特效容器
├── Base (Area2D) # 基地(鹰标志)
├── HUD (CanvasLayer) # 游戏界面
│ ├── ScoreLabel (Label) # 分数显示
│ ├── LivesLabel (Label) # 生命数显示
│ ├── StageLabel (Label) # 关卡显示
│ └── EnemyCountLabel (Label) # 剩余敌人数
└── Camera (Camera2D) # 摄像机(固定视角)游戏场景脚本
// Game.cs - 游戏场景控制器
using Godot;
public partial class Game : Node2D
{
// ========== 节点引用 ==========
[Export] public Node2D TanksContainer { get; set; }
[Export] public Node2D BulletsContainer { get; set; }
[Export] public Node2D EffectsContainer { get; set; }
[Export] public Node2D ItemsContainer { get; set; }
[Export] public Node2D EnemiesContainer { get; set; }
[Export] public Base BaseNode { get; set; }
// 敌人生成器
private EnemySpawner _spawner;
// 当前关卡地图数据
private int[,] _levelData;
public override void _Ready()
{
_spawner = GetNode<EnemySpawner>("EnemySpawner");
// 加载当前关卡
LoadLevel(GameManager.Instance.CurrentStage);
// 生成玩家
SpawnPlayer();
// 连接基地被摧毁的信号
BaseNode.BaseDestroyed += () => GameManager.Instance.CurrentState = GameManager.GameState.GameOver;
}
// 加载关卡
private void LoadLevel(int stage)
{
// 从 JSON 文件加载关卡数据
var filePath = $"res://data/levels/level_{stage}.json";
if (!FileAccess.FileExists(filePath))
{
GD.PrintErr($"关卡文件不存在: {filePath}");
return;
}
using var file = FileAccess.Open(filePath, FileAccess.ModeFlags.Read);
var json = new Json();
json.Parse(file.GetAsText());
var data = json.Data.AsGodotDictionary();
// 解析地图数据并渲染
var mapData = (Godot.Collections.Array)data["map"];
var gameMap = GetNode<GameMap>("GameMap");
gameMap.LoadFromArray(mapData);
}
// 生成玩家坦克
private void SpawnPlayer()
{
var playerScene = GD.Load<PackedScene>("res://scenes/tanks/player.tscn");
var player = playerScene.Instantiate<PlayerTank>();
player.Position = new Vector2(192, 576); // 出生位置:第4列第12行
TanksContainer.AddChild(player);
}
}# game.gd - 游戏场景控制器
extends Node2D
# ========== 节点引用 ==========
@export var tanks_container: Node2D
@export var bullets_container: Node2D
@export var effects_container: Node2D
@export var items_container: Node2D
@export var enemies_container: Node2D
@export var base_node: Node2D # 这里假设 Base 节点的脚本名
# 敌人生成器
var spawner: Node
# 当前关卡地图数据
var level_data: Array = []
func _ready() -> void:
spawner = get_node("EnemySpawner")
# 加载当前关卡
load_level(GameManager.current_stage)
# 生成玩家
spawn_player()
# 连接基地被摧毁的信号
if base_node.has_signal("base_destroyed"):
base_node.base_destroyed.connect(func():
GameManager.current_state = GameManager.GameState.GAME_OVER
)
## 加载关卡
func load_level(stage: int) -> void:
# 从 JSON 文件加载关卡数据
var file_path = "res://data/levels/level_%d.json" % stage
if not FileAccess.file_exists(file_path):
push_error("关卡文件不存在: %s" % file_path)
return
var file = FileAccess.open(file_path, FileAccess.READ)
var json = JSON.new()
json.parse(file.get_as_text())
var data = json.data
# 解析地图数据并渲染
var map_data = data.map
var game_map = get_node("GameMap")
game_map.load_from_array(map_data)
## 生成玩家坦克
func spawn_player() -> void:
var player_scene = load("res://scenes/tanks/player.tscn")
var player = player_scene.instantiate()
player.position = Vector2(192, 576) # 出生位置:第4列第12行
tanks_container.add_child(player)关卡数据格式
每一关的地图用 JSON 文件保存。格式是一个 13x13 的二维数组,每个数字代表一种地形:
{
"stage": 1,
"name": "初始关",
"enemy_types": ["basic", "basic", "fast", "basic", "power"],
"map": [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 2, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
[0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 2, 1, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 9, 1, 0, 0, 0, 0, 0]
]
}其中数字的含义:
| 数字 | 地形 |
|---|---|
| 0 | 空地 |
| 1 | 砖墙 |
| 2 | 钢墙 |
| 3 | 草地 |
| 4 | 水面 |
| 5 | 冰面 |
| 9 | 基地 |
设置主场景
最后,别忘了把主场景设为项目的启动场景:
- 点击菜单 项目 → 项目设置 → 常规 → Application → Run
- 主场景 设为:
res://scenes/main.tscn
这样每次运行游戏,Godot 就会从主场景开始。
下一章预告
项目搭建完毕!现在我们有了游戏的"骨架"——管理器、场景结构、关卡数据格式都已就位。下一章我们将开始实现最有特色的功能:地图编辑器,让玩家可以自己设计关卡。
