Up.
OD RAZU MÓWIE ŻE FUNKCJA MAIN W REPO TO TAKI WIELKI POLIGON, docelowo będzie to ładniejsze ;>
Szczwany ja usunąłem poprzednie posty żeby nie było double znowu.
Dziś opowiem jak to działa.
Projekt jest rozwijany nadal, mam oczywiście szkołe ale i tak dosyć sprawnie idzie.
Kod silnika pisze jak na razie tylko ja, reszta czeka na skończenie go i wtedy bierzemy sie za gre.
To co doszło to:
Okienka z chmurkami, jescze nie dopicowane.

Dialogi oparte na drzewach n-arnych, wczytywane rekurencyjnie, co do głębokości to.. Nie mam pojęcia, pewnie dopókie sie nie wywali stos.


Dialogi były jedną z ciekawszych rzeczy z którymi miałem spory problem, ale to dobrze, bo przynajmniej czegoś sie nauczyłem :)
Struktura pliku z dialogami wygląda tak, gdzie musi być sekwencja
Reply( rozpoczyna rozmowe )
Potem player->reply->player->reply, i tak dalej.
Ogólnie "format" wygląda dosyć brzydko przy bardziej rozbudowanym dialogu, ale jest łatwy w edycji, strukture można zaobserwować na dialogach nr 2 i 3 bo są krótsze.
condition to warunek który będzie musiał być spełniony aby odpowiedź była widoczna, i będe robił coś w stylu VM która będzie to obsługiwać.
action natomiast to akcja którą wykonuje npc i enum od akcji wygląda tak
Kopiuj
<?xml version="1.0" encoding="UTF-8"?>
<dialogs>
<npc id="1">
<reply text="Witaj!">
<player text="Witaj magu!">
<reply text="Chcesz questa?">
<player text="Tak">
<reply text="Na moczarach są złote kalesony, przynieś je!">
<player text="Podejme się!" condition="level > 10">
<reply text="A więc idź, czekam!" action="3" questname="moczary0">
</reply>
</player>
<player text="Nie interesuje mnie to zadanie">
<reply text="Cóż, szkoda" >
</reply>
</player>
</reply>
</player>
<player text="Nie">
<reply text="Twoja strata :P">
</reply>
</player>
<player text="Co dostane w zamian?">
<reply text="Nagrode, bardzo chojną">
</reply>
</player>
</reply>
</player>
<player text="Dawaj itemy" >
<reply text="Czy to napad?">
<player text="Tak" condition="strength > 50">
<reply text="Okej koksie nie bij, masz" action="1" params="103,22,45,125,1024">
</reply>
</player>
<player text="Nie">
<reply text="Uff, to dobrze">
</reply>
</player>
<player text="Być może">
<reply text="Żadam dokładnej odpowiedzi">
<player text="Tak">
<reply text="Więc walcz!">
</reply>
</player>
<player text="Nie">
<reply text="Uff, to dobrze">
<player text="Nom">
<reply text="Hiho">
<player text="Co?">
<reply text="Nic">
<player text="Może jednak coś?">
<reply text="Nie, nic">
<player text="Na pewno?">
<reply text="No, na pewno">
</reply>
</player>
<player text="Daj piniążek">
<reply text="Nie mam :(">
<player text="Masz masz">
<reply text="Jakbym miał, i tak bym nie dał">
</reply>
</player>
<player text="No rozumiem, bida w krainie">
<reply text="Niestety">
<player text="No daj dolara">
<reply text="No nie mam, serio">
<player text="maszmaszmaszmasz">
<reply text="Dobra żebraku, masz 10 golda i ić sobie">
<player text="Dziękuje Ci">
<reply text="Idź już">
</reply>
</player>
<player text="A dej jescze 10 golda">
<reply text="O ty chamie, walcz!">
</reply>
</player>
<player text="Daj jescze plix">
<reply text="Ile?">
<player text="1 zet">
<reply text="Tyle Ci moge dać">
</reply>
</player>
<player text="3 zeta">
<reply text="Okej, ale nie pokazuj sie tu przez tydzień!">
</reply>
</player>
<player text="10 zeta">
<reply text="O ty chamie!">
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
<player text="Żegnaj">
<reply text="3m sie">
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
<player text="Test1">
<reply text="Test3">
</reply>
</player>
<player text="Test2">
<reply text="Test4">
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
</reply>
</player>
<player text="Żegnaj">
<reply text="trzym sie">
</reply>
</player>
</reply>
</npc>
<npc id="2">
<reply text="Witaj w sklepie u Zbycha">
<player text="Witaj, co masz do sprzedania?">
<reply text="Spójrz">
</reply>
</player>
<player text="Co skupujesz?">
<reply text="Spójrz">
</reply>
</player>
<player text="Żegnaj!">
<reply text="Cześć">
</reply>
</player>
</reply>
</npc>
<npc id="3">
<reply text="Witaj :P">
<player text="Siema, co tam?">
<reply text="A dobrze, dobrze">
</reply>
</player>
<player text="Test">
<reply text="Git Test">
</reply>
</player>
<player text="Testowy tekst">
<reply text="Testowa odpowiedź ;)">
</reply>
</player>
</reply>
</npc>
</dialogs>
Kopiuj
enum ATYPE
{
//Wszystko raczej dość logiczne, RESERVED_FIELD to pola które sobie zarezerwowałem na przyszłość
NPC_ONLY_TALK, NPC_GIVE_ITEM, NPC_GET_ITEM, NPC_GIVE_QUEST,
NPC_VALID_QUEST, NPC_FIGHT, NPC_TRADE, NPC_RESERVED_FIELD_0, NPC_RESERVED_FIELD_1,
PLAYER_RESPONSE, ACTION_NOT_VALID=64
};
params to parametry liczbowe parsowane i ładowane do std::vector<int>, czyli np itemy które npc da graczowi.
Struktury z danymi pojedyńczego węzła zaprojektowałem tak, każdy NPC posiada n-arne drzewo struktur Action(dla każdego węzła przypada
jedna struktura, która będzie potem przetwarzana na czymś ala-VM i generowane będą odpowiednie sygnały np OPEN_SHOP, lub VM sama w sobie będzie miała te wszystkie okna i ona będzie je tworzyć.)
Wiem że nie jest to super-wydajne rozwiąnie, ale nie wiem w sumie czy przy dzisiejszej mocy i ilości pamięci komputerów faktycznie takie coś jest problemem, wydaje mi sie - że nie, nie ukrywam że dynamiczne zarządzanie drzewem byłoby trudniejsze do zrobienia, ale nic, czekam na wasze opinie, jeśli to jest fe - poprawie to :)
Kopiuj
struct HelpInfo
{
HelpInfo()
{}
HelpInfo(std::string _cond)
: helpstring(_cond)
{}
HelpInfo(const HelpInfo& r)
{
this->additdata = r.additdata;
this->helpstring = r.helpstring;
}
HelpInfo& operator=(const HelpInfo& rhs)
{
this->additdata = rhs.additdata;
this->helpstring = rhs.helpstring;
return *this;
}
std::vector<int> additdata;
std::string helpstring;
};
struct Action
{
Action()
: type_action(NPC_ONLY_TALK)
, ad_info(){}
Action(std::wstring t, ATYPE at, HelpInfo hi){
text = t;
type_action = at;
ad_info = hi;
}
Action(const Action& r){
this->type_action = r.type_action;
this->ad_info = r.ad_info;
this->text = r.text;
}
void clean(){
type_action = NPC_ONLY_TALK;
ad_info.helpstring.clear();
ad_info.additdata.clear();
}
ATYPE type_action;
HelpInfo ad_info;
std::wstring text;
};
Pomyślałem również że może ktoś/ja kiedyś będe chciał skorzystać drugi raz z niektórych pomysłów, więc wczytywanie i parsowanie danych, oraz formatowanie danych w chmurce, uczyniłem strategiami, które wyglądają tak.
Kopiuj
class IDataFormat
{
public:
IDataFormat(){};
virtual void format(const ItemData, const sf::Vector2f&,unsigned int&, sf::Font&, std::vector<sf::Text>&) = 0;
virtual ~IDataFormat(){};
};
class IDataRead
{
public:
IDataRead(){};
virtual void read(TiXmlElement*) = 0;
virtual Action get() = 0;
virtual ~IDataRead(){};
};
Przykładowo obiekt dziedziczący z IDataFormat(), formatuje dane w chmurce w taki sposób.
Gdzie stricte CAŁE dane nt ułożenia, koloru itp, są przetwarzane na tej funkcji.
Wywoływana ona jest dosyć czytelnie(chyba :p) w taki sposób
Kopiuj
// Wywoływane w main, co iteracje odświeżane, wiem że możnaby zrobić to
// W stylu "refresh-if-needed", ale jak narazie IMO to chyba przedwczesne kombinacje-optymalizacje by były.
void EquipmentWindow::controlCloud(sf::Vector2i vct)
{
// Jeśli mysza najedzie na item, gid będzie niezerowe.
unsigned int gid = getGID(vct);
if(gid)
{
// czyszczone
descriptions.clear();
// przetwarzane
formatter.format(_imgr.getData(gid), cloud.getPosition(),cloud_border,font,descriptions);
hoover = true;
cloud.setPosition(sf::Vector2f(vct.x+15, vct.y));
return;
}
hoover = false;
}
Kopiuj
void EquipFormat::format(const ItemData itm, const sf::Vector2f& cloud_pos,unsigned int &border,sf::Font& font,std::vector<sf::Text>& desc)
{
std::basic_string<sf::Uint32> utf32;
sf::Color col;
sf::Utf8::toUtf32(itm.name.begin(), itm.name.end(), std::back_inserter(utf32));
sf::Text some_name(utf32, font, 15);
switch(itm.quality)
{
case 0: col = sf::Color::Blue; break;
case 1: col = sf::Color::Black; break;
case 2: col = sf::Color::Yellow; break;
case 3: col = sf::Color::Red; break;
}
some_name.setColor(col);
some_name.setPosition(cloud_pos.x, cloud_pos.y);
desc.push_back(some_name);
switch(itm.type)
{
case WEAPON:
utf32.clear();
utf32 = to32UTF<std::string>("Atak:");
utf32 += to32UTF<std::string>(std::to_string(itm.attack));
some_name.setString(utf32);
some_name.setCharacterSize(13);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+16);
some_name.setColor(sf::Color::Red);
desc.push_back(some_name);
utf32.clear();
utf32 = to32UTF<std::string>("Obrona:");
utf32 += to32UTF<std::string>(std::to_string(itm.defence));
some_name.setString(utf32);
some_name.setColor(sf::Color::Green);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+(14*2));
desc.push_back(some_name);
utf32.clear();
utf32 = to32UTF<std::string>("Szybkość:");
utf32 += to32UTF<std::string>(std::to_string((int)itm.speed));
utf32 += to32UTF<std::string>("%");
some_name.setString(utf32);
some_name.setColor(sf::Color::Cyan);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+(14*3));
desc.push_back(some_name);
break;
case ARMOR:
utf32.clear();
utf32 = to32UTF<std::string>("Obrona:");
utf32 += to32UTF<std::string>(std::to_string(itm.defence));
some_name.setString(utf32);
some_name.setCharacterSize(13);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+16);
some_name.setColor(sf::Color::Green);
desc.push_back(some_name);
break;
case RECOVERY:
utf32.clear();
utf32 = to32UTF<std::string>("Siła odnawiania:");
utf32 += to32UTF<std::string>(std::to_string(itm.power));
utf32 += to32UTF<std::string>("%");
some_name.setString(utf32);
some_name.setColor(sf::Color::Yellow);
some_name.setCharacterSize(10);
some_name.setPosition(cloud_pos.x+border, cloud_pos.y+16);
desc.push_back(some_name);
break;
default:
break;
}
utf32.clear();
utf32 = to32UTF<std::wstring>(itm.description);
some_name.setString(utf32);
some_name.setCharacterSize(13);
some_name.setColor(sf::Color::Yellow);
some_name.setPosition(cloud_pos.x, cloud_pos.y+(16*4));
desc.push_back(some_name);
}
Natomiast wczytywanie/parsowanie węzłów drzewa rozwiązane zostało tak.
Oto rekurencyjna funkcja wczytująca drzewo dialogów, dane przetwarzane są w funkcji read, i pobierane funkcją get.
Kopiuj
void NpcManager::loadTree(TiXmlElement* npc_el, NPC &npc, tree<Action>::sibling_iterator go_deeper, bool first)
{
TiXmlElement *reply = npc_el;
tree<Action>::iterator root;
tree<Action>::sibling_iterator sit;
std::string txt = reply->Attribute("text");
Action _tmp(wide_string<std::wstring>(txt), NPC_ONLY_TALK, HelpInfo());
if(first)
root = npc.dialog.insert(npc.dialog.begin(), _tmp);
if(reply->FirstChildElement("player") != NULL)
{
TiXmlElement *player = reply->FirstChildElement("player");
if(first)
sit = root;
else
sit = go_deeper;
while(player)
{
reader.read(player);
auto p_reply = npc.dialog.append_child(sit,reader.get());
if(player->FirstChildElement("reply") != NULL)
{
TiXmlElement* npc_quote = player->FirstChildElement("reply");
reader.read(npc_quote);
npc.dialog.append_child(p_reply,reader.get());
loadTree(player->FirstChildElement("reply"), npc, p_reply, 0);
}
player = player->NextSiblingElement("player");
}
}
else return;
}
Parsowanie rozwiązałem tak, current_processed to struktura Action w polach klasy DataRead, która pobierana jest funkcją get().
Generalnie zrobiłem tak, żeby funkcje które nie odpowiadają stricte za parsowanie, nie były tak "opasłe", co prawda mógłbym uczynić to metodą
klasy NpcManager gdzie wczytuje dane, ale przy modyfikacjach I TAK musiałbym tą klase modyfikować, a jak pisałem, nie chce tego, co najwyżej rozszerzyć strukture o odpowiednie pola.
Kopiuj
void DataRead::read(TiXmlElement* ent)
{
current_processed.clean();
current_processed.text = wide_string<std::wstring>(ent->Attribute("text"));
std::string parsed_node_type = ent->Value();
if(parsed_node_type == "reply")
{
if(ent->Attribute("action") != NULL)
{
current_processed.type_action = (ATYPE)(atoi(ent->Attribute("action")));
std::string params;
if(ent->Attribute("params") != NULL)
params = ent->Attribute("params");
switch(current_processed.type_action)
{
case NPC_GIVE_ITEM:
parseParams(params,current_processed.ad_info.additdata,',');
break;
case NPC_GET_ITEM:
parseParams(params,current_processed.ad_info.additdata,',');
break;
case NPC_GIVE_QUEST:
if(ent->Attribute("questname") != NULL)
current_processed.ad_info.helpstring = ent->Attribute("questname");
break;
case NPC_TRADE:
parseParams(params,current_processed.ad_info.additdata,',');
break;
}
}
}
if(parsed_node_type == "player")
{
ATYPE type;
if(ent->Attribute("action") == NULL)
type = PLAYER_RESPONSE;
else if(ent->Attribute("action") != NULL)
{
type = (ATYPE)atoi(ent->Attribute("action"));
if(type < PLAYER_RESPONSE)
type = ACTION_NOT_VALID;
}
current_processed.type_action = type;
if(ent->Attribute("condition") != NULL)
current_processed.ad_info.helpstring = ent->Attribute("condition");
}
}