diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71667d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,62 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# IDE settings +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index 0a69282..34421c1 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,15 @@ 这是一个2D农场模拟游戏,玩家可以控制角色在农场中移动,开垦土地、种植作物、等待作物生长并收获产品。 +## 🆕 v1.1 更新内容 + +- **新增作物种类**:番茄和玉米,共三种作物可供种植 +- **金币系统**:收获作物可获得金币,不同作物价值不同 +- **商店系统**:可以使用金币购买各种种子 +- **改进的UI**:显示金币数量、当前选中的种子类型 +- **作物颜色区分**:不同作物显示不同颜色(小麦-金黄、番茄-红色、玉米-金色) +- **物品堆叠显示**:背包中相同物品会合并显示数量 + ## 🛠️ 技术栈 - **Python 3** - 主要开发语言 @@ -17,7 +26,7 @@ planting-game/ ├── main.py # 游戏主入口,包含游戏循环和主要逻辑 ├── block.py # 土地块类定义(Block、Soil) -├── character.py # 角色类定义,包含背包管理 +├── character.py # 角色类定义,包含背包和金币管理 ├── item.py # 物品类定义(Item、ItemType、Plant) └── import pygame.py # 早期版本/备份文件 ``` @@ -26,9 +35,10 @@ planting-game/ #### `main.py` - 游戏主程序 - 初始化 Pygame 和游戏窗口(800x600) -- 创建玩家角色和初始物品(锄头、小麦种子) +- 创建玩家角色和初始物品(锄头、种子) - 生成土地块网格 - 处理游戏主循环:事件监听、渲染更新、植物生长逻辑 +- 商店系统逻辑 #### `block.py` - 土地系统 - **Block 类**:基础土地块,包含位置和状态属性 @@ -37,16 +47,26 @@ planting-game/ - 支持种植(plant_crop) - 支持生长(grow)- 基于时间的生长系统 - 支持收获(harvest) + - 不同作物显示不同颜色 #### `character.py` - 角色系统 - 角色位置管理 - 背包系统:添加、移除、获取物品 +- 金币系统:添加、消费金币 #### `item.py` - 物品系统 -- **ItemType 类**:物品类型枚举(工具、种子、植物、产品) +- **ItemType 类**:物品类型枚举(工具、种子、植物、产品、货币) - **Item 类**:基础物品定义 - **Plant 类**(继承Item):植物定义,包含生长时间、产出物品、种子掉落等属性 +## 🌱 作物信息 + +| 作物 | 种子价格 | 生长阶段 | 产品售价 | +|------|----------|----------|----------| +| 小麦 | 10 金币 | 5 阶段 | 15 金币 | +| 番茄 | 20 金币 | 6 阶段 | 25 金币 | +| 玉米 | 30 金币 | 7 阶段 | 35 金币 | + ## 🎮 游戏操作 | 按键 | 功能 | @@ -57,13 +77,19 @@ planting-game/ | D | 向右移动 | | 空格 | 交互(开垦土地/种植/收获)| | E | 打开/关闭背包 | +| B | 打开/关闭商店 | +| 1 | 选择小麦种子 | +| 2 | 选择番茄种子 | +| 3 | 选择玉米种子 | ## 🌱 游戏玩法 1. **开垦土地**:站在未开垦的土地上,背包中有锄头时按空格键开垦 -2. **种植作物**:站在已开垦的土地上,背包中有种子时按空格键种植 -3. **等待生长**:作物会随时间自动生长,生长阶段以数字显示 -4. **收获产品**:作物成熟后(达到最大生长阶段),按空格键收获产品和种子 +2. **购买种子**:按 B 打开商店,点击想要购买的种子 +3. **选择种子**:按 1-3 键选择要种植的种子类型 +4. **种植作物**:站在已开垦的土地上,背包中有种子时按空格键种植 +5. **等待生长**:作物会随时间自动生长,生长阶段以数字显示 +6. **收获产品**:作物成熟后(达到最大生长阶段),按空格键收获产品和种子,同时获得金币 ## 🚀 如何运行 @@ -78,16 +104,16 @@ python main.py ## 📋 未来更新计划 ### 近期计划 (v1.x) -- [ ] 添加更多作物种类(番茄、玉米、南瓜等) +- [x] 添加更多作物种类(番茄、玉米) +- [x] 实现金币系统 +- [x] 添加商店系统 - [ ] 实现浇水系统,影响作物生长速度 - [ ] 添加季节系统,不同季节可种植不同作物 - [ ] 优化玩家角色动画 ### 中期计划 (v2.x) -- [ ] 添加商店系统,可以买卖种子和产品 -- [ ] 实现金币系统 - [ ] 添加更多工具(水壶、斧头等) -- [ ] 添加背包容量限制和堆叠功能 +- [ ] 添加背包容量限制 - [ ] 保存/读取游戏进度 ### 长期计划 (v3.x) diff --git a/__pycache__/block.cpython-312.pyc b/__pycache__/block.cpython-312.pyc deleted file mode 100644 index 2d0d6e4..0000000 Binary files a/__pycache__/block.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/character.cpython-312.pyc b/__pycache__/character.cpython-312.pyc deleted file mode 100644 index 9b14029..0000000 Binary files a/__pycache__/character.cpython-312.pyc and /dev/null differ diff --git a/__pycache__/item.cpython-312.pyc b/__pycache__/item.cpython-312.pyc deleted file mode 100644 index 6d9d4e5..0000000 Binary files a/__pycache__/item.cpython-312.pyc and /dev/null differ diff --git a/block.py b/block.py index 22943c8..67fde38 100644 --- a/block.py +++ b/block.py @@ -1,9 +1,16 @@ import pygame # 定义常量 -PLANT_COLOR = (0, 128, 0) TILE_SIZE = 32 +# 植物颜色字典 +PLANT_COLORS = { + "Wheat Plant": (218, 165, 32), # 金黄色 - 小麦 + "Tomato Plant": (255, 99, 71), # 番茄红 + "Corn Plant": (255, 215, 0), # 玉米金色 +} +DEFAULT_PLANT_COLOR = (0, 128, 0) # 默认绿色 + # 初始化字体 pygame.font.init() FONT = pygame.font.Font(None, 24) # 可以根据需要调整字体大小 @@ -90,8 +97,10 @@ def draw(self, screen): x, y = self.position # 计算植物的大小,根据生长阶段动态变化 plant_size = TILE_SIZE * (self.growth_stage / self.plant.max_stages) + # 获取植物颜色 + plant_color = PLANT_COLORS.get(self.plant.name, DEFAULT_PLANT_COLOR) # 绘制植物矩形,使其居中显示在土地块上 - pygame.draw.rect(screen, PLANT_COLOR, (x + (TILE_SIZE - plant_size) // 2, + pygame.draw.rect(screen, plant_color, (x + (TILE_SIZE - plant_size) // 2, y + (TILE_SIZE - plant_size) // 2, plant_size, plant_size)) # 绘制生长阶段数字标注 diff --git a/character.py b/character.py index ecc14bc..9c220cb 100644 --- a/character.py +++ b/character.py @@ -10,6 +10,7 @@ def __init__(self, name, x, y): self.x = x self.y = y self.inventory = [] # Item list + self.coins = 100 # Starting coins def add_item(self, item): """ @@ -36,3 +37,21 @@ def get_inventory(self): :return: The character's inventory list """ return self.inventory + + def add_coins(self, amount): + """ + Add coins to the character + :param amount: The amount of coins to add + """ + self.coins += amount + + def spend_coins(self, amount): + """ + Spend coins from the character + :param amount: The amount of coins to spend + :return: True if successful, False if not enough coins + """ + if self.coins >= amount: + self.coins -= amount + return True + return False diff --git a/item.py b/item.py index 4bb5070..9df1042 100644 --- a/item.py +++ b/item.py @@ -4,6 +4,7 @@ class ItemType: SEED = 2 PLANT = 3 PRODUCT = 4 + CURRENCY = 5 # 定义物品类 class Item: diff --git a/main.py b/main.py index 57583ce..1104d1a 100644 --- a/main.py +++ b/main.py @@ -12,34 +12,68 @@ TILE_SIZE = 32 WHITE = (255, 255, 255) BLACK = (0, 0, 0) +GOLD_COLOR = (255, 215, 0) MOVE_SPEED = 2 FONT = pygame.font.Font(None, 24) +TITLE_FONT = pygame.font.Font(None, 32) INVENTORY_WIDTH = 200 INVENTORY_HEIGHT = 300 +SHOP_WIDTH = 250 +SHOP_HEIGHT = 200 INVENTORY_BG_COLOR = (220, 220, 220) +SHOP_BG_COLOR = (200, 230, 200) TIP_BG_COLOR = (255, 255, 200) HIGHLIGHT_COLOR = (255, 0, 0) # 创建屏幕 screen = pygame.display.set_mode((WIDTH, HEIGHT)) -pygame.display.set_caption("Simple Stardew Valley") +pygame.display.set_caption("Simple Stardew Valley v1.1") # Create a character instance player = Character("Player", WIDTH // 2, HEIGHT // 2) # 定义小麦种子 -wheat_seed = Item(2, "Wheat Seed", "A seed for planting wheat") +wheat_seed = Item(2, "Wheat Seed", ItemType.SEED, "A seed for planting wheat") # 定义小麦产品 -wheat_product = Item(5, "Wheat", "Mature wheat, can be sold or used") +wheat_product = Item(5, "Wheat", ItemType.PRODUCT, "Mature wheat, sells for 15 coins") # 定义小麦植物 wheat = Plant(4, "Wheat Plant", wheat_seed, 100, wheat_product, 5) +# 定义番茄种子 +tomato_seed = Item(6, "Tomato Seed", ItemType.SEED, "A seed for planting tomatoes") +# 定义番茄产品 +tomato_product = Item(7, "Tomato", ItemType.PRODUCT, "Fresh tomato, sells for 25 coins") +# 定义番茄植物 +tomato = Plant(8, "Tomato Plant", tomato_seed, 150, tomato_product, 6) + +# 定义玉米种子 +corn_seed = Item(9, "Corn Seed", ItemType.SEED, "A seed for planting corn") +# 定义玉米产品 +corn_product = Item(10, "Corn", ItemType.PRODUCT, "Golden corn, sells for 35 coins") +# 定义玉米植物 +corn = Plant(11, "Corn Plant", corn_seed, 200, corn_product, 7) + # 定义工具 hoe = Item(1, "Hoe", ItemType.TOOL, "Used to cultivate land") +# 商店物品价格 +SHOP_ITEMS = { + "Wheat Seed": {"item": wheat_seed, "price": 10, "plant": wheat}, + "Tomato Seed": {"item": tomato_seed, "price": 20, "plant": tomato}, + "Corn Seed": {"item": corn_seed, "price": 30, "plant": corn}, +} + +# 产品售价 +SELL_PRICES = { + 5: 15, # Wheat + 7: 25, # Tomato + 10: 35, # Corn +} + # 添加物品到玩家背包 player.add_item(hoe) player.add_item(wheat_seed) +player.add_item(wheat_seed) # 创建土地块列表 blocks = [] @@ -53,6 +87,8 @@ clock = pygame.time.Clock() grow_timer = 0 is_inventory_open = False +is_shop_open = False +selected_seed_type = "Wheat Seed" # 当前选中的种子类型 def draw_player(screen, x, y): @@ -79,8 +115,24 @@ def draw_player(screen, x, y): (x + 2 * TILE_SIZE // 3, y + 2 * head_radius + body_height + leg_length), 3) +def count_inventory_items(inventory): + """统计背包中每种物品的数量""" + item_counts = {} + for item in inventory: + key = (item.id, item.name) + if key not in item_counts: + item_counts[key] = {"item": item, "count": 0} + item_counts[key]["count"] += 1 + return item_counts + + while running: current_time = pygame.time.get_ticks() # 每次循环获取当前时间 + mouse_x, mouse_y = pygame.mouse.get_pos() # 获取鼠标位置(每帧只获取一次) + + # 预计算物品计数(用于商店和背包显示) + item_counts = count_inventory_items(player.inventory) + for event in pygame.event.get(): if event.type == pygame.QUIT: running = False @@ -99,30 +151,79 @@ def draw_player(screen, x, y): print(f"Found target block at position: {target_block.position}") land_state = target_block.state has_hoe = any(item.id == 1 for item in player.inventory) - has_wheat_seed = any(item.id == 2 for item in player.inventory) + + # 检查背包中的所有种子类型 + available_seeds = {} + for seed_name, seed_info in SHOP_ITEMS.items(): + seed_item = seed_info["item"] + if any(item.id == seed_item.id for item in player.inventory): + available_seeds[seed_name] = seed_info if land_state == 0: # 土地未开垦 if has_hoe: target_block.cultivate() elif land_state == 1 and target_block.plant is None: # 开垦未播种 - if has_wheat_seed: - target_block.plant_crop(wheat, current_time) + # 使用当前选中的种子类型 + if selected_seed_type in available_seeds: + seed_info = available_seeds[selected_seed_type] + target_block.plant_crop(seed_info["plant"], current_time) # 从背包移除种子 for item in player.inventory[:]: - if item.id == 2: + if item.id == seed_info["item"].id: player.inventory.remove(item) break + print(f"Planted {selected_seed_type}") else: harvested = target_block.harvest() if harvested: player.add_item(harvested.product_item) + # 获得金币 + if harvested.product_item.id in SELL_PRICES: + coins_earned = SELL_PRICES[harvested.product_item.id] + player.add_coins(coins_earned) + print(f"Earned {coins_earned} coins!") # 随机生成 1 - 2 个种子 seed_count = random.randint(harvested.seed_harvested_min, harvested.seed_harvested_max) for _ in range(seed_count): player.add_item(harvested.seed) - print(f"Harvested {harvested} and {seed_count} {harvested.seed}") + print(f"Harvested {harvested.name} and {seed_count} seeds") elif event.key == pygame.K_e: is_inventory_open = not is_inventory_open + if is_inventory_open: + is_shop_open = False + elif event.key == pygame.K_b: + is_shop_open = not is_shop_open + if is_shop_open: + is_inventory_open = False + elif event.key == pygame.K_1: + selected_seed_type = "Wheat Seed" + print(f"Selected: {selected_seed_type}") + elif event.key == pygame.K_2: + selected_seed_type = "Tomato Seed" + print(f"Selected: {selected_seed_type}") + elif event.key == pygame.K_3: + selected_seed_type = "Corn Seed" + print(f"Selected: {selected_seed_type}") + elif event.type == pygame.MOUSEBUTTONDOWN and is_shop_open: + mouse_x, mouse_y = pygame.mouse.get_pos() + # 检查点击是否在商店区域 + shop_x = 10 + shop_y = 50 + item_y = shop_y + 30 + for seed_name, seed_info in SHOP_ITEMS.items(): + item_rect = pygame.Rect(shop_x, item_y, SHOP_WIDTH - 20, 25) + if item_rect.collidepoint(mouse_x, mouse_y): + # 购买种子 + price = seed_info["price"] + if player.spend_coins(price): + # 创建新的种子实例 + new_seed = Item(seed_info["item"].id, seed_info["item"].name, + seed_info["item"].type, seed_info["item"].description) + player.add_item(new_seed) + print(f"Bought {seed_name} for {price} coins") + else: + print(f"Not enough coins to buy {seed_name}") + item_y += 30 # 处理按键事件,使用 WASD 控制移动 keys = pygame.key.get_pressed() @@ -157,13 +258,67 @@ def draw_player(screen, x, y): if block.position == (player_tile_x, player_tile_y): pygame.draw.rect(screen, HIGHLIGHT_COLOR, (player_tile_x, player_tile_y, TILE_SIZE, TILE_SIZE), 3) + # 显示金币数量 + coins_text = TITLE_FONT.render(f"Coins: {player.coins}", True, GOLD_COLOR) + pygame.draw.rect(screen, (50, 50, 50), (5, 5, coins_text.get_width() + 10, coins_text.get_height() + 6)) + screen.blit(coins_text, (10, 8)) + + # 显示当前选中的种子类型 + seed_text = FONT.render(f"Selected: {selected_seed_type} (1-3 to change)", True, BLACK) + pygame.draw.rect(screen, TIP_BG_COLOR, (5, 35, seed_text.get_width() + 10, seed_text.get_height() + 4)) + screen.blit(seed_text, (10, 37)) + + # 显示商店界面 + if is_shop_open: + shop_x = 10 + shop_y = 50 + pygame.draw.rect(screen, SHOP_BG_COLOR, (shop_x, shop_y, SHOP_WIDTH, SHOP_HEIGHT)) + pygame.draw.rect(screen, BLACK, (shop_x, shop_y, SHOP_WIDTH, SHOP_HEIGHT), 2) + + title_text = TITLE_FONT.render("SHOP (Click to buy)", True, BLACK) + screen.blit(title_text, (shop_x + 10, shop_y + 5)) + + item_y = shop_y + 30 + for seed_name, seed_info in SHOP_ITEMS.items(): + price = seed_info["price"] + item_rect = pygame.Rect(shop_x + 5, item_y, SHOP_WIDTH - 10, 25) + + # 高亮悬停的物品 + if item_rect.collidepoint(mouse_x, mouse_y): + pygame.draw.rect(screen, (180, 210, 180), item_rect) + + color = (0, 128, 0) if player.coins >= price else (128, 128, 128) + item_text = FONT.render(f"{seed_name}: {price} coins", True, color) + screen.blit(item_text, (shop_x + 10, item_y + 3)) + item_y += 30 + + # 显示种子数量(使用预计算的物品计数) + item_y += 10 + count_title = FONT.render("Your seeds:", True, BLACK) + screen.blit(count_title, (shop_x + 10, item_y)) + item_y += 20 + for seed_name, seed_info in SHOP_ITEMS.items(): + seed_id = seed_info["item"].id + count = sum(data["count"] for (item_id, _), data in item_counts.items() if item_id == seed_id) + count_text = FONT.render(f" {seed_name}: {count}", True, BLACK) + screen.blit(count_text, (shop_x + 10, item_y)) + item_y += 18 + # 显示背包界面 if is_inventory_open: pygame.draw.rect(screen, INVENTORY_BG_COLOR, (WIDTH - INVENTORY_WIDTH - 10, 10, INVENTORY_WIDTH, INVENTORY_HEIGHT)) - inventory_y = 20 - mouse_x, mouse_y = pygame.mouse.get_pos() + pygame.draw.rect(screen, BLACK, (WIDTH - INVENTORY_WIDTH - 10, 10, INVENTORY_WIDTH, INVENTORY_HEIGHT), 2) + + title_text = TITLE_FONT.render("INVENTORY", True, BLACK) + screen.blit(title_text, (WIDTH - INVENTORY_WIDTH, 15)) + + inventory_y = 45 hovered_item = None - for item in player.inventory: + + # 使用预计算的物品计数 + for key, data in item_counts.items(): + item = data["item"] + count = data["count"] color = BLACK if item.type == ItemType.TOOL: color = (0, 0, 255) @@ -171,21 +326,26 @@ def draw_player(screen, x, y): color = (0, 128, 0) elif item.type == ItemType.PRODUCT: color = (139, 69, 19) - item_text = FONT.render(item.name, True, color) + + display_text = f"{item.name}" + if count > 1: + display_text += f" x{count}" + + item_text = FONT.render(display_text, True, color) item_rect = item_text.get_rect(topleft=(WIDTH - INVENTORY_WIDTH, inventory_y)) screen.blit(item_text, item_rect) if item_rect.collidepoint(mouse_x, mouse_y): hovered_item = item - inventory_y += 30 + inventory_y += 25 if hovered_item: desc_text = FONT.render(hovered_item.description, True, BLACK) - desc_rect = desc_text.get_rect(topleft=(WIDTH - INVENTORY_WIDTH, inventory_y)) + desc_rect = desc_text.get_rect(topleft=(WIDTH - INVENTORY_WIDTH, inventory_y + 10)) pygame.draw.rect(screen, TIP_BG_COLOR, desc_rect.inflate(10, 5)) screen.blit(desc_text, desc_rect) # 显示按键提示 - controls_text = FONT.render("W: Up, A: Left, S: Down, D: Right, SPACE: Interact, E: Inventory", True, BLACK) + controls_text = FONT.render("WASD: Move | SPACE: Interact | E: Inventory | B: Shop | 1-3: Select Seed", True, BLACK) screen.blit(controls_text, (WIDTH - controls_text.get_width() - 10, HEIGHT - 30)) # 更新显示