Здесь есть кое-что, что будет немного длинно, но я чувствую, что стоит потратить время и силы на его прочтение.Итак, я начну с некоторых основных концепций и перейду к чуть более сложным аспектам.Я начну с разработки кода, который относится к структурам данных и их байтовым выравниваниям, использования указателей и интеллектуальных указателей, а затем перейду к контейнерам и связанным с ними алгоритмам.
Лично у меня не было бы группы отдельных floats
или doubles
для представления каждой координатной позиции и скорости координат, ускорения и т. Д. Было бы проще создать простой класс или структуру, котораяпредставляет точки и векторы в зависимости от того, как вы их используете.В вашей конкретной ситуации вы работаете только в 2D-пространстве, а не в 3D-пространстве, но концепция все еще применяется;единственное отличие состоит в том, что математические операции, которые могут быть выполнены над ними, например, двухмерный вектор может иметь точечное произведение, но, как правило, не имеет четко определенного перекрестного произведения, хотя оно существует, в то время как в 3D как точечное, так и перекрестное произведениесуществует, и перекрестный продукт в 3D хорошо определен.
Существует много других полезных функций, связанных с векторами, таких как определение длины или величины, определение направления вектора, получение единичного вектора, проверка, является ли это вектором 0, и всеосновные арифметические и логические операции, которые могут быть с ними выполнены: +
, -
, *
, \
как в унарном, так и в двоичном виде.И для *
это не следует путать с перекрестными и точечными произведениями;обычно это будет взятие scalar
и умножение его на этот вектор, и для ваших логических сравнений ==
, !=
, <
, <=
, >=
... Есть также несколько других общихфункции, которые широко используются в отношении точечного произведения, которое включает в себя тригонометрическую форму точечного произведения посредством использования функции cos
и abs
ее magnitude
.Таким образом, имея это в виду, вы можете легко экстраполировать все функциональные возможности точек и векторов из сущности и всех других позиционных объектов, а затем просто иметь одну переменную этого класса для представления того, что необходимо.Еще одна вещь, которая делает математические векторы очень полезными, заключается в том, что вы можете выполнять операции над этими векторами не только скалярным и другим вектором, но также и матрицами, где это позволит выполнять преобразования для них;особенно аффинные преобразования, вращение, перевод, масштабирование ...
Пример Vector2f класса:
class Vector2f {
public:
union { // nameless union to designate both the array elements and the
// individual elements have the same memory location: helps
// with different way of accessing the individual vector components
float f2_[2]; // internal array of float with size 2
struct { // nameless struct
float x_;
float y_;
};
};
// Different ways to construct a vector
inline Vector2f();
inline Vector2f( float x, float y );
inline Vector2f( float* vp );
// operators
inline Vector2f operator+( const Vector2f& v2 ) const;
inline Vector2f operator+() const;
inline Vector2f& operator+=( const Vector2f& v2 );
inline Vector2f operator-( const Vector2f& v2 ) const;
inline Vector2f operator-() const;
inline Vector2f& operator-=( const Vector2f& v2 );
inline Vector2f operator*( const float& value ) const;
inline Vector2f& operator*=( const float& value );
inline Vector2f operator/( const float& value ) const; // check for divide by 0
inline Vector2f& operator/=( const float& value ); // same as above
// Common Functions
inline void normalize();
inline void zero();
inline bool isZero(); // use an epsilon value
inline float dot( const Vector2f v2 ) const;
inline float lenght2() const; // two ways of calculating the length or magnitude
inline float length() const;
inline float getCosAngle( const Vector2f& v2, const bool isNormalized = false );
inline float getAngle( const Vector2f& v2, const bool isNormalized = false, bool inRadians = true );
inline friend Vector2f Vector2f::operator*( const float& value, const Vector2f v2 ) {
return Vector2( value * v2.x_, value * v2.y_ );
}
inline friend Vector2f Vector2f::operator/( const float& value, const Vector2f v2 ) {
Vector2f vec2;
if ( Math::isZero( v2.x_ ) )
vec2.x_ = 0.0f;
else
vec2.x_ = value / v2.x_;
if ( Math::isZero( v2.y_ ) )
vec2.y_ = 0.0f;
else
vec2.y_ = value / v2.y_;
return vec2;
}
};
Что касается конструкторов иСами функции, я не собираюсь их здесь показывать, поскольку их было бы слишком много для отображения, а что касается Math::isZero()
, который в основном является обобщенной математической функцией, которая проверяет, является ли значение меньше некоторого значения эпсилона, чтобы считать его незначительным и разрешитькод для обработки его как нулевого из-за арифметики с плавающей запятой и ошибок округления.
Тогда в вашем существующем классе вы можете просто сделать это:
#include "Vector2f.h"
struct Stats {
int health_;
int mind_;
int action_;
int level_;
};
// Since this is C++ and not C there is no need for `typedef` `structs` although it is still valid C++
struct Entity {
Vector2f position_;
Vector2f velocity_;
Stats stats_;
void *equipment, *abilities, *effects;
};
Это нормально, но все, кроме нас, могут иметь векторы и точки, которые также имеют двойную точность и основаны на целых числах.У нас также могут быть векторы, имеющие 3 координатных положения, чтобы мы могли добиться большего успеха, превратив наш класс Vector2f в тип шаблона:
template<class Type, unsigned N> // Type is float, double, int, etc. N is number of dimensions
class Vector {
// class body here:
};
Еще лучше, вместо записи всего вектораВ этом классе вы можете использовать очень простую в использовании библиотеку, которая очень хорошо работает с приложениями типа 2D и 3D, где их библиотека имеет вид OpenGL и GLSL под названием glm
.Вы можете найти его здесь , так как он очень прост в использовании и установке, так как это библиотека headers
и нет ссылок.Просто включите заголовки и начните использовать его, так как он очень мощный и универсальный.
Что делает использование математического векторного класса таким полезным, так это тот факт, что многие различные типы объектов могут иметь положение, скорость или ускорение.Вы даже можете использовать их для хранения данных сетки, таких как координаты вершины сетки, координаты текстуры, данные цвета, например, из библиотеки glm
: a glm::vec4<float> color
будет иметь r,g,b,a
красный, зеленый, синий и альфа.Где glm::vec2<float> textureCoords
будет иметь u, v
координаты.И самая мощная часть этого - способность просто выполнять математику для векторов со скалярами, другими векторами и матрицами.
Лучшим предложением относительно вашего фактического вопроса, который включает cache coherency
, было бы убедиться, что ваша структура, которую вы создаете, имеет выравнивание 4 byte
.Поэтому, если у вас есть такая структура:
struct Foo {
int a; // assuming 32bit = 4bytes
short b; // assuming 16bit
};
Приведенная выше структура потерпит неудачу при 4-байтовом выравнивании, чтобы исправить это:
struct Foo {
int a;
short b;
private:
short padding; // not used
};
Это поможет решить проблемы с кешем,Порядок, в котором они хранятся, имеет значение, так как он зависит от последовательности переменных, которые выровнены с 4 байтами, обычно встречающимися на 32-битных машинах.Я не знаю, верно ли то же самое для 64-битных машин с 4-байтовым выравниванием, но это то, что нужно учитывать.Еще одна вещь, которую полезно учитывать, заключается в том, что выравнивание слов для некоторой структуры Foo может отличаться в разных ОС и в разных компиляторах.Еще одна вещь, которую следует принять во внимание, - это также архитектура с внутренним расположением типа данных, например его порядком байтов, но это обычно вызывает беспокойство, если вы используете unions
особым образом, bit fields
и выполняете побитовые операции -манипуляции.
Возвращаясь к этому варианту вашего кода:
#include "Vector2f.h"
struct Stats { // assuming 32bit
int health_; // 4 bytes
int mind_; // 4 bytes
int action_; // 4 bytes
int level_; // 4 bytes
};
struct Entity {
Vector2d position_; // float = 8 bytes x 2: 16 bytes
Vector2d velocity_; // float = 8 bytes x 2: 16 bytes
Stats stats_; // 32 bytes
void *equipment, *abilities, *effects; // each pointer 4 bytes.
};
Итак, здесь ваш класс сущностей, где речь идет о variables
, все находятся на 4-байтовом выравнивании, так как все они кратныиз 4 и, как правило, в большинстве случаев это дружественный кеш.
Там, где ваши указатели связаны с косвенным указанием, распределением и освобождением.У вас есть несколько вариантов.Вы можете передать ссылку на объект, если ссылка живет дольше, чем текущий класс (ожидаемая продолжительность жизни), чтобы избежать использования new
и delete
.Вы можете использовать smart pointers
.Вы можете содержать их в std::vector
и вообще не использовать указатели, вы можете взять все эти указатели и поместить их в свою собственную структуру как таковую, а затем иметь единственный указатель на эту структуру в своем классе, или вы можете сделатькак-то так:
class Entity {
private:
Vector<float,2> position_;
Vector<float,2> velocity_;
Stats stats_;
std::tuple<Equipment*, Ability*, Effect*> attributes_;
};
Вот простой пример использования class pointers
в кортеже:
#include <exception>
#include <iostream>
#include <tuple>
class Equipment { public: int x; };
class Ability { public: int x; };
class Effect { public: int x; };
class Player { public:
std::tuple<Equipment*, Ability*, Effect*> attributes;
};
int main() {
try {
Equipment eq;
eq.x = 5;
Ability ab;
ab.x = 7;
Effect ef;
ef.x = -3;
Player p;
p.attributes = std::make_tuple<Equipment*, Ability*, Effect*>( &eq, &ab, &ef );
std::cout << "Equipment: " << std::get<0>( p.attributes )->x << '\n';
std::cout << "Ability: " << std::get<1>( p.attributes )->x << '\n';
std::cout << "Effect: " << std::get<2>( p.attributes )->x << '\n';
} catch( std::runtime_error& e ) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Однако вы должны убедиться, что время жизни предыдущегообъекты: Equipment, Ability, Effect
из жизни вашего Entity
или Player
объекта, чтобы это сработало.Это также может быть неэффективным в некоторых отношениях, потому что игрок может содержать более 1 каждого из вышеперечисленных.Таким образом, самый простой способ сделать это - иметь контейнер std::vector
каждого:
class Entity {
private:
Vector<double, 2> position_;
Vector<double, 2> velocity_;
Stats stats_;
std::vector<Equipment> inventory_;
std::vector<Ability> abilities_;
std::vector<Effect> effects_;
};
Если вам действительно нужны указатели, то вы можете получить что-то вроде этого:
std::vector<std::shared_ptr<T>> objects_;
В вашем классе.Тогда у вас будет контейнер умных указателей, который будет обрабатывать все выделения и деления для вас, если вам не нужна информация указателя для совместного использования, вы можете использовать std::unique_ptr<T>
.
После того, как вы разработали Data Structures
и знаете, как они устроены, вам нужно либо разработать алгоритм, который соответствует вашим потребностям, либо выбрать подходящий уже существующий алгоритм, который уже был написан из любогоиспользуемые библиотеки, такие как stl
или boost
.
Я знаю, что это довольно долго, но я думаю, что я охватил все темы, которые вы предложили, и даже может быть возможно выбрать комбинации из предложенных вами, но каждая из них имеетих компромисс, поскольку всегда есть плюсы и минусы для конкретных вещей, которые вы делаете.Увеличьте скорость работы контейнера и посмотрите на стоимость большего объема памяти и потерю прямого доступа, в отличие от меньшего объема используемой памяти, быстрый поиск, но гораздо более медленную вставку.
Знание того, когда и где использовать, какие типы контейнеров и соответствующий алгоритм является ключом к наличию приложения, которое работает эффективно и с минимальными ошибками.
Надеюсь;это может вам помочь.