添加看视频复活
This commit is contained in:
@@ -1,624 +0,0 @@
|
||||
# 《Rogue Tetris》功能设计文档
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
本项目基于经典俄罗斯方块玩法,加入类肉鸽成长系统。玩家通过消除方块获得积分与经验,达到一定条件后触发升级,从随机出现的强化选项中选择一项,形成不同流派与策略组合。
|
||||
|
||||
游戏核心循环为:
|
||||
|
||||
```text
|
||||
下落方块 → 摆放 → 消行 → 获得积分/经验 → 升级 → 选择强化 → 提升能力 → 继续挑战
|
||||
```
|
||||
|
||||
项目目标是在保留俄罗斯方块基础规则的同时,增强随机性、成长性和可重复游玩价值。
|
||||
|
||||
---
|
||||
|
||||
## 2. 基础玩法设计
|
||||
|
||||
### 2.1 棋盘设计
|
||||
|
||||
游戏区域采用标准俄罗斯方块棋盘:
|
||||
|
||||
```cpp
|
||||
const int BOARD_WIDTH = 10;
|
||||
const int BOARD_HEIGHT = 20;
|
||||
```
|
||||
|
||||
棋盘使用二维数组或 `vector<vector<int>>` 存储。
|
||||
|
||||
```cpp
|
||||
int board[BOARD_HEIGHT][BOARD_WIDTH];
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
| 数值 | 含义 |
|
||||
|---|---|
|
||||
| 0 | 空格 |
|
||||
| 1-7 | 不同类型的普通方块 |
|
||||
| 8 | 特殊方块,例如爆破方块 |
|
||||
|
||||
### 2.2 方块类型
|
||||
|
||||
游戏包含七种经典方块:
|
||||
|
||||
| 类型 | 说明 |
|
||||
|---|---|
|
||||
| I | 长条方块 |
|
||||
| O | 方形方块 |
|
||||
| T | T 型方块 |
|
||||
| S | S 型方块 |
|
||||
| Z | Z 型方块 |
|
||||
| J | J 型方块 |
|
||||
| L | L 型方块 |
|
||||
|
||||
### 2.3 玩家操作
|
||||
|
||||
| 按键 | 功能 |
|
||||
|---|---|
|
||||
| A / ← | 左移 |
|
||||
| D / → | 右移 |
|
||||
| W / ↑ | 旋转 |
|
||||
| S / ↓ | 软降 |
|
||||
| 空格 | 硬降 |
|
||||
| C | 暂存方块 |
|
||||
| P | 暂停 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 肉鸽成长系统设计
|
||||
|
||||
### 3.1 经验获取
|
||||
|
||||
玩家通过消行获得经验。
|
||||
|
||||
| 消除行数 | 获得经验 |
|
||||
|---|---|
|
||||
| 1 行 | 10 EXP |
|
||||
| 2 行 | 25 EXP |
|
||||
| 3 行 | 45 EXP |
|
||||
| 4 行 | 80 EXP |
|
||||
|
||||
### 3.2 升级规则
|
||||
|
||||
玩家经验达到当前等级需求后升级。
|
||||
|
||||
```cpp
|
||||
requiredExp = 100 + level * 50;
|
||||
```
|
||||
|
||||
升级时:
|
||||
|
||||
1. 游戏暂停;
|
||||
2. 随机生成若干强化选项;
|
||||
3. 玩家选择其中一个;
|
||||
4. 应用强化效果;
|
||||
5. 游戏继续。
|
||||
|
||||
### 3.3 升级流程示例
|
||||
|
||||
```cpp
|
||||
void levelUp() {
|
||||
player.level++;
|
||||
player.exp -= player.requiredExp;
|
||||
player.requiredExp = 100 + player.level * 50;
|
||||
|
||||
vector<Upgrade> choices = upgradeSystem.getRandomUpgrades(player.upgradeChoiceCount);
|
||||
int selected = showUpgradeChoices(choices);
|
||||
upgradeSystem.applyUpgrade(choices[selected], player, board);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 强化系统设计
|
||||
|
||||
### 4.1 强化分类
|
||||
|
||||
| 类型 | 说明 |
|
||||
|---|---|
|
||||
| 得分强化 | 提高积分收益 |
|
||||
| 操作强化 | 改善玩家操作体验 |
|
||||
| 消行强化 | 增强消行效果 |
|
||||
| 生存强化 | 降低游戏失败风险 |
|
||||
| 特殊强化 | 改变游戏规则 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 强化内容设计
|
||||
|
||||
### 5.1 得分类强化
|
||||
|
||||
#### 1. 积分倍率
|
||||
|
||||
效果:所有得分提高 20%。
|
||||
|
||||
```cpp
|
||||
player.scoreMultiplier += 0.2f;
|
||||
```
|
||||
|
||||
可重复获得。
|
||||
|
||||
#### 2. 连击奖励
|
||||
|
||||
效果:连续消行时获得额外积分。
|
||||
|
||||
```cpp
|
||||
player.comboBonus += 50;
|
||||
```
|
||||
|
||||
当玩家连续多个方块都产生消行时,触发连击奖励。
|
||||
|
||||
#### 3. 四消强化
|
||||
|
||||
效果:一次消除 4 行时额外获得 300 分。
|
||||
|
||||
```cpp
|
||||
if (linesCleared == 4) {
|
||||
score += 300;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.2 操作类强化
|
||||
|
||||
#### 4. 慢速下落
|
||||
|
||||
效果:方块自然下落速度降低 10%。
|
||||
|
||||
```cpp
|
||||
player.fallSpeed *= 0.9f;
|
||||
```
|
||||
|
||||
#### 5. 额外预览
|
||||
|
||||
效果:增加下一个方块预览数量。
|
||||
|
||||
```cpp
|
||||
player.previewCount += 1;
|
||||
```
|
||||
|
||||
#### 6. 暂存槽
|
||||
|
||||
效果:解锁 Hold 功能,允许玩家暂存当前方块。
|
||||
|
||||
```cpp
|
||||
player.canHold = true;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.3 消行类强化
|
||||
|
||||
#### 7. 清扫者
|
||||
|
||||
效果:每累计消除 10 行,自动清除最底部一行。
|
||||
|
||||
```cpp
|
||||
if (player.totalLinesCleared % 10 == 0) {
|
||||
board.clearBottomLine();
|
||||
}
|
||||
```
|
||||
|
||||
#### 8. 爆破方块
|
||||
|
||||
效果:每隔一定数量的方块,生成一个特殊爆破方块。爆破方块落地后清除周围 3x3 区域。
|
||||
|
||||
```cpp
|
||||
void explode(int x, int y) {
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
board[y + dy][x + dx] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 9. 经验强化
|
||||
|
||||
效果:消行获得经验提高 25%。
|
||||
|
||||
```cpp
|
||||
player.expMultiplier += 0.25f;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.4 生存类强化
|
||||
|
||||
#### 10. 最后一搏
|
||||
|
||||
效果:当玩家即将失败时,自动清除底部 3 行。每局只能触发一次。
|
||||
|
||||
```cpp
|
||||
player.hasRevive = true;
|
||||
```
|
||||
|
||||
#### 11. 减压
|
||||
|
||||
效果:每次升级后,自动清除当前最高的一行障碍。
|
||||
|
||||
```cpp
|
||||
board.clearHighestOccupiedLine();
|
||||
```
|
||||
|
||||
#### 12. 稳定结构
|
||||
|
||||
效果:方块锁定后,有小概率自动填补附近空洞。
|
||||
|
||||
```cpp
|
||||
if (rand() % 100 < 10) {
|
||||
board.fillNearbyHole();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5.5 特殊类强化
|
||||
|
||||
#### 13. 双倍成长
|
||||
|
||||
效果:之后每次升级出现 4 个选项,而不是 3 个。
|
||||
|
||||
```cpp
|
||||
player.upgradeChoiceCount = 4;
|
||||
```
|
||||
|
||||
#### 14. 赌徒
|
||||
|
||||
效果:选择强化时有概率获得双倍效果,也有概率无效果。
|
||||
|
||||
```cpp
|
||||
int chance = rand() % 100;
|
||||
if (chance < 30) {
|
||||
applyUpgradeTwice();
|
||||
} else if (chance < 50) {
|
||||
noEffect();
|
||||
}
|
||||
```
|
||||
|
||||
#### 15. 方块改造
|
||||
|
||||
效果:降低某一种方块的出现概率,例如减少 Z 方块出现概率。
|
||||
|
||||
```cpp
|
||||
blockWeight[BlockType::Z] -= 1;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据结构设计
|
||||
|
||||
### 6.1 方块结构
|
||||
|
||||
```cpp
|
||||
struct Tetromino {
|
||||
int type;
|
||||
int rotation;
|
||||
int x;
|
||||
int y;
|
||||
vector<vector<int>> shape;
|
||||
};
|
||||
```
|
||||
|
||||
### 6.2 玩家状态
|
||||
|
||||
```cpp
|
||||
struct PlayerStats {
|
||||
int score = 0;
|
||||
int level = 1;
|
||||
int exp = 0;
|
||||
int requiredExp = 100;
|
||||
|
||||
int totalLinesCleared = 0;
|
||||
int combo = 0;
|
||||
int comboBonus = 0;
|
||||
|
||||
float scoreMultiplier = 1.0f;
|
||||
float expMultiplier = 1.0f;
|
||||
float fallSpeed = 1.0f;
|
||||
|
||||
bool canHold = false;
|
||||
bool hasRevive = false;
|
||||
|
||||
int previewCount = 1;
|
||||
int upgradeChoiceCount = 3;
|
||||
};
|
||||
```
|
||||
|
||||
### 6.3 强化结构
|
||||
|
||||
```cpp
|
||||
enum class UpgradeType {
|
||||
Score,
|
||||
Control,
|
||||
Clear,
|
||||
Survival,
|
||||
Special
|
||||
};
|
||||
|
||||
struct Upgrade {
|
||||
string name;
|
||||
string description;
|
||||
UpgradeType type;
|
||||
int level;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 计分系统设计
|
||||
|
||||
### 7.1 基础得分
|
||||
|
||||
| 消除行数 | 基础分 |
|
||||
|---|---|
|
||||
| 1 行 | 100 |
|
||||
| 2 行 | 300 |
|
||||
| 3 行 | 500 |
|
||||
| 4 行 | 800 |
|
||||
|
||||
### 7.2 得分计算
|
||||
|
||||
```cpp
|
||||
int calculateScore(int linesCleared) {
|
||||
int baseScore = 0;
|
||||
|
||||
switch (linesCleared) {
|
||||
case 1: baseScore = 100; break;
|
||||
case 2: baseScore = 300; break;
|
||||
case 3: baseScore = 500; break;
|
||||
case 4: baseScore = 800; break;
|
||||
default: return 0;
|
||||
}
|
||||
|
||||
int finalScore = static_cast<int>(baseScore * player.scoreMultiplier);
|
||||
|
||||
if (player.combo > 1) {
|
||||
finalScore += player.combo * player.comboBonus;
|
||||
}
|
||||
|
||||
return finalScore;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 强化随机池设计
|
||||
|
||||
### 8.1 强化池初始化
|
||||
|
||||
```cpp
|
||||
vector<Upgrade> upgradePool;
|
||||
|
||||
void initUpgradePool() {
|
||||
upgradePool.push_back({
|
||||
"积分倍率",
|
||||
"所有得分提高20%",
|
||||
UpgradeType::Score,
|
||||
1
|
||||
});
|
||||
|
||||
upgradePool.push_back({
|
||||
"慢速下落",
|
||||
"方块下落速度降低10%",
|
||||
UpgradeType::Control,
|
||||
1
|
||||
});
|
||||
|
||||
upgradePool.push_back({
|
||||
"最后一搏",
|
||||
"失败时自动清除底部3行,每局一次",
|
||||
UpgradeType::Survival,
|
||||
1
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 随机抽取强化
|
||||
|
||||
```cpp
|
||||
vector<Upgrade> getRandomUpgrades(int count) {
|
||||
vector<Upgrade> result;
|
||||
vector<Upgrade> pool = upgradePool;
|
||||
|
||||
random_shuffle(pool.begin(), pool.end());
|
||||
|
||||
for (int i = 0; i < count && i < pool.size(); i++) {
|
||||
result.push_back(pool[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 主要类设计
|
||||
|
||||
### 9.1 Game 类
|
||||
|
||||
```cpp
|
||||
class Game {
|
||||
private:
|
||||
Board board;
|
||||
Tetromino currentBlock;
|
||||
PlayerStats player;
|
||||
UpgradeSystem upgradeSystem;
|
||||
|
||||
public:
|
||||
void init();
|
||||
void update();
|
||||
void render();
|
||||
void handleInput();
|
||||
void checkGameOver();
|
||||
};
|
||||
```
|
||||
|
||||
### 9.2 Board 类
|
||||
|
||||
```cpp
|
||||
class Board {
|
||||
private:
|
||||
int grid[20][10];
|
||||
|
||||
public:
|
||||
bool checkCollision(Tetromino block);
|
||||
void lockBlock(Tetromino block);
|
||||
int clearLines();
|
||||
void clearBottomLine();
|
||||
void clearHighestOccupiedLine();
|
||||
bool isGameOver();
|
||||
};
|
||||
```
|
||||
|
||||
### 9.3 UpgradeSystem 类
|
||||
|
||||
```cpp
|
||||
class UpgradeSystem {
|
||||
private:
|
||||
vector<Upgrade> upgradePool;
|
||||
|
||||
public:
|
||||
void init();
|
||||
vector<Upgrade> getRandomUpgrades(int count);
|
||||
void applyUpgrade(Upgrade upgrade, PlayerStats& player, Board& board);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 游戏主流程
|
||||
|
||||
```text
|
||||
初始化游戏数据
|
||||
↓
|
||||
生成当前方块和预览方块
|
||||
↓
|
||||
进入游戏主循环
|
||||
↓
|
||||
读取玩家输入
|
||||
↓
|
||||
更新方块位置
|
||||
↓
|
||||
检测碰撞
|
||||
↓
|
||||
无法下落时锁定方块
|
||||
↓
|
||||
检测并消除完整行
|
||||
↓
|
||||
计算得分和经验
|
||||
↓
|
||||
判断是否升级
|
||||
↓
|
||||
若升级,进入强化选择界面
|
||||
↓
|
||||
应用强化效果
|
||||
↓
|
||||
继续游戏
|
||||
↓
|
||||
判断是否失败
|
||||
↓
|
||||
游戏结束,显示最终得分
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 界面设计
|
||||
|
||||
### 11.1 主界面示意
|
||||
|
||||
```text
|
||||
+--------------------+----------------+
|
||||
| | Score: 1200 |
|
||||
| | Level: 3 |
|
||||
| | EXP: 40/250 |
|
||||
| | |
|
||||
| 游戏棋盘 | Next: |
|
||||
| | [T] [L] [I] |
|
||||
| | |
|
||||
| | Upgrades: |
|
||||
| | 积分倍率 Lv2 |
|
||||
+--------------------+----------------+
|
||||
```
|
||||
|
||||
### 11.2 升级界面示意
|
||||
|
||||
```text
|
||||
请选择一个强化:
|
||||
|
||||
[1] 积分倍率
|
||||
所有得分提高20%
|
||||
|
||||
[2] 慢速下落
|
||||
方块下落速度降低10%
|
||||
|
||||
[3] 最后一搏
|
||||
失败时自动清除底部3行,每局一次
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 模块划分
|
||||
|
||||
| 模块 | 功能 |
|
||||
|---|---|
|
||||
| Game | 控制游戏主循环 |
|
||||
| Board | 管理棋盘数据 |
|
||||
| Tetromino | 管理方块形状、旋转、移动 |
|
||||
| Renderer | 负责界面绘制 |
|
||||
| InputManager | 处理键盘输入 |
|
||||
| ScoreSystem | 计算分数与经验 |
|
||||
| UpgradeSystem | 管理强化池、升级、强化应用 |
|
||||
| PlayerStats | 保存玩家成长数据 |
|
||||
|
||||
---
|
||||
|
||||
## 13. 开发优先级
|
||||
|
||||
| 优先级 | 内容 |
|
||||
|---|---|
|
||||
| P0 | 实现基础俄罗斯方块逻辑 |
|
||||
| P0 | 实现消行和计分 |
|
||||
| P1 | 实现经验和升级系统 |
|
||||
| P1 | 实现随机强化三选一 |
|
||||
| P1 | 实现 5 到 8 个基础强化 |
|
||||
| P2 | 实现特殊方块,例如爆破方块 |
|
||||
| P2 | 实现 Hold、预览数量等操作强化 |
|
||||
| P3 | 增加稀有度、成就、存档等扩展内容 |
|
||||
|
||||
---
|
||||
|
||||
## 14. 技术实现建议
|
||||
|
||||
| 技术 | 用途 |
|
||||
|---|---|
|
||||
| C++ | 主体开发语言 |
|
||||
| STL vector | 存储棋盘、方块、强化池 |
|
||||
| enum class | 表示方块类型和强化类型 |
|
||||
| class | 进行模块化封装 |
|
||||
| random | 实现随机强化和随机方块 |
|
||||
| SFML / SDL / Qt | 可选,用于图形界面 |
|
||||
|
||||
如果课程要求较基础,可以先完成控制台版本,再根据时间增加图形界面。
|
||||
|
||||
---
|
||||
|
||||
## 15. 创新点总结
|
||||
|
||||
本项目的主要创新点包括:
|
||||
|
||||
1. 在传统俄罗斯方块中加入经验和等级系统;
|
||||
2. 将消行行为与角色成长绑定;
|
||||
3. 每次升级随机提供强化选项;
|
||||
4. 强化之间可以叠加,形成不同构筑路线;
|
||||
5. 加入生存类和特殊类强化,提高策略空间;
|
||||
6. 每局强化组合不同,提升重复游玩价值。
|
||||
|
||||
---
|
||||
|
||||
## 16. 结论
|
||||
|
||||
《Rogue Tetris》在经典俄罗斯方块的基础上引入类肉鸽升级机制,使游戏从单纯的消行挑战扩展为具有成长路线、随机选择和策略构筑的玩法。玩家每局都能通过不同强化组合形成不同体验,既保留了俄罗斯方块的核心规则,也体现了创新性,适合作为 C++ 课程大作业项目。
|
||||
@@ -1,176 +0,0 @@
|
||||
# Rogue Tetris TODO List
|
||||
|
||||
更新时间:2026-04-25
|
||||
|
||||
当前开发主线:
|
||||
|
||||
1. 整理 UI
|
||||
2. 补充美术资源
|
||||
3. 调整数值
|
||||
|
||||
本清单按这个顺序组织,后续默认先做表现层,再补资源,最后做平衡。结构重构类工作只保留为支撑项,不再作为当前第一优先级。
|
||||
|
||||
## 当前已确认状态
|
||||
|
||||
- [x] 项目可以通过 `build-mingw.ps1` 正常编译
|
||||
- [x] 已有主菜单、经典模式、Rogue 模式、规则页
|
||||
- [x] 已有 Rogue HUD、等级、EXP、升级暂停、三选一升级界面
|
||||
- [x] 已有较完整的强化池、特殊方块、主动技能与反馈提示
|
||||
- [x] 已将 Rogue 主要逻辑拆到独立文件,便于后续继续整理表现层
|
||||
|
||||
## 第一阶段:整理 UI
|
||||
|
||||
目标:先把现有界面统一干净,信息层级明确,布局稳定,操作反馈清楚。
|
||||
|
||||
### 1. 主菜单与规则页
|
||||
|
||||
- [ ] 统一主菜单、规则页、游戏内面板的视觉语言
|
||||
- [ ] 调整标题、正文、提示文案的字号层级
|
||||
- [ ] 检查菜单卡片间距、对齐和高亮态,避免现在的“能用但不够整”
|
||||
- [ ] 规则页重新梳理信息分组
|
||||
建议分成:基础操作、Rogue 机制、主动技能、当前版本说明
|
||||
- [ ] 精简过长文案,减少一屏内的信息拥挤
|
||||
|
||||
### 2. 游戏内 HUD
|
||||
|
||||
- [ ] 统一右侧面板的留白、卡片高度和分区顺序
|
||||
- [ ] 明确 HUD 的核心信息优先级
|
||||
建议顺序:模式 / 分数 / 等级 EXP / 当前强化摘要 / Hold / Next / 按键提示
|
||||
- [ ] 检查经典模式 HUD,避免与 Rogue HUD 风格割裂太大
|
||||
- [ ] 压缩强化摘要显示,避免后期强化太多时面板失控
|
||||
- [ ] 检查提示文本换行和截断,尤其是中文长句
|
||||
|
||||
### 3. 升级选择界面
|
||||
|
||||
- [ ] 统一升级界面中卡片的标题、分类、描述、等级标签布局
|
||||
- [ ] 明确三种状态的视觉差异
|
||||
- [ ] 普通态
|
||||
- [ ] 当前选中态
|
||||
- [ ] 诅咒态
|
||||
- [ ] 补一个真正的“不可选/不显示”处理策略,避免后续扩展时 UI 语义混乱
|
||||
- [ ] 调整升级卡片高度和内部间距,减少文本拥挤
|
||||
- [ ] 检查 3 项 / 5 项升级时的布局一致性
|
||||
- [ ] 检查升级结束返回游戏时的反馈是否足够清楚
|
||||
|
||||
### 4. 浮层与反馈
|
||||
|
||||
- [ ] 统一暂停、失败、升级三类覆盖层的边框、底色和标题样式
|
||||
- [ ] 调整反馈浮层的位置、尺寸和显示时间
|
||||
- [ ] 统一提示文案格式
|
||||
建议统一成:事件标题 + 核心收益/变化
|
||||
- [ ] 检查连续触发多种效果时,反馈是否被覆盖得过快
|
||||
|
||||
### 5. 当前阶段完成标准
|
||||
|
||||
- [ ] 主菜单、规则页、游戏 HUD、升级界面视觉风格一致
|
||||
- [ ] 中文排版、字号、留白、对齐达到可继续细化的基线
|
||||
- [ ] 玩家在不看代码的情况下能读懂主要状态和操作反馈
|
||||
|
||||
## 第二阶段:补充美术资源
|
||||
|
||||
目标:把现在代码里硬绘制的占位 UI,逐步替换为统一命名、统一风格的占位资源。
|
||||
|
||||
### 1. 建立资源规范
|
||||
|
||||
- [ ] 新建 `assets/ui/` 目录
|
||||
- [ ] 统一占位资源命名
|
||||
例如:
|
||||
- [ ] `placeholder_panel_*`
|
||||
- [ ] `placeholder_button_*`
|
||||
- [ ] `placeholder_upgrade_icon_*`
|
||||
- [ ] `placeholder_overlay_*`
|
||||
- [ ] 明确哪些资源继续用 GDI 绘制,哪些改成资源加载
|
||||
|
||||
### 2. 先补最需要的资源
|
||||
|
||||
- [ ] 主菜单卡片背景 / 边框占位资源
|
||||
- [ ] 规则页分区标题装饰
|
||||
- [ ] HUD 面板边框与卡片底图
|
||||
- [ ] 升级卡片图标占位资源
|
||||
- [ ] 升级卡片背景与诅咒态装饰
|
||||
- [ ] 浮层弹窗底图
|
||||
- [ ] 按钮 / 键位提示底图
|
||||
|
||||
### 3. 背景与氛围资源
|
||||
|
||||
- [ ] 评估是否接入现有 `background.bmp`
|
||||
- [ ] 如果保留纯色 / 渐变背景,则补一版更统一的背景方案
|
||||
- [ ] 评估 `background.wav` 是否接入
|
||||
- [ ] 如果暂时不用音频,就在文档里明确标记为“资源已存在但未接入”
|
||||
|
||||
### 4. 资源接入与替换
|
||||
|
||||
- [ ] 给升级卡片图标留出稳定的资源映射方式
|
||||
- [ ] 给菜单 / HUD / 浮层补最小资源加载接口
|
||||
- [ ] 避免资源路径和业务逻辑耦合过深
|
||||
- [ ] 替换时保证没有资源也能回退到当前占位绘制
|
||||
|
||||
### 5. 当前阶段完成标准
|
||||
|
||||
- [ ] UI 主要元素已经有统一的占位资源体系
|
||||
- [ ] `assets` 目录不再只是零散图标和背景图
|
||||
- [ ] 后续替换正式美术时不需要重写界面结构
|
||||
|
||||
## 第三阶段:调整数值
|
||||
|
||||
目标:在界面和资源相对稳定后,再系统调整 Rogue 的节奏、强度和成长曲线。
|
||||
|
||||
### 1. 等级与成长节奏
|
||||
|
||||
- [ ] 调整等级需求曲线
|
||||
- [ ] 调整 EXP 获取速度
|
||||
- [ ] 调整升级频率,避免前期过快、后期过慢
|
||||
|
||||
### 2. 掉落与操作节奏
|
||||
|
||||
- [ ] 调整 Rogue 基础下落速度
|
||||
- [ ] 检查 `slow_fall`、`time_dilation`、`fever`、`high_pressure`、`extreme_player` 的叠加结果
|
||||
- [ ] 避免速度效果叠加后出现明显失控或无感
|
||||
|
||||
### 3. 强化池权重
|
||||
|
||||
- [ ] 调整基础成长类强化的出现频率
|
||||
- [ ] 调整主动技能类强化的出现频率
|
||||
- [ ] 调整进化类强化的前置条件和权重
|
||||
- [ ] 减少明显无效或当前局收益过低的选项
|
||||
- [ ] 检查 3 选 1 / 5 选 2 时的整体体验
|
||||
|
||||
### 4. 特殊机制强度
|
||||
|
||||
- [ ] 调整爆破、激光、十字、彩虹方块的触发频率
|
||||
- [ ] 调整黑洞、清屏炸弹、空中换形的获得与使用强度
|
||||
- [ ] 调整最后一搏、终末清场的保命收益
|
||||
- [ ] 检查特殊机制是否破坏基础俄罗斯方块节奏
|
||||
|
||||
### 5. 回归检查
|
||||
|
||||
- [ ] 经典模式基础体验不受 Rogue 调整影响
|
||||
- [ ] Rogue 模式升级、选择、恢复流程稳定
|
||||
- [ ] 特殊方块落地后结算顺序正确
|
||||
- [ ] 主动技能在暂停、升级、失败状态下不会误触发
|
||||
- [ ] 多次升级、多选升级、诅咒升级组合下状态不乱
|
||||
|
||||
### 6. 当前阶段完成标准
|
||||
|
||||
- [ ] Rogue 模式的升级节奏稳定
|
||||
- [ ] 强化不再明显偏科或频繁出废选项
|
||||
- [ ] 特殊机制保留爽感,但不压垮基础玩法
|
||||
|
||||
## 配套支持项
|
||||
|
||||
这些事项不是当前主线,但在推进 UI / 资源 / 数值时需要同步维护。
|
||||
|
||||
- [ ] `README.md` 按当前版本重写
|
||||
- [ ] 补一份简短人工回归清单文档
|
||||
- [ ] 继续维持 Rogue 逻辑分文件管理,避免重新塞回单文件
|
||||
- [ ] 如果后续 UI 改动开始受零散全局变量影响,再启动 `PieceState` 抽取
|
||||
|
||||
## 暂不优先处理
|
||||
|
||||
- [ ] 现在先不要继续大规模新增强化种类
|
||||
- [ ] 现在先不要优先做深度结构重构
|
||||
- [ ] 现在先不要先调数值再返工 UI
|
||||
|
||||
## 一句话总结
|
||||
|
||||
接下来先把界面整理顺,再把占位资源补齐,最后在稳定表现层上做数值和平衡。
|
||||
Binary file not shown.
@@ -202,6 +202,7 @@ extern bool gameOverFlag;
|
||||
extern bool suspendFlag;
|
||||
extern bool targetFlag;
|
||||
extern bool bgmEnabled;
|
||||
extern bool reviveAvailable;
|
||||
extern int workRegion[20][10];
|
||||
extern Point point;
|
||||
extern Point target;
|
||||
@@ -241,6 +242,8 @@ void ComputeTarget();
|
||||
void Restart();
|
||||
void StartGameWithMode(int mode);
|
||||
void ReturnToMainMenu();
|
||||
void ReviveAfterVideo();
|
||||
void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks);
|
||||
void OpenRulesScreen();
|
||||
void OpenUpgradeMenu();
|
||||
void ConfirmUpgradeSelection();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "stdafx.h"
|
||||
#include "Tetris.h"
|
||||
#include <string>
|
||||
#include <shellapi.h>
|
||||
|
||||
#define MAX_LOADSTRING 100
|
||||
#define GAME_TIMER_ID 1
|
||||
@@ -16,11 +17,17 @@ bool bgmEnabled = true;
|
||||
static bool bgmPlaying = false;
|
||||
static bool bgmUsingMci = false;
|
||||
static constexpr const wchar_t* kBgmAlias = L"TereisBgm";
|
||||
static constexpr const wchar_t* kReviveVideoAlias = L"TereisReviveVideo";
|
||||
|
||||
ATOM MyRegisterClass(HINSTANCE hInstance);
|
||||
BOOL InitInstance(HINSTANCE, int);
|
||||
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
|
||||
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
|
||||
static std::wstring BuildAssetPath(const wchar_t* relativePath);
|
||||
static std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath);
|
||||
static bool FileExists(const std::wstring& path);
|
||||
static void StopBackgroundMusic();
|
||||
static void StartBackgroundMusic();
|
||||
|
||||
static void ResetGameTimer(HWND hWnd)
|
||||
{
|
||||
@@ -28,6 +35,90 @@ static void ResetGameTimer(HWND hWnd)
|
||||
SetTimer(hWnd, GAME_TIMER_ID, currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL, nullptr);
|
||||
}
|
||||
|
||||
static bool PlayReviveVideo(HWND hWnd)
|
||||
{
|
||||
std::wstring videoPath = BuildAssetPath(L"assets\\video\\video.avi");
|
||||
if (!FileExists(videoPath))
|
||||
{
|
||||
videoPath = BuildWorkingDirAssetPath(L"assets\\video\\video.avi");
|
||||
}
|
||||
if (!FileExists(videoPath))
|
||||
{
|
||||
videoPath = BuildAssetPath(L"assets\\video\\video.mp4");
|
||||
}
|
||||
if (!FileExists(videoPath))
|
||||
{
|
||||
videoPath = BuildWorkingDirAssetPath(L"assets\\video\\video.mp4");
|
||||
}
|
||||
if (!FileExists(videoPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool shouldResumeBgm = bgmEnabled;
|
||||
if (bgmPlaying)
|
||||
{
|
||||
StopBackgroundMusic();
|
||||
}
|
||||
|
||||
auto tryPlayWithMci = [&](bool forceMpegVideo) -> bool
|
||||
{
|
||||
mciSendStringW((std::wstring(L"close ") + kReviveVideoAlias).c_str(), nullptr, 0, nullptr);
|
||||
|
||||
std::wstring openCommand = L"open \"" + videoPath + L"\" ";
|
||||
if (forceMpegVideo)
|
||||
{
|
||||
openCommand += L"type mpegvideo ";
|
||||
}
|
||||
openCommand += L"alias ";
|
||||
openCommand += kReviveVideoAlias;
|
||||
|
||||
if (mciSendStringW(openCommand.c_str(), nullptr, 0, hWnd) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring playCommand = std::wstring(L"play ") + kReviveVideoAlias + L" fullscreen wait";
|
||||
MCIERROR playResult = mciSendStringW(playCommand.c_str(), nullptr, 0, hWnd);
|
||||
mciSendStringW((std::wstring(L"close ") + kReviveVideoAlias).c_str(), nullptr, 0, nullptr);
|
||||
return playResult == 0;
|
||||
};
|
||||
|
||||
bool played = tryPlayWithMci(true);
|
||||
if (!played)
|
||||
{
|
||||
played = tryPlayWithMci(false);
|
||||
}
|
||||
|
||||
if (!played)
|
||||
{
|
||||
SHELLEXECUTEINFOW shellInfo = {};
|
||||
shellInfo.cbSize = sizeof(shellInfo);
|
||||
shellInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
shellInfo.hwnd = hWnd;
|
||||
shellInfo.lpVerb = L"open";
|
||||
shellInfo.lpFile = videoPath.c_str();
|
||||
shellInfo.nShow = SW_SHOWNORMAL;
|
||||
|
||||
if (ShellExecuteExW(&shellInfo))
|
||||
{
|
||||
if (shellInfo.hProcess != nullptr)
|
||||
{
|
||||
WaitForSingleObject(shellInfo.hProcess, INFINITE);
|
||||
CloseHandle(shellInfo.hProcess);
|
||||
}
|
||||
played = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldResumeBgm)
|
||||
{
|
||||
StartBackgroundMusic();
|
||||
}
|
||||
|
||||
return played;
|
||||
}
|
||||
|
||||
static std::wstring BuildAssetPath(const wchar_t* relativePath)
|
||||
{
|
||||
wchar_t modulePath[MAX_PATH] = {};
|
||||
@@ -729,6 +820,21 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
break;
|
||||
}
|
||||
|
||||
if (gameOverFlag && reviveAvailable && wParam == 'V')
|
||||
{
|
||||
if (PlayReviveVideo(hWnd))
|
||||
{
|
||||
ReviveAfterVideo();
|
||||
ResetGameTimer(hWnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetFeedbackMessage(_T("视频播放失败"), _T("无法打开复活视频,复活机会未消耗。"), 14);
|
||||
}
|
||||
InvalidateRect(hWnd, nullptr, FALSE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (gameOverFlag || suspendFlag)
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -9,6 +9,7 @@ int tScore = 0;
|
||||
bool gameOverFlag = false;
|
||||
bool suspendFlag = false;
|
||||
bool targetFlag = false;
|
||||
bool reviveAvailable = false;
|
||||
int workRegion[20][10] = { 0 };
|
||||
Point point = { 0, 0 };
|
||||
Point target = { 0, 0 };
|
||||
@@ -1160,6 +1161,7 @@ void Restart()
|
||||
gameOverFlag = false;
|
||||
suspendFlag = false;
|
||||
targetFlag = true;
|
||||
reviveAvailable = true;
|
||||
currentFallInterval = 500;
|
||||
|
||||
ResetPlayerStats(classicStats, false);
|
||||
@@ -1194,6 +1196,45 @@ void Restart()
|
||||
ComputeTarget();
|
||||
}
|
||||
|
||||
void ReviveAfterVideo()
|
||||
{
|
||||
if (!gameOverFlag || !reviveAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
reviveAvailable = false;
|
||||
gameOverFlag = false;
|
||||
suspendFlag = false;
|
||||
currentScreen = SCREEN_PLAYING;
|
||||
|
||||
int playableHeight = GetRoguePlayableHeight();
|
||||
int rowsToClear = playableHeight / 3;
|
||||
if (rowsToClear < 5)
|
||||
{
|
||||
rowsToClear = 5;
|
||||
}
|
||||
|
||||
for (int y = 0; y < rowsToClear && y < playableHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < nGameWidth; x++)
|
||||
{
|
||||
workRegion[y][x] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
type = ConsumeNextType();
|
||||
nType = nextTypes[0];
|
||||
state = 0;
|
||||
holdUsedThisTurn = false;
|
||||
RollCurrentPieceSpecialFlags(true);
|
||||
point = GetSpawnPoint(type);
|
||||
target = point;
|
||||
ComputeTarget();
|
||||
|
||||
SetFeedbackMessage(_T("复活成功"), _T("已清理顶部空间,本局复活机会已用完。"), 14);
|
||||
}
|
||||
|
||||
void StartGameWithMode(int mode)
|
||||
{
|
||||
currentMode = mode;
|
||||
|
||||
@@ -1663,7 +1663,14 @@ void TDrawScreen(HDC hdc, HWND hWnd)
|
||||
{
|
||||
DrawText(hdc, _T("战局崩塌"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
||||
SelectObject(hdc, bodyFont);
|
||||
DrawText(hdc, _T("按 R 重新挑战\r\n或按 M 返回菜单"), -1, &tipRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK);
|
||||
DrawText(
|
||||
hdc,
|
||||
reviveAvailable
|
||||
? _T("按 V 看视频复活(仅一次)\r\n按 R 重新挑战 或按 M 返回菜单")
|
||||
: _T("按 R 重新挑战\r\n或按 M 返回菜单"),
|
||||
-1,
|
||||
&tipRect,
|
||||
DT_CENTER | DT_VCENTER | DT_WORDBREAK);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user