MSAgent в Delphi

Здесь я расскажу, то что знаю о технологии Microsoft Agent и Speech API. А в течении моего рассказа мы попробуем кое-что написать в Delphi! Естественно, это не будет суперполный рассказ, но, думаю, достаточный, чтобы начать использовать в приложениях Delphi технологию MS Agent и Speech API.

Итак, все что нам понадобиться можно найти в конец страницы. Для начала нужно иметь Microsoft Agent Control 2.0 и один или более персонажей.

MS Agent — это обычный активный элемент ActiveX. Этот компонент активно используется Microsoft в пакете MS Office. Да, MS Agent это та самая скрепка, кошка, собачка или кто там у вас… появляющаяся при загрузке приложений MS Office.

MSAgents

Если вы уже скачали и установили MS Agent v2.0, то приступим к установке его в среду Delphi, else goto page down.

Запустите Delphi, откройте меню Component и выберите Install ActiveX control… Попытайтесь найти в списке Microsoft Agent Control 2.0 (Version 2.0), если его там нет, то нажмите кнопку Add и найдите вручную файл Agentctl.dll. Теперь можно нажимать кнопку Install…

Установка ActiveX в Delphi

По умолчанию компонент помещается на вкладку ActiveX (а на какую еще нормальный ActiveX может поместиться?). Все! Теперь можно приступать непосредственно к программированию.

MS Agent имеет большое количество интерфейсов, но для начала нам понадобится немногое:

  • IAgentCtlCharacterEx — Самый основной интерфейс. С его помощью проводятся все действия с персонажем
  • IAgentCtlRequest — Состояние агента, запрос который выполняется персонажем в каждый момент времени доступен через этот интерфейс, поэтому он часто используется для синхронизации при работе с несколькими персонажами одновременно
  • IAgentCtlAnimationNames — Для перебора различных анимаций персонажа
  • IAgentCtlBalloonEx — Для управления баллоном, в котором персонаж выводит читаемый текст или свои мысли

Сейчас мы попробуем сделать так, чтобы при загрузке приложения появлялся какой-нибудь персонаж. В секцию private вставим нужные для работы переменные:

private    { Private declarations}
    Req: IAgentCtlRequest;{Будет хранить текущее действие персонажа}
    Chars: IAgentCtlCharacterEx;{Хранит сам персонаж для обращения к нему}
public    { Public declarations }
end;

Теперь пропишем обработчик события OnCreate главной формы.

procedure TForm1.FormCreate(Sender: TObject);
begin
 Agent1.Characters.Load('MyAgent', 'Genie.acs');  {Здесь мы загружаем персонаж из файла 'merlin.acs' под именем 'Merlin'.   Вы можете здесь вписать свое имя и имя файла, имеющегося у вас персонажа}
 Chars:= Agent1.Characters.Character('MyAgent') as IAgentCtlCharacterEx;  {Запоминаем персонаж в переменной Chars}
 Req:=Chars.Show(0);  {Наконец-то показываем персонаж, и запоминаем, что он делает в данное вермя.   Будем всегда так поступать, т.к. это понадобится, если мы вдруг пожелаем   остановить анимацию в ходе ее выполнения}
end;

Не забудьте прописать выгрузку персонажа при закрытии формы:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); 
begin  
 Agent1.Characters.Unload('MyAgent'); 
end;

Скомпилируйте и запустите проект. Ну как?

А теперь давайте попробуем сделать загрузку персонажа во время работы программы. То есть дадим возможность пользователю загружать персонаж самостоятельно из имеющихся у него на компьютере. Кстати, сейчас мы указывали только имя файла при загрузке пресонажа, без его пути. Поэтому сначала система будет искать персонаж в подпапке каталога с Windows «C:\Windows\MSAgent\Chars\», а если не найдет, то будет error.

Поместим на форму компонет OpenDialog, укажем маску файлов *.acs. Затем поместим на форму батон (ну, то есть Button) и определим обработчик события на щелчок мыши:

