Skip to main content

pvz_rust_wsll_lib/ui/
input_handler.rs

1//! # 输入处理模块 (`input_handler`)
2//!
3//! 负责处理游戏中的用户输入,主要是鼠标点击事件。
4//! 它管理着阳光的收集、商店中植物卡片的选择、以及在网格上放置植物的逻辑。
5
6use crate::ui::grid::{Grid, GRID_START_X, GRID_START_Y, GRID_CELL_HEIGHT, GRID_CELL_WIDTH, GRID_WIDTH, GRID_HEIGHT};
7use crate::plants::{Plant, PlantType};
8use crate::ui::shop::{Shop, SHOP_START_Y, CARD_HEIGHT};
9use crate::ui::shovel::Shovel;
10use crate::entities::sun::Sun;
11use ggez::input::mouse::MouseButton;
12
13/// `InputHandler` 结构体。
14///
15/// 这是一个单元结构体,目前仅包含静态方法用于处理输入事件。
16/// 它不持有任何状态,而是直接操作传入的游戏状态变量。
17pub struct InputHandler;
18
19impl InputHandler {
20    /// 处理鼠标按下事件。
21    ///
22    /// 根据鼠标点击的位置和按键,执行相应的游戏操作,例如:
23    /// - 点击阳光:收集阳光并增加阳光计数。
24    /// - 点击商店卡片:如果阳光充足,则选择相应的植物类型以备放置。
25    /// - 在网格上点击(已选择植物时):如果单元格未被占据且阳光充足,则放置植物。
26    /// - 右键点击:取消当前选择的植物。
27    ///
28    /// # Arguments
29    ///
30    /// * `button` - 按下的鼠标按键 (`ggez::input::mouse::MouseButton`)。
31    /// * `x` - 鼠标点击的屏幕X坐标。
32    /// * `y` - 鼠标点击的屏幕Y坐标。
33    /// * `shop` - 可变的 `Shop` 引用,用于处理商店交互和更新选择状态。
34    /// * `suns` - 可变的阳光实体列表 (`Vec<Sun>`),用于检查点击和移除被收集的阳光。
35    /// * `grid` - 可变的 `Grid` 引用,用于检查网格位置和标记占据状态。
36    /// * `plants` - 可变的植物实体列表 (`Vec<Plant>`),用于添加新放置的植物。
37    /// * `selected_plant` - 可变的 `Option<PlantType>`,表示当前从商店选择的植物类型。
38    /// * `sun_count` - 可变的阳光计数器 (`i32`)。
39    /// * `game_over` - 一个布尔值,指示游戏是否已结束。如果为 `true`,则不处理输入。
40    ///
41    /// # Returns
42    ///
43    /// 如果输入事件导致了游戏状态的改变(例如收集了阳光、选择了植物、放置了植物),
44    /// 则返回 `true`,否则返回 `false`。
45    pub fn handle_mouse_down(
46        button: MouseButton, 
47        x: f32, 
48        y: f32,
49        shop: &mut Shop,
50        suns: &mut Vec<Sun>,
51        grid: &mut Grid,
52        plants: &mut Vec<Plant>,
53        selected_plant: &mut Option<PlantType>,
54        sun_count: &mut i32,
55        game_over: bool,
56        shovel: &mut Shovel
57    ) -> bool {
58        if game_over {
59            return false;
60        }
61
62        if button == MouseButton::Left {
63            // 检查是否点击了铲子
64            if shovel.is_clicked(x, y) {
65                shovel.is_dragging = true;
66                // 取消选中植物
67                *selected_plant = None;
68                shop.selected_plant = None;
69                return true;
70            }
71            
72            // 如果铲子正在拖动中,检查是否可以铲除植物
73            if shovel.is_dragging {
74                if shovel.dig(x, y, grid, plants) {
75                    // 铲除成功,重置铲子状态
76                    shovel.reset();
77                    return true;
78                }
79                return false; // 点击在网格外或没有植物,铲子动作不生效
80            }
81            
82            // 检查是否点击了阳光
83            let initial_sun_count = *sun_count;
84            suns.retain(|sun| {
85                if sun.contains_point(x, y) {
86                    *sun_count += 25;
87                    false // Remove the sun
88                } else {
89                    true // Keep the sun
90                }
91            });
92            if *sun_count > initial_sun_count { 
93                return true; // 如果点击了阳光,不处理其他点击
94            }
95
96            // 处理商店卡片点击 (优先于放置植物)
97            if y < SHOP_START_Y + CARD_HEIGHT + 20.0 { // 商店区域的大致检查
98                if let Some(plant_type) = shop.handle_click(x, y, *sun_count) {
99                    *selected_plant = Some(plant_type);
100                    return true; // 如果点击了卡片,停止处理
101                }
102            }
103
104            // 处理植物放置逻辑
105            if selected_plant.is_some() {
106                if x >= GRID_START_X && x <= GRID_START_X + GRID_CELL_WIDTH * GRID_WIDTH as f32 &&
107                   y >= GRID_START_Y && y <= GRID_START_Y + GRID_CELL_HEIGHT * GRID_HEIGHT as f32 {
108                   if Self::place_plant(x, y, grid, plants, selected_plant, sun_count, shop) {
109                       // 放置成功,清除选择状态
110                       shop.selected_plant = None;
111                       return true;
112                   } else {
113                       // 如果放置失败,取消选择
114                       *selected_plant = None;
115                       shop.selected_plant = None;
116                       return false;
117                   }
118               } else {
119                    // 点击在网格外,取消选择
120                    *selected_plant = None;
121                    shop.selected_plant = None;
122                    return false;
123               }
124            }
125        } else if button == MouseButton::Right {
126            // 右键取消选择
127            *selected_plant = None;
128            shop.selected_plant = None;
129            return true;
130        }
131        
132        false
133    }
134
135    /// 处理在网格上放置植物的逻辑。
136    ///
137    /// 当玩家在选择了植物类型后点击网格时,此方法被调用。
138    /// 它会检查目标网格单元格是否有效、是否已被占据,以及玩家是否有足够的阳光。
139    /// 如果所有条件满足,则创建一个新的植物实例,将其添加到游戏世界,
140    /// 更新阳光数量,标记网格单元格为已占据,并清除当前的植物选择状态。
141    ///
142    /// # Arguments
143    ///
144    /// * `x` - 鼠标点击的屏幕X坐标。
145    /// * `y` - 鼠标点击的屏幕Y坐标。
146    /// * `grid` - 可变的 `Grid` 引用。
147    /// * `plants` - 可变的植物实体列表 (`Vec<Plant>`)。
148    /// * `selected_plant` - 可变的 `Option<PlantType>`,包含待放置的植物类型。
149    /// * `sun_count` - 可变的阳光计数器 (`i32`)。
150    ///
151    /// # Returns
152    ///
153    /// 如果植物成功放置,则返回 `true`。否则(例如,位置无效、已被占据、阳光不足),
154    /// 返回 `false`。
155    fn place_plant(
156        x: f32, 
157        y: f32, 
158        grid: &mut Grid,
159        plants: &mut Vec<Plant>,
160        selected_plant: &mut Option<PlantType>,
161        sun_count: &mut i32,
162        shop: &mut Shop
163    ) -> bool {
164        if let Some(plant_type) = selected_plant {
165            if let Some((grid_x, grid_y)) = grid.get_grid_position(x, y) {
166                // 检查是否已有植物
167                if !grid.is_occupied(grid_x, grid_y) && *sun_count >= plant_type.cost() {
168                    let plant = Plant::new(*plant_type, grid_x, grid_y);
169                    plants.push(plant);
170                    *sun_count -= plant_type.cost();
171                    grid.occupy(grid_x, grid_y);
172
173                    // 在植物成功放置后,才触发卡片冷却
174                    shop.trigger_card_cooldown(*plant_type);
175
176                    // 放置植物后取消选择状态
177                    *selected_plant = None;
178
179                    return true;
180                }
181            }
182        }
183        false
184    }
185}