植物明星大乱斗血包道具

植物明星大乱斗血包道具制作,简单的bullet类延展设计
菜鸟的游戏学习实践记录O.O

先来看看血包的游戏内展示

ICON

其中那个红色的小球,就是血包,吃了之后可以加30hp。

具体思路及代码设计

1.私有属性与接口设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
\\ hp_ball.h

class HpBall
{
public:
HpBall() = default;
~HpBall() = default;

void on_draw(const Camera& camera);
void on_update(int delta);
bool check_if_exceeds_screen(); // 判断血包是否处于屏幕外
void on_collision(Player*& player); // 血包与角色碰撞后的加血逻辑
bool check_collision(Player*& player); // 血包与角色是否碰撞
bool check_can_remove() const; // 血包是否可被移除

private:
Vector2 position;
Vector2 size;
Vector2 velocity;

int add_hp = 30;
bool can_remove = false;

PlayerID target_id = PlayerID::P1; // 与血包发生碰撞的角色ID
Timer timer_hp_ball_remove; // 血包自动消失的计时器
};

由于我们需要知道与血包碰撞的角色ID,同时还要对该角色的hp进行修改操作,所以不得不来到player类中增加接口:

1
2
3
4
5
// player.h

public:
void add_hp(int add) { hp = min(100, hp + add); }
PlayerID& get_playerID() { return id; }

2.血包的生命周期管理

hp_ball类与bullet类十分相似,因此我们可以仿照bullet的生命周期,设计一个全局vector:

1
2
3
// main.cpp

std::vector<HpBall*> hp_ball_list;

但是,与bullet类不同的是,hp_ball类的生成并不归player管,而应该由更上层的模块进行管理,为避免过多的全局变量,生成与删除hp_ball的工作我们都交给game_scene

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// game_scene.h

private:
Timer timer_create_hp_ball; // 用于定时循环重复生成新的血包
bool can_hp_ball_create = true; // 血包是否可被生成

public:
// on_enter中
timer_create_hp_ball.set_wait_time(4000); // 生成血包的间隔时间
timer_create_hp_ball.set_one_shot(false); // 血包会重复生成
timer_create_hp_ball.set_callback([&]()
{
can_hp_ball_create = true;
}); // 血包消失后,立即设置为可生成状态

// on_update中
timer_create_hp_ball.on_update(delta);
if (can_hp_ball_create)
{
HpBall* hp_ball = new HpBall();
hp_ball_list.push_back(hp_ball);
can_hp_ball_create = false;
}

// 与bullet相同
hp_ball_list.erase(std::remove_if(
hp_ball_list.begin(), hp_ball_list.end(),
[](const HpBall* hp_ball)
{
bool deletable = hp_ball->check_can_remove();
if (deletable) delete hp_ball;
return deletable;
}),
hp_ball_list.end());

for (HpBall* hp_ball : hp_ball_list)
hp_ball->on_update(delta);

// on_draw中
for (HpBall* hp_ball : hp_ball_list)
hp_ball->on_draw(camera);

3.随机生成血包位置

众所那个周知,使用

1
2
srand((int)time(0));
rand();

就可以生成每次都不一样的随机数,但如果粗暴地将这一套放入循环内,或HpBall的构造函数中,就会惊讶地发现,每次小球生成的位置居然是一样的,虽然每次运行后小球的位置会有所改变,但小球在整局游戏中,都只会在这个位置生成了,非常boring。(原理应该是随机数种子在生成时被固定了?我太菜了还没有完全搞懂sos)

总之,解决方法是这样的:

1
2
3
// main.cpp

srand((int)time(0)); // 将这句放入main()语句中

然后就可以在HpBall中使用构造函数随机生成x轴位置了,每次实例化HpBall都会拥有一个随机的x坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// hp_ball.h

HpBall()
{
size.x = 70, size.y = 70;
position.x = rand() % getwidth();
position.y = 35;
velocity.y = 0.3f, velocity.x = 0;

timer_hp_ball_remove.set_wait_time(5000);
timer_hp_ball_remove.set_one_shot(true);
timer_hp_ball_remove.set_callback([&]()
{
can_remove = true;
});
}

完整的HpBall代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// hp_ball.h

#ifndef _HP_BALL_H_
#define _HP_BALL_H_

#include "easyx.h"
#include "vector2.h"
#include "player_id.h"
#include "player.h"

#include <time.h>
#include <stdlib.h>
#include <functional>
#include <vector>

extern Player* player_1;
extern Player* player_2;

class HpBall
{
public:
HpBall()
{
size.x = 70, size.y = 70;
position.x = rand() % getwidth();
position.y = 35;
velocity.y = 0.3f, velocity.x = 0;

timer_hp_ball_remove.set_wait_time(5000);
timer_hp_ball_remove.set_one_shot(true);
timer_hp_ball_remove.set_callback([&]()
{
can_remove = true;
});
}

~HpBall() = default;

void on_draw(const Camera& camera)
{
setlinecolor(RGB(255, 155, 50)); // 绘制血包边框
setfillcolor(RGB(200, 75, 10)); // 绘制血包颜色
fillcircle(position.x, position.y, 35);
}

void on_update(int delta)
{
position += velocity * (float)delta;

if (check_if_exceeds_screen())
can_remove = true;

timer_hp_ball_remove.on_update(delta);

on_collision(player_1);
on_collision(player_2);
}

bool check_if_exceeds_screen() // 判断血包是否处于屏幕外
{
return (position.x + size.x <= 0 || position.x >= getwidth()
|| position.y + size.y <= 0 || position.y >= getheight());
}

void on_collision(Player*& player)
{
if (check_collision(player))
{
target_id = player->get_playerID();
if (target_id == PlayerID::P1)
player_1->add_hp(add_hp);
else
player_2->add_hp(add_hp);

can_remove = true;
}
}

bool check_collision(Player*& player)
{
return this->position.x + this->size.x / 2 >= player->get_position().x
&& this->position.x + this->size.x / 2 <= player->get_position().x + player->get_size().x
&& this->position.y + this->size.y / 2 >= player->get_position().y
&& this->position.y + this->size.y / 2 <= player->get_position().y + player->get_size().y;
}

bool check_can_remove() const
{
return can_remove;
}

private:
Vector2 position;
Vector2 size;
Vector2 velocity;

int add_hp = 30;
bool can_remove = false;

PlayerID target_id = PlayerID::P1;
Timer timer_hp_ball_remove;
};

#endif // !_HP_BALL_H_

除了hp_ball.h外,game_scenemainplayer类都有改动,以及相互extern的操作,所以以上并非全部的实现代码。

碎碎念

其实HpBall应该完全可以通过继承Bullet类来实现,修改一下Bullet类便可以更完美地实现解耦,日后我也会继续进行改进。

其实我是一名经济学大二的学生哈哈哈哈哈,但制作游戏真的太好玩了,也十分谢谢大V老师启发并满足了我学习的欲望,这也是我第一篇学习记录,希望可以成为一个美好的开端吧。
Best Wishes To Everyone

最新消息

大V老师直播做了个我的高级版,私密马赛,发晚了:no_mouth:

作者

VoidGameSpace

发布于

2024-06-09

更新于

2024-10-18

许可协议

评论