Struktura danych bez ECS w silniku gry

Struktura danych bez ECS w silniku gry
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Obmyślam strukturę zarządzania danymi w silniku gry, pisanym w C++. Z jednej strony chciałbym, aby całość była dosyć elastyczna i nadawała się do różnych projektów (nie chcę silnika zaprojektowanego z myślą o jednej grze, czy jednym typie gier), ale też nie porywam się z motyką na słońce i chcę go używać do małych, hobbystycznych gierek, więc wolę trzymać się zwykłej obiektówki, bez systemów w stylu ECS.

Tyle tytułem wstępu. Obecnie zamysł na całość mam taki. Jest główna klasa abstrakcyjna ObjectBase:

Kopiuj
class ObjectBase
{
protected:
	virtual void Update() = 0;
 	virtual void Render() = 0;
};

Każdy taki obiekt może istnieć samodzielnie, jako Object, stanowiący wrapper na ObjectBase:

Kopiuj
template<typename T> requires std::is_base_of_v<ObjectBase, T>
class Object
{
private:
	T* Data = nullptr;
	ObjectBase** Link = nullptr;
public:
	T* operator->();
	void Allocate();
	void Destroy();
};

Albo też jako część grupy obiektów ObjectList:

Kopiuj
template<typename T> requires std::is_base_of_v<ObjectBase, T>
class ObjectList
{
private:
	T* List = nullptr;
	ObjectBase** Link = nullptr;
public:
	T& operator[](const uint16_t& uiIndex);
	void Allocate(const uint16_t& uiIndex);
	void Destroy(const uint16_t& uiIndex);
	void AllocateAll();
	void DestroyAll();
};

Z tych komponentów można potem zbudować szkielet poziomu gry, np.:

Kopiuj
class LevelBase
{
private:
	ObjectBase** List;
public:
	void AllocateAll();
	void DestroyAll();
};

class Level : public LevelBase
{
public:
	Object<Player> Player;
	struct NPC
	{
		ObjectList<Merchant> Merchant;
	} NPC;
	struct Enemies
	{
		ObjectList<Orc> Orc;
		ObjectList<Skeleton> Skeleton;
	} Enemies;
	struct Weapons
	{
		ObjectList<Sword> Sword;
		ObjectList<Dagger> Dagger;
	} Weapons;
};

Dzięki temu obiekty są dosyć wygodnie posortowane i można uzyskać łatwy dostęp do rzeczywistych typów bez konieczności rzutowania, np.:

Kopiuj
Level level[4];
level[0].Enemies.Skeleton[24].Damage = 10;

Dodatkowo wszystkie obiekty są spięte w jedną listę wskaźników ObjectBase** List, do której łatwo może podpiąć się globalny dla silnika ObjectBase** World, z którego dane o obiektach świata gry czerpią wątki odpowiedzialne za odświeżanie logiki i renderowanie. Z tego względu oba wrappery, tj. Object i ObjectList mają wskaźnik ObjectBase** Link, który muszą pozyskać z klasy Level. Dzięki temu wywołując metodę Allocate() i jej pochodne, nie tylko następuje alokacja pamięci dla obiektu, ale zostaje też on podpięty pod ObjectBase** List i tym samym staje się częścią świata (inaczej silnik go nie "zobaczy").

Minusy są takie, że każdy Level wymaga wielgaśniego konstruktora, bo każdy wrapper musi jakoś uzyskać dostęp do ObjectBase** List, żeby móc podpinać do niej nowe obiekty. Po drugie jest to dosyć nieelastyczne, bo jak chcę dodać nowy element do poziomu albo nową grupę elementów, to muszę przerabiać klasę i konstruktor (zwłaszcza gdybym chciał dla każdego poziomu mieć indywidualną strukturę).

Mógłbym mieć po prostu jedną, wielką listę obiektów, a potem uzyskiwać do nich dostęp przez rzutowanie, np.:

