Nebula CommunityСтатьи

Введение в Nebula 2: Mangalore (продолжение)

Автор:

Продолжение статьи Введение в Nebula 2: Mangalore. В данном продолжении рассказывается как создавать анимированные объекты, как управлять ими с помощью клавиатуры и как делать для них collision detection.

Часть 6. Актеры

Часть 6. Актеры

В этой части статьи мы рассмотрим как работать с анимированными графическими объектами, как управлять объектами при помощи клавиатуры, как делать статические препятствия.

Итак, пора нам уже добавить в наш мир какое-нибудь прямоходячее существо. В мангалоре подобные существа называются актерами. В дальнейшем мы будем экпериментировать над "актером" kila. Копируем все ресурсы, которые связаны с этим объектом (смотрите скрипт kila.n2) из папки небулы в папку нашего проекта. Теперь настраиваем анимации. Открываем в Microsoft Excel 2003 таблицу anims.xml. В этой таблице связываются состояние актера с какой-либо из его анимаций. Добавляем столбец kila. У нашего актера только одна анимация. Поэтому напротив всех трех состояний вводим название анимации "one". Переходим к программному коду. Добавляем в наш главный класс TutorialApp функцию SetupPlayer. Опять также создаем entity, но теперь вместо graphics property создаем и крепим к entity ActorGraphicsProperty. Через атрибут AnimSet указываем название столбца в таблице anims.xml, который соответствует актеру (у нас это kila). Чтобы переключиться на другую анимацию надо послать для entity сообщение Message::GfxSetAnimation. Также теперь отбираем свойство ChaseCameraProperty у ящика и ставим его нашему актеру. В итоге код функции SetupPlayer.

void TutorialApp::SetupPlayer()
{
  Ptr<Game::Entity> pPlrEntity = pFactoryManager->CreateEntityByClassName("Entity");
  Ptr<Game::Property> pGraphProp = pFactoryManager->CreateProperty("ActorGraphicsProperty");
  Ptr<Game::Property> pCamProp = pFactoryManager->CreateProperty("ChaseCameraProperty");

  pPlrEntity->AttachProperty(pGraphProp);
  pPlrEntity->AttachProperty(pCamProp);

  matrix44 trm;
  trm.ident();
  trm.translate(vector3(300.f,300.f,310.f));

  pPlrEntity->SetMatrix44(Attr::Transform,trm);
  pPlrEntity->SetString(Attr::Name,"player");
  pPlrEntity->SetString(Attr::Id,"player");
  pPlrEntity->SetString(Attr::Graphics,"actors/kila");
  pPlrEntity->SetString(Attr::AnimSet,"kila");

  pEntityManager->AttachEntity(pPlrEntity);
  pFocusManager->SetCameraFocusEntity(pPlrEntity);

  Ptr<Message::GfxSetAnimation> pIdleAnimMsg = Message::GfxSetAnimation::Create();
  pIdleAnimMsg->SetBaseAnimation("Idle");
  pPlrEntity->SendSync(pIdleAnimMsg);
}

Не забываем добавить вызов SetupPlayer в функцию Open. Компилируем и запускаем. На экране мы видим шагающего актера, мимо пролетает ящик, а где то в дали земля. Давайте сделаем, чтобы наш актер тоже не висел в воздухе. Создаем свойство ActorPhysicsProperty и добавляем его нашему актеру. Это свойство отличается от обыкновенного PhysicsProperty тем, что оно всегда создает вокруг нашего актера капсуль, при чем для любого объекта создается капсуль с одними и теме же размерами, и который при симулировании физики всегда ориентирован вдоль оси Y и никогда не может завалиться на бок. Также это свойство используется для ragdoll. Ragdoll настраивается через файл композита (как это сделать смотрите файл compositeloader.cpp), который передается entity для ActorPhysicsProperty через атрибут Physics. Нам это свойство не совсем нравится, потому что нельзя изменить параметры капсуля. Поэтому создаем свое свойство ActorPhysicsProperty2, которое наследует ActorPhysicsProperty. Наше свойство будет отличаться только тем, что оно будет брать соответствующие атрибуты и создавать капсуль нужного нам размера.

actorphysicsproperty2.h

#ifndef ACTORPHYSICSPROPERTY2_H
#define ACTORPHYSICSPROPERTY2_H

#include <properties/actorphysicsproperty.h>

namespace Attr
{
  DeclareFloat(ActorCapsuleHeight);
  DeclareFloat(ActorCapsuleRadius);
  DeclareFloat(ActorCapsuleHover);
}

namespace Properties
{
  class ActorPhysicsProperty2: public ActorPhysicsProperty
  {
    DeclareRtti;
    DeclareFactory(ActorPhysicsProperty2);
  public:
    ActorPhysicsProperty2();
    ~ActorPhysicsProperty2();

    void EnablePhysics();
  };

  RegisterFactory(ActorPhysicsProperty2);
}

#endif

actorphysicsproperty2.cpp

#include "properties/actorphysicsproperty2.h"
#include <game/entity.h>
#include <game/time/gametimesource.h>
#include <attr/attributes.h>
#include <physics/server.h>
#include <physics/level.h>
#include <mathlib/polar.h>

