C++主机游戏:修改允许不同敌人统计数据的战斗功能



我正在使用2D阵列网格创建一个游戏,玩家控制一个角色,该角色周围有敌人(具有不同的统计数据/值)。当角色与敌人在同一网格上时,程序会启动一个战斗功能,该功能包含大量代码,用于指示战斗的工作方式。然而,由于我希望敌人有不同的统计数据,我一直在使用结构来组织角色/敌人的统计数据。

例如:

struct Character { //character stats
int hp;
int atk;
int warpstk;
int heal;
int mana;
int money;
};
struct Enemy { //enemy stats
int mhp;
int matk;
int mwarpstk;
int mheal;
};
Character Player =
{
50, //Player.hp
10, //Player.atk
18, //Player.warpstk
12,  //Player.heal
30, //Player.mana
50, //Player.money
};
Enemy Goblin =
{
40, //Goblin.mhp
10, //Goblin.matk
12, //Goblin.mwarpstk
8, //Goblin.heal
};
Enemy Dragon = 
{
100, //Goblin.mhp
50, //Goblin.matk
70, //Goblin.mwarpstk
15, //Goblin.heal
};
//I want to modify the follow function so that it would change who the player is fighting depending on the grid placement.
void battleSequence(){
while (Player.hp > 0 && Goblin.hp > 0)
{
//randon battle code
}
int main()
{
if (player.x == goblin.x && player.y == goblin.y)
{
battleSequence();
if (Goblin.mhp < 1)
{
goblin.x = 25; //gave the goblin coordinates an out of bounds coordinate to leave loop
goblin.y = 25; //gave the goblin coordinates an out of bounds coordinate to leave loop
system("cls");
}
}
//Probably gonna do the same conditional for the dragon, but it entirely depends on the battle function
}

然而,如果我的角色和地精在同一个网格上时调用战斗序列函数,我该怎么做,这样就没有必要为每个敌人创建战斗函数了?我如何才能避免重复的代码躺在我的程序中?因为到目前为止,我有一个函数只使用两个变量来计算玩家数据:player和Goblin,由于函数的长度(此处未显示),我不想为player和Dragon创建另一个函数。

换言之,我如何能够创建一个每个敌人都可以访问的通用战斗序列功能,但我只会与";龙;如果我在他们的网格上;哥布林;如果我在他们的网格上?

(如果你需要更多的清晰度,请问,因为这很难理解我的问题。问你是否需要我当前进度的github存储库)

你能简单地制作一个名为characterValues或其他东西的基础结构吗?它包含X和Y位置、hp、att、Def、heal以及所有角色中常见的其他内容,或者至少是执行战斗所需的内容。然后玩家结构包含一个这样的实例,以及敌人结构。

当你需要计算战斗时,你只需要将player.charterValues与goblin.charaterValues或dragon.charaterValues进行比较。如果两个角色的X和Y值相同,则使用他们的健康和攻击数据执行战斗。额外的信息可以单独存储到这个结构中,比如玩家的魔法值,或者敌人的战利品数据,等等。

如果你用c++写这篇文章,你应该看看并阅读关于继承的知识,如果你还没有,这可能会带来一些额外的技巧。但是使用简单的c,它可以使用嵌套结构来完成。祝你比赛顺利。

正如在另一个答案中已经指出的,你必须找出所有实体(玩家、地精和龙)的共同点,并创建一个"基本结构";,例如由所有公共属性组成的struct CommonEntity

如果你想让战斗序列严格地是玩家对怪物(或"敌人"),那么玩家属性就没有必要成为struct CommonEntity的一部分。在这种情况下,只有所有常见的怪物属性都在struct CommonEntity中就足够了,并且可以将玩家属性分开。

然而,如果怪物对怪物的战斗也是可能的,并且如果你想要相同的功能来处理这两种类型的战斗,那么最好在struct CommonEntity中包含所有玩家属性,这样战斗功能就可以以完全相同的方式对待玩家和怪物,甚至不必知道战斗参与者是玩家还是怪物。

struct CommonEntity可能看起来像这样:

struct CommonEntity
{
//map coordinates
int x;
int y;
//combat stats
int hp;
int atk;
int warpstk;
int heal;
};

所有这些属性都由玩家和所有怪物共享。如果mana也可以在战斗中使用,您可能需要将其添加到struct中,并将其设置为零(怪物)和非零(玩家)。

然而,您可能希望能够存储附加信息,并且附加信息的类型应该取决于实体是玩家、地精还是龙。例如,如果是玩家,您希望能够存储玩家拥有的money的数量。因此,您可能想要使用union,如下所示:

struct AdditionalPlayerData
{
int money;
};
struct AdditionalGoblinData
{
//empty for now
};
struct AdditionalDragonData
{
//empty for now
};
struct Entity
{
//type of entity
enum class EntityType { player, goblin, dragon } type;
CommonEntity common;
union
{
AdditionalPlayerData player;
AdditionalGoblinData goblin;
AdditionalDragonData dragon;
};
};

现在,您的struct能够存储一般信息和特定于实体类型的附加信息。您总是可以通过查看type成员来查看哪个union字段是有效的。

评估战斗的函数可能不关心额外的信息,它只需要访问common成员中包含的信息。因此,您将不再需要不同的函数来处理不同的实体类型,因为该函数可能根本不关心实体的类型。如果它出于某种原因确实关心,它仍然可以查找type成员并访问union中相应的附加信息。


解决问题的更面向对象的方法如下:

您可以通过为游戏中存在的所有实体(例如class Playerclass Enemy)定义一个公共基类class Entity来使用C++继承。此外,您可以将class Enemy本身作为class Goblinclass Dragon等的基类。然后,您可以创建一个类型为class Entity *的数组(C样式或std::array)或std::vector,以引用游戏中存在的所有实体。

检查遭遇和处理实际战斗的函数只需要处理类型为class Entity的对象,而不关心这些对象实际上是什么派生类。如果这些函数确实需要确定实体的类型,它们可以调用基类上的虚拟函数,然后该函数将自动调用派生类中的相应函数。在我下面的例子中,处理遭遇战和战斗的函数将调用class Entity的虚拟函数GetEntityTypeName,以获得实体类型(即"玩家"、"妖精"或"龙")的名称,它们使用该名称将关于遭遇战/战斗的消息打印到屏幕上。

#include <iostream>
#include <vector>
#include <memory>
#include <random>
class Entity
{
protected:
Entity() = delete;
Entity( int x, int y, int hp, int atk, int warpstk, int heal )
: x(x), y(y), hp(hp), atk(atk), warpstk(warpstk), heal(heal)
{}
public:
int GetX() { return x; }
int GetY() { return y; }
int GetHP() { return hp; }
int GetAttack() { return atk; }
int GetWarpStk() { return warpstk; }
int GetHeal() { return heal; }
void SetX( int newval ) { x = newval; }
void SetY( int newval ) { y = newval; }
void SetHP( int newval ) { hp = newval; }
//the following virtual function will call the derived class's
//function to retrieve the name of the entity type as a string
virtual const char* GetEntityTypeName() = 0;
protected:
//entity's coordinates
int x;
int y;
//entity's combat stats
int hp;
int atk;
int warpstk;
int heal;
};
//class Player inherits from class Entity
class Player : public Entity
{
public:
Player() = delete;
Player( int x, int y ) : Entity(x,y,50,10,18,12), mana(30), money(50) {}
//define additional functions
int GetMana() { return mana; }
int GetMoney() { return money; }
void SetMana( int newval ) { mana = newval; }
void SetMoney( int newval ) { money = newval; }
virtual const char* GetEntityTypeName() { return "player"; }
protected:
//define additional stats that are specific to player
int mana;
int money;
};
class Enemy : public Entity
{
protected:
Enemy() = delete;
Enemy( int x, int y, int hp, int atk, int warpstk, int heal )
: Entity(x, y, hp, atk, warpstk, heal)
{}
public:
//define additional functions here that are specific
//to all enemies, but not specific to certain enemy
//types (goblin, dragon, etc.)
//currently no additional functions
protected:
//define additional stats here that are specific to
//all enemies, but not specific to certain enemy types
//currently no additional stats
};
class Goblin : public Enemy
{
public:
Goblin() = delete;
Goblin( int x, int y ) : Enemy(x,y,40,10,12,8) {}
virtual const char* GetEntityTypeName() { return "goblin"; }
private:
//define additional stats here that are specific to goblins
//currently no additional stats
};
class Dragon : public Enemy
{
public:
Dragon() = delete;
Dragon( int x, int y ) : Enemy(x,y,100,50,70,15) {}
virtual const char* GetEntityTypeName() { return "dragon"; }
private:
//define additional stats here that are specific to dragons
//currently no additional stats
};
int MakeRandom( int min, int max, std::mt19937 &rng )
{
std::uniform_int_distribution<int> dis( min, max );
return dis( rng );
}
void PerformAttack( Entity &attacker, Entity &defender, std::mt19937 &rng )
{
int attack_strength  = MakeRandom( 0, attacker.GetAttack(), rng );
std::cout << " " << attacker.GetEntityTypeName() << " hits " <<
defender.GetEntityTypeName() << " for " << attack_strength << " pointsn";
defender.SetHP( defender.GetHP() - attack_strength );
}
void PerformBattleRound( Entity &e1, Entity &e2, std::mt19937 &rng )
{
Entity *first, *second;
//randomize which entity attacks first
if ( MakeRandom( 0, 1, rng ) == 0 )
{
first = &e1;
second = &e2;
}
else
{
first = &e2;
second = &e1;
}
//perform first attack
PerformAttack( *first, *second, rng );
if ( second->GetHP() <= 0 )
{
std::cout << "  " << second->GetEntityTypeName() << " diesnn";
return;
}
else
{
std::cout << "  " << second->GetEntityTypeName() << " has " <<
second->GetHP() << " HP remainingn";
}
//perform second (counter) attack
PerformAttack( *second, *first, rng );
if ( first->GetHP() <= 0 )
{
std::cout << "  " << first->GetEntityTypeName() << " diesnn";
return;
}
else
{
std::cout << "  " << first->GetEntityTypeName() << " has " << 
first->GetHP() << " HP remainingn";
}
std::cout << "n";
}
void ProcessEncounters( std::vector<std::unique_ptr<Entity>> &entity_list, std::mt19937 &rng )
{
//this function does not only check encounters between player
//and enemies, but also enemies and enemies
int size = entity_list.size();
for ( int i = 0; i < size; i++ )
{
for ( int j = i + 1; j < size; j++ )
{
//check if both entities have the same coordinates
if (
entity_list[i]->GetX() == entity_list[j]->GetX() &&
entity_list[i]->GetY() == entity_list[j]->GetY()
)
{
//print information about encounter
std::cout <<
entity_list[i]->GetEntityTypeName() << " encounters " <<
entity_list[j]->GetEntityTypeName() << " at (" <<
entity_list[i]->GetX() << "," <<
entity_list[i]->GetY() << ")n";
PerformBattleRound( *entity_list[i], *entity_list[j], rng );
}
}
}
}
int main()
{
//create a vector to contain all entities
std::vector<std::unique_ptr<Entity>> entity_list;
//seed the random number generator
std::random_device rd;
std::mt19937 rng( rd() );
//create player at coordinates (5,7)
entity_list.push_back( std::make_unique<Player>( 5, 7 ) );
//create goblin at coordinates (2,3)
entity_list.push_back( std::make_unique<Goblin>( 2, 3 ) );
//create goblin at coordinates (5,7)
entity_list.push_back( std::make_unique<Goblin>( 5, 7 ) );
//create goblin at coordinates (8,9)
entity_list.push_back( std::make_unique<Goblin>( 8, 9 ) );
//create dragon at coordinates (8,9)
entity_list.push_back( std::make_unique<Dragon>( 8, 9 ) );
ProcessEncounters( entity_list, rng );
}
}