procedure TForm1.Button1Click(Sender: TObject); 
begin  
if OpenDialog1.Execute then //Если выбрали файл     begin     
 if Chars<>nil then {Если какой-то персонаж загружен, выгружаем его}         
  Agent1.Characters.Unload('MyAgent');
  Agent1.Characters.Load('MyAgent', OpenDialog1.FileName);
  Chars:=  Agent1.Characters.Character('MyAgent') as IAgentCtlCharacterEx;
  Req:=Chars.Show(0);    
 end; 
end;

Теперь давайте поперемещаем наш персонаж по экрану. К примеру, вписываем в два поля редактирования координаты позиции агента, и по клику соответстующей кнопки персонаж перелетает на новое место. Два Edit’а обзовем PosX и PosY, а в OnClick «соответствующей кнопки» запишем:

procedure TForm1.Button2Click(Sender: TObject); 
begin
   Chars.Stop(Req);   {Останавливаем выполняемое действие.
   Правда остановим его,в том случае если мы его запоминали}
   Req:=Chars.MoveTo(StrToInt(PosX.Text), StrToInt(PosY.Text), 1); 
end;

Надеюсь, вы не догадаетесь ввести в поля редактирования буковки, ведь это место не защищено от подобных ситуаций. Вы, наверное,поняли, что метод MoveTo персонажа перемещает его в позицию X, Y. Третий целочисленный параметр указывает с какой скоростью нужно перемещать персонаж. Чем больше число, тем медленнее перемещается агент. При нуле персонаж будет перелетать мнгновенно.

Еще одно интересное замечание. Анализируя значение Req.Status можно выяснить следующую информацию о запросе к MS Agent’у:

0 — успешно выполнено
1 — аварийно завершено
2 — не закончено
3 — прервано
4 — выполняется

Теперь попробуем сделать так, чтобы при загрузке персонажа в ListBox помещались названия всех анимаций, а по выделению одной из анимаций в списке, она выполнялась персонажем. Сначала создадим процедуру вывода всех анимаций. Например вот так:

procedure ShowAgentAnim; 
var   AEnum: IEnumVariant;
   flag: Cardinal;
   V: OleVariant;
begin
  with Form1 do
    begin
     AEnum:=(Chars.AnimationNames.Enum) as IEnumVariant;
     {Получаем интерфейс анимаций агента}
     AEnum.Reset; //Сбрасываем список на первую анимацию
     ListBox1.Items.Clear;
     repeat
       AEnum.Next(1, V, flag);
       if VarToStr(V) <> '' Then
         ListBox1.Items.Add(V);
     until flag=0;
     {Флажок будет равен 0, когда мы узнали имя последней анимации}
    end;
end;

Еще одно отступление. Попробуйте сейчас скомпилировать проект. Если Delphi показывает ошибку в строчке AEnum: IEnumVariant — необъявленный идентификатор, то наверняка в списке включаемых модулей в секции uses отсутствует модуль ActiveX. Включите его вручную, и ошибка изчезнет, так как IEnumVariant объявлен именно в этом модульке.

Однако, замечено странное явление в Delphi7. Об этом многие писали мне, а так же в гостевой. В этой процедуре программа падает при выполнении в строке:

AEnum:=(Chars.AnimationNames.Enum) as IEnumVariant;

Падает с самым общим исключением EAccessViolation. Такое явление наблюдается только в Delphi7. При компиляции на Delphi6 в Win2000 — все нормально, а в Delphi5 на Win98 — тем более. До сих пор не разобрался точно в чем причина. Но есть некоторое решение проблемы. Я попробовал в Delphi7 на WinXP файлы созданные при импортировании агента (AgentObjects_TLB.pas, AgentServerObjects_TLB.pas — в папке

$Delphi\Imports) заменить файлами, созданными в Delphi5. На моей системе заработало, пишите, кому помогло, а кому нет. На всякий случай выкладываю эти файлы, если у кого нет. Качать здесь!

