Здесь я расскажу, то что знаю о технологии 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.
Если вы уже скачали и установили 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 (а на какую еще нормальный 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;
На этом ,пожалуй, можно закончить. Думаю, теперь вам стали понятны основные принципы программирования агентов.