11-角色技能设计和子弹基类实现
【从零开始的C++游戏开发】玩家子弹发射和角色技能实现 | EasyX制作植物明星大乱斗_哔哩哔哩_bilibili
1 攻击方式
游戏中两种角色都有普通攻击和特殊攻击两种方式。
- 婉豆射手的普通攻击,可以朝着自己面朝的地方发射子弹,攻击到别人可以积攒能量,能量值蓄满后可以释放特殊攻击技能,快速喷射出一连串的子弹。
- 龙日葵的普通哦攻击是向着自己面朝的斜上方抛射日光炸弹,日光炸弹会受重力影响呈不太容易瞄准的曲线运动,但它碰触到敌人之后造成的伤害和积攒的能量值更高。龙日葵也可以在能量攒满后释放技能。在对手的头顶处召唤巨大的日光炸弹,造成更大的上海范围和更高额的能量回馈。
我们可以对游戏中所有的抛射物进行大一统,它们只是动画贴图和范围伤害等数值上的差异。

所以我们可以抽象一个Bullet基类,豌豆子弹、日光炸弹和超大型的日光炸弹都继承自这个基类。
2 Bullet基类
我们新建bullet.h, pea_bullet.h, sun_bullet.h, sun_bullet_ex.h
class Bullet
{
public:
Bullet() = default;
~Bullet() = default;
protected:
Vector2 size; // 子弹尺寸
Vector2 position; // 子弹位置
Vector2 velocity; // 子弹速度
int damage = 10;
};
接下来我们思考,如何实现子弹在碰撞发生后的消失逻辑
这其实与之前在动画类设计时提到的玩家死亡消失的案例很像。
子弹在碰到敌人后,就应该被立即设置为无效状态。从而防止在后续的帧更新时与敌人发生多次碰撞。但是我们需要播放子弹的消失动画,所以不能立即将子弹对象从场景中删除掉。
也就是说,场景中每个子弹都有三种状态:正常状态、无效状态、可以被删除的状态。
- 在正常状态下,我们播放子弹移动的动画,并在每一帧中检测它与玩家的碰撞。当它与目标玩家发生碰撞后,进入到无效状态。
- 在无效状态下,我们不再对子弹的碰撞进行检测,此时播放子弹的销毁动画,在动画播放结束后,子弹进入到可以被删除的状态。从而在场景更新时被移除掉。
所以我们定义valid和can_remove两个标值来区分这三种状态。
我们还需要添加一个callback回调函数
我们希望子弹的碰撞目标是明确且唯一的。也就是说1P玩家发射的子弹只会对2P玩家造成伤害,防止被自己的子弹打中。所以需要一个变量来明确目标玩家ID
int damage = 10; // 子弹伤害
bool valid = true; // 子弹是否有效
bool can_remove = false; // 子弹是否可以被移除
std::function<void()> callback; //子弹碰撞回调函数
PlayerID target_id = PlayerID::P1; // 子弹碰撞目标玩家ID
之后我们定义用来设置和获取子弹伤害的方法。这在初始化和检测子弹碰撞的要用到。
void set_damage(int val)
{
damage = val;
}
int get_damage()
{
return damage;
}
检测碰撞还需要子弹的位置和尺寸,以及碰撞对象的设置。
void set_position(float x, float y)
{
position.x = x, position.y = y;
}
const Vector2& get_position() const
{
return position;
}
const Vector2& get_size() const
{
return size;
}
void set_velocity(float x, float y)
{
velocity.x = x, velocity.y = y;
}
void set_valid(bool flag)
{
valid = flag;
}
bool get_valid() const
{
return valid;
}
void set_callback(std::function<void()> callback)
{
this->callback = callback;
}
最后我们定义一个check_can_remove()方法
bool check_can_remove() const
{
return can_remove;
}
之后我们编写在子类中可以覆盖的虚函数
on_collide()将会在子弹发生碰撞后执行
virtual void on_collide()
{
if (callback)
callback();
}
除此之外,我们还需要一个用于检测碰撞的函数
virtual bool check_collision(const Vector2& position, const Vector2& size)
{
// 检测子弹中心点是否在碰撞体中
return this->position.x + this->size.x / 2 >= position.x
&& this->position.x + this->size.x / 2 <= position.x + size.x
&& this->position.y + this->size.y / 2 >= position.y
&& this->position.y + this->size.y / 2 <= position.y + size.y;
}
这里的判断依据是判断子弹对象矩形的中心位置坐标

最后,我们添加两个老朋友:on_updata()和on_draw()方法
virtual void on_update(int delta) { }
virtual void on_draw(const Camera& camera) const { }
这时,我们可以想到一个问题,如果子弹没有碰撞到碰撞对象,不就永远不会被删除了吗?
如果我们把子弹销毁动画播放完的时刻当作can_remove变量设置为true的时刻,那么没有激活销毁动画的子弹对象,就会永不休止地在游戏世界中继续存在下去,造成内存泄漏。
所以,我们还需要对已经飞到屏幕外不可见地子弹对象进行销毁。
对于检测子弹是否飞出屏幕,这部分逻辑同样是所有继承自Bullet的子类通用的内容。
所以我们在protected部分定义下面这个方法,在内部检测子弹的矩形边界是否已经位于屏幕矩形之外
protected:
bool check_if_exceeds_screen()
{
return (position.x + size.x <= 0 || position.x >= getwidth()
|| position.y + size.y <=0 || position.y >= getheight());
}
【从零开始的C++游戏开发】角色技能设计和子弹基类实现 | EasyX制作植物明星大乱斗_哔哩哔哩_bilibili
评论
如果你已登录 GitHub,就可以直接在这里评论。