26 января 2009 г.

Заводы

Вот думал тут над тем, чтобы реализовать завод по производству игровых сущностей entity, т.е все игровые обьекты наследуются от некого предка Entity который обладает базовой функциональностью применимой к большей части игровых объектов и заведует логикой. завод должен уметь:
регистрировать название объекта в игре, создавать объект по игровому названию, названию класса создавать какой-либо другой объект с начальными установками из своего нутра.
Сразу оговорюсь, что это всего лишь мысли в слух и надо срочно покурить кинигу паттернов C++ на предмет паттерна завод/фабрика, дабы избежать велосипедизма с многократными имплементациями различных фабрик.

т.е есть завод с конвеерами по производству данного типа обьекта, например:






  1. // пример приведён псевдо кодом,

  2. // семантика 'L' это мой ник: Lolmen

  3. // во избежании пересечений с неоговоренными

  4. // в сообщении деталями.


  5. // создаём конвеер таким вот образом:

  6. Factory.Register( CLASS_NAME, gameName );


  7. // например в классе бомбы сделаем след:

  8. // в bomb.cpp перед непосредственной имплементацией класса LBomb


  9. RegisterFactory( LBomb, "fake_bomb" ); 


  10. // Добавит конвеер для рождения бомб, сделает ссылку на бомбу

  11. при создании оной в список игровых Entities.


  12. // теперь нам нужно допустим,

  13. // родить обьект в мире при каком-то дейтвии,

  14. // например при вводе в консоль entity_create "entity_name" "position[x y]"

  15. // например entity_create 512.0 286.0 spawn


  16. void LWorld::OnConsoleCmdCreateEnt( const LString &name, const LVec2D &position, bool_t bShouldSpawn )

  17. {

  18.    LEntity *pSomeObj = CreateEntityByName( name, bShouldSpawn );

  19.    

  20.    if ( pSomeObj ) // убедимся что такой конвеер есть!

  21.       pSomeObj->SetPosition( position );

  22. }


  23. // или в классе гранатомёта родить гранату:


  24. void LGrenadeLauncher::PrimaryAttack( void )

  25. {

  26.    LGrenade *pGrenade = 0; 

  27.    // метод Create рождает энитю указывая начальные положения в мире

  28.    // и флаг рождать или просто создать

  29.    if ( Create( pGrenade, "projectile_grenade", GetShootPos(), GetAngles(), true ) )

  30.    {

  31.       pGrenade->SetMoveType( MT_FLY ); // тип перемещения

  32.       pGrenade->SetLifeTime( 10.0f  ); // время жизни 10 секунд

  33.    }

  34. }


  35. // удалять тоже просто:


  36. void RemoveEntity( LString &name, LEntity *pEntity );




Удаление и создание работает через список конвееров завода и список созданных в мире игровых сущностей.
Удобно также осуществлять поиск какого-то класса, например поиск в сфере указанного радиуса всех Entity с именами "projectile_detonation_bomb"

5 комментариев:

  1. Во-первых смущает отсутствие общности
    то LEntity *pSomeObj = CreateEntityByName( name, bShouldSpawn )
    то Create( pGrenade, "projectile_grenade", GetShootPos(), GetAngles(), true )

    Причем во втором случае приходится передавать ссылку на указатель, что делает невозможным любые преобразования фактического аргумента. Как следствие - придется писать отдельную ф-ю для каждого типа указателя.

    Гораздо логичнее везде использовать первый вариант, он и выразительнее и намного более гибкий.

    Во-вторых удивляет передача в RemoveEntity имени. Зачем это нужно? Разве указатель однозначно не идентифицирует объект?

    ОтветитьУдалить
  2. Кстати, да, ты прав...
    метод Create принимает указатель на LEntity, т.е передаётся он, чтобы в последствии получить нечто вроде как и первом варианте, т.е не надо писать кучу функций... просто удобнее писать
    if ( Create( pEnt, ... ), который вернёт булевый результат, и доункаст от template T чем
    LGrenade* pGrenade = (LGrenade*)Create( ... );
    if ( pGrenade )
    {
    ...
    }

    Который вернёт LEntity

    с RemoveEntity подумал, да, имя передавать лишнее.

    ОтветитьУдалить
  3. "метод Create принимает указатель на LEntity"
    1 Если он принимает указатель, то никакого смысла в последующем обращении через него нет. Он просто не изменится. Изменится его копия.
    2 Если он принимает ссылку на указатель, то код
    Create( pGrenade,...) синтаксически неверен, ибо pGrenade не является ссылкой на указатель типа LEntity. Не надейся что pGrenade автоматически приведется к LEntity, с ссылками такое не проходит...

    А насчет удобства. В любом случае придется доункастить, а писать можно и не только

    LGrenade* pGrenade = (LGrenade*)Create( ... );
    if ( pGrenade )

    можно и

    if ( pGrenade = (LGrenade*)Create( ... ))

    но в любом случае это лучше чем

    if ( Create( pEnt, ... )) { LGrenade* pGrenade = (LGrenade*)pEnt; ... }

    ОтветитьУдалить
  4. подскажите что значит "Завод"? и в случае создания
    это кому облегчит жизнь?

    ОтветитьУдалить
  5. Почитай про фабрики классов :)

    ОтветитьУдалить