Kopiuj
class World
{
private:
	ObjectBase** List = nullptr;
public:
	template<typename T>
	T& Get(const uint16_t uiIndex)
	{
		return *static_cast<T*>(List[uiIndex]);
	}
};

World world;
world.Get<Orc>(12).Health = 76;

Tylko wtedy musiałbym wiedzieć, że np. obiekt o indeksie 1276 jest np. dokładnie tym mieczem, którego akurat szukam, co byłoby raczej mało praktyczne. Z tego względu najbardziej by mi odpowiadało, gdyby dało się to zrobić dynamiczne i modularnie. Na przykład tworzę sobie obiekt od szablonu:

Kopiuj
World<Orc, Sword, Dragon> world;

A potem mogę się odwoływać do poszczególnych typów obiektów, gdzie każdy typ ma własną listę, z własnymi indeksami:

Kopiuj
world.Get<Orc>(0).Health = 11; //Pierwszy Orc z listy Orców
world.Get<Dragon>(0).AoEDamage = 76.21f; //Pierwszy smok z listy smoków.

To też nie idealne rozwiązanie, ale już w miarę sensowne do ogarnięcia. No ale to wymagałoby (?) specjalizacji szablonów, a tego chyba nie da się (?) zrobić modularnie, przez dziedziczenie.

Ktoś ma jakieś pomysły albo sugestie jak taki system zbudować, żeby był wygodny i w miarę elastyczny?

edytowany 3x, ostatnio: Riddle
Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
0

więc wolę trzymać się zwykłej obiektówki, bez systemów w stylu ECS.

Wszystkie gierki które dotychczas tworzyłem były oparte o ECS (i żadnej z tych gierek w życiu nie przepisałbym na obiektówkę), więc jeśli szukasz czegoś wygodnego, elastycznego i sound, to jest to imo jedyna sensowna opcja.

Choć oczywiście nie wynajdywałbym koła na nowo - w samym Ruście jest z dwadzieścia różnych bibliotek implementujących ten pattern, więc domyślam się, że dla C++ powinny ich być setki.


edytowany 5x, ostatnio: Patryk27
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Oczywiście masz rację, że tak to się robi obecnie, natomiast mnie zastanawia, jak sobie radzono z tym kiedyś, gdy do dyspozycji była tylko obektówka i to bez bajerów z C++11 i w górę. To co wymyśliłem jest jakkolwiek zbliżone do dawniej stosowanych rozwiązań, czy zupełnie pobłądziłem? :).

flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:mniej niż minuta
  • Lokalizacja:Tuchów
  • Postów:12163
1

Z tego co napisałeś, mam wrażenie, że chcesz świat podłączyć pod zewnętrzną listę/y obiektów w nim się znajdujących, zamiast stworzyć obiekt świata, wyposażonego w listę/y obiektów, z których się składa. Czemu tak?


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 1x, ostatnio: flowCRANE
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:25 minut
  • Postów:8398
1

ale ty chyba i tak robisz coś w stylu systemow z ECS, gdzie masz osobną tablicę na każdy typ jednostki?

Kopiuj
	struct Enemies
	{
		ObjectList<Orc> Orc;
		ObjectList<Skeleton> Skeleton;
	} Enemies;
	struct Weapons
	{
		ObjectList<Sword> Sword;
		ObjectList<Dagger> Dagger;
	} Weapons;
Kopiuj
private:
	T* Data = nullptr;
	ObjectBase** Link = nullptr;

No i czym jest ObjectBase?
Czy tu emulujesz dziedziczenie za pomocą wskaźników? Coś na zasadzie "tu mamy obiekt, a to jest wskaźnik do mojego przodka"? (jak w dziedziczeniu prototypowym). Czy coś źle zrozumiałem?

Kopiuj
level[0].Enemies.Skeleton[24].Damage = 10;

Dobra, ale w realnym kodzie chyba nie będzie tego na sztywno i tak ustawione. Dlaczego w ogóle istniałaby potrzeba ustawić damage = 10 dla skeletonu o indeksie 24?
albo tu:

Kopiuj
world.Get<Orc>(12).Health = 76;