namespace Attr
{
  DefineFloat(ActorCapsuleHeight);
  DefineFloat(ActorCapsuleRadius);
  DefineFloat(ActorCapsuleHover);
}

namespace Properties
{
  ImplementRtti(Properties::ActorPhysicsProperty2,Properties::ActorPhysicsProperty);
  ImplementFactory(Properties::ActorPhysicsProperty2);

  ActorPhysicsProperty2::ActorPhysicsProperty2()
  {
  }

  ActorPhysicsProperty2::~ActorPhysicsProperty2()
  {
  }

  void ActorPhysicsProperty2::EnablePhysics()
  {
    n_assert(!this->IsEnabled());

    Game::Entity *pEntity = GetEntity();
    
    n_assert(pEntity);

    // copy&paste из ActorPhysicsProperty с небольшими исправлениями
    // create a char physics entity
    this->charPhysicsEntity = Physics::CharEntity::Create();
    this->charPhysicsEntity->SetUserData(GetEntity()->GetUniqueId());
    
    if (pEntity->HasAttr(Attr::Physics))
      this->charPhysicsEntity->SetCompositeName(pEntity->GetString(Attr::Physics));

    if(pEntity->HasAttr(Attr::ActorCapsuleHeight))
      charPhysicsEntity->SetHeight(pEntity->GetFloat(Attr::ActorCapsuleHeight));

    if(pEntity->HasAttr(Attr::ActorCapsuleRadius))
      charPhysicsEntity->SetRadius(pEntity->GetFloat(Attr::ActorCapsuleRadius));

    if(pEntity->HasAttr(Attr::ActorCapsuleHover))
      charPhysicsEntity->SetHover(pEntity->GetFloat(Attr::ActorCapsuleHover));

    this->charPhysicsEntity->SetTransform(GetEntity()->GetMatrix44(Attr::Transform));

    // attach physics entity to physics level
    Physics::Level* physicsLevel = Physics::Server::Instance()->GetLevel();
    n_assert(physicsLevel);
    physicsLevel->AttachEntity(this->charPhysicsEntity);

    // make sure we are standing still
    this->Stop();

    // initialize feedback loops for motion smoothing
    nTime time = Game::GameTimeSource::Instance()->GetTime();
    const matrix44& entityMatrix = GetEntity()->GetMatrix44(Attr::Transform);
    this->smoothedPosition.Reset(time, 0.001f, this->positionGain, entityMatrix.pos_component());

    polar2 headingAngle(entityMatrix.z_component());
    this->smoothedHeading.Reset(time, 0.001f, this->headingGain, headingAngle.rho);

    // call parent
    AbstractPhysicsProperty::EnablePhysics();
  }
}

Добавляем ActorPhysicsProperty2 для нашего entity. Компилируем и запускаем. К сожалению, наш актер по-прежнему стоит на месте и падать не собирается. Нажимаем на F2, чтобы убедится, что нашему актеру присоединен физический объект. Как мы видим он синего цвета, т.е он выключен. Возможно вы скажите, что надо включить физику, точно так же как мы это делали для ящика. Но, к сожалению, это не помогает. После пошаговой отладки я выяснил, что постоянно блокирует нашего актера работа функций Physics::CharEntity::OnStepBefore и Physics::CharEntity::CheckGround. Посмотрев на эти функции, я понял, что физика включится, только после того как мы начнем управлять нашим актером. Не знаю в чем была задумка разработчиков, но при данных обстоятельствах нам всегда надо будет размещать актера возле земли. И неизвестно как будет вести себя актер, допустим, когда он попадет в яму. Если игрок, управляющий актером перестанет давать команды движения, то наш актер зависнет в воздухе.

Ладно, смиримся пока с этим обстоятельством. Давайте сделаем управление актером. Создаем свойство PlayerInputProperty, которое наследует InputProperty. По названиям тут все понятно что к чему. Это свойство будет перехватывать сообщения, которые посылает клавиатура и мышь. Обрабатывая, эти сообщения мы будем посылать сообщения нашему актеру, чтобы он соответствующим образом двигался. Помните я говорил, что, чтобы изменить состояние какого-либо объекта, то ему надо послать соответствующее сообщение. Сообщения обрабатывают property, поэтому смотрите программный код того или иного property и вы узнаете какие сообщения оно обрабатывает и что при этом менятеся.

playerinputproperty.h

#ifndef PLAYERINPUTPROPERTY_H
#define PLAYERINPUTPROPERTY_H

#include <properties/inputproperty.h>

namespace Properties
{
  class PlayerInputProperty: public InputProperty
  {
    DeclareRtti;
    DeclareFactory(PlayerInputProperty);
  public:
    PlayerInputProperty();
    ~PlayerInputProperty();

    /// setup default entity attributes
    void SetupDefaultAttributes();
    /// called when input focus is gained
    void OnObtainFocus();
    /// called when input focus is lost
    void OnLoseFocus();

    bool Accepts(Message::Msg *msg);
    void HandleMessage(Message::Msg *msg);
  };

  RegisterFactory(PlayerInputProperty);
}

#endif
Страницы: 1 2 3 Следующая »

#Mangalore, #Nebula

27 мая 2008 (Обновление: 1 фев 2011)