M aki n g D ial o g u e a n d N a v iga t i on f or N P C s i n G o d ot F or t he ga m e I a m wor ki n g on ( w hich I w ill c r ea t e a not he r bl o g post ab out a t a not he r t i m e ), I n eeded N P C s t ha t t he p la y e r c ou ld i nt e r ac t w i t h a n d m ake t he ga m e wor ld feel li v ed i n a n d li v el y T he s e cha r ac t e rs n eeded to be able to ha v e c onv e rs a t i ons w i t h t he p la y e r , t he y n eeded to ha v e t hi n g s t ha t t he y sp e nt t hei r da ys d o i n g , a n d t he y n eeded to be able to d o t hi n g s on sp ecial da ys o f t he y ea r T he p la y e r n eeded to feel like t he wor ld w a s li v ed i n , a n d t ha t t he y a r e c om i n g to t hi s p lace , not t ha t t hi s p lace w a s m ade f or t he m S o I s e t out to i mp le m e nt e v e ryt hi n g A fe w o f my r e qu i r e m e nts : T able o f C ont e nts I ntro d u c t i on D ial o g u e D a t a M o del R e n de r i n g D ial o g u e G ibbe r i s h C ha tt e r A ddi n g i t e ms S ched u le s N a v iga t i on C on cl us i on B u il t i n C # ( a s my w h o le ga m e i s i n C #, not gd s c r i pt ). E xt e ns ible f or my n eed s E a s il y c on fig ur able on a p e r - N P C ba s i s W hile G o d ot ha s a num be r o f dial o g u e syst e ms out t he r e , t he y a r e all b u il t i n gd s c r i pt , so t he y w e r e n ʼ t g o i n g to be us able f or m e S o I s e t out to b u ildi n g my own M a ny o f t he dial o g u e too l s out t he r e us e J S ON a s t he c on fig ur a t i on f orm a t , a n d o f t e n e v e n ha v e a fa n c y no de - ba s ed edi tor f or b u ildi n g t he tr ee s w i t h T hi s i s g r ea t , b ut I did n ʼ t w a nt to ha v e to b u ild a n edi tor f or i t , a n d J S ON j ust i sn ʼ t t ha t h um a n - f r ie n dl y ( not to m e nt i on , J S ON d o e sn ʼ t ha v e g oo d support f or mu l t ili n e str i n g s !), so I n eeded a diffe r e nt so l ut i on W hile I did e n d up support i n g J S ON , I al so decided I w a nt ed to support Y AML , a n d my r ea son i n g behi n d i t i s t ha t i t i s h um a n r eadable a n d ea sy e nou gh to wr i t e a n d mo dif y t ha t my p a rtn e r , w h o i s t he a rt i st , s h ou ld be able to ea s il y r ead a n d wr i t e t he s e file s , w i t h out ha v i n g to un de rst a n d all t he diffe r e nt sym b o l s or stru c tur e o f J S ON , j ust ha s to un de rst a n d i n de nt a t i on D a t a M o del I e n ded up us i n g t he Y a m l D ot N e t p ackage f or r eadi n g i n t he y a m l file s A f t e r I g ot t ha t fig ur ed out , I c r ea t ed my D ial o g u e M a n age r a n d g ot st a rt ed T he D ial o g u e M a n age r cla ss li v e s on a C ontro l no de a n d c ontro l s all o f t he U I ele m e nts a s w ell 0:00 / 0:16 1 a s r eadi n g a n d di sp la y i n g t he D ial o g u e T he D ial o g u e M a n age r ge ts p a ss ed t he p a t h to a Y AML or J S ON file , a n d w ill r ead i t i n , b u ildi n g up t he D ial o g u e o bjec t : namespace PH.Services.DialogueSystem { public struct Dialogue { public string Name { get; set; } public Conversation Introduction { get; set; } public Dictionary<string, Conversation> Conversations { get; set; } } public struct Response { public string Name { get; set; } public string To { get; set; } } public struct Conversation { public string Text { get; set; } public string To { get; set; } public List<Response> Responses { get; set; } public Dictionary<string,object> Conditions { get; set; } public string Animation { get; set; } public Conversation (string text, string to) { Text = text; To = to; Responses = null; Conditions = null; Animation = ""; } public Conversation (string text) { Text = text; To = ""; Responses = null; Conditions = null; Animation = ""; } } } A s you ca n s ee ab ov e , w e ha v e a `Dialogue` o bjec t t ha t ha s a n a m e f or t he N P C , a n i ntro d u c t i on c onv e rs a t i on , a n d a c o llec t i on o f c onv e rs a t i ons de not ed b y a str i n g ide nt ifie r E ach c onv e rs a t i on po i nt ha s som e T e xt , w he r e i t ca n g o to af t e rw a r d s or al t e rn a t i v el y som e r e spons e s t he p la y e r ca n gi v e ( all ow i n g b r a n chi n g ), som e c on di t i ons f or w h y t he dial o g u e s h ou ld a pp ea r ( su ch a s da y , t i m e , sp ecial e v e nts , e t c .), a n d a n i m a t i on to p la y on t he cha r ac t e r N ow , t he a n i m a t i on to p la y i s likel y to cha n ge w he n w e i mp le m e nt cha r ac t e r portr ai ts , a s w e m ade t he deci s i on af t e r t hi s w a s b u il t t ha t w e w e r e g o i n g to e n d up d o i n g t ha t A n d he r e i s w ha t t ha t da t a o bjec t tr a ns la t e s to , Y AML w i s e : name: Jo introduction: text: | Hello! Here are some tools to get you started! [item path=Tools/Hoe.tscn]Jo gave you a Hoe![/item] [item path=Tools/Sickle.tscn]Jo gave you a Sickle![/item] [item path=Tools/WateringCan.tscn]Jo gave you a Watering Can![/item] [item path=Tools/Axe.tscn]Jo gave you an Axe![/item] responses: - name: Cool! to: intro-continue - name: Bye! to: close conversations: intro-continue: conditions: default: true text: | You have a lot of farming to do! Here, take these! [item path=Seeds/CarrotSeeds.tscn count=20]Added 20 Carrot Seeds! [/item] [item path=Seeds/PumpkinSeeds.tscn count=20]Added 20 Pumpkin Seeds! [/item] [item path=Seeds/MandrakeSeeds.tscn count=20]Added 20 Mandrake Seeds![/item] to: close special-rainbow: text: This is a [rainbow]special[/rainbow] dialog! to: close conditions: relationshipLevel: 2 season: Spring sprint-generic: text: I don't know who you are, but I have a good feeling about you! to: sprint-generic-2 conditions: relationshipLevel: 0 season: Spring spring-generic-2: text: | I wish you the best in everything. [item path=Seeds/CarrotSeeds.tscn count=5]Added 5 Carrot Seeds! [/item] [item path=Seeds/PumpkinSeeds.tscn count=5]Added 5 Pumpkin Seeds! [/item] [item path=Seeds/MandrakeSeeds.tscn count=5]Added 5 Mandrake Seeds! [/item] to: close N ow , t he r e i s a l ot mor e g o i n g on t he r e , sp ecificall y r ela t ed to i t e ms , t ha t w e w ill ge t to la t e r A s ide f rom t ha t , you ca n s ee j ust h ow ea sy i t i s to b u ild a n d wr i t e dial o g u e Y ou ha v e support f or mu l t i - li n e t e xt , BBC o de , b r a n chi n g a n d c on di t i ons I t ʼ s wort h m e nt i on i n g t ha t `to: close` C l os e i sn ʼ t a dial o g u e po i nt , b ut i nst ead i t t ell s t he syst e m t ha t t he cha r ac t e r i s d on e t alki n g to t he p la y e r R e n de r i n g D ial o g u e N ow t ha t w e ca n r ead t he Y AML file a n d b u ild out dial o g u e , h ow d o w e di sp la y i t ? W ell , on i nt e r ac t w i t h t he N P C ( or w ha t e v e r i t i s t ha t ha s dial o g u e ), t he f o ll ow i n g i s called : _dialogueManager.Read(dialoguePath) .SetAnimationTree(_animationTree) .FindConversation(_state.RelationshipLevel) .ShowIntroduction(!_state.Introduced) .Render(); T hi s call chai n s h ou ld be r ela t i v el y str aigh t f orw a r d T he dial o g u e m a n age r i s to ld to r ead a dial o g u e f rom a sp ecified p a t h a n d l o ad i t up , t he a n i m a t i on tr ee ( to p la y a n i m a t i ons ) i s s e t , a c onv e rs a t i on i s s ea r ch f or ( ig nor e t he R ela t i ons hi p L e v el f or now ), w hich m a y be r e p laced if w e ha v e not bee n i ntro d u ced y e t , a n d t he n r e n de r ed F or fi n di n g a c onv e rs a t i on , w e e ss e nt iall y j ust l oop t h rou gh all o f t he c onv e rs a t i ons i n t he dial o g u e , a n d check to s ee if t he y m a t ch t he c on di t i ons , if so , w e stor e t ha t R e n de r i n g i s w he r e i t ge ts most i nt e r e st i n g , he r e w e l o ad t he t e xt i nto a R ich T e xt L abel ʼ s B bc o de T e xt prop e rty , b u ild out t he b uttons t ha t s h ou ld e x i st i n t he r e spons e s li st , a n d t he n di sp la y t he dial o g u e w i n d ow foreach (Control child in _actionsContainer.GetChildren()) child.QueueFree(); if (_conversation.Responses?.Count > 0) { foreach (var response in _conversation.Responses) { var nextBtn = new Button(); nextBtn.Text = response.Name; nextBtn.Connect("pressed", this, "_OnActionPressed", new string[] { response.To }); _actionsContainer.AddChild(nextBtn); } } else if (_conversation.To != null && _conversation.To != "") { 2 var nextBtn = new Button(); nextBtn.Text = "..."; if (_conversation.To == "close") nextBtn.Text = "Bye!"; nextBtn.Connect("pressed", this, "_OnActionPressed", new string[] { _conversation.To }); _actionsContainer.AddChild(nextBtn); } else throw new System.Exception("Missing responses and to in dialogue"); I n he r e , you ca n s ee t ha t if w e ha v e r e spons e s , t ha t w e c r ea t e b uttons a n d add i t to our _ ac t i ons C ont ai n e r , ot he rw i s e , w e check t ha t w e ha v e a to , a n d c r ea t e a n ac t i on ba s ed on t ha t I f n ei t he r e x i st , w e t h row a n e x ce pt i on , w hich wou ld h op ef u ll y be ca u gh t i n t e st i n g O n e ot he r i mport a nt t hi n g to not e , h ow e v e r , i s I al so s e t t he R ich T e xt L abel ʼ s V i s ible C ha r ac t e rs prop e rty to 0, so t ha t i t ca n st a rt wr i t i n g out t he t e xt G ibbe r i s h C ha tt e r F or wr i t i n g out t e xt , I did n ʼ t w a nt i t to be dead s ile nt , nor did I w a nt to ha v e to r ec or d all t he t e xt i n t he ga m e , so I m ade a syst e m s i m ila r to A n i m al C ross i n g , to all ow gibbe r i s h - soun di n g sp each to c om e out W he n I decided to d o i t t hi s w a y , I r e m e m be r ed a Y ou T u be v ide o I s a w ab out i t b y B li psoun d s I r ec or ded mys elf sp eaki n g e v e ry le tt e r o f t he al p habe t , a s w ell a s t he num be rs 0-9, sp ed t he m up 2.5 x , a n d t he n sp li t t he m each i nto t hei r own a u di o file , n a m ed t hei r le tt e r I n t he D ial o g u e M a n age r , I l o aded i n all t he file s i nto a dic t i on a ry w i t h t hei r cha r ac t e r a s t he ke y , a n d t he soun d file a s t hei r v al u e var chars = new string[] { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", }; characterSounds = new Dictionary<string, AudioStream>(); foreach (var ch in chars) 3 characterSounds[ch] = ResourceLoader.Load<AudioStream> ($"res://Assets/Audio/Dialog/{ch}.ogg"); I al so c r ea t ed a qu e u e o f a u di o p la y e rs , w i t h a la r ge num be r o f a u di o p la y e rs , so t ha t t he y ca n p la y s ligh t l y ov e r each ot he r , w i t h out t hi n g s bei n g c ut o ff , or j ust not p la y i n g a t all _audioPlayers = new Queue<AudioStreamPlayer>(); for (var i = 0; i < textSpeed / 2; i++) { var player = new AudioStreamPlayer(); AddChild(player); player.Connect("finished", this, nameof(_OnStreamFinished), new AudioStreamPlayer[] { player }); player.Bus = AudioManager.DIALOG_BUS; _audioPlayers.Enqueue(player); } // ... private void _OnStreamFinished(AudioStreamPlayer streamPlayer) => _audioPlayers.Enqueue(streamPlayer); T he n i n t he `_Process` c omm a n d , w e st a rt r e v eali n g cha r ac t e rs , a n d w he n a n e w cha r ac t e r a pp ea rs , p la y i t ʼ s a u di o b yt e public override void _Process(float delta) { // If we have no text to display, then exit early. if (_text.VisibleCharacters >= _text.Text.Length) return; // Get what the visible characters is at prior to adjusting it, so we can // guarantee we don't make too many sounds. var before = _text.VisibleCharacters; // _displayAmount is a float so that we don't show hundreds of characters in a second _displayAmount += delta * textSpeed; // And then round it to an Int, to see where are at. _text.VisibleCharacters = Mathf.RoundToInt(_displayAmount); // Return early if we should not make any sounds if (_text.VisibleCharacters <= 0 || before >= _text.VisibleCharacters) return; // Get the latest visible character and play that sound. var curChar = _text.Text[_text.VisibleCharacters - 1].ToString(); if (characterSounds.ContainsKey(curChar) && _audioPlayers.Count > 0) { var audio = _audioPlayers.Dequeue(); audio.Stream = characterSounds[curChar]; audio.Play(); } } T he n , I j ust m ade i t so t ha t if you click on t he dial o g u e w i n d ow , i t fill s i n all t he t e xt , a n d t ha t ʼ s r eall y all t ha t r e m ai ns T he r e i s st ill a l ot o f c o de I did n ʼ t s h ow , b ut t hi s s h ou ld gi v e you a n idea o f h ow to a ppro ach t hi s , a n d a s ide f rom Y AML , pr e tty mu ch all o f t hi s s h ou ld be achie v able i n gd s c r i pt a s w ell A ddi n g i t e ms T he la st t hi n g wort h m e nt i on i n g a r e t h os e `[item]` bbc o de st a t e m e nts T he s e all ow m e to add i t e ms to t he p la y e r b y j ust ha v i n g i t i n t he t e xt t ha t t he cha r ac t e r sp eak s namespace PH.Services.DialogueSystem.BBCode { [Tool, ClassName] public class AddItem : RichTextEffect { public string bbcode { get => "item"; } [Signal] public delegate void Appeared(string itemPath, int count); private Dictionary<string, bool> _added = new Dictionary<string, bool>(); public override bool _ProcessCustomFx(CharFXTransform charFx) { charFx.Color = new Color(0, 1, 1, 1); charFx.Offset = new Vector2(10, 0); // Get<T>() is an extension method I made, because Godot is missing so // many generic methods. // This allows us to stick a random (non letter) character in, and just hide it, // instead of displaying some blue text. charFx.Visible = charFx.Env.Get<bool>("v", true); // All items are in subfolders of this root folder, so let's hard hardcode // the first part of this path, to allow shorter paths in the dialogue. var itemPath = "res://Scenes/GameObjects/Items/" + charFx.Env.Get<string>("path", ""); var count = Mathf.RoundToInt(charFx.Env.Get<float>("count", 0)); var key = $"{itemPath}:{count}"; if (!_added.ContainsKey(key) || !_added[key]) EmitSignal(nameof(Appeared), itemPath, count); return true; } public void OnAppeared(string itemPath, int count, SceneTree tree) { _added.Add($"{itemPath}:{count}", true); var itemScene = ResourceLoader.Load<PackedScene>(itemPath); if (itemScene == null) { return; } var item = itemScene.Instance<Item>(); tree.Root.GetNode<UI> (UI.ROOT_PATH).Inventory.AddItemToNextSlot(item, (uint)count); } public void OnHide() => _added = new Dictionary<string, bool>(); } } T wo i mport a nt t hi n g s to not e r igh t o ff t he ba t : A s ide f rom t ha t , I had to c r ea t e a pu blic v a r iable called `bbcode` ( HA S to be all l ow e r ca s e ), t ha t all ows m e to decla r e w ha t t he bbc o de t e xt i s , a n d t he n I us e s ig n al s c om bi n ed w i t h som e i nt e rn al f un c t i ons to ha v e a n effec t S i n ce r e sour ce s ha v e no acce ss to t he s ce n e , you ha v e to p a ss t he s ce n e i nto t he m T o d o t hi s he r e , I p a ss i t i n v ia a s ig n al , w hich i s c onn ec t ed t h rou gh a s i mp le s c r i pt on t he R ich T e xt L abel namespace PH.Services.DialogueSystem { public class BBText : RichTextLabel { public override void _EnterTree() { foreach (RichTextEffect effect in CustomEffects) { if (effect.HasSignal("Appeared") && effect.HasMethod("OnAppeared")) effect.Connect("Appeared", effect, "OnAppeared", new SceneTree[] { GetTree() }); if (effect.HasMethod("OnHide")) Connect("hide", effect, "OnHide"); } } } } . T he us e o f t he `Tool` A ttr ib ut e A cc or di n g to t he R ich T e xt L abel d o c um e nt a t i on , `RichTextEffect` s n eed to be m a r ked a s T oo l s 4 . T he `ClassName` a ttr ib ut e T hi s i s my cla ss _ n a m e C # p l u gi n i n ac t i on , all ow i n g t he e xport i n g o f c ustom r e sour ce s 5 I t i s pr e tty str aigh t f orw a r d , f or each c ustom effec t , c onn ec t t he s ig n al s a s n eeded !! N o v ide o f or t hi s on e , not m u ch to s h ow , sorry ! !! S ched u le s a r e a n i nt e r e st i n g t hi n g T he y r e qu i r e t ha t you ha v e som e sort o f t i m e syst e m i n your ga m e I n m i n e , I ha v e a t i m e m a n age r w hich stor e s t he s ec on d , m i nut e , h our , da y , a n d s ea son ( mont h ) o f t he ga m e T he n s ec on d s a r e c ount ed us i n g `_Process` , a n d w he n s ec on d s hi t 60, i t c oun d s up a m i nut e , a n d w he n t ha t hi ts 60, i t c oun d s up a n h our , e t c I t ʼ s a pr e tty str aigh t f orw a r d a n d s i mp le t i m e syst e m O n e i mport a nt t hi n g to not e i s t ha t a nyt i m e t he m i nut e s a n d h ours cha n ge , s ig n al s a r e e m i tt ed T h os e s ig n al s a r e t ied i nto t he N P C f or s ched u li n g purpos e s // These are some autowire attributes I created. // https://github.com/m50/Godot-CSharp-Autowire [Connect(TimeManager.ROOT_PATH, nameof(TimeManager.MinuteChanged))] [Connect(TimeManager.ROOT_PATH, nameof(TimeManager.HourChanged))] private void _OnTimeChanged(uint by) { var curEvent = _scheduler.GetCurrentEvent( _timeManager.Hours, _timeManager.Minutes, _timeManager.Day, _timeManager.Season ); if (curEvent != _CurEvent) _CurEvent = curEvent; } A s w e ca n s ee , a nyt i m e t he t i m e cha n ge s , w e ge t t he c urr e nt e v e nt f rom our s ched u le r i nst a n ce , a n d t he n if t he c urr e nt e v e nt i s diffe r e nt , w e s e t a n e w c urr e nt e v e nt `_CurEvent` i s a prop e rty t ha t kick s o ff n e w n a v iga t i on private Scheduler.Event _CurEvent { get => _curEvent; set { _curEvent = value; _navigator.NavigateTo(_curEvent.Scene, _curEvent.PointOfInterest); } } N ow , I won ʼ t g o i nto t he r ela t i v el y c omp le x da t a mo del f or t he s ched u le r , b ut I w ill j ust gi v e a sm all sn i pp e t o f t he s ched u le : spring: monday: - from: 10 to: hour: 16 minute: 30 pointOfInterest: Cottage scene: TestWorld facing: "x": 0 "y": 1 # ... specialEvents: - day: 2 season: Spring events: - from: 6 to: 18 pointOfInterest: SouthShore scene: TestWorld2 facing: "x": 0 "y": -1 A s you ca n s ee he r e , t he da t a mo del i s b ro ke n up i nto 5 ca t eg or ie s , t he 4 s ea sons a n d sp ecial E v e nts T he 4 s ea sons a r e b ro ke n up i nto t he da ys o f t he w eek s , a n d t he n a s ched u le i s prov ided f or t he da ys , a s a n a rr a y o f o bjec ts , c ont ai n g t he f rom a n d to t i m e , a n d w he r e a n d w ha t di r ec t i on t he y s h ou ld be st a n di n g F rom / to ca n t ake j ust a num be r to s e t t he h our , or ca n t ake a n o bjec t o f h our a n d m i nut e T hi s i s ha n dled w i t h a c ustom ca st on my t i m e st a mp o bjec t S p ecial e v e nts a r e stor ed a s a n a rr a y o f o bjec ts , c ont ai n g a da y a n d s ea son o f t he sp ecial e v e nt , a n d w ha t t hei r s ched u le ( t he e v e nts ) i s f or t ha t da y T hi s all ows m e to ha v e cha r ac t e rs run to sp ecial l o ca t i ons on t hei r bi rt hda y , a nn i v e rs a ry , or h o lida y f or e x a mp le T he `pointOfInterest` i s a no de p a t h f rom a root `PointsOfInterest` N o de 2 D o bjec t , i n t he sp ecified `scene` T hi s all ows m e to p lace a b un ch o f P os i t i on 2 D s i n a s ce n e a n d n a m e t he m sp ecial n a m e s to m ake i t ea sy e nou gh to fi n d t he m M y G a m e M a n age r s c r i pt ha n dle s r e turn i n g `Level` o bjec ts , w hich a r e w ha t I a m calli n g `scene` he r e T o ge t t he c urr e nt e v e nt , a s s ee n ea r lie r , i t ʼ s pr e tty str aigh t f orw a r d : public Event GetCurrentEvent(uint hour, uint minute, uint day, TimeManager.Seasons season) { var events = _schedule.GetSpecialEvent(day, season); if (events == null) events = _schedule.GetSeason(season).GetDayOfWeek((int)day); return events.FirstOrDefault((e) => e.Contains(new TimeStamp(hour, minute))); } F i rst , I a s k t he s ched u le to fi n d a sp ecial e v e nt f or t he da y / s ea son t ha t w e a r e i n , el s e fi n d s t he e v e nts f or t he da y o f t he w eek f or t he s ea son w e a r e i n , t he n w e ge t t he fi rst e v e nt t ha t c ont ai ns t he sp ecified h our / m i nut e O n e i mport a nt po i nt o f t he c ont ai ns i s t ha t t i m e wr a pp i n g e x i sts , so w e n eed to acc ount f or `from` a n d `to` wr a pp i n g ov e r t he 24 h r ba rr ie r public bool Contains(TimeStamp timeslot) { var pastFrom = From.Hour <= timeslot.Hour && (From.Minute <= timeslot.Minute || From.Minute == 0); var beforeTo = To.Hour > timeslot.Hour && (To.Minute > timeslot.Minute || To.Minute == 0); if (From.Hour > To.Hour && timeslot.Hour < 24) beforeTo = true; else if (From.Hour > To.Hour && timeslot.Hour >= 0) pastFrom = true; return pastFrom && beforeTo; } T he r e i sn ʼ t a nyt hi n g mor e to i t , outs ide o f t he e xt e ns i v e da t a mo del T he stru c ts each c ont ai n som e hel p e r f un c t i ons a n d c omp a r i son too l s a n d w ha t not , to m ake wor ki n g w i t h t he da t a mo del ea s ie r H e r e i s a s c r ee ns h ot o f t he da t a mo del f o lded up , to gi v e a bi t o f a n ov e rv ie w o f all t he c ompon e nts : A s s ee n ea r lie r , w e ha v e a n a v iga tor cla ss , w hich ha n dle s all t he n a v iga t i on ( us i n g N a v iga t i on 2 D no de s f rom G o d ot ) f or t he cha r ac t e r O n ce w e ha v e a po i nt O f I nt e r e st , t he N a v iga tor cla ss l oo k s i t up , a n d b u ild s a p a t h public void NavigateTo(string scene, string pointOfInterest) { // Return early if we don't know where we are going. if (scene == "" || pointOfInterest == "" || scene == null || pointOfInterest == null) return; var destScene = _gameManager.GetScene(scene); var poi = destScene.PointsOfInterest[pointOfInterest]; // If we have a path already, let's clear it. There was likely a time jump. _navPoints.Clear(); // If the NPC's destination is not in it's current scene: if (NPC.CurrentScene.Name != scene) { // Find the doorway to the scene we need to go to var doorway = NPC.CurrentScene.FindDoorwayTo(scene); // and find the point to be able to travel. var doorwayPoint = doorway.GetNode<CollisionShape2D> ("CollisionShape2D"); // Get the path. var points = NPC.CurrentScene.Navigation2D .GetSimplePath(NPC.Position, doorwayPoint.Position + doorway.Position); foreach (var point in points) _navPoints.Enqueue(point); // After we get the path to the scene switcher, we get the path to our // point of interest. points = _gameManager.GetScene(scene).Navigation2D .GetSimplePath(doorway.positionInNewScene, poi.Position); foreach (var point in points) _navPoints.Enqueue(point); } // Else, it's destination is in it's current scene, so this is pretty simple, // get the path, and queue up the points. else { var points = new Queue<Vector2>(NPC.CurrentScene.Navigation2D .GetSimplePath(NPC.Position, poi.Position)); foreach (var point in points) _navPoints.Enqueue(point); } } O n e t hi n g I g ot ca u gh t on , so I w ill m ake a po i nt to m e nt i on i t he r e , all t he de st i n a t i on po i nts n eed to be r ela t i v e to t he N a v iga t i on 2 D I n t hi s ca s e , t he YS ort t ha t t he N P C bel on g s to , t he N a v iga t i on 2 D , a n d t he d oorw a y a r e all r ela t i v e to t he s a m e root no de : t he s ce n e , so w e n eed to m ake sur e all our pos i t i ons a r e r ela t i v e to t ha t T hi s i s w h y t he r e i s no us e o f G l o bal P os i t i on I ge n e r all y wor k i n G l o bal P os i t i on , a s i t i s mor e acc ur a t e , b ut i t ac tu all y b r eak s t hi n g s w i t h N a v iga t i on 2 D ! I f you k now h ow to ge t N a v iga t i on 2 D to wor k i n G l o bal P os i t i on , p lea s e le t m e k now on tw i tt e r ! O n ce w e ha v e a p a t h , w e n eed to ac tu all y w alk to i t ! T hi s i s ha n dled i n t he `_Process` m e t h o d H e r e , w e ge t t he n e xt po i nt w e a r e tr a v eli n g to , a n d if w e a r e n ʼ t c urr e nt l y tr a v eli n g to a po i nt , w e mov e i nto a M ov e T o st a t e , w hich mov e s our cha r ac t e r to t ha t po i nt public override void _Process(float delta) { if (_navPoints.Count == 0) return; var nextPoint = Vector2.Zero; try { nextPoint = _navPoints.Peek(); } catch { } if (nextPoint == Vector2.Zero) NPC.StateMachine.Travel(nameof(Idle)); else if (!_currentlyTraveling) { GD.Print("Moving to ", nextPoint, " with offset ", NPC.CurrentScene.GlobalPosition, " equating to ", nextPoint + NPC.CurrentScene.GlobalPosition); nextPoint += NPC.CurrentScene.GlobalPosition; _currentlyTraveling = true; NPC.StateMachine.Travel(nameof(MoveTo)); var moveToState = (NPC.StateMachine.CurrentState as MoveTo); moveToState.MovePoint = nextPoint; FacingDir = NPC.GlobalPosition.DirectionTo(nextPoint); } } T he n , on ce t he cha r ac t e r a rr i v e s , w e turn i t to face t he di r ec t i on out li n ed i n i t ʼ s s ched u le T he r e i s al so support he r e f or p la y i n g a n a n i m a t i on , h ow e v e r t hi s w ill likel y cha n ge to sp ecif y i n g a st a t e ( su ch a s `Fishing` f or e x a mp le ) t ha t m ake s t he N P C us e a sp ecific c o dified st a t e , r a t he r t ha n j ust a n a n i m a t i on st a t e T ha t i s all t he r e r eall y i s to t he i mp le m e nt a t i on I s ki mm ed ov e r qu i t e a bi t , a s t hi s w a s al r ead y qu i t e l on g a s i s , a n d s ki pp ed ov e r h u ge ch un k s o f t he c o de M ost o f i t s h ou ld be pr e tty ea sy to fill i n t he ga ps o f T hi s w a s most l y m ea nt to be a n out li n e , r a t he r t ha n a st e p - b y - st e p tutor ial ( if i t w e r e t ha t , I ʼ d pro babl y ha v e b ro ke n t hi s up i nto mu l t i p le a rt icle s , beca us e wow , t hi s i s l on g !). I t ʼ s i mport a nt to r e - i t e r a t e t ha t if you a r e a gd s c r i pt de v el op e r , all o f t hi s i s poss ible t he r e a s w ell , t he ke y diffe r e n ce s bei n g t ha t you won ʼ t be able to pr e - defi n e a da t a mo del like I did , a n d i nst ead e v e ryt hi n g e x i st i n g i n a dic t i on a ry , a n d you w ill be r e qu i r ed to us e J S ON r a t he r t ha n Y AML ( t h ou gh , t he r e i s a y a m l p l u gi n , so if you ca n b u ild t he C ++ c o de f or i t , you s h ou ld be g oo d to g o ). K ee p a n e y e out f or my bl o g post ac tu all y di s c uss i n g my ga m e , a s w ell a s f utur e posts ab out ot he r c oo l t hi n g s I b u ild f or t he ga m e A n d if you r ead to he r e , you de s e rv e a c oo kie ! 🍪 L i n k s 6 . h ttps :// gi t h u b c om / aa u b ry / Y a m l D ot N e t . h ttps :// d o c s g o d ot e n gi n e or g / e n / st able / tutor ial s / g u i / bbc o de _ i n _ r ich t e xt label h tm l . h ttps :// www youtu be c om / w a t ch ? v =4 W 57 W y 6 v e U M & fea tur e = e m b _ l o g o . h ttps :// d o c s g o d ot e n gi n e or g / e n / st able / tutor ial s / g u i / bbc o de _ i n _ r ich t e xt label h tm l . h ttps :// gi t h u b c om / m 50/ G o d ot - C S ha rp - N o de - E xports . h ttps :// gi t h u b c om / B eliaa r / g o d ot - y a m l