tak jakby strasznie na sztywno to ustalane. A co za różnica dla gry, czy zaatakował gracza Ork czy Smok? Ktoś zaatakował, ale to jakoś powinno być uogólnione przecież.


edytowany 3x, ostatnio: LukeJL
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0
furious programming napisał(a):

Z tego co napisałeś, mam wrażenie, że chcesz świat podłączyć pod zewnętrzną listę/y obiektów w nim się znajdujących, zamiast stworzyć obiekt świata, wyposażonego w listę/y obiektów, z których się składa. Czemu tak?

Bo u mnie level jest całym światem w danym momencie i dzięki temu mogę zrobić coś takiego:

Kopiuj
World.Level[0].Destroy(); //Niszczy wszystkie obiekty starego poziomu i zwalnia pamięć.
world.Level[1].Load(); //Alokuje pamięć i tworzy wszystkie obiekty nowego poziomu.
World.GetLevel(1); //Zczytuje listę obiektów (po prostu swapując wskaźnik) przez co silnik może na nich pracować.
//Przy czym World widzi wszystkie obiekty jako ObjectBase**, więc ma dostęp tylko to metod klasy bazowej.
LukeJL napisał(a):

No i czym jest ObjectBase?

Wkleiłem kod w pierwszym poście. To "pusta" klasa, zawierająca metody abstrakcyjne związane z obróbką danego obiektu przez wątek odświeżający logikę i wątek renderujący, które odpowiednio wywołują sobie Update() i Render(), bez wchodzenia w szczegóły obiektu.

Czy tu emulujesz dziedziczenie za pomocą wskaźników? Coś na zasadzie "tu mamy obiekt, a to jest wskaźnik do mojego przodka"? (jak w dziedziczeniu prototypowym). Czy coś źle zrozumiałem?

Chyba nie...? Chociaż nie jestem pewien co masz na myśli, pisząc o "emulowaniu dziedziczenia".

Kopiuj
level[0].Enemies.Skeleton[24].Damage = 10;

Dobra, ale w realnym kodzie chyba nie będzie tego na sztywno i tak ustawione. Dlaczego w ogóle istniałaby potrzeba ustawić damage = 10 dla skeletonu o indeksie 24?
albo tu:

Kopiuj
world.Get<Orc>(12).Health = 76;

tak jakby strasznie na sztywno to ustalane. A co za różnica dla gry, czy zaatakował gracza Ork czy Smok? Ktoś zaatakował, ale to jakoś powinno być uogólnione przecież.

To tylko przykład, generalnie chodzi mi o to, żeby mieć w miarę łatwy dostęp do poszczególnych typów obiektów, bez konieczności rzutowania. Wiadomo, że w praktyce nie będę ręcznie ustawiał danych np. dla jednego wroga czy NPC, ale już dla całej grupy? Czemu nie. Mógłbym np. mieć zaklęcie, które zabija wszystkie szkielety na planszy i wtedy mógłbym zrobić tak:

Kopiuj
for (uint32_t I = 0; I < Enemies.Skeleton.Count; I++)
{
	Enemies.Skeleton[I].Health = 0;
}

Alternatywą byłoby:

  1. Oblecieć w pętli wszystkie obiekty przetrzymywane przez kontener World, a więc dosłownie wszystkie elementy poziomu, w tym też np. ściany, skały, chmury, drzewa, przedmioty itd.

  2. Sprawdzić który z obiektów jest szkieletem (np. poprzez wewnętrzny identyfikator nadany każdemu obiektowi albo dynamic_cast).

  3. Rzutować każdy wykryty w powyższy sposób obiekt na Skeleton, a następnie ustawić mu Health na 0.

    Wolę pierwszy sposób :).