上面的代码将打印以下输出:

player encounters goblin at (5,7)
goblin hits player for 1 points
player has 49 HP remaining
player hits goblin for 10 points
goblin has 30 HP remaining
goblin encounters dragon at (8,9)
goblin hits dragon for 2 points
dragon has 98 HP remaining
dragon hits goblin for 42 points
goblin dies

由于随机数生成器,每次运行程序时输出都会有所不同。

正如你所看到的,我不需要一个额外的函数来处理每种类型的实体,因为处理遭遇战/战斗的函数甚至不关心他们正在处理的实体类型。

请注意,如果游戏中有10000个实体,并且敌人也可以相遇(不仅仅是玩家),那么将需要近500万(!)次比较才能将每个实体的坐标与其他实体进行比较,因为时间复杂性为O(n2)。因此,如果您有那么多实体,您可能需要考虑将地图划分为一个网格,例如100*100个瓦片,并保留该网格的每个瓦片中存在的所有实体的单独列表。这样,假设有10000个实体,你只需要将每个实体平均与另一个实体的坐标进行比较,而不是将所有其他10000个实体进行比较。但只要游戏中的实体少于100个,这就应该不是问题。此外,如果敌人不能相遇,那么这也不是问题,因为时间复杂性将仅为O(n)而不是O(n2)。

最新更新