Jusper Jusper

alexprey, вероятно, что они хотят спроецировать систему подписок + лизинг.
Мне оч любопытно, насколько это зайдет в нашей стране.

alexprey alexprey

Мне вот интересно чем это будет отличаться от обычной рассрочки, которую предлагают во всех сетевых магазинах техники? Дополнением подпиской?

alexprey alexprey

О, дискорд стор, ждем обязательно рассказ о процессе подачи игры в этот магазин

rommio_g rommio_g

Класс! Нравятся такие игры!

Jusper Jusper

rommio_g, как же мне нравится стилистика. так держать!

Jusper Jusper

nikita.kart, дам тебе офигенный совет.
Есть тулзы для Dungeons and Dragons 4.0. Одна - для создания персонажей, другая для ведения конкретной сцены и отслеживания боевки. Рекомендую посмотреть референсы их интерфейсов...

nikita.kart nikita.kart

Всем привет) Разрабатываю текстовую рпг
devtribe.ru/p/BookOfDungeon

rommio_g rommio_g

Даже у огромных древних существ есть свои проблемы...

Dreaman Dreaman

Звук конечно оставляет желать лучшего, но в целом интересно ;)

Jusper Jusper

Dreaman,

Классные ассеты! Но не дешёвые конечно ) Кстати, если будет время и желание, то потом как-нибудь сделай подборку хороших бесплатных ассетов. Таких тоже полно ;)

Dreaman Dreaman

Классные ассеты! Но не дешёвые конечно ) Кстати, если будет время и желание, то потом как-нибудь сделай подборку хороших бесплатных ассетов. Таких тоже полно ;)

Jusper Jusper

iRediKurou, а вы разве на анриле сидите?

Логотип проекта Unreal Engine

Муки диалоговые

Муки диалоговые

Йохохо и бутылка рому! В общем, поскольку у вашего покорного разламывается башка, он не нашел ничего лучше, чем настрочить небольшую заметку об истории борьбы и преодоления.

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

Муки диалоговые — Unreal Engine — DevTribe: Разработка игр (blueprints, C++, ue4, разработка игр, укрупнение, диалоговаясистема)

Теперь представьте, что за гаплык творится, когда таких диалогов становится полсотни, и в некоторых - по 15 вот такого типа блоков.

Будучи ленивой задницей, я решил, что мне надо ужать это до чего-то хотя бы вот такого:

Муки диалоговые — Unreal Engine — DevTribe: Разработка игр (blueprints, C++, ue4, разработка игр, укрупнение, диалоговаясистема)

Но как?

В данный момент диалоги лежат в виде индексированных текстовых файлов в папке Content\Text\<locale>\phr_<dialog_id>_<phrase_id>.txt

Чтобы пилить диалоги (и вообще всю текстовку) и не сойти с ума, пришлось запилить вот такую софтину:

Муки диалоговые — Unreal Engine — DevTribe: Разработка игр (blueprints, C++, ue4, разработка игр, укрупнение, диалоговаясистема)

Очевидно, что несложно добавить некоторый функционал, который бы помог решить проблему, но что, йолки, делать? Идея родилась следующая: поскольку данные о фразе диалога хранятся в унифицированной структуре:

Муки диалоговые — Unreal Engine — DevTribe: Разработка игр (blueprints, C++, ue4, разработка игр, укрупнение, диалоговаясистема)

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

//Это записывает данные
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, ибо нефиг.

Интерфейс вышел вот такой:

Муки диалоговые — Unreal Engine — DevTribe: Разработка игр (blueprints, C++, ue4, разработка игр, укрупнение, диалоговаясистема)

Справа как раз и можно глянуть на манифест в зашифрованном виде. Галочки с надписями 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;
	
}

Окай, дело. Громоздко, но работает, гном счастлив. Теперь остается добавить немного соли, сыра и сахару, в виде уже блюпринтовой функции, которая будет это дело обрабатывать. А точнее, даже двух. Первая читает данные из манифеста и пишет их в структуру:

Муки диалоговые — Unreal Engine — DevTribe: Разработка игр (blueprints, C++, ue4, разработка игр, укрупнение, диалоговаясистема)

Вторая ей помогает, формируя массивы на основе набора флагов:

Муки диалоговые — Unreal Engine — DevTribe: Разработка игр (blueprints, C++, ue4, разработка игр, укрупнение, диалоговаясистема)

Запускаем @ проверяем!

Муки диалоговые — Unreal Engine — DevTribe: Разработка игр (blueprints, C++, ue4, разработка игр, укрупнение, диалоговаясистема)

И вот таким вот образом я никоим образом не поправил себе состояние башки, но зато немного упростил жизнь с формированием блоков диалогов в моем magnum opus. Всем мира и корзинок!



Что то мне подсказывает что можно было бы обойтись более простой кровью... В виде набора хелпкрных функций для блупринта формировать граф диалога и потом использовать его. Хотя я не уверен (вообще без понятия как работает блюпринт) поэтому возможно это единственное решение.
Но я помню тогда еще в далекие времена моддинга вара диалоговая система у меня строилась на каком то таком коде

Int dialogId = CreateDialogForUnit(...)
AnswerId1 = CreateAnswerForDialog(dialogId,...)
Answer_AddOpenDialogAction(AnswerId, anotherDialogId)
AnswerId2 = CreateAnswerForDialog(dialogId,...)
Answer_AddAction(Answer2,...)

И потом оно все автоматически работало. Может быть можно было бы это обернуть в удобные хелперы для блюпринта. Единственный минус обязательно рекомпилить для фикса диалогов

Хех, там что так что эдак рекомпиляция понадобится, каждый диалог сейчас нуждается в индивидуальной настройке узловой структуры, в которую заливаются фразы. Ну и, как ни крути, задача выросла из попытки привести в чувство уже существующую, но до ужаса субоптимальную систему, не ломая ее - потому что на нее уже завязано немало контента. Как-то так =)

Дарин, ну т.е. сделать такой граф диалогов в блюпринте впринципе возможно?

Вполне можно, через ту же структуру. Хотя, наверное, удобнее было бы реализовывать это дело с помощью C++-класса вынесенного в отдельный объект-контроллер диалогов.

Дарин, ну не всегда удобно писать код для определения диалогов и настройки связей, особенно если есть возможно прокликивать и перетаскивать нужные блоки