edytowany 4x, ostatnio: Crow
flowCRANE
No czyli w sumie opisujemy to samo — lista obiektów, z ktorych składa się ”świat”, znajdują się w jakimś zbiorczym obiekcie (u Ciebie jest to level), a nie gdzieś na zewnątrz. Poczatkowo zrozumiałem, że tak nie jest.
CR
@furious programming: A jak przechowujesz obiekty u siebie? Trzymasz je w jednej, dużej liście? W drzewie? Masz jakiś interface do kontrolowania typów obiektów, czy działasz jedynie na metodach abstrakcyjnych i nie obchodzi cię jakiego to typu obiekt?
flowCRANE
Odpiszę w poście.
LukeJL
to co to jest to ObjectBase** Link? myślalem jeszcze, że linked list, ale coś mi nie grało. Chociaż myśląc po C++owemu wskaźnik to może być tablica, więc wskaźnik wskaźników to może być wskaźnik na tablicę obiektów?
CR
@LukeJL: Dokładnie tak, ObjectBase** Link to wskaźnik na tablicę polimorficznych wskaźników. Dzięki temu w jednej tablicy mogą siedzieć różne typy ze wspólnym przodkiem, co w przypadku zarządzania obiektami gry jest w zasadzie niezbędne.
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:mniej niż minuta
  • Lokalizacja:Tuchów
  • Postów:12163
1

@Crow: u mnie będzie to wyglądało tak, że świat jest w 3D (jedna wielka mapa), mapa będzie podzielona na sektory (małe sześciany), a każdy z nich będzie zawierał listę obiektów, które się w danym sześcianie znajdują. Obiekty, z których będzie zbudowany teren (wszystko co statyczne), będą umieszczone tylko w tych listach, natomiast obiekty wymagające spawnowania i ogólnie szybkiego dostępu (w tym gracze, potworki, NPC-e itp), będą przechowywane w dodatkowych listach, natomiast kopie ich referencji będą trzymane również w listach sektorów (obiekty będą refcountowane).

Głównie chodzi o to, że raytracer musi śmigać po sektorach mapy i tak szybko jak się da, testować kolizje z obiektami — dlatego każdy sześcian musi posiadać referencje wszystkich obiektów, które się w nim znajdują. Natomiast dodatkowe listy są potrzebne do tego, aby mieć błyskawiczny dostęp do obiektów, które w każdej klatce wymagają spawnu, despawnu i aktualizacji. Szukanie tych obiektów w gigantycznej mapie trwałoby milion lat, w małej liście mam do nich dostęp praktycznie od razu. Takich dodatkowych list będzie wiele, w tym na pewno jedna dla bohaterów (graczy) i jedna dla wszystkich zespawnowanych potworków. Przy czym raczej skuszę się na cache'owanie większej liczby typów obiektów, ale to wyjdzie w praniu.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 1x, ostatnio: flowCRANE
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0
furious programming napisał(a):

mapa będzie podzielona na sektory (małe sześciany), a każdy z nich będzie zawierał listę obiektów, które się w danym sześcianie znajdują.

No, czyli coś jak moje levele.

Obiekty, z których będzie zbudowany teren (wszystko co statyczne), będą umieszczone tylko w tych listach, natomiast obiekty wymagające spawnowania i ogólnie szybkiego dostępu (w tym gracze, potworki, NPC-e itp), będą przechowywane w dodatkowych listach, natomiast kopie ich referencji będą trzymane również w listach sektorów (obiekty będą refcountowane).

Ale czym są te listy? To coś w stylu kontenerów polimorficznych?

Kopiuj
struct Object
{

}

struct Sword : public Object
{

}

struct Gun : public Object
{

}

Object* List[20];
List[0] = new Sword;
List[1] = new Gun;
//itd.

Czyli że lista składa się ze wskaźników na klasę bazową, w które ty upychasz potem różne polimorficzne klasy potomne. Czy może osobne listy konkretnych obiektów, z odgórnie znanym typem:

Kopiuj
Sword* sword[10];
Gun* gun[10];

sword[0] = new Sword;
gun[0] = new Gun;
//itd.

???

edytowany 2x, ostatnio: Crow
flowCRANE
Moderator Delphi/Pascal
  • Rejestracja:ponad 13 lat
  • Ostatnio:mniej niż minuta
  • Lokalizacja:Tuchów
  • Postów:12163
