我正在C++年制作一个简单的2D自上而下的塞尔达风格游戏,但是我无法生成敌人职业的多个实例。每当我生成多个敌人时,只有第一个会记录任何碰撞检测;所有其他敌人似乎都只是渲染到屏幕上的视觉"幽灵"。当第一个敌人死去时,唯一可以死的敌人,然后所有其他"鬼魂"也随之消失。
我创建了一个敌人管理器类,它使用矢量列表来容纳活跃的敌人,检查每个敌人与传入的任何盒子的碰撞,并更新/渲染敌人。
class cEnemyMgr {
public:
std::vector<cEnemy*> mobList;
cEnemyMgr(){}
~cEnemyMgr(){
for (int i=0; i < mobList.size(); i++) {
mobList[i]->texture.Close();
//delete mobList[i];
}
}
void render() {
for (int i=0; i < mobList.size(); i++) {
mobList[i]->render();
}
}
void update(float dt){
for (int i=0; i < mobList.size(); i++) {
if ( mobList[i]->hp <= 0 ){
mobList[i]->die();
mobList.pop_back();
} else {
mobList[i]->update(dt);
}
}
}
void spawnMob(int x, int y){
cEnemy* pEnemy = new cMeleeEnemy();
pEnemy->init(x, y);
mobList.push_back(pEnemy);
}
cEnemy* checkCollisions(int x, int y, int wd, int ht){
for (int i=0; i < mobList.size(); i++) {
int left1, left2;
int right1, right2;
int top1, top2;
int bottom1, bottom2;
left1 = x;
right1 = x + wd;
top1 = y;
bottom1 = y + ht;
left2 = mobList[i]->pos.x;
right2 = mobList[i]->pos.x + 64;
top2 = mobList[i]->pos.y;
bottom2 = mobList[i]->pos.y + 64;
if ( bottom1 < top2 ) { return NULL; }
if ( top1 > bottom2 ) { return NULL; }
if ( left1 > right2 ) { return NULL; }
if ( right1 < left2 ) { return NULL; }
return mobList[i];
}
}
};
敌人职业本身是非常基本的;cEnemy是基础职业,cMeleeEnemy就是从它派生出来的。它具有标准的HP,dmg和移动变量,因此它可以在屏幕上爬行,尝试与玩家的头像发生碰撞,并对受到玩家的攻击做出反应。所有这些都很好用,只是当我尝试拥有多个敌人时,只有第一个生成的敌人可以正常工作,而其余的都是空壳,只是屏幕上的纹理。如果我是在同一块中快速显式调用生成 Mob,还是使用计时器动态间隔它们,都没有关系;结果是一样的。谁能指出我正确的方向?
--编辑--这是 for enemy.h 的代码:
#ifndef ENEMY_H
#define ENEMY_H
#include "texture.h"
#include "timer.h"
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
class cEnemy {
public:
int hp;
int dmg;
D3DXVECTOR2 pos;
D3DXVECTOR2 fwd;
D3DXVECTOR2 vel;
D3DCOLOR color;
int speed;
float rotate;
bool hitStun;
float hitTime;
CTexture texture;
virtual void init(int x, int y) = 0;
virtual void update(float dt) = 0;
virtual void die() = 0;
void render(){
texture.Blit(pos.x, pos.y, color, rotate);
}
void takeDamage(int dmg) {
if (hitStun == false){
extern CTimer Timer;
hitTime = Timer.GetElapsedTime();
hp -= dmg;
color = 0xFFFF0000;
hitStun = true;
}
}
void hitStunned(float duration) {
extern CTimer Timer;
float elapsedTime = Timer.GetElapsedTime();
if ( elapsedTime - hitTime > duration ){
color = 0xFFFFFFFF;
hitStun = false;
}
}
};
class cPlayer : public cEnemy {
public:
int facing;
void init(int x, int y);
void update(float dt);
void die();
};
class cMeleeEnemy : public cEnemy {
public:
cMeleeEnemy(){}
~cMeleeEnemy(){
texture.Close();
}
void init(int x, int y);
void update(float dt);
void die();
};
#endif
和敌人.cpp:
#include "enemy.h"
void cPlayer::update(float dt){
// Player Controls
if ( KEY_DOWN('W') ) {
pos.y -= speed * dt;
facing = 0;
} else if( KEY_DOWN('S') ) {
pos.y += speed * dt;
facing = 2;
}
if ( KEY_DOWN('A') ) {
pos.x -= speed * dt;
facing = 3;
} else if( KEY_DOWN('D') ) {
pos.x += speed * dt;
facing = 1;
}
// Hit Recovery
if ( hitStun == true ) {
hitStunned(1.0);
}
}
void cMeleeEnemy::update(float dt){
extern cPlayer player1;
extern int ScreenWd;
extern int ScreenHt;
D3DXVECTOR2 dir;
dir = player1.pos - pos;
D3DXVec2Normalize(&dir, &dir);
//fwd = (fwd * 0.2) + (dir * 0.8);
fwd = dir;
vel = vel + fwd * speed * dt;
pos = pos + vel * dt;
//keep em on screen
if ( pos.x < 0 ) { pos.x = 0; }
if ( pos.x > ScreenWd - 64 ) { pos.x = ScreenWd - 64; }
if ( pos.y < 0 ) { pos.y = 0; }
if ( pos.y > ScreenHt - 64 ) { pos.y = ScreenHt - 64; }
// Hit Recovery
if ( hitStun == true ) {
hitStunned(0.5);
}
}
void cMeleeEnemy::die(){
extern int score;
extern int numMobs;
score += 1;
numMobs -= 1;
//texture.Close();
}
void cPlayer::die(){
extern char gameState[256];
sprintf(gameState, "GAMEOVER");
}
void cMeleeEnemy::init(int x, int y){
hp = 6;
dmg = 1;
speed = 25;
fwd.x = 1;
fwd.y = 1;
vel.x = 0;
vel.y = 0;
pos.x = x;
pos.y = y;
rotate = 0.0;
color = 0xFFFFFFFF;
hitStun = false;
texture.Init("media/vader.bmp");
}
void cPlayer::init(int x, int y){
facing = 0;
hp = 10;
dmg = 2;
color = 0xFFFFFFFF;
speed = 100;
fwd.x = 1;
fwd.y = 1;
vel.x = 0;
vel.y = 0;
pos.x = x;
pos.y = y;
rotate = 0.0;
hitStun = false;
texture.Init("media/ben.bmp");
}
如你所知,我还没有那么有经验。这是我第一个自己的学校项目。我只想说,我对应该在哪里关闭纹理和删除对象有点困惑。谢谢你们的时间,伙计们!
在 checkCollisions
函数中,每次循环后返回 NULL
或对象在敌方向量的第一个索引的位置。
因此,当第一个鬼魂未命中时,checkCollisions
函数将返回NULL
,而不是遍历向量中的每个后续重影。
若要解决此问题,请将checkCollisions
函数更改为以下内容:
cEnemy* checkCollisions(int x, int y, int wd, int ht){
for (int i=0; i < mobList.size(); i++) {
int left1, left2;
int right1, right2;
int top1, top2;
int bottom1, bottom2;
left1 = x;
right1 = x + wd;
top1 = y;
bottom1 = y + ht;
left2 = mobList[i]->pos.x;
right2 = mobList[i]->pos.x + 64;
top2 = mobList[i]->pos.y;
bottom2 = mobList[i]->pos.y + 64;
if ( bottom1 < top2 ) { continue; }
if ( top1 > bottom2 ) { continue; }
if ( left1 > right2 ) { continue; }
if ( right1 < left2 ) { continue; }
return mobList[i];
}
return NULL;
}
希望这有帮助!
编辑:
请注意,当您从列表中删除敌人时,如果它的HP为0或更低,则使用mobList.pop_back()
,但这会从向量中删除最后一个元素,您应该使用类似以下内容的内容从列表中删除您想要的敌人:
std::remove_if( mobList.begin(), mobList.end() []( cEnemy* pEnemy )->bool
{
if( pEnemy->hp <= 0 )
{
pEnemy->die();
return true;
}
else
{
pEnemy->update();
return false;
}
});
问题解决了!我用mobList.erase()
方法替换了pop_back()
。
void update(float dt){
for (int i=0; i < mobList.size(); i++) {
if ( mobList[i]->hp <= 0 ){
mobList[i]->die();
mobList.erase(mobList.begin() + i);
} else {
mobList[i]->update(dt);
}
}
}
谢谢大家的帮助,非常感谢!