-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfeed.xml
2296 lines (2111 loc) · 196 KB
/
feed.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
<title>Csharp and Game Development Notes</title>
<link href="https://pilvimaa.fi/feed.xml" rel="self" />
<link href="https://pilvimaa.fi" />
<updated>2024-09-29T15:51:50+03:00</updated>
<author>
<name>Pilvinen</name>
</author>
<id>https://pilvimaa.fi</id>
<entry>
<title>Localizing Godot's default Splash Screen</title>
<author>
<name>Pilvinen</name>
</author>
<link href="https://pilvimaa.fi/localizing-godots-default-splash-screen/"/>
<id>https://pilvimaa.fi/localizing-godots-default-splash-screen/</id>
<media:content url="https://pilvimaa.fi/media/posts/21/ProphetOfTheLonelyStar_fi.png" medium="image" />
<updated>2024-09-29T15:51:50+03:00</updated>
<summary>
<![CDATA[
<img src="https://pilvimaa.fi/media/posts/21/ProphetOfTheLonelyStar_fi.png" alt="" />
Getting Godot’s default static splash screen localized is a nightmare. One might assume it’s as easy as 1, 2, ,3, just set a remap for it and you’re done, but…
]]>
</summary>
<content type="html">
<![CDATA[
<p><img src="https://pilvimaa.fi/media/posts/21/ProphetOfTheLonelyStar_fi.png" class="type:primaryImage" alt="" /></p>
<p>Getting Godot’s default static splash screen localized is a nightmare. One might assume it’s as easy as 1, 2, ,3, just set a remap for it and you’re done, but oh no, of course it isn’t supported.</p><p>Furthermore, because all the user space code loads only after the splash screen has already been shown, it complicates things.</p><p>And to make the matters even worse, of course any runtime changes to <code>ProjectSettings</code> are not persistent, so you can’t just set a new path for the boot splash when the locale changes.</p><p>However, there is a way.</p><h2 id="project-settings">Project Settings</h2>
<p>In the Godot Editor under <code>Project Settings > General > Application > Boot Splash</code></p><ol>
<li>Set <code>Show Image</code> to <code>true</code>.</li>
<li>Set <code>Image</code> to your default image.</li>
</ol>
<p>Under <code>Project Settings > General > Application > Config</code> set <code>Project Settings Override</code> to <code>override.cfg</code>.</p><h2 id="writing-to-overridecfg">Writing to override.cfg</h2>
<p>When your locale changes, call:</p><pre><code class="language-cs">// Set the splash image to graphics localized to Finnish.
ProjectSettings.SetSetting("application/boot_splash/image", "user://path/to/my/localized/splash_fi.png");
// Save the changed setting to override.cfg.
ProjectSettings.SaveCustom("override.cfg");
</code></pre>
<p>This special <code>override.cfg</code> file will be created in the folder where your game’s executable resides. It will be loaded via the project settings as defined above.</p><h2 id="setting-the--splashes-in-user">Setting the splashes in user://</h2>
<p>Trying to use the splashes from <code>res://</code> resulted in an utter failure for me. The solution was copying the images to <code>user://</code>. Of course you can’t just copy them, that would be too simple. But we can write the image files.</p><p>You can do this how ever you like, here’s what I did:</p><pre><code class="language-cs">private static void TryToCopySplashImagesToUserFolder() {
// Copy the splash screen images to the user folder if they don't exist there.
string splashScreenFolderPath = Paths.GetSplashScreenFolderPath();
string splashScreenFolderFullPathFi = Path.Combine(splashScreenFolderPath, "ProphetOfTheLonelyStar_fi.png");
string splashScreenFolderFullPathEn = Path.Combine(splashScreenFolderPath, "ProphetOfTheLonelyStar_en.png");
// Check if the folder exists.
if (!Directory.Exists(splashScreenFolderPath)) {
Directory.CreateDirectory(splashScreenFolderPath);
}
// Write splash screens to user folder.
// English splash screen. if (!File.Exists(splashScreenFolderFullPathEn)) {
string fileNameEn = "ProphetOfTheLonelyStar_en.png";
string imageFilePathEn = $"res://Textures/SplashScreen/{fileNameEn}";
string writeFilePathEn = Path.Combine(Paths.GetSplashScreenFolderPath(), fileNameEn);
Texture2D imageEn = GD.Load<Texture2D>(imageFilePathEn);
if (!File.Exists(writeFilePathEn)) {
// Write the imageEn to the destination file.
try {
imageEn.GetImage().SavePng(writeFilePathEn);
} catch (Exception e) {
Console.WriteLine($"Splash screen image file could not be written: {writeFilePathEn}");
Console.WriteLine(e);
}
}
}
// Finnish splash screen.
if (!File.Exists(splashScreenFolderFullPathFi)) {
string fileNameFi = "ProphetOfTheLonelyStar_fi.png";
string imageFilePathFi = $"res://Textures/SplashScreen/{fileNameFi}";
string writeFilePathFi = Path.Combine(Paths.GetSplashScreenFolderPath(), fileNameFi);
Texture2D imageFi = GD.Load<Texture2D>(imageFilePathFi);
if (!File.Exists(writeFilePathFi)) {
// Write the imageFi to the destination file.
try {
imageFi.GetImage().SavePng(writeFilePathFi);
} catch (Exception e) {
Console.WriteLine($"Splash screen image file could not be written: {writeFilePathFi}");
Console.WriteLine(e);
}
}
}
}
</code></pre>
<p>These were the main points. Fill in the blanks for your own project and you should be good to go.</p>
]]>
</content>
</entry>
<entry>
<title>Game Structure and Software Architecture</title>
<author>
<name>Pilvinen</name>
</author>
<link href="https://pilvimaa.fi/game-structure-and-software-architecture/"/>
<id>https://pilvimaa.fi/game-structure-and-software-architecture/</id>
<media:content url="https://pilvimaa.fi/media/posts/20/SoftwareArchitecture.png" medium="image" />
<category term="structure"/>
<category term="design"/>
<category term="architecture"/>
<updated>2024-09-20T10:56:56+03:00</updated>
<summary>
<![CDATA[
<img src="https://pilvimaa.fi/media/posts/20/SoftwareArchitecture.png" alt="Software Architecture" />
This is a straightforward simple memo on what all game structure should be like, in the most general sense. It’s for me. It’s not for you. It hasn’t been checked…
]]>
</summary>
<content type="html">
<![CDATA[
<p><img src="https://pilvimaa.fi/media/posts/20/SoftwareArchitecture.png" class="type:primaryImage" alt="Software Architecture" /></p>
<p>This is a straightforward simple memo on what all game structure should be like, in the most general sense. It’s for me. It’s not for you. It hasn’t been checked very thoroughly and might contain errors, even big ones, especially the parts generated with the help of generative AI.</p><h2 id="project-structure">Project Structure</h2>
<h3 id="parent-to-child-dependencies">Parent-to-Child Dependencies</h3>
<p>All the dependencies should be designed to be accessed from parent to child.
By ensuring that parents handle dependencies, you avoid issues where children or siblings aren’t fully initialized.
This also keeps objects modular, so each is only responsible for its own scope.
Do not depend from child to parent. The parent isn’t loaded yet when the child is ready.
Do not depend on siblings. They might or might not be there. Let the parent handle it.
Things should be self contained. Objects should be responsible for simple things which the parent manages.</p><h3 id="separation-of-logic-and-data">Separation of Logic and Data</h3>
<p>Separate logic from data.
It is always a good idea to split behavior from data models, ensuring a clean and maintainable codebase.</p><h2 id="saving-state">Saving State</h2>
<p>EF Core with SQLite should be used for object relational mapping. All state saving and loading should be handled by EF Core. Standard practices should be used for organizing the state objects.</p><p>EF Core (Entity Framework Core) is an Object-Relational Mapper (ORM), which means it translates between your C# classes and database tables. It handles CRUD (Create, Read, Update, Delete) operations for you, but requires a few key elements:</p><ul>
<li><strong>DbContext:</strong> The class responsible for managing database connections and mapping entities (your C# objects) to tables in your database.</li>
<li><strong>Entities:</strong> The C# classes that represent your database tables.</li>
<li><strong>Migrations:</strong> EF Core’s way of managing changes to your database schema.</li>
</ul>
<h3 id="defining-your-dbcontext">Defining Your DbContext</h3>
<p>The <code>DbContext</code> class is where you configure your connection to the database and define which entities will be tracked.</p><p>Here’s a minimal example of a <code>DbContext</code>:</p><pre><code class="language-cs">public class GameDbContext : DbContext {
// Define DbSet properties for each entity (table) in your database
public DbSet<Player> Players { get; set; }
public DbSet<InventoryItem> InventoryItems { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
// Configure EF Core to use SQLite
optionsBuilder.UseSqlite("Data Source=game.db");
}
}
</code></pre>
<p>This <code>GameDbContext</code> class will automatically map your <code>Player</code> and <code>InventoryItem</code> entities to tables in an SQLite database (<code>game.db</code>).</p><h3 id="defining-entities">Defining Entities</h3>
<p>Entities are C# classes that represent rows in your database tables. Each property corresponds to a column in the table.</p><p>For example, here’s a <code>Player</code> entity:</p><pre><code class="language-cs">public class Player {
public int Id { get; set; } // Primary key
public string Name { get; set; }
public int Level { get; set; }
public List<InventoryItem> Inventory { get; set; } // Navigation property
}
</code></pre>
<p>And the <code>InventoryItem</code>:</p><pre><code class="language-cs">public class InventoryItem {
public int Id { get; set; } // Primary key
public string ItemName { get; set; }
public int Quantity { get; set; }
public int PlayerId { get; set; } // Foreign key to Player
public Player Player { get; set; } // Navigation property
}
</code></pre>
<p>In this example:</p><ul>
<li>Each <code>Player</code> has a collection of <code>InventoryItems</code>.</li>
<li>The <code>InventoryItem</code> table will have a foreign key (<code>PlayerId</code>) to the <code>Player</code> table.</li>
</ul>
<h3 id="basic-crud-operations">Basic CRUD Operations</h3>
<p>Create</p><pre><code class="language-cs">using (var context = new GameDbContext()) {
var player = new Player {
Name = "Jack",
Level = 5
};
context.Players.Add(player);
context.SaveChanges(); // Persist changes to the database
}
</code></pre>
<p>Read</p><pre><code class="language-cs">using (var context = new GameDbContext()) {
var player = context.Players
.Include(p => p.Inventory) // Load related inventory items
.FirstOrDefault(p => p.Name == "Jack");
Console.WriteLine(player.Name + " has " + player.Inventory.Count + " items.");
}
</code></pre>
<p>Update</p><pre><code class="language-cs">using (var context = new GameDbContext()) {
var player = context.Players.FirstOrDefault(p => p.Name == "Jack");
if (player != null) {
player.Level += 1;
context.SaveChanges(); // Save the updated data
}
}
</code></pre>
<p>Delete</p><pre><code class="language-cs">using (var context = new GameDbContext()) {
var player = context.Players.FirstOrDefault(p => p.Name == "Jack");
if (player != null) {
context.Players.Remove(player);
context.SaveChanges();
}
}
</code></pre>
<h3 id="tips-for-effective-use">Tips for Effective Use</h3>
<p><strong>Avoid Querying the DbContext Multiple Times:</strong> When you need related data, use <strong>eager loading</strong> (<code>.Include()</code>) or <strong>lazy loading</strong> so you don’t make multiple database round trips.</p><p>Example of eager loading:</p><pre><code class="language-cs">var playerWithInventory = context.Players.Include(p => p.Inventory).FirstOrDefault();
</code></pre>
<p><strong>Use Transactions for Batch Operations:</strong> If you are performing multiple updates, wrap them in a transaction for better performance and consistency.</p><pre><code class="language-cs">using (var transaction = context.Database.BeginTransaction()) {
// Multiple operations here
transaction.Commit();
}
</code></pre>
<p><strong>Split Read and Write Contexts:</strong> If your game has a lot of read-heavy operations, you can create separate contexts for reading and writing. This can reduce lock contention in some cases.</p><h3 id="relationships-between--the-database-and-c-objects">Relationships Between the Database and C# Objects</h3>
<h4 id="key-parts">Key Parts:</h4>
<ol>
<li><p><strong><code>List<InventoryItem></code> in the <code>Player</code> class</strong><br> This is a collection (or list) of items that a player owns. It’s purely a C# list inside the <code>Player</code> object. It represents all the items that the player has in their inventory.</p></li>
<li><p><strong><code>DbSet<InventoryItem></code> in the <code>GameDbContext</code> class</strong><br> This is a way for Entity Framework (EF Core) to track all the inventory items in your game. It knows how to load items from the database, save them back to the database, and query them.</p></li>
</ol>
<h4 id="relationship-between-them">Relationship Between Them:</h4>
<ul>
<li>The <strong><code>DbSet<InventoryItem></code></strong> in <code>GameDbContext</code> manages <em>all</em> the <code>InventoryItem</code> objects stored in your database. Think of it like a “big box” where every inventory item in the entire game is kept.</li>
<li>The <strong><code>List<InventoryItem></code></strong> inside the <code>Player</code> class is more specific. It only holds the items that belong to that particular player. When you retrieve a player from the database, EF Core also helps fill in the <code>List<InventoryItem></code> with just the items that belong to that player.</li>
</ul>
<h4 id="how-an-item-is-created-and-added">How an Item is Created and Added</h4>
<ol>
<li><strong>Creating a New Item:</strong> When you create an <code>InventoryItem</code>, you are making a new C# object in memory. For example:</li>
</ol>
<pre><code class="language-cs">var newItem = new InventoryItem {
ItemName = "Sword",
Quantity = 1,
PlayerId = player.Id // Foreign key linking to the player
};
</code></pre>
<ul>
<li><p>This creates an object in memory but <strong>it’s not yet part of the database</strong>.</p></li>
<li><p><strong>Adding it to the Player’s Inventory:</strong> Next, you add it to the player’s inventory list:</p></li>
</ul>
<pre><code class="language-cs">player.Inventory.Add(newItem);
</code></pre>
<ul>
<li><p>Now the new <code>InventoryItem</code> is in the <code>Player</code>‘s list (<code>player.Inventory</code>), but it still <strong>doesn’t exist in the database</strong> yet.</p></li>
<li><p><strong>Adding the Item to the Database (Making it Permanent):</strong> To save the new item to the database, you have to tell EF Core to track it and save it. Here’s how:</p></li>
</ul>
<pre><code class="language-cs">context.InventoryItems.Add(newItem); // Add the item to the DbSet
context.SaveChanges(); // Save changes to the database
</code></pre>
<ul>
<li><code>context.InventoryItems.Add(newItem)</code> tells EF Core to prepare this new item for being stored in the database.</li>
<li><code>context.SaveChanges()</code> takes everything EF Core is tracking (including this new item) and writes it into the database.</li>
</ul>
<p>Now the item exists both in <strong>memory</strong> (in <code>player.Inventory</code>) and in the <strong>database</strong> (inside the <code>InventoryItems</code> table).</p><h3 id="who-or-what-is-responsible-for-the-objects">Who or What is Responsible for the Objects?</h3>
<ul>
<li><strong>You (the Developer)</strong> create and manage the actual objects (<code>InventoryItem</code>, <code>Player</code>, etc.) in C# code.</li>
<li><strong>EF Core</strong> manages the communication between your code (in memory) and the database (on disk). It knows how to save and load these objects.</li>
</ul>
<h4 id="what-is-the-real-object">What is the Real Object?</h4>
<ul>
<li>The <strong>real object</strong> is the one in memory. For example, <code>newItem</code> is the actual object you create.</li>
<li>When you save it to the database, it becomes a row in the database, but when you load it back later, EF Core re-creates the object in memory.</li>
</ul>
<h4 id="are-there-temporary-objects">Are There Temporary Objects?</h4>
<ul>
<li>Yes, the <code>InventoryItem</code> you create in memory is a “temporary object” until you save it to the database using <code>context.SaveChanges()</code>.</li>
<li>After saving, it becomes permanent in the database. EF Core keeps a reference to this same object in memory, so it doesn’t create multiple copies unnecessarily.</li>
</ul>
<h4 id="are-they-the-same-object-with-multiple-references">Are They the Same Object with Multiple References?</h4>
<ul>
<li>Yes. Once you add an item to both <code>player.Inventory</code> and <code>context.InventoryItems</code>, these are two references to the <strong>same object</strong>.<ul>
<li><code>player.Inventory</code> holds a reference to the <code>InventoryItem</code> in the context of the player.</li>
<li><code>context.InventoryItems</code> manages that same object but in the context of the database.</li>
</ul>
</li>
</ul>
<h4 id="how-do-you-use-all-of-this">How Do You Use All of This?</h4>
<ol>
<li><strong>Create the item:</strong><br> Create a new item in memory like this:</li>
</ol>
<pre><code class="language-cs">var newItem = new InventoryItem {
ItemName = "Shield",
Quantity = 1,
PlayerId = player.Id
};
</code></pre>
<p><strong>Add the item to the player:</strong><br>Add it to the player’s <code>Inventory</code> list:</p><pre><code class="language-cs">player.Inventory.Add(newItem);
</code></pre>
<p><strong>Track the item in EF Core:</strong><br>Tell EF Core about this item by adding it to the <code>DbSet</code>:</p><pre><code class="language-cs">context.SaveChanges();
</code></pre>
<p>Now, the item is:</p><ul>
<li>In the player’s inventory (<code>player.Inventory</code>).</li>
<li>In the database, managed by EF Core (<code>context.InventoryItems</code>).</li>
</ul>
<h3 id="quick-summary">Quick Summary:</h3>
<ul>
<li><strong><code>List<InventoryItem></code> in <code>Player</code>:</strong> Holds items only for that player in memory.</li>
<li><strong><code>DbSet<InventoryItem></code> in <code>GameDbContext</code>:</strong> Manages all inventory items in the game and connects them to the database.</li>
<li><strong>Creating an item:</strong> You create the item in memory, add it to the player’s list, and then save it to the database with EF Core.</li>
<li><strong>Real object:</strong> The item is real in memory, and EF Core makes sure the object gets saved and retrieved from the database properly. Multiple references to the same object point to the same instance.</li>
</ul>
<p>This approach helps ensure that your game objects exist in both memory and the database in a well-structured, efficient manner.</p><h3 id="who-should-initialize-player-with-inventory-items">Who Should Initialize <code>Player</code> with Inventory Items?</h3>
<p>Classes should not be responsible for initializing themselves with its data. That would violate the Single Responsibility Principle, where a class should only have one reason to change. The Player class, for example, should focus on modeling the player and its properties, not managing data access.</p><p>The better option is to have a controller or manager responsible for fetching the <code>Player</code> and its associated data from the database. This manager could be something like a <code>PlayerService</code> or <code>GameDataLoader</code> and it would query the database, populate the <code>Player</code> object with the data, and return the fully initialized <code>Player</code> object.</p><p>It might look something like:</p><pre><code class="language-cs">public class PlayerService {
private readonly GameDbContext _context;
public PlayerService(GameDbContext context) {
_context = context;
}
public Player GetPlayerWithInventory(int playerId) {
return _context.Players
.Include(p => p.Inventory) // Eager load inventory items
.FirstOrDefault(p => p.Id == playerId);
}
}
</code></pre>
<p>This way the <code>Player</code> class stays clean and doesn’t know anything about the database.</p><h3 id="multiple-operations-on-the-same-object">Multiple Operations on the Same Object</h3>
<p>It can feel like there are two separate operations:</p><ol>
<li>Adding items to the <code>player.Inventory</code> list.</li>
<li>Adding items to the <code>DbSet<InventoryItem></code>.</li>
</ol>
<p>It can feel like you’re duplicating the same work by managing both the list and the database. Let’s clarify what’s going on and how to approach it.</p><h3 id="are-there-really-two-sets-of-objects">Are There Really Two Sets of Objects?</h3>
<p>Not exactly. When you create an <code>InventoryItem</code>, it exists as a single object in memory. When you add it to <code>player.Inventory</code> and later to <code>context.InventoryItems</code>, you’re just creating <strong>two references to the same object</strong>. EF Core tracks changes to the object via <code>context.InventoryItems</code>, but you’re free to access that object through <code>player.Inventory</code>.</p><h3 id="is-it-normal">Is It Normal?</h3>
<p>Yes, this pattern of managing both the collection (like <code>player.Inventory</code>) and saving to the database separately is pretty common in EF Core and ORMs in general. However, the redundancy you’re feeling can be reduced.</p><h3 id="how-to-simplify">How to Simplify</h3>
<p>Instead of managing both the player’s inventory list and the database context separately, let’s clean it up so that you only interact with the <code>Player</code> object and let EF Core handle saving both <code>Player</code> and <code>InventoryItem</code> in one go.</p><ul>
<li><strong>One Object Graph</strong>: You should treat the <code>Player</code> and its <code>Inventory</code> as a single “object graph.” When you save the <code>Player</code>, EF Core will automatically save all related <code>InventoryItem</code> objects.</li>
</ul>
<p>Here’s how you might do that:</p><pre><code class="language-cs">using (var context = new GameDbContext()) {
// Create the player and the item
var player = new Player { Name = "Jack", Level = 5 };
var newItem = new InventoryItem { ItemName = "Sword", Quantity = 1 };
// Add the item to the player's inventory
player.Inventory.Add(newItem);
// Add the player (with inventory) to the context
context.Players.Add(player);
// Save everything at once
context.SaveChanges(); // Saves both Player and InventoryItem in one go
}
</code></pre>
<p>In this example:</p><ul>
<li>You’re only managing the <code>Player</code> object.</li>
<li>By adding the <code>InventoryItem</code> to the <code>player.Inventory</code>, EF Core will automatically track that item because it’s part of the <code>Player</code>’s “object graph.”</li>
<li>When you call <code>context.SaveChanges()</code>, EF Core will save the <code>Player</code> and all related <code>InventoryItem</code> objects in one transaction.</li>
</ul>
<h3 id="is-it-code-smell">Is It Code Smell?</h3>
<p>The approach where you’re managing <code>player.Inventory</code> and <code>context.InventoryItems</code> separately can feel like a code smell if you’re not leveraging EF Core’s ability to track relationships between entities. If you’re manually handling both, it feels like duplication and is error-prone.</p><p>However, when you treat the <code>Player</code> and <code>InventoryItems</code> as part of a single object graph (as shown above), it becomes simpler and more natural. EF Core can track everything with just one call to <code>SaveChanges()</code>.</p><h3 id="summary">Summary</h3>
<ul>
<li><strong>Who initializes the <code>Player</code>?</strong> A <code>PlayerService</code> (or similar) should be responsible for loading <code>Player</code> objects with their <code>InventoryItems</code> from the database.</li>
<li><strong>Are two object sets necessary?</strong> No, ideally you treat <code>Player</code> and <code>InventoryItem</code> as a single object graph. Add items to <code>player.Inventory</code> and let EF Core handle the persistence for both in one operation.</li>
<li><strong>Cleaner approach:</strong> Use EF Core’s relationship tracking to save both <code>Player</code> and <code>InventoryItem</code> with one <code>SaveChanges()</code> call, minimizing manual duplication of work.</li>
</ul>
<p>This approach should streamline your code and make it feel less “janky.”</p><h3 id="performing-logic-without-becoming-tied-to-database-operations">Performing Logic Without Becoming Tied to Database Operations</h3>
<p>Let’s explore how the <code>Receive(IItem givenItem, IItemGiver giver)</code> method would fit into this scheme.</p><p>The <code>Receive</code> method allows the player to receive an item from another entity like an NPC. Here’s how we can integrate this with our previously discussed EF Core setup without causing duplication or breaking cohesion.</p><h3 id="1-what-would-receive-do">1. <strong>What Would <code>Receive</code> Do?</strong></h3>
<p>The <code>Receive</code> method in <code>Player</code> is responsible for:</p><ul>
<li>Adding the received item (<code>givenItem</code>) to the player’s inventory.</li>
<li>Optionally tracking who gave the item (if needed for gameplay logic).</li>
<li>Ensuring the item is properly saved to the database.</li>
</ul>
<p>The key here is that the method should manage the player’s inventory but let EF Core handle the database part. <code>Receive</code> can operate on the object graph (the player and its items) without worrying about how EF Core persists those changes.</p><h3 id="2-adjusting-the-receive-method-to-work-with-ef-core">2. <strong>Adjusting the <code>Receive</code> Method to Work with EF Core</strong></h3>
<p>Let’s start by looking at a potential implementation of the <code>Receive</code> method:</p><pre><code class="language-cs">public class Player {
public List<InventoryItem> Inventory { get; private set; }
public void Receive(IItem givenItem, IItemGiver giver) {
// Assume givenItem is a valid InventoryItem object
InventoryItem item = (InventoryItem)givenItem;
// Add the item to the player's inventory
Inventory.Add(item);
// Optionally log the giver or update some logic based on the giver
Console.WriteLine($"{giver.Name} gave you {item.ItemName}.");
// The item now exists in the player's inventory list
// Saving to the database happens outside of this method
}
}
</code></pre>
<p>In this method:</p><ul>
<li><code>givenItem</code> is cast to <code>InventoryItem</code> and added to the player’s <code>Inventory</code> list.</li>
<li>The method handles adding the item to the player’s memory representation of inventory but <strong>doesn’t directly deal with the database</strong>.</li>
<li>Database operations (saving the item) happen outside this method.</li>
</ul>
<h3 id="3-who-manages-database-saving">3. <strong>Who Manages Database Saving?</strong></h3>
<p>You want the <code>Receive</code> method to be simple and focus on the game logic, not database handling. Database saving should be handled at a higher level, probably by the <strong>game logic controller</strong> or <strong>service</strong>.</p><p>For example, an NPC gives the player an item, and after the game logic is complete, the system saves the changes to the database.</p><p>Here’s how that might look at the controller/service level:</p><pre><code class="language-cs">public class GameController {
private readonly GameDbContext _context;
public GameController(GameDbContext context) {
_context = context;
}
public void GiveItemToPlayer(Player player, IItem givenItem, IItemGiver giver) {
// Player receives the item from the NPC
player.Receive(givenItem, giver);
// Track changes to the player's inventory in the database
_context.Players.Update(player);
// Since the InventoryItem is part of the Player's object graph, EF Core will
// automatically track the new item and save it
_context.SaveChanges();
}
}
</code></pre>
<p>Here:</p><ul>
<li><strong>The <code>Player</code> handles the game logic</strong>: The player gets the item from the NPC via <code>Receive(givenItem, giver)</code>.</li>
<li><strong>The <code>GameController</code> handles the database logic</strong>: After the player has received the item, the controller updates the player’s state in the database by calling <code>Update(player)</code> and <code>SaveChanges()</code>.</li>
</ul>
<h3 id="4-why-does-this-make-sense">4. <strong>Why Does This Make Sense?</strong></h3>
<ul>
<li><strong>Separation of Concerns</strong>: The <code>Player</code> class focuses only on game logic (receiving items, managing its own state). It doesn’t deal with saving or database operations, which are handled at a higher level (in this case, the <code>GameController</code>).</li>
<li><strong>Single Object Graph</strong>: The <code>Player</code> and its <code>InventoryItem</code> are part of a single object graph. When the <code>GameController</code> saves the <code>Player</code>, EF Core will automatically detect that a new <code>InventoryItem</code> was added to the player’s <code>Inventory</code> and will save both the <code>Player</code> and the new item in one operation.</li>
<li><strong>No Duplication of Objects</strong>: You’re still only working with one set of objects in memory. There’s no separate management of the database objects and in-memory objects.</li>
</ul>
<h3 id="5-handling-the-giver-npc">5. <strong>Handling the Giver (NPC)</strong></h3>
<p>If the giver (NPC) needs to be tracked (for example, if it’s important to know which NPC gave the item), you can log or store that information in the <code>Receive</code> method or another part of the <code>Player</code> class. Here’s an example:</p><pre><code class="language-cs">public class Player {
public List<InventoryItem> Inventory { get; private set; }
public void Receive(IItem givenItem, IItemGiver giver) {
InventoryItem item = (InventoryItem)givenItem;
// Add to the player's inventory
Inventory.Add(item);
// Log or track the giver if needed
Console.WriteLine($"{giver.Name} gave {item.ItemName}");
// Example: Track giver info in the item
item.GivenBy = giver.Name;
// Database saving is handled externally
}
}
</code></pre>
<p>In this case, you might store the giver’s information inside the <code>InventoryItem</code> or just log it. This depends on your game’s needs.</p><h3 id="does-this-fit-the-pattern"><strong>Does This Fit the Pattern?</strong></h3>
<p>Absolutely. This design adheres to the principles we’ve discussed:</p><ul>
<li><strong>Parent-to-Child Dependency</strong>: The <code>Player</code> manages its own inventory (<code>InventoryItem</code> is part of the <code>Player</code> object graph).</li>
<li><strong>Single Responsibility</strong>: The <code>Player</code> class focuses only on its own game logic (receiving items), while the <code>GameController</code> is responsible for coordinating between game logic and the database.</li>
<li><strong>No Object Duplication</strong>: You only manage one set of objects in memory. EF Core tracks the changes and handles persistence in the background.</li>
</ul>
<h4 id="example-flow">Example Flow:</h4>
<ol>
<li>The NPC (giver) wants to give an item.</li>
<li>The <code>GameController</code> calls <code>player.Receive(givenItem, giver)</code>.</li>
<li>The <code>Player</code> receives the item and adds it to its <code>Inventory</code>.</li>
<li>The <code>GameController</code> calls <code>SaveChanges()</code> to persist both the <code>Player</code> and the new <code>InventoryItem</code> to the database.</li>
</ol>
<h4 id="summary-1">Summary</h4>
<ul>
<li><strong><code>Player.Receive()</code></strong> manages the logic of receiving the item, while <strong>database operations</strong> are handled at a higher level (like a controller or manager).</li>
<li><strong>EF Core automatically tracks</strong> changes to the <code>InventoryItem</code> through the <code>Player</code>’s object graph.</li>
<li>You avoid duplication by keeping everything in one object graph and only persisting the whole player state when needed.</li>
</ul>
<p>This approach is clean, avoids code smells, and keeps your game logic and data access responsibilities well-separated.</p><h2 id="managing-dependencies">Managing dependencies</h2>
<p><code>[Export]</code> is the Godot way of doing dependency injection. Ideally the exported node would be a child node mapped to a parent controller, as explained in [[#Parent-to-Child Dependencies]] above.</p><p>When a clear parent-child relationship cannot be used, like, for example in a situation where UI needs data to display different strategies can be applied.</p><h3 id="where-should-requests-originate">Where Should Requests Originate?</h3>
<p>The request to give an item should indeed originate from the <strong>NPC</strong> (or more precisely, from an interaction between the NPC and the Player). However, the <strong>NPC</strong> itself doesn’t need to manage the whole process—its role can remain simple, while the higher-level game logic (likely in the <strong>GameController</strong>) handles the actual mechanics.</p><h3 id="2-the-npc-as-a-dumb-object">2. <strong>The NPC as a “Dumb” Object</strong></h3>
<p>The NPC can be more of a “dumb” object. The NPC just holds some data and methods to initiate an action (like giving an item), but the <strong>NPC should not be responsible for complex game logic</strong> like inventory management or interacting directly with the database. This keeps the NPC clean and focused on what it represents in the game world.</p><p>In other words, the NPC initiates the process by signaling its intent to give an item, but the <strong>GameController</strong> or some other game system handles the heavy lifting.</p><p>Here’s a possible structure:</p><h3 id="3-how-the-hierarchy-and-interaction-would-work">3. <strong>How the Hierarchy and Interaction Would Work</strong></h3>
<ol>
<li><strong>NPC</strong>: The NPC triggers the action, such as giving an item, when it interacts with the player.</li>
<li><strong>GameController</strong>: The <code>GameController</code> (or a similar system) manages what actually happens when the NPC gives the item to the player, including updating the game state, calling the player’s <code>Receive()</code> method, and saving the changes.</li>
<li><strong>Player</strong>: The player receives the item, and the player’s inventory is updated. The player doesn’t handle persistence or interaction logic—that’s handled by the <code>GameController</code>.</li>
</ol>
<h3 id="4-example-flow-of-giving-an-item">4. <strong>Example Flow of Giving an Item</strong></h3>
<h4 id="npc-class">NPC Class</h4>
<p>The NPC is a simple entity. It has an item to give and can trigger the <code>GiveItemToPlayer()</code> method in the <code>GameController</code>.</p><pre><code class="language-cs">public class NPC : IItemGiver {
public string Name { get; set; }
public InventoryItem ItemToGive { get; set; }
public void InteractWithPlayer(Player player, GameController controller) {
// The NPC asks the GameController to give the player an item
controller.GiveItemToPlayer(player, ItemToGive, this);
}
}
</code></pre>
<p>In this case:</p><ul>
<li>The <strong>NPC</strong> has an <code>ItemToGive</code>, but it doesn’t handle the details of giving the item.</li>
<li>When the player interacts with the NPC, the <strong>NPC triggers</strong> the interaction by calling the <code>GameController</code>.</li>
</ul>
<h4 id="gamecontroller-class">GameController Class</h4>
<p>The <strong>GameController</strong> handles the actual transaction, manages the logic of giving the item, and persists the changes to the database.</p><pre><code class="language-cs">public class GameController {
private readonly GameDbContext _context;
public GameController(GameDbContext context) {
_context = context;
}
public void GiveItemToPlayer(Player player, IItem givenItem, IItemGiver giver) {
// The player receives the item
player.Receive(givenItem, giver);
// Update the database to reflect the new item in the player's inventory
_context.Players.Update(player);
_context.SaveChanges();
}
}
</code></pre>
<p>Here:</p><ul>
<li>The <strong>GameController</strong> coordinates the interaction between the <strong>NPC</strong> and the <strong>Player</strong>.</li>
<li>It ensures that the item is added to the player’s inventory and saved in the database.</li>
</ul>
<h4 id="player-class">Player Class</h4>
<p>The <strong>Player</strong> is responsible for adding the item to its inventory but doesn’t deal with persistence or interaction logic.</p><pre><code class="language-cs">public class Player {
public List<InventoryItem> Inventory { get; private set; }
public void Receive(IItem givenItem, IItemGiver giver) {
InventoryItem item = (InventoryItem)givenItem;
Inventory.Add(item);
Console.WriteLine($"{giver.Name} gave you a {item.ItemName}.");
}
}
</code></pre>
<h3 id="hierarchy-and-parent-to-child-dependency"><strong>Hierarchy and Parent-to-Child Dependency</strong></h3>
<p>To maintain the <strong>parent-to-child dependency</strong>, the <strong>NPC</strong> doesn’t need to directly know about the <strong>GameController</strong> or even the <strong>Player</strong> in a deep sense. It just triggers an action (like <code>InteractWithPlayer</code>) and passes in the necessary data.</p><p>The <strong>GameController</strong> acts as the “parent” managing the flow between the NPC and the Player. It orchestrates the logic from a higher level, ensuring that the NPC, Player, and database all interact correctly without causing any child-to-parent dependencies or complex cross-object interactions.</p><p>In this setup:</p><ul>
<li><strong>NPC → GameController → Player</strong> is the flow of control.</li>
<li>The <strong>NPC</strong> triggers the event, but it’s the <strong>GameController</strong> that manages the relationship between NPCs, Players, and the database.</li>
</ul>
<p>This avoids any circular dependencies, keeps the NPC simple (it doesn’t manage the database or game state directly), and maintains a clear, hierarchical flow of control.</p><h3 id="6-why-this-structure-makes-sense">6. <strong>Why This Structure Makes Sense</strong></h3>
<ul>
<li><strong>Separation of Concerns</strong>: The <strong>NPC</strong> only knows about the fact that it wants to give an item. It doesn’t care about how the item is added to the player’s inventory or saved to the database.</li>
<li><strong>Centralized Game Logic</strong>: The <strong>GameController</strong> coordinates the overall interaction and handles the persistence. This prevents messy dependencies between unrelated objects and keeps the architecture clean.</li>
<li><strong>Single Source of Truth</strong>: There’s no risk of having multiple versions of the same data floating around. The <strong>Player</strong> manages the inventory, and EF Core handles the persistence as one unified object graph.</li>
</ul>
<h3 id="summary-2">Summary</h3>
<ul>
<li><strong>The NPC</strong> initiates the action to give an item but doesn’t handle the complex logic. It triggers the interaction by calling the <code>GameController</code>.</li>
<li><strong>The GameController</strong> orchestrates the interaction, calling the player’s <code>Receive()</code> method and saving the changes to the database.</li>
<li><strong>The Player</strong> receives the item, but doesn’t manage the persistence or higher-level game logic.</li>
<li>This structure avoids child-to-parent dependencies and keeps the <strong>NPC</strong> and <strong>Player</strong> focused on their respective responsibilities, while the <strong>GameController</strong> handles game logic and database interaction.</li>
</ul>
<p>This way, everything stays clean, focused, and manageable, adhering to solid architectural principles.</p><h3 id="what-about-returning-state">What About Returning State?</h3>
<p>you sometimes need to get state back from child objects. However, how you handle that state depends on how tightly you want to couple your objects and what kind of architecture you’re following. There are two key approaches you can consider:</p><ol>
<li><strong>Fetching State When Necessary</strong>: This is the classic way where you allow some controlled flow of information back up the hierarchy.</li>
<li><strong>Designing Around the Principle of Always Valid State</strong>: This approach reduces the need to query state and focuses on ensuring that the system is always in a valid state by design.</li>
</ol>
<p>Let’s explore both.</p><h4 id="fetching-state-back-from-child-objects"><strong>Fetching State Back from Child Objects</strong></h4>
<p>In many cases, it’s reasonable for a parent (like the <code>GameController</code>) to query child objects (like the <code>Player</code> or <code>NPC</code>) for their state. This is especially true when decisions need to be made based on real-time data (e.g., checking the player’s health, inventory capacity, etc.).</p><h4 id="example-checking-the-players-health">Example: Checking the Player’s Health</h4>
<p>Imagine you need to check the player’s health to decide if they can continue in combat or if they need healing:</p><pre><code class="language-cs">public class GameController {
public void CheckPlayerHealth(Player player) {
if (player.Health < 20) {
Console.WriteLine("Player needs healing!");
} else {
Console.WriteLine("Player is healthy.");
}
}
}
public class Player {
public int Health { get; private set; }
public Player(int health) {
Health = health;
}
}
</code></pre>
<p>Here:</p><ul>
<li>The <strong>GameController</strong> is querying the <strong>Player</strong> for its current health.</li>
<li>The flow remains parent-to-child (the <code>GameController</code> calls methods on <code>Player</code>), but it’s getting information back for decision-making.</li>
</ul>
<p><strong>This approach is sensible when:</strong></p><ul>
<li>You need real-time, up-to-date information from child objects.</li>
<li>The parent object (e.g., <code>GameController</code>) needs to make decisions based on child state.</li>
</ul>
<h3 id="2-designing-for-always-valid-state">2. <strong>Designing for Always Valid State</strong></h3>
<p>Another approach is to minimize or eliminate the need for fetching state by designing your objects to always maintain a valid state. In this approach, each object is responsible for managing its state and ensuring that it is always “correct” from the perspective of the game logic. This aligns with the <strong>tell, don’t ask</strong> principle, where instead of querying state, you tell the object to act and trust that it will maintain its own state properly.</p><h4 id="example-players-health-in-an-always-valid-system">Example: Player’s Health in an “Always Valid” System</h4>
<p>Instead of checking the player’s health manually, you could have the <strong>Player</strong> class manage its own health logic:</p><pre><code class="language-cs">public class Player {
public int Health { get; private set; }
public Player(int health) {
Health = health;
}
public void TakeDamage(int damage) {
Health -= damage;
// Automatically handle death
if (Health <= 0) {
HandleDeath();
}
}
private void HandleDeath() {
Console.WriteLine("Player has died.");
// Additional logic for death could go here
}
}
</code></pre>
<p>In this example:</p><ul>
<li>The <strong>Player</strong> object itself manages its state (health), and you don’t need to query it in the <code>GameController</code> to check if it needs healing or if it’s dead.</li>
<li>The <strong>Player</strong> ensures that its own health logic is valid at all times. If health reaches 0, it automatically handles death internally.</li>
</ul>
<p><strong>This approach is sensible when:</strong></p><ul>
<li>You want to decentralize state management and avoid state queries.</li>
<li>You prefer each object to be self-sufficient in managing its own state, reducing external checks.</li>
</ul>
<h3 id="when-to-fetch-state-vs-when-to-rely-on-always-valid-state">When to Fetch State vs. When to Rely on “Always Valid State”</h3>
<h4 id="fetching-state"><strong>Fetching State</strong></h4>
<p>This approach works best when you have:</p><ul>
<li>Complex decision-making that requires real-time information from child objects.</li>
<li>Systems where the parent (controller) needs to make decisions based on child state.</li>
</ul>
<p>You should use this approach if:</p><ul>
<li>You need to query conditions like the player’s inventory, health, or quest status to make game-wide decisions.</li>
<li>The flow of data needs to be flexible and responsive.</li>
</ul>
<h4 id="designing-for-always-valid-state"><strong>Designing for Always Valid State</strong></h4>
<p>This approach reduces complexity by making each object responsible for its own state, making the system less dependent on constant state checks.</p><p>You should use this approach if:</p><ul>
<li>You want to reduce the need for querying state and want each object to maintain itself.</li>
<li>You prefer objects that react to commands and automatically handle their own logic, ensuring that no external checks are needed.</li>
</ul>
<h3 id="hybrid-approach-valid-state--occasional-queries">Hybrid Approach: Valid State + Occasional Queries</h3>
<p>In practice, most games use a combination of both approaches.</p><ul>
<li><strong>“Always Valid State”</strong>: For simpler systems like inventory management, health, or abilities, where the player and other entities handle their own state and transitions.</li>
<li><strong>Fetching State</strong>: For higher-level game systems or controllers that need to coordinate multiple entities or query complex data (like checking for quest completion or making decisions based on several objects’ states).</li>
</ul>
<p>For example, the <code>GameController</code> might periodically <strong>query</strong> the player’s health and inventory, but <strong>rely</strong> on the player itself to always keep its inventory and health in a valid state.</p><h3 id="example-of-combining-both-approaches">Example of Combining Both Approaches</h3>
<pre><code class="language-cs">public class GameController {
public void UpdateGameState(Player player) {
// Query player state when needed
if (player.Health < 20) {
Console.WriteLine("Warning: Player health is low.");
}
// Assume player always maintains a valid inventory
player.ReceiveNewItem(new InventoryItem("Health Potion"));
}
}
public class Player {
public int Health { get; private set; }
public List<InventoryItem> Inventory { get; private set; }
public Player(int health) {
Health = health;
Inventory = new List<InventoryItem>();
}
// Player manages its own health and reacts accordingly
public void TakeDamage(int damage) {
Health -= damage;
if (Health <= 0) HandleDeath();
}
// Always valid inventory management
public void ReceiveNewItem(InventoryItem item) {
Inventory.Add(item);
Console.WriteLine($"Received {item.ItemName}.");
}
private void HandleDeath() {
Console.WriteLine("Player has died.");
}
}
</code></pre>
<p>In this hybrid example:</p><ul>
<li>The <strong>GameController</strong> queries the player’s health when needed.</li>
<li>The <strong>Player</strong> manages its inventory and health internally, always ensuring valid state without needing to be micromanaged by the <code>GameController</code>.</li>
</ul>
<h3 id="conclusion">Conclusion</h3>
<ul>
<li><strong>Fetching state</strong> is necessary when the controller needs real-time information to make decisions, and it’s totally acceptable in many cases.</li>
<li><strong>Always valid state</strong> design reduces the need for querying and ensures objects maintain themselves in a valid state, which simplifies logic and reduces dependencies.</li>
<li><strong>Hybrid approaches</strong> are often best in games, where certain systems (like health, inventory, or quests) manage themselves, while others (like high-level game logic) still require state checks.</li>
</ul>
<p>In short, aim for valid state whenever possible, but don’t be afraid to query state when needed for complex game logic. Both patterns can work together effectively depending on the situation.</p><h2 id="c-events-and-signals-for-decoupling">C# Events and Signals for Decoupling</h2>
<p>Use the event system to create loosely coupled communication mechanism between nodes. This allows nodes that don’t have a direct relationship to communicate without being tightly bound.
The UI can emit an event when it needs data, and any node (or manager) can listen to those signals and provide the needed data.</p><h2 id="using-godot-resources-to-share-data">Using Godot Resources to Share Data</h2>
<p>Godot <code>Resource</code> can be used to share data between various objects as long as you are aware of the limitations - only one party should ever be responsible for changing the data, while several can read it at the same time. A shared <code>Resource</code> gives instantaneous access to the data across multiple objects. You will also have to be careful and deploy a default strategy in case the data is not available at any given time.</p><h2 id="service-locator--manager-pattern">Service Locator / Manager Pattern</h2>
<p>Can be used to create a centralized manager or service locator to handle instances of shared dependencies like data required by UI. This pattern helps when the relationships is not clear or direct.
For example, you can have a <code>GameManager</code> or <code>DataManager</code> node responsible for holding game state, UI data, or other global information.
You’d reference this node using Godot’s <code>GetTree().Root</code> or a singleton (Autoload) which would allow child nodes, such as your UI elements, to fetch the necessary data without needing a direct parent-child relationship.</p><h2 id="godot-singleton-autoload">Godot Singleton Autoload</h2>
<p>For truly global data (such as player stats, global settings, etc.) consider using an <code>Autoload</code>. This allows any node to access global state without needing a direct reference. This should be used sparingly to avoid overloading the global namespace with too many dependencies.</p>
]]>
</content>
</entry>
<entry>
<title>How to setup SQLite database with EF Core and Godot</title>
<author>
<name>Pilvinen</name>
</author>
<link href="https://pilvimaa.fi/how-to-setup-sqlite-database-with-ef-core-and-godot/"/>
<id>https://pilvimaa.fi/how-to-setup-sqlite-database-with-ef-core-and-godot/</id>
<media:content url="https://pilvimaa.fi/media/posts/19/Database.png" medium="image" />
<category term="tutorial"/>
<category term="sqlite"/>
<category term="ef core"/>
<category term="database"/>
<category term="Godot"/>
<category term="Csharp"/>
<updated>2024-07-03T01:24:09+03:00</updated>
<summary>
<![CDATA[
<img src="https://pilvimaa.fi/media/posts/19/Database.png" alt="Database" />
EF Core is a popular high level object relational mapper (ORM) which can have various databases at the backend. As a database noobie, I recently had a bit of a…
]]>
</summary>
<content type="html">
<![CDATA[
<p><img src="https://pilvimaa.fi/media/posts/19/Database.png" class="type:primaryImage" alt="Database" /></p>
<p>EF Core is a popular high level object relational mapper (ORM) which can have various databases at the backend.</p><p>As a database noobie, I recently had a bit of a trouble setting it up with Godot and SQLite. There was very little practical information available from a beginner’s perspective for setting it up successfully with Godot.</p><p>I hit a dead end several times trying to follow the official MS docs and bunch of tutorials, but eventually with a help from a friend got it working.</p><p>So I thought I might as well write a quick article about it - from my own unique database noob perspective. In case someone else is having the same challenges.</p><p>If you’re looking for educated, accurate, precise and scientific description of how this piece of… technology… works, avert your eyes and never return!</p><p>At the time of writing this, Godot 4.2.2 has been out for a while and Godot 4.3 hasn’t yet been released. I’m running on .NET 8 and using C# 12. These instructions have been written for that in mind and specifically for Rider IDE. If you’re dealing with another IDE or the command line, you will have to figure those parts out for yourself.</p><h1 id="setting-up-a-class-library">Setting up a class library</h1>
<p>Since we’re going to use the Migrations feature of EF Core, to get things working properly with Godot’s SDK, you’re going to need to create your database as a separate project, as a <strong>class library</strong>.</p><p>To do that, right click your solution in Rider’s solution explorer and select <strong>Add > New Project</strong> and make it a <strong>Class Library</strong>. It will be created directly under your main project for easy access. You can call it “Database”, or what ever you like.</p><h1 id="linking">Linking</h1>
<p>To be able to do anything with the Database project it needs to be linked with your main project. So right click in Rider’s solution explorer your main project and select <strong>Add > Reference</strong>, select the box for Database and click the <strong>Add</strong> button. Done.</p><h1 id="dealing-with-assemblyinfo-duplicate-attributes">Dealing with AssemblyInfo duplicate attributes</h1>
<p>You might have noticed that there’s now a funny new folder in your main project, which is from the Database project. You might be tempted to exclude it, and if you do, you’re on your own dealing with potential issues that might arise from that.</p><p>In this tutorial we’re going to edit the your main project’s <code>.csproj</code> file and add inside the <code><PropertyGroup></PropertyGroup></code> tags near the top the following mystical tags:</p><pre><code class="language-xml"><GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</code></pre>
<p>And its good buddy:</p><pre><code class="language-xml"><GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
</code></pre>
<p>And by doing this magical ritual you will get rid of <strong>compilation errors</strong> caused by not excluding the aforementioned folders from your Database project - by doing which you will avoid <em>another set of problems</em>.</p><p>Just do it. You can change your setup later on your own if you’re not happy with it. Let’s first get things working.</p><h1 id="dealing-with-nuget">Dealing with NuGet</h1>
<p>The three packages you need to install from NuGet are:</p><ul>
<li><p><code>Microsoft.EntityFrameworkCore.Sqlite</code></p></li>
<li><p><code>Microsoft.EntityFrameworkCore.Design</code></p></li>
<li><p><code>Microsoft.EntityFrameworkCore.Tools</code></p></li>
</ul>
<p>You will need to add these to your <strong>Database</strong> project. You will likely not need them in your main project.</p><h1 id="lets-add-some-classes">Let’s add some classes</h1>
<p>We’re going to need some code to be able to do anything with the database.</p><h3 id="savegamemanifestcs">SaveGameManifest.cs</h3>
<p>You can arrange your projects how ever you like, but I recommend you create in the <strong>Database</strong> project a folder named <code>DatabaseSets</code>. This folder will hold your C# classes which essentially represent your database tables.</p><p>For fun let’s add <code>SaveGameManifest.cs</code> file in that folder which will represent the data in your table. It’s basically a schema or “entity”. It’s the stuff you want to store in the database.</p><pre><code class="language-cs">using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Database;
[Table("SaveGameManifest")] // Name of the table in the database for this class.
[PrimaryKey(nameof(DatabaseId))] // They key you can directly fetch the data with.
public sealed class SaveGameManifest {
// The primary Guid based key to access this save game manifest.
[Column(nameof(DatabaseId))] // The database column name.
public Guid DatabaseId { get; set; }
// The name of the save game as inputted by user.
[Column(nameof(SaveName)), MaxLength(128),] // EF Core doesn't like strings of unrestricted length.
public string SaveName { get; set; } = string.Empty;
// The date when the save game was last saved.
[Column(nameof(LastSaved))]
public DateTime LastSaved { get; set; } = DateTime.Now;
// Required for database. Do not remove.
public SaveGameManifest() {}
public SaveGameManifest(Guid databaseId, string saveName) {
DatabaseId = databaseId;
SaveName = saveName;
}
}
</code></pre>
<p>And that’s it. We decorated our class and properties with attributes which instruct EF Core what everything is, ie. how it should be saved in the database. SQLite database is basically just a fancy a big Excel sheet. <strong>Tables</strong> tell you what type of stuff is in there in general (they are kind like file system folders), and <strong>Columns</strong> tell you what should go in here, and <strong>rows</strong> of the columns contain the actual data, data, data and more data.</p><h3 id="databaseconnectioncs">DatabaseConnection.cs</h3>
<p>I like to name the <code>DbContext</code> class <code>DatabaseConnection</code>, it makes it easier for me to comprehend what is going on.</p><p>There are lots of ways you could set up this file, so don’t be mistaken that this is the only way or even necessarily a good way. This is the database noob way that I came up with, the way which made sense to me personally.</p><p>This class essentially represents your database / gives you access to everything.</p><p>So in my setup you would call <code>DatabaseConnection.Instance.SaveGameManifests</code>, for example, to gain access to the <code>SaveGameManifest</code> tables via the <code>DbSet</code>. And you might call <code>DatabaseConnection.Instance.SaveChanges()</code> to save the database after making changes, etc.</p><pre><code class="language-cs">using Database;
using Microsoft.EntityFrameworkCore;
namespace YourMainProjectNameSpace;
// We need to inherit DbContext. This allows us to connect to the database.
public sealed class DatabaseConnection : DbContext {
// I set this up as a singleton. But you can do what ever you like.
private static DatabaseConnection _instance;
/*
To be able to pass the user folder path to our database (where our database
is saved) I created a Configure method which must be called before using
the database for the first time. This is all about that.
*/
private static bool _isConfigured = false;
public static void Configure(string userFolder) {
if (_instance == null) {
_instance = new DatabaseConnection(userFolder);
_isConfigured = true;
}
}
public static DatabaseConnection Instance {
get {
if (!_isConfigured) throw new InvalidOperationException("DatabaseHandler is not configured. Call Configure() before accessing Instance.");
return _instance;
}
}
// These are used in setting up the paths.
private string _userFolder;
private string _databaseFolder = "database";
private string _databasePath = "database.db";
private string _fullDatabaseFilePath;
/*
DbSets contain essentially the tables of the database.
You should probably have all your tables defined here in this class,
Instead of trying to spread them around or anything like that. I hear it's
a common practice, so it should be good enough for you too.
So below here, in production code, would be a big list of DbSet<T>s.
*/
public DbSet<SaveGameManifest> SaveGameManifests { get; set; }
// Again, empty constructor is needed or everything fails to work.
public DatabaseConnection() {}
/*
And here is the constructor that we use to build things up.
Notice that we take in the user:// folder path here. But if you prefer
an alternate method, do what ever you like.
*/
public DatabaseConnection(string userFolder) {
_userFolder = userFolder;
// Console.WriteLine("Initializing DatabaseHandler.");
// Generate the file paths.
GeneratePaths();
// Console.WriteLine("Database file paths generated.");
// Attempt to create the database file if it doesn't exist yet.
TryCreateDatabaseFile(_fullDatabaseFilePath);
}
/*
This bit of code is needed to configure that we're using SQLite and set the path.
Feel free to throw in more config options if you need them.
*/
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlite($"Data Source={_fullDatabaseFilePath}");
}
private void GeneratePaths() {
//Console.WriteLine("Generating database file paths.");
// Get the full path to the database file.
_fullDatabaseFilePath = Path.Combine(_userFolder, _databaseFolder, _databasePath);
}
/// <summary>
/// If the database file does not exist, create it.
/// </summary>
private static void TryCreateDatabaseFile(string fullDatabaseFilePath) {
//Console.WriteLine("Trying to create the database file.");
// If the database exists already, we don't need to create it.
bool databaseExists = File.Exists(fullDatabaseFilePath);
if (databaseExists) return;
//Console.WriteLine("Database does not exist. Trying to create a new SQLite database file.");
// Try to create the database folder first and then the database file.
try {
string fullFolderPath = Path.GetDirectoryName(fullDatabaseFilePath);
Directory.CreateDirectory(fullFolderPath ?? throw new InvalidOperationException());
File.Create(fullDatabaseFilePath).Close();
} catch (Exception e) {
Console.WriteLine(e);
throw;
}
// Check if the database file exists. It should.
if (!File.Exists(fullDatabaseFilePath)) {
throw new Exception("Failed to create the database file.");
}
//Console.WriteLine("SQLite database file created successfully.");
}
}
</code></pre>
<h3 id="maincs">Main.cs</h3>
<p>In our test this file resides in your main project - or where ever, but what I’m trying to say, it’s <em><strong>not</strong></em> in the <strong>Database</strong> project in this example.</p><p>The <code>Main.cs</code> represents some kind of controller or other class which does something practical with the database.</p><p>For our purposes we simply want to output something to see that everything works.</p><pre><code class="language-cs">
using Database;
using Godot;
using Microsoft.EntityFrameworkCore;
using YourMainProjectNamespace.Database; // (Unless you excluded the database folder)
namespace YourMainProjectNamespace;
// Oh yes, we're likely in Godot now doing something fun with the database.
public partial class Main : Node3D {
/*
In this example we store a reference to the connection, but normally you might
just use a using block for the duration of the database operation you want
to perform. You can do it both ways depending on the requirements.
*/
private DatabaseConnection _databaseConnection;
public override async void _Ready() {
/*
Configure the database connection. We need to run configure
and pass the user data directory from Godot to the database
class library before we can access the instance.
(Simply because we happened to code it that way in this example.
You do what you like. It doesn't matter.)
*/
DatabaseConnection.Configure(OS.GetUserDataDir());
_databaseConnection = DatabaseConnection.Instance;
try {
// Run migrate to create/update the database and tables if they don't exist/aren't up-to-date.
await _databaseConnection.Database.MigrateAsync();
// Now we can use the database after we've run Migrate. Not before.
// Add test data to the database so we have something to show.
await AddTestDataEntriesAsync();
// Do something for fun.
await RunDatabaseTests();
} catch (Exception e) {
GD.PrintErr(e);
throw;
}
}
/// <summary>
/// Add test data to the database.
/// </summary>
private async Task AddTestDataEntriesAsync() {
// Instantiate test data.
SaveGameManifest saveGameManifest = new (Guid.NewGuid(), "Test Save Game");
// Add test data to database.
await DatabaseConnection.Instance.AddAsync(saveGameManifest);
// Save changes to database to disc.
await DatabaseConnection.Instance.SaveChangesAsync();
}
/// <summary>
/// Run database tests to see if it's working.
/// </summary>
private async Task RunDatabaseTests() {
// Get data from database and output it.
Console.WriteLine("Fetching SaveGameManifests from database...");
// Fetch all SaveGameManifest entries from the database table.
DbSet<SaveGameManifest> results = DatabaseConnection.Instance.SaveGameManifests;
// Output the results.
Console.WriteLine("Results:");
await foreach (SaveGameManifest manifest in results.AsAsyncEnumerable()) {
Console.WriteLine($"DatabaseId: {manifest.DatabaseId}, SaveName: {manifest.SaveName}, LastSaved: {manifest.LastSaved}");
}
Console.WriteLine($"Results count: {results.Count()}");
}
}
</code></pre>
<h1 id="database-tab">Database tab</h1>
<p>At this point you might like to, or need to, connect the database in the database tab in Rider. Among other things this will allow you to inspect the contents of the SQLite database, which can be very helpful in debugging.</p><h1 id="migrations">Migrations</h1>
<p>Now that we’re done with all of that, create a <code>Migrations</code> folder in the <strong>Database</strong> project.</p><p>Right click on the <code>Database</code> project in the Solution Explorer and there should be entry for <strong>Entity Framework Core</strong> near the top of the context menu that pops open.</p><p>Click <strong>Add Migration</strong>.</p><p>The name can be anything you like which helps you understand what the migration is.</p><p>Migration project and Startup project should be <code>Database</code>.</p><p>DbContext class should be <code>DatabaseConnection</code>.</p><p>The <code>Migrations</code> folder has to point to the <code>Migrations</code> folder you created.</p><p><code>Target Framework</code> should probably be set to the .NET version you’re using.</p><p>Everything else can be at defaults. Hit OK.</p><p>If all goes well you should see <code>DatabaseConnectionModelSnapshot.cs</code> file being generated and also a file called something like <code>20240702182449_Initial.Designer.cs</code>.</p><p>You should now have a fully functional setup ready for running.</p><h1 id="how-to-use-migrations">How to use migrations?</h1>
<p>Every time you’ve changed the schema, ie. your tables, columns, added new types, removed columns, tables, changed types, changed names, etc. In other words every time your data definitions change and your database types don’t anymore reflect your types in code, you should run <code>Entity Framework Core > Add Migration</code>.</p><p>This will update the migration path of your data and tell your database how it should update and change between different versions.</p><h1 id="problems">Problems</h1>
<p>The database will be created in Godot’s <code>user://</code> folder. If you have trouble finding it you can open Godot and go to <code>Editor > Open Editor Data Folder</code> and look it up from there.</p><p>You can always just delete the database file and delete the Migrations files when testing things. Don’t do that in production when you’ve released something. You should only add new migrations then, and never remove them, if they have already been released out to the wild.</p><p>Unless I forgot something or you’ve got a different kind of setup, these instructions should more or less work. I did not, however, specifically go out of my way to test these instructions step by step to insure that they work on a clean system. I wrote the instructions from memory. So there may be factual errors. If that’s the case, you can contact me on Star and Serpent’s Discord. The link is on Star and Serpent’s home page.</p>
]]>
</content>
</entry>
<entry>
<title>Bullet parenting trick</title>
<author>
<name>Pilvinen</name>
</author>
<link href="https://pilvimaa.fi/bullet-parenting-trick/"/>
<id>https://pilvimaa.fi/bullet-parenting-trick/</id>
<media:content url="https://pilvimaa.fi/media/posts/18/bullet_hell.png" medium="image" />
<category term="Inheritance"/>
<category term="Godot"/>
<updated>2024-02-09T11:06:05+02:00</updated>
<summary>
<![CDATA[
<img src="https://pilvimaa.fi/media/posts/18/bullet_hell.png" alt="" />
In Godot if you have a bullet parented to your player, obviously the bullets will move when the player moves. So you need a node outside of your player hierarchy…
]]>
</summary>
<content type="html">
<![CDATA[
<p><img src="https://pilvimaa.fi/media/posts/18/bullet_hell.png" class="type:primaryImage" alt="" /></p>
<p>In Godot if you have a bullet parented to your player, obviously the bullets will move when the player moves.</p><p>So you need a node outside of your player hierarchy to parent them to, eg. something like:</p><pre><code>- World
- Bullets
- Player
- EnemyA
- EnemyB
</code></pre>
<p>This works fine, but can make controlling the bullets a chore due to required boiler plate code.</p><p>But… what if you don’t need to do that?</p><p>If you put a node of type <code>Node</code> inside your Player scene it will break the inheritance hierarchy because Node is such a simple type.</p><p>In that case you can have:</p><pre><code>- Player (Node2D)
- Bullets (Node)
- Bullet (Node2D)
- Bullet (Node2D)
- Bullet (Node2D)
- Bullet (Node2D)
</code></pre>
<p>And with this the movement of <code>Player</code> won’t affect position of <code>Bullets</code> because the inheritance hierarchy doesn’t work between them.</p>
]]>
</content>
</entry>
<entry>
<title>What to return when you don't have anything to return?</title>
<author>
<name>Pilvinen</name>