1
Crow napisał(a):

Ale czym są te listy? To coś w stylu kontenerów polimorficznych?

Raczej w stylu kontenerów generycznych. Różnica polega na tym, że ani nie używam wbudowanych w język generyków, ani OOP — dla mnie wszystko jest buforami bajtów, a generyczność uzyskuję zwykłymi, nietypowanymi wskaźnikami. Lista obiektów, to po prostu spójny ciąg pointerów na cokolwiek, na co może wskazywać wskaźnik (głównie na ”obiekty”, czyli instacje zwykłych struktur alokowanych dynamicznie za pomocą GetMem). To tak w skrócie.

Czyli że lista składa się ze wskaźników na klasę bazową, w które ty upychasz potem różne polimorficzne klasy potomne. Czy może osobne listy konkretnych obiektów, z odgórnie znanym typem:

Nie wiem czy powinienem o tym pisać, żeby nie odciągać Cię od kodu, jaki sam piszesz. Ty używasz klas, pewnie też interfejsów, więc spokojnie możesz sobie zrobić drzewo dziedziczenia (co będzie kłopotem/redundancją, dlatego wymyślono ECS-a). U mnie wszystko jest strukturalno-proceduralne, w stylu surowego C.

Jeśli chodzi o te listy, to cała mapa będzie reprezentowana jako trójwymiarowa macierz list, w której jedna lista opisuje jeden sześcienny sektor (o rozmiarze potęgi dwójki, albo 64×64×64, albo 128×128×128 pikseli). Każda z tych list będzie przechowywać wskaźniki na obiekty dowolnego typu — od obiektów terenu po aktorów. Każdy obiekt będzie dziedziczyć ze struktury bazowej, więc będę mógł je łatwo rozróżniać (to samo możesz robić używając pascalowego operatora is w przypadku klas, ale nie wiem jak się to w C++ testuje). Zewnętrzne (dodatkowe) listy, te których chcę użyć do szybkiego dostępu do kluczowych obiektów, będą już wyspecjalizowane — np. lista z potworkami będzie zawierać wyłącznie wskaźniki na potworki, ale dowolnego typu.


Pracuję nad własną, arcade'ową, docelowo komercyjną grą z gatunku action/adventure w stylu retro (pixel art), programując silnik i powłokę gry od zupełnych podstaw, przy użyciu Free Pascala i SDL3. Więcej informacji znajdziesz na moim mikroblogu.
edytowany 2x, ostatnio: flowCRANE
AK
  • Rejestracja:ponad 6 lat
  • Ostatnio:około rok
  • Postów:3561
1
LukeJL napisał(a):

No i czym jest ObjectBase?

Mnie też razi ta nazwa, w sensie za szeroka.
Jak zawsze wtedy, intuicja podpowiada ze projektant nie bardzo wiedział co chce.


