#c #templates #game-physics #double-dispatch
#c #шаблоны #игра-физика #двойная отправка
Вопрос:
Я хочу позволить компилятору создавать соединения функций для системы физических столкновений. У меня есть функция проверки столкновения:
template <typename T, typename U>
inline void Collision(Tamp; t, Uamp; u)
{
if(u.CheckCollision(t.GetCollider()))
{
u.HitBy(t);
t.Hit(u);
}
}
Затем я использую объект collider для хранения объекта collision.
template <typename T>
class Collidable
{
T m_Collider;
VictimEffect m_HitBy;
MenaceEffect m_Hit;
public:
void SetCollider(const Tamp; t) { m_Collider = t; }
void SetVictim(VictimEffect effect) { m_HitBy = effect; }
void SetMenace(MenaceEffect effect) { m_Hit = effect; }
Tamp; GetCollider()
{
return m_Collider;
}
template <typename V>
void HitBy(Vamp; menace)
{
m_HitBy.HitBy(menace.GetCollider());
}
template <typename V>
void Hit(Vamp; victim)
{
m_Hit.Hit(victim.GetCollider());
}
template <typename V>
bool CheckCollision(Vamp; menace)
{
return m_Collider.CheckCollision(menace);
}
};
Я добавил уровень косвенности для функций эффекта
class VictimEffect
{
public:
template<typename C>
void HitBy(Camp;)
{;}
};
class MenaceEffect
{
public:
template<typename C>
void Hit(Camp;)
{;}
};
В итоге я хочу получить способ вызова функций в любом объекте, который имеет правильную сигнатуру функции, но не требует типов, которые не будут попадать.
class Wall;
class Ball : public MenaceEffect
{
public:
void Hit(Wallamp; wall);
void Hit(Ballamp; ball);
};
class Wall : public VictimEffect
{
public:
void HitBy(Ballamp; ball);
};
Обратите внимание, я не хочу иметь функцию для стены, ударяющейся о стену. Я хочу писать или предоставлять функции только для взаимодействий, которые должны произойти. Я знаю, что это не работает, потому что VictimEffect не «видит» производную функцию.
Я хочу отправлять без необходимости указывать параметры шаблона везде, где они используются. Я хочу создать свой эффект collidable отдельно от collidable и передать его объекту collidable в общем виде. Я не хочу иметь разные сталкивающиеся объекты для каждой коллекции вещей, с которыми можно столкнуться. В принципе, я хочу
vector<collidable> movingThings;
vector<collidable> staticThings;
// get iterators ...
Collide(Moving, Static);
I need more layers of indirection here, but I just can’t see it. I know it can be done because all of the information is available at compile time. There is no need for a dynamic polymorphic solution.
Any help is greatly appreciated!
Update
What I am trying to do is build my own modern (as in Andrei’s book) game library. So far I have most of the relationships worked out, but this one has me stumped. This isn’t a time critical issue, but rather a results critical issue. I’ve been working in games for many many years and I don’t like any game engine I’ve worked on. This one is meant to be simple to use and easy to extend. It will work on any platform now that console compilers have gotten not terrible.
Here’s a simple example of what I want to be able to write.
int main()
{
// System must come first
System system(640, 480, false);
Renderer renderer;
Mouse mouse;
Keyboard keys;
// Make the ball
Bounce ball;
Sprite ballSprite("02.bmp");
CollisionBox ballPhysics;
BallCommand ballBehavior;
ball.SpriteMover = boost::bind(amp;Sprite::Observer<Pos2D>, amp;ballSprite, _1);
ball.PhysicsMover = boost::bind(amp;CollisionBox::Move, amp;ballPhysics, _1);
// Make the bounds
Boundary bounds;
// Set up Physics
Collidable<Boundary> boundC;
boundC.SetCollider(bounds);
boundC.SetVictim(ballBehavior);
Collidable<CollisionBox> ballC;
ballC.SetCollider(ballPhysics);
PretendPhysics physics;
physics.SetBall(ballC);
physics.SetBounds(boundC);
while(!mouse.LeftClick() amp;amp; !keys.AnyKey())
{
ball.MoveRel(ballBehavior.Delta());
physics.Update();
renderer.Clear();
renderer.Render(ballSprite);
renderer.SwapBuffers();
}
}
Вся физика — это WIP и не там, где я хочу, чтобы она заканчивалась, но она приближается к этому. Системный объект инициализирует Windows и средство визуализации, зависящее от платформы. В данном случае это OpenGL. Он создает все компоненты Windows, хотя и скомпилирован как консольное приложение. Я не хотел, чтобы люди имели дело с main () против winmain (). Кроме физики, наследования нет. Три вызова рендеринга будут заменены одним после того, как я загружу сцены в систему. Я планирую использовать то, что я написал некоторое время назад, в сочетании с библиотекой шаблонов View Гэри Пауэлла и Мартина Вайзера.
Я знаю, что просто неправильно смотрю на это. Посетитель мог бы помочь изменить мой подход.
Итак, это то, что я пытаюсь сделать.
Обновление 2 ОК. Я получил некоторое вдохновение от комментариев и ответов на данный момент, и это то, к чему я пришел, но я еще не закончил.
template <typename T, typename V = VictimEffect, typename M = MenaceEffect>
class Collidable
{
T m_Collider;
V m_HitBy;
M m_Hit;
public:
Collidable(T collide, V victim, M menace) : m_Collider(collide), m_HitBy(victim), m_Hit(menace) {;}
Collidable(T collide) : m_Collider(collide) {;}
Collidable(T collide, V victim) : m_Collider(collide), m_HitBy(victim) {;}
Tamp; GetCollider()
{
return m_Collider;
}
template <typename V>
void HitBy(Vamp; menace)
{
m_HitBy.HitBy(menace.GetCollider());
}
template <typename V>
void Hit(Vamp; victim)
{
m_Hit.Hit(victim.GetCollider());
}
template <typename V>
bool CheckCollision(Vamp; menace)
{
return m_Collider.CheckCollision(menace);
}
};
Затем, чтобы использовать это, я делаю это
Collidable<Boundary, BallCommand> boundC(bounds, ballBehavior);
Collidable<CollisionBox> ballC(ballPhysics);
Omnifarious был прав. Я терял информацию о типе, передавая базу. Теперь база существует только для того, чтобы перехватывать вещи, которые меня не волнуют.
Для моего контейнера я думаю, что могу использовать список типов для сбора каждого типа, добавляемого в контейнер, и использовать это для объявления контейнеров определенного типа, которые затем обрабатывают информацию. Одна загвоздка в том, что я не могу удалить обычные итераторы, в которые было бы проще передавать функциональные объекты.
возможно, C 0X может помочь с этой новой переменной неизвестного типа. (Я забыл, как это называется).
Комментарии:
1. Я не верю, что информация доступна во время компиляции. Это было «стерто» из знаний времени компиляции путем помещения экземпляров базового класса (или, что более вероятно, ссылок (в общем смысле, количество указателей) базового типа, которые ссылаются на производные экземпляры) в ваши векторы. Я думаю, вы каким-то образом застряли, используя виртуальные функции.
2. Я знаю, что как только я выполнил наследование, информация теряется, и я знаю, что есть еще одна проблема — собрать все в одну коллекцию. Возможно, в этот момент это невозможно сделать, но что касается запуска события, которое имеет общую сигнатуру и отличается только аргументом. Я думаю, что это можно сделать. Я могу сделать это с помощью Ball и Wall, но я не могу сделать это с помощью (Ball Wall) для одного объекта и Wall для другого, если это имеет смысл. Я также могу сделать это, специализируя каждый возможный результат, но это утомительно.
3. @Tavison — Как только вы начнете использовать виртуальные функции, кажется, что шаблон посетителя класса даст вам то, что вы хотите.
4. Тэвисон, ты можешь лучше объяснить, что ты пытаешься сделать, пожалуйста? Я не понял вашего вопроса. Дайте нам полный код взаимосвязи между Collidable и Victim / MenaceEffect?
5. Это может помочь в правильном направлении. Мне удалось шаблонизировать шаблон observer для перемещения физических объектов и рендеринга с использованием шаблона command. Я не тороплюсь с решением. Это больше похоже на книгу Андрея, пытающегося найти чистые универсальные решения для разработки игрового движка. Я создаю современный игровой движок в свободное время, потому что я ненавижу все те, которые я использовал до сих пор. Спасибо за совет посетителю. Я вижу, что в современном дизайне C у Андрея есть шаблон посетителя. Отличная помощь.
Ответ №1:
Может быть, что-то вроде этого? (только часть попадания, попадание симметрично)
class Wall;
class MenaceBase {
public:
template<typename T>
void hit(Tamp; t) {
t.hitImpl(*this);
}
};
class VictimBase {
public:
};
template<typename M>
class Menace {
public:
virtual void hit(Mamp;) = 0;
};
template<typename V>
class Victim {
public:
virtual void hitBy(Vamp;) = 0;
};
class Ball : public MenaceBase, public Menace<Wall>, public Menace<Ball> {
public:
void hit(Wallamp; wall) {}
void hit(Ballamp; ball) {}
};
class Wall : public VictimBase, public Victim<Ball> {
public:
void hitBy(Ballamp; ball) {}
void hitImpl(Menace<Wall>amp; menace) {
menace.hit(*this);
}
};
Комментарии:
1. Да, это определенно приближается к цели. Я все еще хотел бы сделать это без virtual. Я знаю, это звучит странно, но 12 или 13 лет назад я решил не использовать наследование без крайней необходимости. Это не всегда был лучший код или хороший код, но это помогло мне разобраться в некоторых интересных методах. Я думаю, что этот ответ правильный для рабочего кода, но не то, что я ищу в качестве упражнения для размышлений. Это определенно помогает. Большое спасибо.
2. Хорошо, значит, проблема не в использовании virtual? Вы можете согласиться
dynamic_cast
?3. Это заняло у меня минуту, но я понимаю, к чему вы клоните. Мне было бы интересно посмотреть, что у вас есть, но я не хочу, чтобы это было конечным решением. Я думаю, что мы подходим к решению с двух разных концов, и окончательный ответ находится где-то посередине.
Ответ №2:
ОК. Вот чем я закончил.
class VictimEffect
{
public:
template<typename C>
void HitBy(Camp;)
{;}
};
class MenaceEffect
{
public:
template<typename C>
void Hit(Camp;)
{;}
};
template <typename T, typename M = MenaceEffect, typename V = VictimEffect>
class Collidable
{
T m_Collider;
M m_WeHit;
V m_WeWereHit;
public:
Collidable(T collide, M menaceEffect, V victimEffect) : m_Collider(collide), m_WeHit(menaceEffect), m_WeWereHit(victimEffect) {;}
Collidable(T collide) : m_Collider(collide) {;}
Collidable(T collide, M menaceEffect) : m_Collider(collide), m_WeHit(menaceEffect) {;}
Tamp; GetCollider()
{
return m_Collider;
}
template <typename V>
void HitBy(Vamp; menace)
{
m_WeWereHit.HitBy(menace.GetCollider());
}
template <typename V>
void Hit(Vamp; victim)
{
m_WeHit.Hit(victim.GetCollider());
}
template <typename V>
bool CheckCollision(Vamp; menace)
{
return m_Collider.CheckCollision(menace);
}
};
template <typename T, typename U>
inline void Collision(Tamp; t, Uamp; u)
{
if(u.CheckCollision(t.GetCollider()))
{
u.HitBy(t);
t.Hit(u);
}
}
Нет необходимости наследовать эффекты жертвы или угрозы. Они есть, если вам нужны универсальные решения. Типами M и V может быть любой объект, поэтому, хотите вы наследовать или нет, или как вы хотите его использовать. Я должен создать все варианты для ptr и т.д. Если все объекты using наследуются от базовых классов, то их можно использовать так, как если бы они были написаны с использованием динамического полиморфизма. Если вы не хотите наследовать и вместо этого сохраняете специальные отдельные списки, вы тоже можете это сделать.
Использовать это просто
class Boundary
{
// Put whatever code here.
bool CheckCollision(CollisionBox bounce)
};
class BallCommand
{
public:
void Hit(Boundaryamp; bounds)
{
if(bounds.XHit())
{
xDir *= -1;
}
if(bounds.YHit())
{
yDir *= -1;
}
}
};
Где-то в вашем коде вы это используете.
Collidable<Boundary> boundC(bounds);
Collidable<std::shared_ptr<CollisionBox>, BallCommandamp;> ballC(ballPhysics, ballBehavior);
Collision(ballC, boundC);
Предстоит проделать еще большую работу по очистке, но я думаю, что это довольно хорошее начало. Я конвертирую в std::function, чтобы сделать это еще проще.
Спасибо Omnifarious и Giovanni за то, что помогли мне это продумать.