- 臧启航 的博客
c++2D我的世界
- @ 2025-6-14 19:37:52
更新日志
2025/6/14
制作基本玩法如下:
| 操作 | 移动 | 跳跃 | 切换物品栏 | 破坏方块 | 放置方块 | 退出 |
|---|---|---|---|---|---|---|
| 按键 | ←/→ |
空格 | 1~9 |
D(delete) |
P(put) |
Q(quit) |
2025/6/15~2025/6/20
1.增加世界范围至1000*100
大范围世界可以更好的探索
2.修改放置和破坏逻辑
自由选择放置/破坏
3.添加更多信息
如血量、饱食度
4.添加新方块
现在方块类型如下:
| 空气 | 草方块 | 泥土 | 石头 |
|---|---|---|---|
| 煤炭矿 | 铁矿 | 钻石矿 | 原木 |
| 叶子 | 沙子 | 水 | 基岩 |
2025/6/21
优化打印方法
删除边框+减少闪烁+同步显示
优化钻石矿生成逻辑
解决了钻石矿生成在地表的BUG
减少了钻石矿脉在地下的数量
更改矿洞生成逻辑
不使用椭圆而是随机游走
矿石更加密集的分布(异常出现在地表不关我事)
2025/6/22
更改随机方式
把随机方法改成了rand(),可以自定义种子
添加指令
按t键开启聊天,打开后可以输入指令
| 指令 | \seed |
\tp @p ~±x ~±y |
|---|---|---|
| 功能 | 获取种子 | 传送到 |
添加苹果
打破树叶有的概率掉落苹果
修改世界生成的一部分逻辑
不产生浮空水
树冠更加还原
2025/6/23~2025/6/26
添加饱食度机制
每移动50次/等待10分钟会掉一格饱食度
拿着苹果按P键可以吃
改进地形生成
水湖不再是简简单单的一个长方形了
矿石和洞穴不会裸露在地表
绘图升级
饱食度和血量显示
以及若干项其他的
2025/7/4
渲染大改造
只能看到表面的方块
2025/8/30
更改重力系统
玩家落地不会穿过方块
准备更新
添加合成系统
打开背包可以合成物品
添加更多物品类型
自己看
修改物理系统
沙子下落
水和岩浆流动,相遇会发生反应
BUG反馈
代码
// 字字落实
#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <conio.h>
#include <windows.h>
#include <algorithm>
#include <sstream>
#include <unordered_map>
using namespace std;
// 控制台颜色常量
const int BLACK = 0;
const int BLUE = 1;
const int GREEN = 2;
const int CYAN = 3;
const int RED = 4;
const int MAGENTA = 5;
const int BROWN = 6;
const int LIGHTGRAY = 7;
const int DARKGRAY = 8;
const int LIGHTBLUE = 9;
const int LIGHTGREEN = 10;
const int LIGHTCYAN = 11;
const int LIGHTRED = 12;
const int LIGHTMAGENTA = 13;
const int YELLOW = 14;
const int WHITE = 15;
// 确保 ENABLE_VIRTUAL_TERMINAL_PROCESSING 被定义
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
// 光标定位函数
void gotoXY(HANDLE hOut, int x, int y) {
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(hOut, pos);
}
// ANSI转义序列映射
void setColor(HANDLE hOut, int textColor, int bgColor) {
static /*const*/ map<int, int> colorMap = {
{BLACK, 30}, {RED, 31}, {GREEN, 32}, {BROWN, 33},
{BLUE, 34}, {MAGENTA, 35}, {CYAN, 36}, {WHITE, 37},
{DARKGRAY, 90}, {LIGHTRED, 91}, {LIGHTGREEN, 92}, {YELLOW, 93},
{LIGHTBLUE, 94}, {LIGHTMAGENTA, 95}, {LIGHTCYAN, 96}, {LIGHTGRAY, 97}
};
// for (int i=0;i<=14;i++) {
// colorMap[i]=30;
// }
auto textIt = colorMap.find(textColor);
auto bgIt = colorMap.find(bgColor);
string colorCode;
if (textIt != colorMap.end() && bgIt != colorMap.end()) {
colorCode = "\033[" + to_string(textIt->second) + ";" + to_string(bgIt->second + 10) + "m";
}
else {
colorCode = "\033[0m";
}
DWORD written;
WriteConsole(hOut, colorCode.c_str(), colorCode.length(), &written, NULL);
}
// 重置为默认颜色
void resetColor(HANDLE hOut) {
const char* resetCode = "\033[0m";
DWORD written;
WriteConsole(hOut, resetCode, strlen(resetCode), &written, NULL);
}
enum class BlockType {
AIR, // 空气
GRASS, // 草方块
DIRT, // 泥土
STONE, // 石头
COAL_ORE, // 煤炭矿
IRON_ORE, // 铁矿
DIAMOND_ORE, // 钻石矿
WOOD, // 原木
LEAF, // 叶子
SAND, // 沙子
WATER, // 水
BEDROCK, // 基岩
COAL, // 煤炭
IRON, // 铁
DIAMOND, // 钻石
APPLE, // 苹果
NONE // 无物品
};
// 物品栏槽位
struct InventorySlot {
BlockType type;
int count;
};
struct BlockDisplay {
string line1; // 第一行内容
string line2; // 第二行内容
int bgColor; // 背景颜色
string name; // 方块名称
};
BlockDisplay getBlockDisplay(BlockType type) {
static const map<BlockType, BlockDisplay> displayMap = {
{BlockType::AIR, {" ", " ", LIGHTCYAN, " 空气 "}},
{BlockType::GRASS, {"草方", "块 ", GREEN, "草方块"}},
{BlockType::DIRT, {"泥土", " ", BROWN, " 泥土 "}},
{BlockType::STONE, {"石头", " ", DARKGRAY, " 石头 "}},
{BlockType::COAL_ORE, {"煤炭", "矿 ", DARKGRAY, "煤炭矿"}},
{BlockType::IRON_ORE, {"铁矿", " ", DARKGRAY, " 铁矿 "}},
{BlockType::DIAMOND_ORE, {"钻石", "矿 ", LIGHTBLUE, "钻石矿"}},
{BlockType::WOOD, {"原木", " ", BROWN, " 原木 "}},
{BlockType::LEAF, {"叶子", " ", LIGHTGREEN, " 叶子 "}},
{BlockType::SAND, {"沙子", " ", YELLOW, " 沙子 "}},
{BlockType::WATER, {"水 ", " ", BLUE, " 水 "}},
{BlockType::BEDROCK, {"基岩", " ", DARKGRAY, " 基岩 "}},
{BlockType::COAL, {"煤炭", " ", DARKGRAY, " 煤炭 "}},
{BlockType::IRON, {"铁 ", " ", DARKGRAY, " 铁 "}},
{BlockType::DIAMOND, {"钻石", " ", LIGHTBLUE, " 钻石 "}},
{BlockType::APPLE, {"苹果", " ", RED, " 苹果 "}},
{BlockType::NONE, {" ", " ", BLACK, " "}}
};
return displayMap.at(type);
}
// 检查方块是否可穿过(空气、水、叶子或原木)
bool isPassable(BlockType type) {
return type == BlockType::AIR || type == BlockType::WATER ||
type == BlockType::LEAF || type == BlockType::WOOD;
}
// 检查方块是否实体(不可穿过)
bool isSolid(BlockType type) {
return !isPassable(type);
}
// 检查方块是否可以放置
bool canPlace(BlockType type) {
return type != BlockType::AIR && type != BlockType::COAL &&
type != BlockType::IRON && type != BlockType::DIAMOND &&
type != BlockType::APPLE && type != BlockType::NONE;
}
// 检查方块是否可以使用
bool canUse(BlockType type) {
return type == BlockType::APPLE;
}
// 检查方块是否可以食用
bool canEat(BlockType type) {
return type == BlockType::APPLE;
}
// 检查方块是否可以破坏
bool canBreak(BlockType type) {
return type != BlockType::AIR && type != BlockType::BEDROCK;
}
// 获取方块破坏后的掉落物
BlockType getDropItem(BlockType type) {
switch (type) {
case BlockType::COAL_ORE: return BlockType::COAL;
case BlockType::IRON_ORE: return BlockType::IRON;
case BlockType::DIAMOND_ORE: return BlockType::DIAMOND;
case BlockType::LEAF:
if (rand() % 100 > 20) {
return BlockType::NONE;
} else {
return BlockType::APPLE;
}
default: return type;
}
}
// 随机游走隧道生成函数
void generateRandomTunnel(vector<vector<BlockType>>& world, int startX, int startY,
int length, int width,
int WIDTH, int HEIGHT, int BEDROCK_LAYER) {
int x = startX;
int y = startY;
for (int i = 0; i < length; i++) {
// 确保位置在边界内
x = max(0, min(WIDTH - 1, x));
y = max(0, min(HEIGHT - 1, y));
// 清除隧道区域(圆形)
int radius = width / 2;
for (int dx = -radius; dx <= radius; dx++) {
for (int dy = -radius; dy <= radius; dy++) {
if (dx*dx + dy*dy <= radius*radius + 1) { // +1 使形状更自然
int nx = x + dx;
int ny = y + dy;
if (nx >= 0 && nx < WIDTH && ny >= 0 && ny < HEIGHT) {
// 确保不会破坏基岩及其上方的石头层
// 只允许破坏 y < 95 的方块
if (ny < HEIGHT - BEDROCK_LAYER) {
if (world[ny][nx] == BlockType::STONE) {
world[ny][nx] = BlockType::AIR;
}
}
}
}
}
}
// 随机选择下一步方向(70%水平,30%垂直)
double direction = (1.0 * (rand() % 100) / 100);
if (direction < 0.7) { // 水平移动
if ((1.0 * (rand() % 100) / 100) < 0.5) x++; // 右
else x--; // 左
} else { // 垂直移动
if ((1.0 * (rand() % 100) / 100) < 0.5) y++; // 下
else y--; // 上
}
// 10%概率增加宽度变化
if ((1.0 * (rand() % 100) / 100) < 0.1) {
width = max(1, min(6, width + ((1.0 * (rand() % 100) / 100) < 0.5 ? 1 : -1)));
}
}
}
// 生成1000×100的随机地形(带矿洞)
vector<vector<BlockType>> generateWorld() {
const int WIDTH = 1000;
const int HEIGHT = 100;
const int BEDROCK_LAYER = 5; // 基岩层厚度
const int DIAMOND_MIN_Y = HEIGHT - 15; // 钻石矿最小生成高度 (y>85)
const int MAX_CAVE_DEPTH = HEIGHT - 20; // 矿洞最大深度 (y<80)
// 创建世界网格(初始为空气)
vector<vector<BlockType>> world(HEIGHT, vector<BlockType>(WIDTH, BlockType::AIR));
// 生成高度图(使用简单噪声)
vector<int> groundHeight(WIDTH);
for (int x = 0; x < WIDTH; x++) {
// 基础高度(平原地面在50高度)
int baseHeight = 50;
// 添加轻微噪声(±4格)
double noise = sin(x * 0.02) * 1.5 + cos(x * 0.05) * 1.2 + sin(x * 0.03) * 1.3;
groundHeight[x] = baseHeight + static_cast<int>(noise);
// 确保高度在合理范围内
groundHeight[x] = max(10, min(HEIGHT - 10, groundHeight[x]));
}
// 填充世界(从顶部到底部)
for (int x = 0; x < WIDTH; x++) {
// 基岩层(世界底部)
for (int y = HEIGHT - BEDROCK_LAYER; y < HEIGHT; y++) {
if (y == HEIGHT - 1) {
world[y][x] = BlockType::BEDROCK;
} else {
world[y][x] = BlockType::STONE; // 基岩上方的石头层
}
}
// 从地表向下填充
for (int y = groundHeight[x]; y < HEIGHT - BEDROCK_LAYER; y++) {
// 地表层 - 草方块
if (y == groundHeight[x]) {
world[y][x] = BlockType::GRASS;
}
// 浅层(泥土)
else if (y <= groundHeight[x] + 3) {
world[y][x] = BlockType::DIRT;
}
// 深层(石头和矿石)
else {
// 深层矿石生成(煤炭、铁矿、钻石)
double r = (1.0 * (rand() % 100) / 100);
if (r < 0.02) { // 2%的概率生成煤炭矿
world[y][x] = BlockType::COAL_ORE;
} else if (r < 0.005) { // 0.5%的概率生成铁矿
world[y][x] = BlockType::IRON_ORE;
} else if (r < 0.0005 && y >= DIAMOND_MIN_Y) { // 0.05%的概率生成钻石矿,且只在地下15层以内
world[y][x] = BlockType::DIAMOND_ORE;
} else {
world[y][x] = BlockType::STONE;
}
}
}
}
// 使用随机游走算法生成矿洞
for (int i = 0; i < 60; i++) { // 增加矿洞数量到60个
// 随机选择起始点(在地表下)
int startX = static_cast<int>((1.0 * (rand() % 100) / 100) * (WIDTH - 20)) + 10;
// 确保矿洞不会在接近基岩的地方生成(至少离基岩层10格以上)
int minY = groundHeight[startX] + 5;
int maxY = MAX_CAVE_DEPTH; // 最大深度限制
if (maxY < minY) continue; // 如果地表高度太高,跳过
int startY = minY + static_cast<int>((1.0 * (rand() % 100) / 100) * (maxY - minY));
// 矿洞参数
int tunnelLength = 80 + static_cast<int>((1.0 * (rand() % 100) / 100) * 120); // 80-200步长
int tunnelWidth = 2 + static_cast<int>((1.0 * (rand() % 100) / 100) * 3); // 2-5格宽
int branchChance = 20; // 20%概率产生分支
int maxBranches = 3; // 最多3个分支
// 主矿洞生成
generateRandomTunnel(world, startX, startY, tunnelLength, tunnelWidth, WIDTH, HEIGHT, BEDROCK_LAYER);
// 生成分支
int branches = 0;
for (int j = 0; j < tunnelLength && branches < maxBranches; j++) {
if (static_cast<int>((1.0 * (rand() % 100) / 100) * 100) < branchChance) {
int branchX = startX;
int branchY = startY;
int branchLength = 30 + static_cast<int>((1.0 * (rand() % 100) / 100) * 70); // 30-100步长
int branchWidth = 1 + static_cast<int>((1.0 * (rand() % 100) / 100) * 2); // 1-3格宽
// 随机选择分支方向(上下左右)
int dir = static_cast<int>((1.0 * (rand() % 100) / 100) * 4);
switch (dir) {
case 0: branchX += 5; break; // 右
case 1: branchX -= 5; break; // 左
case 2: branchY += 5; break; // 下
case 3: branchY -= 5; break; // 上
}
// 确保分支不会在基岩附近生成
if (branchY < HEIGHT - BEDROCK_LAYER - 5) {
generateRandomTunnel(world, branchX, branchY, branchLength, branchWidth, WIDTH, HEIGHT, BEDROCK_LAYER);
branches++;
}
}
}
}
// 添加小型矿脉(包括钻石)
for (int i = 0; i < 100; i++) {
int veinX = static_cast<int>((1.0 * (rand() % 100) / 100) * (WIDTH - 20)) + 10;
// 矿脉类型(煤炭、铁矿或钻石)
BlockType veinType;
double r = (1.0 * (rand() % 100) / 100);
if (r < 0.6) {
veinType = BlockType::COAL_ORE; // 60%煤炭
} else if (r < 0.9) {
veinType = BlockType::IRON_ORE; // 30%铁矿
} else {
veinType = BlockType::DIAMOND_ORE; // 10%钻石
}
// 根据矿脉类型设置生成深度
int minDepth, maxDepth;
if (veinType == BlockType::DIAMOND_ORE) {
minDepth = DIAMOND_MIN_Y; // 钻石矿最小深度
maxDepth = HEIGHT - BEDROCK_LAYER - 5; // 钻石矿最大深度
} else {
minDepth = groundHeight[veinX] + 10; // 其他矿石最小深度
maxDepth = HEIGHT - BEDROCK_LAYER - 5; // 其他矿石最大深度
}
// 确保深度范围有效
if (minDepth >= maxDepth) continue;
int veinY = minDepth + static_cast<int>((1.0 * (rand() % 100) / 100) * (maxDepth - minDepth));
int veinSize = 5 + static_cast<int>((1.0 * (rand() % 100) / 100) * 10); // 5-15格大小
for (int dx = -veinSize; dx <= veinSize; dx++) {
for (int dy = -veinSize; dy <= veinSize; dy++) {
int xPos = veinX + dx;
int yPos = veinY + dy;
if (xPos >= 0 && xPos < WIDTH && yPos >= 0 && yPos < HEIGHT) {
// 矿脉形状(随机斑点)
if (abs(dx) + abs(dy) < veinSize && (1.0 * (rand() % 100) / 100) < 0.7) {
// 确保不会替换基岩层
if (world[yPos][xPos] == BlockType::STONE && yPos < HEIGHT - BEDROCK_LAYER) {
world[yPos][xPos] = veinType;
}
}
}
}
}
}
// 添加湖泊(在地表)
for (int i = 0; i < 20; i++) { // 增加湖泊数量
int lakeX = static_cast<int>((1.0 * (rand() % 100) / 100) * (WIDTH - 10)) + 5;
int lakeY = groundHeight[lakeX]; // 地表位置
int lakeWidth = 5 + static_cast<int>((1.0 * (rand() % 100) / 100) * 8);
int lakeDepth = 2 + static_cast<int>((1.0 * (rand() % 100) / 100) * 3);
for (int dx = -lakeWidth / 2 - 1 - static_cast<int>((1.0 * (rand() % 100) / 100) * 3); dx < lakeWidth / 2 + 1 + static_cast<int>((1.0 * (rand() % 100) / 100) * 3); dx++) {
int xPos = lakeX + dx;
if (xPos < 0 || xPos >= WIDTH) continue;
for (int dy = 0; dy < lakeDepth + 3; dy++) {
int yPos = lakeY + dy;
if (yPos < HEIGHT && yPos >= 0) {
if (world[yPos - 1][xPos] == BlockType::AIR) {
world[yPos][xPos] = BlockType::SAND;
} else if (world[yPos][xPos] == BlockType::AIR) {
world[yPos][xPos] = BlockType::DIRT;
} else if (world[yPos][xPos] == BlockType::GRASS) {
world[yPos][xPos] = BlockType::DIRT;
}
}
}
}
for (int dx = -lakeWidth / 2; dx < lakeWidth / 2; dx++) {
int xPos = lakeX + dx;
if (xPos < 0 || xPos >= WIDTH) continue;
int newLakeDepth = lakeDepth;
if (dx == -lakeWidth / 2 || dx == lakeWidth / 2 - 1) {
newLakeDepth--;
}
for (int dy = 0; dy < newLakeDepth; dy++) {
int yPos = lakeY + dy;
if (yPos < HEIGHT && yPos >= 0) {
world[yPos][xPos] = BlockType::WATER;
}
}
for (int dy = 1; dy <= 10; dy++) {
int yPos = lakeY - dy;
world[yPos][xPos] = BlockType::AIR;
}
}
}
// 在地表生成树木(草方块上)
for (int x = 0; x < WIDTH; x++) {
if ((1.0 * (rand() % 100) / 100) < 0.05 && world[groundHeight[x]][x] == BlockType::GRASS) {
int treeHeight = 3 + static_cast<int>((1.0 * (rand() % 100) / 100)); // 3-4格高
// 生成树干(从地表向上)
for (int i = 1; i <= treeHeight && (groundHeight[x] - i) >= 0; i++) {
world[groundHeight[x] - i][x] = BlockType::WOOD;
}
// 生成树叶(树干顶部)
int canopyY = groundHeight[x] - treeHeight;
if (canopyY >= 0) {
// 树叶层(树冠)
int leafRadius = 2;
for (int dy = 0; dy >= -leafRadius - 1; dy--) {
for (int dx = -leafRadius; dx <= leafRadius; dx++) {
int nx = x + dx;
int ny = canopyY + dy;
if (nx >= 0 && nx < WIDTH && ny >= 0 && ny < HEIGHT) {
if (world[ny][nx] == BlockType::AIR) {
world[ny][nx] = BlockType::LEAF;
}
}
}
if (leafRadius == 2) {
leafRadius = 1;
}
}
// 树顶
if (canopyY - 1 >= 0) {
world[canopyY - 1][x] = BlockType::LEAF;
}
}
}
}
// 修复y=95层问题:确保基岩层上方的石头层不被破坏
for (int x = 0; x < WIDTH; x++) {
for (int y = HEIGHT - BEDROCK_LAYER; y < HEIGHT - 1; y++) { // y=95到98
// 如果这些位置被破坏成了空气,恢复为石头
if (world[y][x] == BlockType::AIR) {
world[y][x] = BlockType::STONE;
}
}
}
// 在深层添加额外的钻石矿
for (int i = 0; i < 500; i++) { // 增加钻石矿生成次数
int diamondX = static_cast<int>((1.0 * (rand() % 100) / 100) * WIDTH);
int diamondY = DIAMOND_MIN_Y + static_cast<int>((1.0 * (rand() % 100) / 100) * (HEIGHT - BEDROCK_LAYER - DIAMOND_MIN_Y - 1));
if (diamondY >= DIAMOND_MIN_Y && diamondY < HEIGHT - BEDROCK_LAYER) {
// 确保位置是石头
if (world[diamondY][diamondX] == BlockType::STONE) {
world[diamondY][diamondX] = BlockType::DIAMOND_ORE;
}
}
}
return world;
}
// 玩家结构
struct Player {
int x; // X坐标(世界坐标,-499~500)
int y; // Y坐标(世界坐标,0~99)
float velocity; // 垂直速度
bool inWater; // 是否在水中
bool onGround; // 是否站在地面上
bool canJump; // 是否可以跳跃
vector<InventorySlot> inventory; // 物品栏 (9个槽位)
vector<InventorySlot> backpack; // 背包 (27个槽位)
int selectedSlot; // 当前选中的物品栏槽位
int health; // 血量(0-20)
int hunger; // 饱食度(0-20)
int Hunger; // 累计饥饿
};
// 初始化玩家
void initPlayer(Player& player) {
player.x = 0; // 世界中心
player.y = 45; // 地表上方
player.velocity = 0.0f;
player.inWater = false;
player.onGround = false;
player.canJump = true;
player.health = 20;
player.hunger = 20;
player.inventory = vector<InventorySlot>(9, {BlockType::NONE, 0});
player.backpack = vector<InventorySlot>(27, {BlockType::NONE, 0}); // 初始化背包
player.selectedSlot = 0;
}
// 添加物品到物品栏或背包
void addToInventory(Player& player, BlockType type) {
// 无法获得空气、水和基岩
if (type == BlockType::AIR || type == BlockType::WATER || type == BlockType::BEDROCK) {
return;
}
// 先尝试物品栏
for (auto& slot : player.inventory) {
if (slot.type == type) {
slot.count++;
return;
}
}
// 接着尝试背包
for (auto& slot : player.backpack) {
if (slot.type == type) {
slot.count++;
return;
}
}
// 物品栏找空槽位
for (auto& slot : player.inventory) {
if (slot.type == BlockType::NONE) {
slot.type = type;
slot.count = 1;
return;
}
}
// 背包找空槽位
for (auto& slot : player.backpack) {
if (slot.type == BlockType::NONE) {
slot.type = type;
slot.count = 1;
return;
}
}
// 物品栏和背包都满了,物品掉落(目前不会发生)
}
// 使用当前选中的物品
bool useSelectedItem(Player& player) {
InventorySlot& slot = player.inventory[player.selectedSlot];
if (slot.type == BlockType::NONE || slot.count <= 0) {
return false;
}
// 减少数量
slot.count--;
// 如果数量为0,清空槽位
if (slot.count == 0) {
slot.type = BlockType::NONE;
}
return true;
}
// 生成血量字符串
string getHealthBar(int value) {
string bar;
// 每2点一个单位
for (int i = 0; i < 10; i++) {
int points = value - i * 2;
if (points >= 2) {
bar += "■■ "; // 满
} else if (points >= 1) {
bar += "■□ "; // 半
} else {
bar += "□□ "; // 空
}
}
return bar;
}
// 生成饱食度字符串
string getHungerBar(int value) {
string bar;
// 每2点一个单位
for (int i = 9; i >= 0; i--) {
int points = value - i * 2;
if (points >= 2) {
bar += "■■ "; // 满
} else if (points >= 1) {
bar += "□■ "; // 半
} else {
bar += "□□ "; // 空
}
}
return bar;
}
// 检查方块是否可见
bool isVisible(Player player, int worldX, int worldY, const vector<vector<BlockType>>& world) {
// 玩家位置总是可见
if (worldX == player.x && worldY == player.y) {
return true;
}
// 检查方块是否可穿过
if (worldY >= 0 && worldY < 100 && worldX >= -499 && worldX < 501) {
int arrayX = worldX + 499;
int arrayY = worldY;
BlockType block = world[arrayY][arrayX];
if (isPassable(block)) {
return true;
}
// 检查四个方向相邻方块是否可穿过
int dx[] = {0, 0, -1, 1};
int dy[] = {-1, 1, 0, 0};
for (int i = 0; i < 4; i++) {
int nx = worldX + dx[i];
int ny = worldY + dy[i];
if (ny >= 0 && ny < 100 && nx >= -499 && nx < 501) {
int nArrayX = nx + 499;
int nArrayY = ny;
BlockType neighbor = world[nArrayY][nArrayX];
if (isPassable(neighbor)) {
return true;
}
}
}
}
return false;
}
// 渲染背包界面
void renderBackpack(HANDLE hOut, Player& player, int selectedIndex) {
// 清屏
system("cls");
// 显示标题
gotoXY(hOut, 0, 0);
setColor(hOut, BROWN, BLACK);
cout << "============================================== 背包 ==============================================";
// 渲染背包内容 (3行9列)
for (int i = 0; i < 3; i++) {
gotoXY(hOut, 0, i * 2 + 2);
for (int j = 0; j < 9; j++) {
int index = i * 9 + j;
const InventorySlot& slot = player.backpack[index];
BlockDisplay itemDisplay = getBlockDisplay(slot.type);
string name = itemDisplay.name;
int color = itemDisplay.bgColor;
// 高亮选中的槽位
if (index == selectedIndex) {
setColor(hOut, BLACK, LIGHTGRAY);
} else {
resetColor(hOut);
}
cout << "[";
if (index == selectedIndex) {
setColor(hOut, color, LIGHTGRAY); // 选中的槽位:灰色背景
} else {
setColor(hOut, WHITE, color); // 其他槽位:黑色背景
}
cout << name;
if (index == selectedIndex) {
setColor(hOut, BLACK, LIGHTGRAY); // 选中的槽位:灰色背景
} else {
resetColor(hOut); // 其他槽位:黑色背景
}
cout << ":" << slot.count << "]";
// 添加空格分隔
resetColor(hOut);
if (j < 8) cout << " ";
else cout << " ";
}
}
// 显示物品栏标题
gotoXY(hOut, 0, 9);
setColor(hOut, BROWN, BLACK);
cout << "--------------------------------------------- 物品栏 ---------------------------------------------";
// 渲染物品栏
gotoXY(hOut, 0, 11);
for (int i = 0; i < 9; i++) {
const InventorySlot& slot = player.inventory[i];
BlockDisplay itemDisplay = getBlockDisplay(slot.type);
string name = itemDisplay.name;
int color = itemDisplay.bgColor;
resetColor(hOut);
cout << "[";
setColor(hOut, WHITE, color);
cout << name;
resetColor(hOut);
cout << ":" << slot.count << "]";
// 添加空格分隔
resetColor(hOut);
if (i < 8) cout << " ";
else cout << " ";
}
// 操作提示
gotoXY(hOut, 0, 13);
resetColor(hOut);
cout << "操作说明:";
gotoXY(hOut, 0, 14);
cout << "- 方向键: 选择背包物品";
gotoXY(hOut, 0, 15);
cout << "- 数字键(1-9): 将选中物品放入物品栏对应槽位";
gotoXY(hOut, 0, 16);
cout << "- E/ESC: 关闭背包";
}
// 渲染世界(包含玩家和状态信息)
void renderWorld(HANDLE hOut, const vector<vector<BlockType>>& world, const Player& player,
int selectedX = -1, int selectedY = -1, bool initialRender = false) {
const int VIEW_WIDTH = 23;
const int VIEW_HEIGHT = 11;
// 计算视图范围(以玩家为中心)
int startX = player.x - VIEW_WIDTH / 2;
int startY = player.y - VIEW_HEIGHT / 2;
// 确保视图在边界内
startX = max(-499, min(501 - VIEW_WIDTH, startX));
startY = max(0, min(100 - VIEW_HEIGHT, startY));
static vector<vector<BlockType>> lastWorld(VIEW_HEIGHT, vector<BlockType>(VIEW_WIDTH, BlockType::AIR));
static int lastPlayerX = -1000;
static int lastPlayerY = -1000;
static int lastSelectedX = -1000;
static int lastSelectedY = -1000;
static int lastHealth = -1;
static int lastHunger = -1;
static int lastSelectedSlot = -1;
static vector<InventorySlot> lastInventory(9, {BlockType::NONE, 0});
// 渲染视图范围内的方块
for (int viewY = 0; viewY < VIEW_HEIGHT; viewY++) {
int worldY = startY + viewY;
for (int viewX = 0; viewX < VIEW_WIDTH; viewX++) {
int worldX = startX + viewX;
BlockType block = BlockType::AIR;
// 检查可见性
bool visible = isVisible(player, worldX, worldY, world);
if (!visible) {
block = BlockType::NONE; // 不可见方块显示为黑色
} else if (worldY >= 0 && worldY < 100 && worldX >= -499 && worldX < 501) {
int arrayX = worldX + 499;
int arrayY = worldY;
block = world[arrayY][arrayX];
}
// 检查是否需要重绘
bool needRedraw = initialRender ||
lastWorld[viewY][viewX] != block ||
(worldX == player.x && worldY == player.y) != (worldX == lastPlayerX && worldY == lastPlayerY) ||
(worldX == selectedX && worldY == selectedY) != (worldX == lastSelectedX && worldY == lastSelectedY);
if (needRedraw) {
// 计算控制台位置
int consoleY = viewY * 2 + 2;
int consoleX = viewX * 4;
// 移动到方块位置
gotoXY(hOut, consoleX, consoleY);
// 获取方块信息
BlockDisplay display = getBlockDisplay(block);
// 检查是否是选中的方块
if (worldX == selectedX && worldY == selectedY) {
setColor(hOut, BLACK, WHITE); // 白底黑字
// 检查是否是玩家位置
if (worldX == player.x && worldY == player.y) {
cout << "玩家";
} else {
cout << display.line1;
}
}
else if (worldX != player.x or worldY != player.y or block != BlockType::AIR) {
// 检查是否是玩家位置
if (worldX == player.x && worldY == player.y) {
setColor(hOut, WHITE, RED); // 玩家红色背景
} else {
setColor(hOut, WHITE, display.bgColor); // 方块背景色
}
cout << display.line1;
}
else {
setColor(hOut, WHITE, RED); // 玩家红色背景
cout << "玩家";
}
// 移动到第二行
gotoXY(hOut, consoleX, consoleY + 1);
// 检查是否是选中的方块
if (worldX == selectedX && worldY == selectedY) {
setColor(hOut, BLACK, WHITE); // 白底黑字
// 检查是否是玩家位置
if (worldX == player.x && worldY == player.y) {
cout << " ";
} else {
cout << display.line2;
}
}
else if (worldX != player.x or worldY != player.y or block != BlockType::AIR) {
// 检查是否是玩家位置
if (worldX == player.x && worldY == player.y) {
setColor(hOut, WHITE, RED); // 玩家红色背景
} else {
setColor(hOut, WHITE, display.bgColor); // 方块背景色
}
cout << display.line2;
}
else {
cout << " ";
}
// 更新缓存
lastWorld[viewY][viewX] = block;
}
}
}
// 显示血量和饱食度(使用■、□表示)
if (initialRender || player.health != lastHealth) {
gotoXY(hOut, 0, VIEW_HEIGHT * 2 + 2);
setColor(hOut, LIGHTRED, BLACK);
cout << getHealthBar(player.health);
gotoXY(hOut, 0, VIEW_HEIGHT * 2 + 3);
setColor(hOut, LIGHTRED, BLACK);
cout << getHealthBar(player.health);
lastHealth = player.health;
}
if (initialRender || player.hunger != lastHunger) {
gotoXY(hOut, 54, VIEW_HEIGHT * 2 + 2);
setColor(hOut, YELLOW, BLACK);
cout << getHungerBar(player.hunger);
gotoXY(hOut, 54, VIEW_HEIGHT * 2 + 3);
setColor(hOut, YELLOW, BLACK);
cout << getHungerBar(player.hunger);
lastHunger = player.hunger;
}
// 显示物品栏
bool inventoryChanged = false;
for (int i = 0; i < 9; i++) {
if (player.inventory[i].type != lastInventory[i].type ||
player.inventory[i].count != lastInventory[i].count ||
(i == player.selectedSlot) != (i == lastSelectedSlot)) {
inventoryChanged = true;
break;
}
}
if (initialRender || inventoryChanged) {
gotoXY(hOut, 0, VIEW_HEIGHT * 2 + 4);
for (int i = 0; i < 9; i++) {
const InventorySlot& slot = player.inventory[i];
BlockDisplay itemDisplay = getBlockDisplay(slot.type);
string name = itemDisplay.name;
int color = itemDisplay.bgColor;
if (i == player.selectedSlot) {
setColor(hOut, BLACK, LIGHTGRAY); // 选中的槽位:灰色背景
} else {
resetColor(hOut); // 其他槽位:黑色背景
}
cout << "[";
if (i == player.selectedSlot) {
setColor(hOut, color, LIGHTGRAY); // 选中的槽位:灰色背景
} else {
setColor(hOut, WHITE, color); // 其他槽位:黑色背景
}
cout << name;
if (i == player.selectedSlot) {
setColor(hOut, BLACK, LIGHTGRAY); // 选中的槽位:灰色背景
} else {
resetColor(hOut); // 其他槽位:黑色背景
}
cout << ":" << slot.count << "]";
// 添加空格分隔
resetColor(hOut);
if (i < 8) cout << " ";
else cout << " ";
// 更新缓存
lastInventory[i] = slot;
}
lastSelectedSlot = player.selectedSlot;
}
// 显示坐标
if (initialRender || player.x != lastPlayerX || player.y != lastPlayerY) {
resetColor(hOut);
gotoXY(hOut, 0, 1);
cout << "X:" << player.x << " Y:" << 99 - player.y << " ";
lastPlayerX = player.x;
lastPlayerY = player.y;
}
// 更新选中位置缓存
lastSelectedX = selectedX;
lastSelectedY = selectedY;
}
int main() {
// 启用ANSI转义序列支持
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
GetConsoleMode(hOut, &dwMode);
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hOut, dwMode);
// 设置控制台窗口大小
COORD size = { 100, 30 };
SetConsoleScreenBufferSize(hOut, size);
SMALL_RECT rc = { 0, 0, 99, 29 };
SetConsoleWindowInfo(hOut, true, &rc);
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(hOut, &cursor_info);
// 设置随机种子
cout << "请输入种子(空表示随机):\033[?25h";
setColor(hOut, BROWN, BLACK);
string s;
getline(cin, s);
int seed;
if (s == "") {
seed = time(nullptr);
} else {
seed = stoll(s);
}
cout << "\033[?25l";
resetColor(hOut);
srand(seed);
system("cls");
// 生成1000×100的世界
cout << "正在生成世界..." << endl;
vector<vector<BlockType>> world = generateWorld();
cout << "世界生成完成!按任意键开始游戏" << endl;
_getch();
system("cls");
// 初始化玩家
Player player;
initPlayer(player);
// 方块选择模式变量
bool inSelectionMode = false;
bool isDiggingMode = false;
int selectedX = player.x;
int selectedY = player.y + 1; // 默认选择玩家下方方块
// 背包模式变量
bool isBackpackOpen = false;
int backpackSelected = 0; // 背包选中的槽位
// 游戏主循环
bool initialRender = true;
while (true) {
bool playerMoved = false; // 标记玩家是否移动
bool playerJumped = false; // 标记玩家是否跳跃
bool blockChanged = false; // 标记方块是否被破坏或放置
// 处理输入
if (_kbhit()) {
char key = _getch();
// 如果在背包模式
if (isBackpackOpen) {
// 方向键移动选择框
if (key == 75 && backpackSelected > 0) { // 左箭头
backpackSelected--;
// 重新渲染背包
renderBackpack(hOut, player, backpackSelected);
}
else if (key == 77 && backpackSelected < 26) { // 右箭头
backpackSelected++;
// 重新渲染背包
renderBackpack(hOut, player, backpackSelected);
}
else if (key == 72 && backpackSelected >= 9) { // 上箭头
backpackSelected -= 9;
// 重新渲染背包
renderBackpack(hOut, player, backpackSelected);
}
else if (key == 80 && backpackSelected < 18) { // 下箭头
backpackSelected += 9;
// 重新渲染背包
renderBackpack(hOut, player, backpackSelected);
}
// 数字键交换物品
else if (key >= '1' && key <= '9') {
int slotIndex = key - '1'; // 物品栏槽位
// 获取选中的背包物品
InventorySlot& backpackSlot = player.backpack[backpackSelected];
InventorySlot& invSlot = player.inventory[slotIndex];
// 如果两个槽位的物品类型相同,则合并
if (backpackSlot.type == invSlot.type) {
invSlot.count += backpackSlot.count;
backpackSlot.type = BlockType::NONE;
backpackSlot.count = 0;
}
// 否则交换两个槽位的物品
else {
InventorySlot temp = backpackSlot;
backpackSlot = invSlot;
invSlot = temp;
}
// 重新渲染背包
renderBackpack(hOut, player, backpackSelected);
}
// E/ESC关闭背包
else if (key == 'e' || key == 'E' || key == 27) {
isBackpackOpen = false;
initialRender = true; // 重新渲染世界
system("cls"); // 清屏
}
}
// 如果在选择模式
else if (inSelectionMode) {
// 方向键移动选择框
if (key == 75 && selectedX > -499) { // 左箭头
if (isVisible(player, selectedX - 1, selectedY, world)) {
selectedX--;
}
}
else if (key == 77 && selectedX < 500) { // 右箭头
if (isVisible(player, selectedX + 1, selectedY, world)) {
selectedX++;
}
}
else if (key == 72 && selectedY > 0) { // 上箭头
if (isVisible(player, selectedX, selectedY - 1, world)) {
selectedY--;
}
}
else if (key == 80 && selectedY < 99) { // 下箭头
if (isVisible(player, selectedX, selectedY + 1, world)) {
selectedY++;
}
}
// 空格确认操作
else if (key == 32) { // 空格
// 确保在世界范围内
if (selectedY >= 0 && selectedY < 100 && selectedX >= -499 && selectedX < 501) {
// 转换为数组索引
int arrayX = selectedX + 499;
int arrayY = selectedY;
// 挖掘操作
if (isDiggingMode) {
// 检查是否可以破坏(基岩、水、空气不能挖掘)
BlockType targetBlock = world[arrayY][arrayX];
if (targetBlock != BlockType::BEDROCK &&
targetBlock != BlockType::WATER &&
targetBlock != BlockType::AIR) {
// 获取掉落物
BlockType drop = getDropItem(targetBlock);
// 添加到物品栏
addToInventory(player, drop);
// 破坏方块(变为空气)
world[arrayY][arrayX] = BlockType::AIR;
blockChanged = true;
}
}
// 放置操作
else {
// 获取当前选中的物品
BlockType selected = player.inventory[player.selectedSlot].type;
// 检查是否可以放置
if (canPlace(selected)) {
// 检查目标位置是否可以放置(非水方块或玩家位置不能放置)
if ((world[arrayY][arrayX] == BlockType::AIR || world[arrayY][arrayX] == BlockType::WATER) &&
(selectedX != player.x || selectedY != player.y)) {
// 使用物品
if (useSelectedItem(player)) {
// 放置方块
world[arrayY][arrayX] = selected;
blockChanged = true;
}
}
}
}
}
inSelectionMode = false;
}
// ESC退出选择模式
else if (key == 27) { // ESC
inSelectionMode = false;
}
}
// 不在任何特殊模式
else {
// 打开背包
if (key == 'e' || key == 'E') {
isBackpackOpen = true;
backpackSelected = 0; // 默认选中第一个槽位
renderBackpack(hOut, player, backpackSelected);
}
// 左移
else if (key == 75 && player.x > -499) { // 左箭头
// 检查左侧方块是否可穿过
int arrayX = (player.x - 1) + 499;
int arrayY = player.y;
if (arrayX >= 0 && arrayX < 1000 && arrayY >= 0 && arrayY < 100) {
if (isPassable(world[arrayY][arrayX])) {
player.x--;
player.Hunger += 240;
playerMoved = true;
}
}
}
// 右移
else if (key == 77 && player.x < 500) { // 右箭头
// 检查右侧方块是否可穿过
int arrayX = (player.x + 1) + 499;
int arrayY = player.y;
if (arrayX >= 0 && arrayX < 1000 && arrayY >= 0 && arrayY < 100) {
if (isPassable(world[arrayY][arrayX])) {
player.x++;
player.Hunger += 240;
playerMoved = true;
}
}
}
// 跳跃
else if (key == 32) { // 空格
// 只有在地面上或水中才能跳跃
if (player.canJump && (player.onGround || player.inWater)) {
player.Hunger += 240;
player.velocity = -1.1f;
player.onGround = false;
player.canJump = false; // 防止连续跳跃
playerJumped = true;
}
}
// 物品栏选择 (1-9)
else if (key >= '1' && key <= '9') {
int slot = key - '1'; // 转换为0-8的索引
if (slot >= 0 && slot < 9) {
player.selectedSlot = slot;
}
}
// 破坏方块 (D键)
else if (key == 'd' || key == 'D') {
inSelectionMode = true;
isDiggingMode = true;
selectedX = player.x;
selectedY = player.y; // 默认选择玩家方块
}
// 放置方块/使用物品 (P键)
else if (key == 'p' || key == 'P') {
BlockType selected = player.inventory[player.selectedSlot].type;
// 使用物品
if (canUse(selected)) {
// 食用食物
if (canEat(selected)) {
if (player.hunger != 20) {
// 苹果
if (selected == BlockType::APPLE) {
player.hunger = min(player.hunger + 4, 20);
}
// 消耗物品
useSelectedItem(player);
}
}
}
// 放置方块
else {
inSelectionMode = true;
isDiggingMode = false;
selectedX = player.x;
selectedY = player.y;
}
}
// 指令
else if (key == 't' || key == 'T') {
setColor(hOut, BROWN, BLACK);
cout << "\033[?25h";
gotoXY(hOut, 0, 27);
cout << " ";
gotoXY(hOut, 0, 27);
while (!_kbhit());
if (_getch() != 10) {
char c;
cin >> c;
if (c == '/') {
string op;
cin >> op;
// 获取种子
if (op == "seed") {
gotoXY(hOut, 0, 27);
cout << " ";
gotoXY(hOut, 0, 27);
cout << seed << endl;
}
// 传送
else if (op == "tp") {
string obj;
cin >> obj;
string sx, sy;
cin >> sx >> sy;
int nx, ny;
if (sx == "~") {
nx = player.x;
} else if (sx[0] == '~') {
if (sx[1] == '+') {
nx = player.x + stoll(sx.substr(2));
} else if (sx[1] == '-') {
nx = player.x - stoll(sx.substr(2));
} else {
nx = player.x + stoll(sx.substr(1));
}
} else {
nx = stoll(sx);
}
if (sy == "~") {
ny = 99 - player.y;
} else if (sy[0] == '~') {
if (sy[1] == '+') {
ny = 99 - player.y + stoll(sy.substr(2));
} else if (sy[1] == '-') {
ny = 99 - player.y - stoll(sy.substr(2));
} else {
ny = 99 - player.y + stoll(sy.substr(1));
}
} else {
ny = stoll(sy);
}
ny = 99 - ny;
gotoXY(hOut, 0, 27);
cout << " ";
gotoXY(hOut, 0, 27);
if (obj != "@p") {
cout << "选择对象失败" << endl;
} else if (nx > 500 or nx < -499 or ny > 98 or ny < 0) {
cout << "超出世界范围,无法传送" << endl;
} else {
player.x = nx;
player.y = ny;
}
}
} else {
gotoXY(hOut, 0, 27);
cout << " ";
gotoXY(hOut, 0, 27);
}
resetColor(hOut);
cout << "\033[?25l";
}
}
// 退出
else if (key == 'q' || key == 'Q') {
break;
}
}
}
// 饥饿
player.Hunger++;
if (player.Hunger >= 12000) {
if (player.hunger > 0) {
player.hunger--;
}
player.Hunger = 0;
}
// 物理更新(只在非选择模式和非背包模式)
if (!inSelectionMode && !isBackpackOpen) {
player.velocity = min(player.velocity + 0.1f, 1.0f); // 重力加速度
int newY = player.y + static_cast<int>(player.velocity);
// 检查是否在水中
bool wasInWater = player.inWater;
int arrayX = player.x + 499;
int arrayY = player.y;
if (arrayX >= 0 && arrayX < 1000 && arrayY >= 0 && arrayY < 100) {
player.inWater = (world[arrayY][arrayX] == BlockType::WATER);
} else {
player.inWater = false;
}
// 如果在水里,减缓下落速度
if (player.inWater) {
if (player.velocity > 1.0f) {
player.velocity *= 0.8f;
} else {
player.velocity += 0.0001f;
}
player.canJump = true; // 在水中时可以重置跳跃能力
}
// 碰撞检测
bool wasOnGround = player.onGround;
player.onGround = false;
if (newY != player.y) {
// 检查下方方块是否可穿过
int belowX = player.x + 499;
int belowY = newY;
if (belowY < 100 && belowY >= 0 && belowX >= 0 && belowX < 1000) {
if (isPassable(world[belowY][belowX])) {
// 可以下落
player.y = newY;
} else {
// 碰到实体方块
if (player.velocity > 0) {
// 落到方块上
player.y = newY - 1;
player.onGround = true;
player.canJump = true; // 落地后可以再次跳跃
} else {
// 碰到基岩层
player.y = newY + 1;
}
player.velocity = 0.0f;
}
} else {
// 边界处理
if (newY < 0) {
player.y = 0;
player.velocity = 0.0f;
} else if (newY >= 100) {
player.y = 99;
player.velocity = 0.0f;
player.onGround = true;
player.canJump = true;
}
}
}
// 边界检查
player.x = max(-499, min(500, player.x));
player.y = max(0, min(99, player.y));
// 更新地面状态
int groundCheckX = player.x + 499;
int groundCheckY = player.y + 1;
if (groundCheckY < 100 && groundCheckY >= 0 && groundCheckX >= 0 && groundCheckX < 1000) {
player.onGround = isSolid(world[groundCheckY][groundCheckX]);
}
// 在水中时自动重置跳跃能力
if (player.inWater) {
player.canJump = true;
}
}
// 如果不是背包模式,渲染世界
if (!isBackpackOpen) {
// 显示模式提示
gotoXY(hOut, 0, 0);
resetColor(hOut);
if (inSelectionMode) {
if (isDiggingMode) {
cout << "挖掘模式: 用方向键选择方块,空格挖掘,ESC取消";
} else {
cout << "放置模式: 用方向键选择位置,空格放置,ESC取消";
}
} else {
cout << "移动: 方向键 跳跃: 空格 挖掘: D 放置/使用: P 背包: E 指令: t 退出: Q";
}
cout << " ";
// 渲染世界(在选择模式下传递选中位置)
if (inSelectionMode) {
renderWorld(hOut, world, player, selectedX, selectedY, initialRender);
} else {
renderWorld(hOut, world, player, -1, -1, initialRender);
}
initialRender = false;
}
// 控制帧率
Sleep(50);
}
// 结束画面
system("cls");
cout << "感谢游玩!" << endl;
return 0;
}