Bo C to najlepszy język, każdy uczeń ci to powie
flowCRANE
Jeśli to klasa będąca bazową dla wszystkich innych obiektów, to ta nazwa mówi wszystko.
AK
No właśnie nie "obiektów" (w sensie java / C#) a ... obiektów gry ? obiektów aktywnych ? obiektów wizualnych ? metody wskazują, ze coś nie funguje
flowCRANE
To jest C++, nie Java, nie C#. Chodzi o obiekty gry, nie instancje klas. A to czy aktywne czy wizualne czy jakieś inne, to nie ma na tym poziomie abstrakcji znaczenia — to bazowa klasa obiektu, z którego mają dziedziczyć wszystkie inne.
LukeJL
czyli to prostu tzw. GameObject, czyli klasa bazowa wszystkich innych klas dla obiektów gry. W sumie to ma jeszcze sens w językach statycznie typowanych, że bez wspólnego przodka albo bez implementacji jednego interfejsu nie da się osiągnąć polimorfizmu czy trzymać razem zmienne innego typu.
flowCRANE
Dokładnie. Przy czym OP wybrał zwykłe OOP zamiast ECS-a, więc bazowa klasa po prostu musi istnieć, bo z niej wszystko musi dziedziczyć. Nazwa jest w porządku — krótka i mówi wszystko.
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

A czy dałoby się przynajmniej zmusić jakoś do działania poniższą strukturę?

Kopiuj

//KLASA Z LISTĄ INT

class Type_Int
{
private:
	int* List = nullptr;
public:
	template<typename>
	auto& Get(const uint16_t& uiID); //<- Bazowe Get()

	template<>
	auto& Get<int>(const uint16_t& uiID) //<- Specjalizacja dla int
	{
		return List[uiID];
	}
};

//KLASA Z LISTĄ FLOAT

class Type_Float
{
private:
	float* List = nullptr;
public:
	template<typename>
	auto& Get(const uint16_t& uiID); //<- Bazowe Get()

	template<>
	auto& Get<float>(const uint16_t& uiID) //<- Specjalizacja dla float
	{
		return List[uiID];
	}
};

//KLASA ZBIORCZA

template<typename... T>
struct Collector : public T...
{};

//TEST

int main()
{
	Collector<Type_Int, Type_Float> collector;
 	collector.Get<int>(0) = 23;
  	collector.Get<float>(0) = 65.03f;
}

Kompilator oczywiście krzyczy o "ambigious", bo zarówno Type_Int jak i Type_Float korzystają z takiego samego, bazowego Get(). Da się to jakoś obejść?

edytowany 1x, ostatnio: Crow
several
  • Rejestracja:ponad 15 lat
  • Ostatnio:około godziny
1

Double Dispatch wydaje się być jakąś obiektową alternatywą dla ECS. Nie użyłem tego w praktyce w projekcie gry, ale czysto hipotetycznie może to wprowadzić jakiś porządek i być otwartym na rozszerzenia jednocześnie, podałem przykład w innym wątku Sposób implementacji systemów wpływających na wszystko

Nie mogę jednak zagwarantować, że ten mechanizm Ci się sprawdzi. Widziałem użycie tego w różnych projektach (nie grach) tworzonych w językach, które nie wspierały double dispatch "out of the box" i wyglądało to kiepsko. Wyglądało to tak, jakby ludzie dopiero co przeczytali o "visitor pattern" i strasznie chcieli się nim pobranzlować. Tym nie mniej wydaje mi się, że jeśli nie będziesz się sugerował nazwą wzorca i nie będziesz myślał ani chciał tworzyć żadnych visitorów tylko po prostu zaimplementować double dispatch to może to zadziałać jeśli koniecznie chcesz wypróbować OOP.

(edit)
A tak offtopując, to wydaje mi się, że tworząc grę to struktura danych to najmniej ciekawy i jeden z łatwiejszych problemów do rozwiązania. IMHO nie powinieneś sobie tym zawracać głowy aż do momentu, gdy bałagan zaczyna przeszkadzać w renderowaniu i w tym momencie wybrać coś co rozwiąże Twój problem. A najlepiej podpatrzeć jak inni rozwiązali ten problem przed Tobą np. w takim https://github.com/MrFrenik/Enjon/tree/master/Source by jak najszybciej wrócić do ciekawszych problemów.


edytowany 1x, ostatnio: several
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
1

Dzięki za sugestię. Właśnie opracowuję system trochę przypominający w użyciu ECS, tzn. tablice tworzone dynamicznie, sparowane z typami (coś jak już tu opisywałem, tylko teraz jeszcze spięte w std::tuple). Na dniach powinienem mieć funkcjonalny prototyp i go tu wrzucę, może komuś się przyda :).
A co do "ciekawszych problemów", to pewnie masz rację, ale to mój pierwszy "poważny" silnik i myślę o nim w taki sposób, że nie mogę mieć X bez Y, a Y bez Z i tak po nitce do kłębka... Żeby dobrze opracować buforowanie danych, muszę wiedzieć, jak te dane są przechowywane itd.

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)