Муки диалоговые
Йохохо и бутылка рому! В общем, поскольку у вашего покорного разламывается башка, он не нашел ничего лучше, чем настрочить небольшую заметку об истории борьбы и преодоления.
Предположим, что вы делаете квыст с кучей диалогов. Диалоги и без того сильно подрезаны синдромом массэффекта, потому как кое-кто решил, что пяти ответов хватит, и теперь нехочет перепиливать кучу систем под это дело, так вдобавок добавление одной фразы выглядит как-то так:
Теперь представьте, что за гаплык творится, когда таких диалогов становится полсотни, и в некоторых - по 15 вот такого типа блоков.
Будучи ленивой задницей, я решил, что мне надо ужать это до чего-то хотя бы вот такого:
Но как?
В данный момент диалоги лежат в виде индексированных текстовых файлов в папке Content\Text\<locale>\phr_<dialog_id>_<phrase_id>.txt
Чтобы пилить диалоги (и вообще всю текстовку) и не сойти с ума, пришлось запилить вот такую софтину:
Очевидно, что несложно добавить некоторый функционал, который бы помог решить проблему, но что, йолки, делать? Идея родилась следующая: поскольку данные о фразе диалога хранятся в унифицированной структуре:
Почему бы не повторить ее в виде своеобразного файла-манифеста, который и будет нести блок информации о фазе диалога? Сказано-сделано, на выходе имеем такой вот код на делфи для чтения/записи данных:
//Это записывает данные procedure TForm3.Button1Click(Sender: TObject); var i,j:integer; begin Memo1.Clear; //adding data if CheckBox1.Checked=true then Memo1.Text:=Memo1.Text+'1' else Memo1.Text:=Memo1.Text+'0'; if CheckBox2.Checked=true then Memo1.Text:=Memo1.Text+'1' else Memo1.Text:=Memo1.Text+'0'; if CheckBox3.Checked=true then Memo1.Text:=Memo1.Text+'1' else Memo1.Text:=Memo1.Text+'0'; if CheckBox4.Checked=true then Memo1.Text:=Memo1.Text+'1' else Memo1.Text:=Memo1.Text+'0'; if CheckBox5.Checked=true then Memo1.Text:=Memo1.Text+'1' else Memo1.Text:=Memo1.Text+'0'; //adding data for j:=0 to 2 do for i:=0 to 4 do begin Memo1.Text:=Memo1.Text+StringGrid1.Cells[j,i]+';'; end; Memo1.Lines.SaveToFile(contentloc+'text\phasemanifest_'+form1.edit2.text+'_'+Form1.Edit3.Text+'.txt'); end; //а это читает данные procedure TForm3.Button2Click(Sender: TObject); var ms,wrd:string; i,j:integer; k:integer; begin Memo1.Lines.LoadFromFile(contentloc+'text\phasemanifest_'+form1.edit2.text+'_'+Form1.Edit3.Text+'.txt'); ms:=memo1.Text; if ms[1]='1' then CheckBox1.Checked:=true else CheckBox1.Checked:=false; if ms[2]='1' then CheckBox2.Checked:=true else CheckBox2.Checked:=false; if ms[3]='1' then CheckBox3.Checked:=true else CheckBox3.Checked:=false; if ms[4]='1' then CheckBox4.Checked:=true else CheckBox4.Checked:=false; if ms[5]='1' then CheckBox5.Checked:=true else CheckBox5.Checked:=false; wrd:=''; i:=0; j:=0; for k:=6 to length(ms) do begin if ms[k]=';' then begin StringGrid1.Cells[j,i]:=wrd; wrd:=''; i:=i+1; if i>4 then begin i:=0; j:=j+1; end; end else begin wrd:=wrd+ms[k]; end; end; end;
Будучи редкостным лентяем, вместо использования заассайненного файла, я пользовал методы сохранения/загрузки на компоненте Memo, ибо нефиг.
Интерфейс вышел вот такой:
Справа как раз и можно глянуть на манифест в зашифрованном виде. Галочки с надписями use отвечают за указание на то, будет ли этот кусок добавлен в массивы флагов в конечной структуре.
Отлично! Дело сделано!
Ээээ... Стопэ. А как игра будет читать это дело?
В свое время я раскопал решения для удобного чтения текстовых файлов с диска в анрыловский апи:
//ЭТО В ХЕДЕРЕ UFUNCTION(BlueprintCallable, Category = "save") static bool FileSaveString(FString SaveTextB, FString FileNameB); UFUNCTION(BlueprintPure, Category = "save") static bool FileLoadString(FString FileNameA, FString& SaveTextA); //ЭТО В ЦПП ФАЙЛЕ bool Ucppfunctions::FileSaveString(FString SaveTextB, FString FileNameB) { return FFileHelper::SaveStringToFile(SaveTextB, *(FPaths::ProjectDir() + FileNameB)); } bool Ucppfunctions::FileLoadString(FString FileNameA, FString& SaveTextA) { return FFileHelper::LoadFileToString(SaveTextA, *(FPaths::ProjectDir() + FileNameA)); }
Это, конечно, чудесно - я могу без проблем прочесть манифест... Но как его проанализировать и выдернуть данные? Ну, к счастью, тип FString прописан по-людски, и потому вполне работает вот такой код:
//ЭТО В ХЕДЕРЕ UFUNCTION(BlueprintCallable, Category = "save") static void ProcessPhraseManifest(FString FManText, bool& use0, bool& use1, bool& use2, bool& use3, bool& use4, bool& rfc0, bool& rfc1, bool& rfc2, bool& rfc3, bool& rfc4, int& rfid0, int& rfid1, int& rfid2, int& rfid3, int& rfid4, int& rphid0, int& rphid1, int& rphid2, int& rphid3, int& rphid4); //ЭТО В ЦППШКЕ void Ucppfunctions::ProcessPhraseManifest(FString FManText, bool& use0, bool& use1, bool& use2, bool& use3, bool& use4, bool& rfc0, bool& rfc1, bool& rfc2, bool& rfc3, bool& rfc4, int& rfid0, int& rfid1, int& rfid2, int& rfid3, int& rfid4, int& rphid0, int& rphid1, int& rphid2, int& rphid3, int& rphid4) { bool u0; bool u1; bool u2; bool u3; bool u4; if (FManText[0] == '0') { u0 = false; } else { u0 = true; } if (FManText[1] == '0') { u1 = false; } else { u1 = true; } if (FManText[2] == '0') { u2 = false; } else { u2 = true; } if (FManText[3] == '0') { u3 = false; } else { u3 = true; } if (FManText[4] == '0') { u4 = false; } else { u4 = true; } use0 = u0; use1 = u1; use2 = u2; use3 = u3; use4 = u4; int i = 5; int ph = 0; int itm = 0; int ll = FManText.FString::Len(); FString wrd; while (i < ll - 1) { if (FManText[i] == ';') { if (ph == 0) { if (itm == 0) { if (wrd == "0") { rfc0 = false; } else { rfc0 = true; } } if (itm == 1) { if (wrd == "0") { rfc1 = false; } else { rfc1 = true; } } if (itm == 2) { if (wrd == "0") { rfc2 = false; } else { rfc2 = true; } } if (itm == 3) { if (wrd == "0") { rfc3 = false; } else { rfc3 = true; } } if (itm == 4) { if (wrd == "0") { rfc4 = false; } else { rfc4 = true; } } } if (ph == 1) { if (itm == 0) { rfid0=FCString::Atoi(*wrd); } if (itm == 1) { rfid1 = FCString::Atoi(*wrd); } if (itm == 2) { rfid2 = FCString::Atoi(*wrd); } if (itm == 3) { rfid3 = FCString::Atoi(*wrd); } if (itm == 4) { rfid4 = FCString::Atoi(*wrd); } } if (ph == 2) { if (itm == 0) { rphid0 = FCString::Atoi(*wrd); } if (itm == 1) { rphid1 = FCString::Atoi(*wrd); } if (itm == 2) { rphid2 = FCString::Atoi(*wrd); } if (itm == 3) { rphid3 = FCString::Atoi(*wrd); } if (itm == 4) { rphid4 = FCString::Atoi(*wrd); } } wrd = ""; itm++; if (itm == 5) { itm = 0; ph++; } } else { wrd += FManText[i]; } i++; } return; }
Окай, дело. Громоздко, но работает, гном счастлив. Теперь остается добавить немного соли, сыра и сахару, в виде уже блюпринтовой функции, которая будет это дело обрабатывать. А точнее, даже двух. Первая читает данные из манифеста и пишет их в структуру:
Вторая ей помогает, формируя массивы на основе набора флагов:
Запускаем @ проверяем!
И вот таким вот образом я никоим образом не поправил себе состояние башки, но зато немного упростил жизнь с формированием блоков диалогов в моем magnum opus. Всем мира и корзинок!
Смотрите также:
Комментарии
Что то мне подсказывает что можно было бы обойтись более простой кровью... В виде набора хелпкрных функций для блупринта формировать граф диалога и потом использовать его. Хотя я не уверен (вообще без понятия как работает блюпринт) поэтому возможно это единственное решение.
Но я помню тогда еще в далекие времена моддинга вара диалоговая система у меня строилась на каком то таком коде
Int dialogId = CreateDialogForUnit(...) AnswerId1 = CreateAnswerForDialog(dialogId,...) Answer_AddOpenDialogAction(AnswerId, anotherDialogId) AnswerId2 = CreateAnswerForDialog(dialogId,...) Answer_AddAction(Answer2,...)
И потом оно все автоматически работало. Может быть можно было бы это обернуть в удобные хелперы для блюпринта. Единственный минус обязательно рекомпилить для фикса диалогов
Хех, там что так что эдак рекомпиляция понадобится, каждый диалог сейчас нуждается в индивидуальной настройке узловой структуры, в которую заливаются фразы. Ну и, как ни крути, задача выросла из попытки привести в чувство уже существующую, но до ужаса субоптимальную систему, не ломая ее - потому что на нее уже завязано немало контента. Как-то так =)
Вполне можно, через ту же структуру. Хотя, наверное, удобнее было бы реализовывать это дело с помощью C++-класса вынесенного в отдельный объект-контроллер диалогов.
Дарин, ну не всегда удобно писать код для определения диалогов и настройки связей, особенно если есть возможно прокликивать и перетаскивать нужные блоки
CollectableItemData.cs
[CreateMenuItem(fileName = "newItem", menuName = "Data/Items/Collectable", order = 51]