Теперь придется вставить нашу процедуру создания списка анимаций в обработчик события создания формы и в процедуру загрузки персонажа. Таким образом наши процедурки примут вид:

procedure TForm1.FormCreate(Sender: TObject);
begin
   Agent1.Characters.Load('MyAgent', 'Genie.acs');
   Chars:= Agent1.Characters.Character('MyAgent') as IAgentCtlCharacterEx;
   ShowAgentAnim; //Создаем список анимаций
   Req:=Chars.Show(0);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
     begin
     if Chars<>nil then
       Agent1.Characters.Unload('MyAgent');
     Agent1.Characters.Load('MyAgent', OpenDialog1.FileName);
     Chars:= Agent1.Characters.Character('MyAgent') as IAgentCtlCharacterEx;
     ShowAgentAnim; //Создаем список анимаций
      Req:=Chars.Show(0);
    end;
end;

Ну и наконец создадим процедуру проигрывания анимаций по клику в ListBox’е:

procedure TForm1.ListBox1Click(Sender: TObject);
begin
   Chars.Stop(Req);
   Req:=Chars.Play(ListBox1.Items.Strings[ListBox1.ItemIndex])
end;

Самым интересным методом у MS Agent является , наверное, метод Speak:

function Speak(Text: OleVariant; Url: OleVariant): IAgentCtlRequest;

Персонаж прочитает вам голосом введенный текст. Правда для этого вам придется установить пакет речевых функций Speech API и какой-нибудь голосовой синтезатор — движок Text-To-Speech (TTS). Подробнее программирование речи в Delphi мы резберем в следующем резделе Speech API в Delphi, там мы продолжим совершенствовать проект, а сейчас посмотрим как синхронизировать два персонажа между собой, то есть попробуем устроить между ними небольшой диалог.

Естественно, что нам понадобится еще одна переменная Chars2: IAgentCtlCharacterEx, а для того чтобы достичь «понимания» между двумя персонажами такой вот метод:

function Wait(const WaitForRequest: IDispatch): IAgentCtlRequest;

Сохраните как-нибудь текущий проект, я, к примеру, его обозвал AgentTest, и попробуйте создать новый проект с таким кодом в создании формы:

procedure TForm1.FormCreate(Sender: TObject);
begin
   Agent1.Characters.Load('MyAgent', 'Genie.acs');
   Chars:=Agent1.Characters.Character('MyAgent') as IAgentCtlCharacterEx;
   Chars.MoveTo(200, 200, 1);
   Chars.Show(0);
   Chars.Set_LanguageID($419);//Меняем язык на русский
   Agent1.Characters.Load('MyAgent2', 'Merlin.acs');
   Chars2:=Agent1.Characters.Character('MyAgent2') as IAgentCtlCharacterEx;
   Chars2.Set_LanguageID($419);
   Chars.Speak('Привет! '+Chars.Get_Description, '');
   Chars.Play('GestureLeft');
   Chars2.Wait(Chars.Speak('А вот и мой друг Мерлин!', ''));    {Мерлин ждет пока его представит Джинн}
   Chars2.MoveTo(400, 200, 0);
   Chars2.Show(0); //А теперь появляется сам Мерлин
   Chars.Play('RestPose');
   Chars2.Speak('Привет!', '');
   Chars.Play('LookLeft');
   Chars.Wait(Chars2.Speak('Спасибо за представление!', ''));
   Chars2.Wait(Chars.Play('Acknowledge'));
   Chars.Play('LookLeft');
   Chars.Wait(Chars2.Speak('Привет! '+Chars2.Get_Description, ''));
   Chars2.Wait(Chars.Play('Congratulate'));
   Chars.Play('RestPose');
   Chars2.Speak('Пока!', '');
   Chars2.Play('Wave');
   Chars2.Hide(0);
end;

На этом ,пожалуй, можно закончить. Думаю, теперь вам стали понятны основные принципы программирования агентов.

Speak up! Let us know what you think.