-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathREADME.html
More file actions
2357 lines (2108 loc) · 141 KB
/
README.html
File metadata and controls
2357 lines (2108 loc) · 141 KB
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
<!DOCTYPE html>
<html>
<head>
<title>README.md</title>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<style>
/* https://github.com/microsoft/vscode/blob/master/extensions/markdown-language-features/media/markdown.css */
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
body {
font-family: var(--vscode-markdown-font-family, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif);
font-size: var(--vscode-markdown-font-size, 14px);
padding: 0 26px;
line-height: var(--vscode-markdown-line-height, 22px);
word-wrap: break-word;
}
#code-csp-warning {
position: fixed;
top: 0;
right: 0;
color: white;
margin: 16px;
text-align: center;
font-size: 12px;
font-family: sans-serif;
background-color:#444444;
cursor: pointer;
padding: 6px;
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
}
#code-csp-warning:hover {
text-decoration: none;
background-color:#007acc;
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
}
body.scrollBeyondLastLine {
margin-bottom: calc(100vh - 22px);
}
body.showEditorSelection .code-line {
position: relative;
}
body.showEditorSelection .code-active-line:before,
body.showEditorSelection .code-line:hover:before {
content: "";
display: block;
position: absolute;
top: 0;
left: -12px;
height: 100%;
}
body.showEditorSelection li.code-active-line:before,
body.showEditorSelection li.code-line:hover:before {
left: -30px;
}
.vscode-light.showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(0, 0, 0, 0.15);
}
.vscode-light.showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(0, 0, 0, 0.40);
}
.vscode-light.showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
.vscode-dark.showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(255, 255, 255, 0.4);
}
.vscode-dark.showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(255, 255, 255, 0.60);
}
.vscode-dark.showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
.vscode-high-contrast.showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(255, 160, 0, 0.7);
}
.vscode-high-contrast.showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(255, 160, 0, 1);
}
.vscode-high-contrast.showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
img {
max-width: 100%;
max-height: 100%;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:focus,
input:focus,
select:focus,
textarea:focus {
outline: 1px solid -webkit-focus-ring-color;
outline-offset: -1px;
}
hr {
border: 0;
height: 2px;
border-bottom: 2px solid;
}
h1 {
padding-bottom: 0.3em;
line-height: 1.2;
border-bottom-width: 1px;
border-bottom-style: solid;
}
h1, h2, h3 {
font-weight: normal;
}
table {
border-collapse: collapse;
}
table > thead > tr > th {
text-align: left;
border-bottom: 1px solid;
}
table > thead > tr > th,
table > thead > tr > td,
table > tbody > tr > th,
table > tbody > tr > td {
padding: 5px 10px;
}
table > tbody > tr + tr > td {
border-top: 1px solid;
}
blockquote {
margin: 0 7px 0 5px;
padding: 0 16px 0 10px;
border-left-width: 5px;
border-left-style: solid;
}
code {
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
font-size: 1em;
line-height: 1.357em;
}
body.wordWrap pre {
white-space: pre-wrap;
}
pre:not(.hljs),
pre.hljs code > div {
padding: 16px;
border-radius: 3px;
overflow: auto;
}
pre code {
color: var(--vscode-editor-foreground);
tab-size: 4;
}
/** Theming */
.vscode-light pre {
background-color: rgba(220, 220, 220, 0.4);
}
.vscode-dark pre {
background-color: rgba(10, 10, 10, 0.4);
}
.vscode-high-contrast pre {
background-color: rgb(0, 0, 0);
}
.vscode-high-contrast h1 {
border-color: rgb(0, 0, 0);
}
.vscode-light table > thead > tr > th {
border-color: rgba(0, 0, 0, 0.69);
}
.vscode-dark table > thead > tr > th {
border-color: rgba(255, 255, 255, 0.69);
}
.vscode-light h1,
.vscode-light hr,
.vscode-light table > tbody > tr + tr > td {
border-color: rgba(0, 0, 0, 0.18);
}
.vscode-dark h1,
.vscode-dark hr,
.vscode-dark table > tbody > tr + tr > td {
border-color: rgba(255, 255, 255, 0.18);
}
</style>
<style>
/* Tomorrow Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* Tomorrow Comment */
.hljs-comment,
.hljs-quote {
color: #8e908c;
}
/* Tomorrow Red */
.hljs-variable,
.hljs-template-variable,
.hljs-tag,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class,
.hljs-regexp,
.hljs-deletion {
color: #c82829;
}
/* Tomorrow Orange */
.hljs-number,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params,
.hljs-meta,
.hljs-link {
color: #f5871f;
}
/* Tomorrow Yellow */
.hljs-attribute {
color: #eab700;
}
/* Tomorrow Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet,
.hljs-addition {
color: #718c00;
}
/* Tomorrow Blue */
.hljs-title,
.hljs-section {
color: #4271ae;
}
/* Tomorrow Purple */
.hljs-keyword,
.hljs-selector-tag {
color: #8959a8;
}
.hljs {
display: block;
overflow-x: auto;
color: #4d4d4c;
padding: 0.5em;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
</style>
<style>
/*
* Markdown PDF CSS
*/
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif, "Meiryo";
padding: 0 12px;
}
pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
border-radius: 3px;
overflow-x: auto;
white-space: pre-wrap;
overflow-wrap: break-word;
}
pre:not(.hljs) {
padding: 23px;
line-height: 19px;
}
blockquote {
background: rgba(127, 127, 127, 0.1);
border-color: rgba(0, 122, 204, 0.5);
}
.emoji {
height: 1.4em;
}
code {
font-size: 14px;
line-height: 19px;
}
/* for inline code */
:not(pre):not(.hljs) > code {
color: #C9AE75; /* Change the old color so it seems less like an error */
font-size: inherit;
}
/* Page Break : use <div class="page"/> to insert page break
-------------------------------------------------------- */
.page {
page-break-after: always;
}
</style>
<script src="https://unpkg.com/mermaid/dist/mermaid.min.js"></script>
</head>
<body>
<script>
mermaid.initialize({
startOnLoad: true,
theme: document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast')
? 'dark'
: 'default'
});
</script>
<h2 id="godot-cpp-3d-tutorial-work-in-progress">Godot CPP 3D Tutorial [Work in progress]</h2>
<p>By Ken Burchfiel</p>
<p>Released under the MIT license</p>
<p>This step-by-step guide will demonstrate how to create a 3D multiplayer game in Godot using C++. It is based on my <a href="https://github.com/kburchfiel/godot_cpp_3d_demo">Cube Combat demo</a>, but (unlike that project) guides you more explicitly through the steps involved, both within your code editor and Godot, to put this kind of game together.</p>
<p><em>Note: This resource is being created without the use of generative-AI tools.</em></p>
<h2 id="part-1-getting-started">Part 1: Getting started</h2>
<p>(Note: Many of these steps will resemble those in the excellent 'Getting started' section of the official GDExtension documentation at https://docs.godotengine.org/en/4.6/tutorials/scripting/cpp/gdextension_cpp_example.html . Certain code blocks within this section derive from that document as well.)</p>
<ol>
<li>
<p>Create a new folder that will store your Godot project and its corresponding code. I'll call mine <code>godot_cpp_3d_tutorial</code>, but the name you choose is of course up to you.</p>
</li>
<li>
<p>First, you'll want to download the latest stable version of godot-cpp from https://github.com/godotengine/godot-cpp . (As of 2026-04-17, a stable version of version 10.x hasn't yet been released, so I went ahead and downloaded the beta version with a commit ID of 4862a9d (https://github.com/godotengine/godot-cpp/tree/4862a9dcf1471c9ea19680b9faadb5b6a9432092 .) Whether you download and unzip or simply clone it, make sure that exists within your project folder within a folder named 'godot-cpp'.</p>
</li>
<li>
<p>Next, open up this godot-cpp folder within your terminal and run <code>scons platform=linux</code> (replacing <code>linux</code> with your own OS if needed). This will compile all of the source code needed to apply this library. (It will <em>also</em> generate additional code files that aren't visible in an uncompiled version of the repository, such as the one on GitHub).</p>
</li>
<li>
<p>Go ahead and create a 'src' folder within your root project folder. This folder will store your source C++ code and its compiled variants.</p>
</li>
<li>
<p>Create a 'project' folder within this root folder also. Next, open up Godot, which you can download from https://godotengine.org/ if you haven't already. (I'm using Godot 4.6 for this project, but this tutorial should be applicable for newer releases also for a decent while.) Within the loading screen, hit the Create button at the top left. I chose 'Cpp 3D Tutorial' as my project name and the 'project' folder I just created as my path. Once you've filled in these items, hit Create on the bottom right.</p>
<p><img src="/tutorial_screenshots/new_game_project.png" alt=""></p>
</li>
<li>
<p>Close back out of the editor for now. Before we create a scene, we should first create a GDExtension class within C++ that can be used as the basis for that scene. (This is a different approach than what you might be used to with GDScript.)</p>
</li>
<li>
<p>Before we start writing our own C++ code, let's get a few crucial setup tasks out of the way. First, within your project folder, which will now have a number of Godot-generated items (including a project.godot) file, go ahead and create a new folder called 'bin.' Within this folder, create a new file called gdexample.gdextension, then paste the following material into it:</p>
<pre class="hljs"><code><div>[configuration]
entry_symbol = "example_library_init"
compatibility_minimum = "4.1"
reloadable = true
[libraries]
macos.debug = "./libgdexample.macos.template_debug.dylib"
macos.release = "./libgdexample.macos.template_release.dylib"
windows.debug.x86_32 = "./gdexample.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "./gdexample.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "./gdexample.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "./gdexample.windows.template_release.x86_64.dll"
linux.debug.x86_64 = "./libgdexample.linux.template_debug.x86_64.so"
linux.release.x86_64 = "./libgdexample.linux.template_release.x86_64.so"
linux.debug.arm64 = "./libgdexample.linux.template_debug.arm64.so"
linux.release.arm64 = "./libgdexample.linux.template_release.arm64.so"
linux.debug.rv64 = "./libgdexample.linux.template_debug.rv64.so"
linux.release.rv64 = "./libgdexample.linux.template_release.rv64.so"
</div></code></pre>
<p>(This is an exact copy of the corresponding code within Godot's official GDExtension documentation (Reference 1)).</p>
<p>(I could have changed 'gdexample' and 'example_libary_init' to more meaningful entries, but for boilerplate like this, it's often safest to stick with the existing version.)</p>
</li>
<li>
<p>Next, within your src folder, create a new file called 'register_types.h.' Copy and paste the following code into that file:</p>
<pre class="hljs"><code><div>#pragma once
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level);
void uninitialize_example_module(ModuleInitializationLevel p_level);
</div></code></pre>
<p>(Source: Reference 1)</p>
</li>
<li>
<p>These two files won't need to be modified further. The same is not the case for the following code, which we'll update over time to include the classes that we'll create within this tutorial. Go ahead and paste it into a new 'register_types.cpp' file within your src folder:</p>
<pre class="hljs"><code><div>#include "register_types.h"
#include <gdextension_interface.h>
#include <godot_cpp/core/defs.hpp>
#include <godot_cpp/godot.hpp>
using namespace godot;
void initialize_example_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}
void uninitialize_example_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
}
extern "C" {
// Initialization.
GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) {
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
init_obj.register_initializer(initialize_example_module);
init_obj.register_terminator(uninitialize_example_module);
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
return init_obj.init();
}
}
</div></code></pre>
<p>(Source: Reference 1, with a few lines removed so that we can add in our own versions later)</p>
</li>
<li>
<p>We'll also need to add a SConstruct file to our root folder in order to successfully compile our GDExtension classes. Visit https://docs.godotengine.org/en/4.6/_downloads/8df537a8866633825684515bce40faa6/SConstruct , which should prompt you to save an SConstruct file to your computer. Go ahead and save it into your root folder as 'SConstruct' (without any extension). (You can also access this download by visiting https://docs.godotengine.org/en/4.6/tutorials/scripting/cpp/gdextension_cpp_example.html , then searching for a link with the text "the SConstruct file we prepared.")</p>
<p>Here's what your root folder should look like at this point (excluding certain Godot-created project files):</p>
<pre class="hljs"><code><div>godot_cpp_3d_tutorial/ [my root folder name; your name may vary]
--godot-cpp/ [contains your compiled godot-cpp repository]
--project/
----bin/
------gdexample.gdextension
--src/
----register_types.cpp/
----register_types.h
--SConstruct
</div></code></pre>
</li>
<li>
<p>Although we haven't created any classes yet, this will be a good time to compile the code we've added in so far. Navigate to your root folder and then run <code>scons platform=[your_os]</code> (e.g. <code>scons platform=linux</code> in my case). This is the same command you used to compile godot-cpp--just in your project folder rather than the godot-cpp folder. You should see <code>scons: done building targets.</code> appear in the terminal after all files have been compiled and linked.</p>
</li>
</ol>
<h2 id="part-2-adding-in-a-class">Part 2: Adding in a class</h2>
<p>Now that we've gotten those setup tasks out of the way, we can begin programming our own GDExtension classes. Let's start with the Main class, which will govern the game area and some fundamental gameplay logic.</p>
<ol>
<li>
<p>Within your src folder, create a new file called 'main.h'. Copy the following code into this file:</p>
<pre class="hljs"><code><div>#pragma once
#include <godot_cpp/classes/node.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
using namespace godot;
class Main: public Node {GDCLASS(Main, Node)
protected:
static void _bind_methods();
public:
Main();
~Main();
void _ready();
};
</div></code></pre>
<p>(Source: Reference 8)</p>
<p>Here, we're declaring a GDExtension class called 'Main' that will inherit from Node. We're also adding some fundamental functions (e.g. constructors, destructors, _bind_methods(), and _ready()). Lots more will be added to this file later on, but I wanted to focus on the items we'll need at this moment.</p>
</li>
<li>
<p>Next, create a file within src/ called 'main.cpp' and paste the following code into it:</p>
<pre class="hljs"><code><div>
#include "main.h"
void Main::_bind_methods() {}
Main::Main() {}
Main::~Main() {}
void Main::_ready() {
UtilityFunctions::print("Main::_ready() just got called.");
}
</div></code></pre>
<p>(Source: Reference 8)</p>
<p>This is all pretty barebones so far, but we'll expand this file quite a bit later in the tutorial.</p>
<p>We don't need to add anything just yet to <code>_bind_methods()</code>, but since that function is a core part of many GDExtension classes, I figured I would add it here.</p>
</li>
<li>
<p>Go back to register_types.cpp. Directly under <code>#include "register_types.h"</code>, add:</p>
<pre class="hljs"><code><div>#include "main.h"
</div></code></pre>
</li>
<li>
<p>Next, within the <code>initialize_example_module()</code> function, add the following text right before the final closing bracket:</p>
<pre class="hljs"><code><div>GDREGISTER_RUNTIME_CLASS(Main);
</div></code></pre>
<p>These two updates allow Godot to learn about our Main GDExtension class. We'll repeat these simple updates for all other classes that we define.</p>
<p>Note: I'm using GDREGISTER_RUNTIME_CLASS here rather than GDREGISTER_CLASS so that this code will only run when I'm actually running my project. With GDREGISTER_CLASS, code can also run (or at least attempt to run) in the editor, which can cause some irritating crashes. (For instance, suppose your code for a class references a Pivot object that you haven't yet created. If you then attempt to launch the editor after compiling this code, the editor will attempt to find this object, fail, and potentially crash. To avoid the need to comment out that code, add the object into your editor, and then recompile it, you can simply make that class a <em>runtime</em> class.)</p>
<p>(Sources: References 8, 9, and 10)</p>
</li>
<li>
<p>Go ahead and run <code>scons platform=[your_os]</code> again. (If you haven't closed your terminal since the last time you ran this command, you may be able to access this line by pressing your Up Arrow key.) You should again see <code>scons: done building targets.</code> once the compilation process completes.</p>
</li>
</ol>
<h2 id="part-3-setting-up-maintscn">Part 3: Setting up main.tscn</h2>
<p>Now that we have a class (albeit a very simple one), we can create a scene based on it.</p>
<ol>
<li>
<p>Reopen Godot and open your project. Next, within the 'Create Root Node:' menu, select 'Other Node' and enter 'Main' in the search bar. Hopefully, you will see your newly-created Main node within the list of available nodes:</p>
<p><img src="/tutorial_screenshots/main_node.png" alt=""></p>
<p>If you <em>don't</em> see this node (a situation I've faced quite a few times), this likely means that you forgot one of the earlier steps (such as adding references to this node to your register_types.cpp file).</p>
</li>
<li>
<p>Select this node, then save your scene as main.tscn. Next, click the play button near the top right of the editor in order to get the debug message we defined within <code>Main::_ready()</code> to display. This will bring up a scene-selection dialog box; you can hit 'Select Current', since we'll indeed want this to be our main scene.</p>
</li>
<li>
<p>A gameplay window with a gray box will open. Nothing will appear inside it, which is to be expected. However, you <em>should</em> see <code>Main::_ready() just got called.</code> appear within your Output tab in the lower half of the main editor window:</p>
<p><img src="/tutorial_screenshots/main_ready.png" alt=""></p>
</li>
<li>
<p>While we're inside the editor, let's go ahead and create a game area. (Many of the following steps were based on the 'Setting up the game area' section of the Your First 3D Game Tutorial (Reference 3).) Right click on Main and select Add Child Node, then search for (and select) StaticBody3D. Rename it Ground within your scene tree on the left side of the editor. Next, add a CollisionShape3D and a MeshInstance3D as children of Ground.</p>
</li>
<li>
<p>Select the MeshInstance3D item within the scene tree if you haven't already. Click the 'empty' box to the right of Mesh in the Inspector and select a new BoxMesh. Next, click on CollisionShape3D within the scene tree; within the Shape row of the Inspector, choose a BoxShape3D.</p>
</li>
<li>
<p>Next, click on the Ground object within the scene tree, then click on Transform within the Inspector. Change the y entry within the Position (not Scale!) menu to -0.5 so that the top of the Ground, which is 1 meter thick, has a y position of 0 rather than 0.5.</p>
</li>
<li>
<p>Click the CollisionShape resource, then select the BoxShape. Within the Size menu that appears, change the x and z values to 60; keep the y value untouched at 1. Next, click on the MeshInstance, then click on the chain to the right of 'Scale' within the Transform section such that it appears broken. (This way, changes to one dimension won't affect the others.) As you did with the BoxShape, change the x and z scales to 60.0 and leave the y scale untouched at 1.0. Your Ground object should now look like a thin square.</p>
</li>
<li>
<p>Let's change the drab, white color of the Ground to something more interesting. Select your MeshInstance3D in the scene tree, then click the downwards-facing arrow to the right of the Mesh (and its corresponding gray cube) in the Inspector and select 'Edit.' Within the Material section, create a new StandardMaterial3D, then click the downwards arrow to the right of the white sphere that appears and select Edit. Click on the Albedo section, then select a color of your choice.</p>
<p>Here's what the Ground should look like at this point:</p>
<p><img src="/tutorial_screenshots/ground.png" alt=""></p>
</li>
<li>
<p>Before we can get to the 'action' part of this scene, we'll need lights and a camera. Add a DirectionalLight3D as a child of Main, then set its y transform to 20.0. (The x and z transforms can stay at 0.0.) Change its x rotation to -90, or whatever allows the light to point directly down at the ground. (You'll know it's working when you see the ground brighten up.) Check the Shadow box within the Light3D section of the Inspector as well.</p>
</li>
<li>
<p>Next, add a Marker3D as a child of Main and set its y and z transforms to 25.0 and -40.0, respectively. Finally, add a Camera3D as a child of the Marker3D. Set its x and y rotations (accessible within the Transform section of the Inspector) to -45 and 180, respectively. (You can scroll up to the top of the Inspector to get a preview of what the camera will display.)</p>
</li>
<li>
<p>Try running the scene again. You should now see your game area within the window that appears:</p>
<p><img src="/tutorial_screenshots/game_area.png" alt=""></p>
</li>
<li>
<p>Now that we have a game scene in place, this will be a good time to begin work on our Mnchar (main character) class. There's plenty more C++ code that will get added to main.cpp and main.h, but those additions will be easier to implement and debug once we have actual characters and projectiles to manage.</p>
</li>
</ol>
<h2 id="part-4-laying-the-foundations-for-our-mnchar-class">Part 4: Laying the foundations for our Mnchar class</h2>
<p>Our Mnchar class, which players will be able to control via game controllers, will fire projectiles and (potentially) get hit by other projectiles. We'll eventually configure our game such that anywhere from 2-8 Mnchars can get added to the game scene at the start of each game; however, that configuration will involve a Hud class that we won't be setting up for a little while.</p>
<ol>
<li>
<p>To begin the setup process, create both a 'mnchar.h' and a 'mnchar.cpp' file within your src folder. Enter the following code into mnchar.h:</p>
<pre class="hljs"><code><div>#pragma once
#include <godot_cpp/classes/character_body3d.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
using namespace godot;
class Mnchar : public CharacterBody3D {
GDCLASS(Mnchar, CharacterBody3D)
private:
double movement_speed = 14;
double rotation_speed = 0.15;
protected:
static void _bind_methods();
public:
Mnchar();
~Mnchar();
void set_movement_speed(const double movement_speed);
double get_movement_speed() const;
void set_rotation_speed(const double rotation_speed);
double get_rotation_speed() const;
};
</div></code></pre>
<p>(Source: References 1, 2, and 4)</p>
<p>This code is very similar to that found within main.h. Two new additions of note are <code>movement_speed</code> and <code>rotation_speed</code> along with their corresponding setter and getter functions. We'll make it possible to update both of these values within the editor.</p>
<p>Also note that, because Mnchar will extend CharacterBody3D, we need to include its header file within this source file. (I chose to use CharacterBody3D as my player's class because the Your First 3D Game tutorial uses this same class; see Reference 5. Code for the CharacterBody3D class itself can be found in References 6 and 7.</p>
</li>
<li>
<p>Next, within mnchar.cpp, enter the following:</p>
<pre class="hljs"><code><div>#include "mnchar.h"
#include <godot_cpp/core/class_db.hpp>
using namespace godot;
void Mnchar::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_movement_speed"),
&Mnchar::get_movement_speed);
ClassDB::bind_method(D_METHOD("set_movement_speed", "p_movement_speed"),
&Mnchar::set_movement_speed);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "movement_speed"),
"set_movement_speed", "get_movement_speed");
ClassDB::bind_method(D_METHOD("get_rotation_speed"),
&Mnchar::get_rotation_speed);
ClassDB::bind_method(D_METHOD("set_rotation_speed", "p_rotation_speed"),
&Mnchar::set_rotation_speed);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rotation_speed"),
"set_rotation_speed", "get_rotation_speed");
}
Mnchar::Mnchar() {}
Mnchar::~Mnchar() {}
void Mnchar::set_movement_speed(const double p_movement_speed) {
movement_speed = p_movement_speed;
}
double Mnchar::get_movement_speed() const { return movement_speed; }
void Mnchar::set_rotation_speed(const double p_rotation_speed) {
rotation_speed = p_rotation_speed;
}
double Mnchar::get_rotation_speed() const { return rotation_speed; }
</div></code></pre>
<p>(Source: References 1 and 2)</p>
<p>This code, like that in main.h, is quite barebones; our priority is simply to add in a class that the Godot editor will recognize. Once we create and configure a Mnchar scene within the editor, we'll then come back and extend this code.</p>
<p>However, I did also add in code that will allow us to modify the movement and rotation speeds within the editor. This isn't a crucial part of this particular game, but it <em>will</em> allow you to test out different movement and rotation values without having to recompile the code--not that that's a huge hurdle.</p>
<p>The speed-adjustment code consists of two functions (<code>set_movement_speed()</code> and <code>get_movement_speed()</code>) that let us specify and retrieve, respectively, the player's speed. Both of these functions (including the former's <code>p_movement_speed</code> argument) have corresponding bind_method() calls within <code>_bind_methods()</code>. In addition, we have an ADD_PROPERTY() call that tells the editor about the <code>movement_speed</code> variable along with its setter and getter functions. (These are all based on the <code>amplitude</code> property within the GDExample class in Reference 1.)</p>
<p>The rotation-adjustment code is very similar. I simply copied and pasted my relevant movement-adjustment code, then replaced all cases of 'movement' with 'rotation.'</p>
<p>(It is <em>not</em> necessary to add in <code>bind_method()</code> and <code>ADD_PROPERTY()</code> calls like this for every attribute of a class. However, they're very important for certain items, such as signals--which we'll get to later.)</p>
</li>
<li>
<p>This new code should compile at this point; however, if we tried to do so, we most likely wouldn't be able to locate a Mnchar class within our editor. That's because we also need to update register_types.cpp with information about this class. Fortunately, this is easy to do so. Right under <code>#include "main.h"</code>, add <code>#include "mnchar.h"</code>. Next, right under <code>GDREGISTER_RUNTIME_CLASS(Main);</code>, add <code>GDREGISTER_RUNTIME_CLASS(Mnchar);</code>.</p>
</li>
<li>
<p><em>Now</em> run <code>scons platform=[your_os]</code> to compile this new Mnchar-related code. In my case, the editor still didn't show the Mnchar class within the 'Create New Node' menu after this step, but it did present it once I closed and relaunched my editor. Thus, I'd recommend that you do the same at this point.</p>
</li>
</ol>
<h2 id="part-5-setting-up-mnchartscn">Part 5: Setting up mnchar.tscn</h2>
<ol>
<li>
<p>Back in the editor, create a new scene. Click the 'Other Node' button under the 'Create Root Node:' prompt; search for 'Mnchar'; then double-click it. Next, save this scene as mnchar.tscn.</p>
<p>You <em>should</em> see the custom Movement Speed property that we configured near the top of the Inspector menu, along with our default value (14). Changing this value in the editor will also change the Mnchar's behavior within our game.</p>
<p><img src="/tutorial_screenshots/early_mnchar_scene.png" alt=""></p>
<p>(I have found, however, that this property will sometimes disappear from the editor after compiling my code. This might be caused by an issue with my current setup, but it might also be a glitch within the editor itself. Closing, then relaunching the editor always seems to resolve this issue, thankfully.)</p>
</li>
<li>
<p>Add a Node3D as a child of Mnchar and rename it 'Pivot'. In many cases, we'll apply movement and rotation actions to this node rather than to Mnchar itself. Next, add a MeshInstance as a child of Pivot; name it 'Body'; click the 'empty' text within its Mesh section in the Inspector; and select a BoxMesh. Next, click the downwards-pointing arrow to the right of the gray cube in the Inspector and select Edit. (Reference 5)</p>
</li>
<li>
<p>Within the Size section of the edit menu, change the x, y, and z values from 1.0 to 2.0. Next, go down to the Material section and select a new StandardMaterial 3D. Unlike with the game area, we won't choose a color for this material just yet--as we'll actually use C++ to update this color instead. (This will make it easier to assign different colors to different Mnchar instances.)</p>
</li>
<li>
<p>We'll also want to create a turret for our player. Create a new MeshInstance3D child of Pivot; name it 'Turret'; assign it a new BoxMesh; go into that mesh's edit menu; change the x, y, and z sizes to 0.5, 0.5, and 0.5; and give it a new StandardMaterial3D.</p>
</li>
<li>
<p>Next, navigate back to the Turret's main Inspector menu. (You can do so a few different ways; one of which is to select the Body, then the Turret again.) Within the Node3D section, set the z transform to 1.25 meters. That way, the turret will be adjacent to the forward face of the Pivot.</p>
</li>
<li>
<p>Add a CollisionShape3D as a child of <em>Mnchar</em> (not Pivot). Click on the 'empty' text within the Shape section of the Inspector; add in a BoxShape3D; click the downwards arrow to the right of 'BoxShape3D'; and select 'Edit.' Change the x, y, and z Size values to 2.0 in order to make it the exact same size as the Body component.</p>
<p>(You could also update these Size values, along with the transform of the CollisionShape, to allow it to fit over both the Body and Turret objects; alternatively, you could create a separate CollisionShape3D for the Turret. That would prevent the Turret from being able to enter into other objects. But this simpler approach will suffice for this tutorial.) (Reference 5)</p>
<p>Once you're finished with these updates, go ahead and save the scene.</p>
</li>
<li>
<p>In order to move the Mnchar with a controller (or, for development purposes, a keyboard), we'll need to add input actions to our game's Input Map. Navigate to this map by clicking Project in the top left of the Godot editor window, then selecting Project Settings; the Input Map should be the second tab from the left. (Reference 5)</p>
</li>
<li>
<p>For now, we'll just add in keyboard entries; once we're further in the development process, we'll add in controller entries also. Click on the 'Add New Action' text within the Input Map menu; type move_left_0; and hit the '+ Add' button to the right of this window. Next, click the + sign to the right of the new 'move_left_0' entry that has appeared; and hit your J key (or, if you're using a different layout like I am, where the J key would be on a QWERTY keyboard).</p>
<p>(You're welcome to use a key other than J, such as Left Arrow, if you'd like. The use of 'J' will make more sense in the context of all the keys we'll be adding in.)</p>
<p><img src="/tutorial_screenshots/first_input_map_entry.png" alt=""></p>
</li>
<li>
<p>Perform the same steps for the following action names and keys:</p>
<ol>
<li>move_right_0 (L key)</li>
<li>move_forward_0 (I key)</li>
<li>move_back_0 (K key)</li>
<li>rotate_left_0 (S key)</li>
<li>rotate_right_0 (F key)</li>
<li>fire_0 (Space Bar)</li>
<li>reset_0 (O key)</li>
</ol>
</li>
<li>
<p>Once you've finished this process, your input map should look like the following:</p>
<p><img src="/tutorial_screenshots/player_0_keyboard_input_map.png" alt=""></p>
<p>By the way, the reason for adding '_0' to the end of these actions is to allow different sets of controls to be distinguished for different players later on.</p>
<p>Close out of the input map and save your mnchar.tscn file.</p>
</li>
</ol>
<h2 id="part-6-adding-a-mnchar-to-the-game-area">Part 6: Adding a Mnchar to the game area</h2>
<p>We're almost ready to add in code that will let us move our Mnchar around the game area. First, though, we need to <em>add</em> the Mnchar to the game area.</p>
<p>One option would be to instantiate a Mnchar as a child scene of main.tscn (Reference 4). However, since we're creating a multiplayer game whose player count might change from round to round, it will be more ideal to add Mnchars to this scene via code. (That way, a specific number of Mnchars can be placed within the game area depending on how many players choose to enter a given game.) The following steps will allow us to add a Mnchar to the scene using C++.</p>
<ol>
<li>
<p>Within main.h, add the following code right above <code>protected:</code></p>
<pre class="hljs"><code><div>private:
Ref<PackedScene> mnchar_scene;
</div></code></pre>
<p>Next, add the following right below <code>~Main();</code></p>
<pre class="hljs"><code><div>Ref<PackedScene> get_mnchar_scene();
void set_mnchar_scene(Ref<PackedScene>);
</div></code></pre>
<p>(Reference 8)</p>
<p>Finally, after <code>#include <godot_cpp/variant/utility_functions.hpp></code> , add:</p>
<pre class="hljs"><code><div>#include <godot_cpp/classes/packed_scene.hpp>
#include "mnchar.h"
</div></code></pre>
</li>
<li>
<p>Next, within main.cpp, add the following code within <code>Main::_bind_methods():</code></p>
<pre class="hljs"><code><div> ClassDB::bind_method(D_METHOD("get_mnchar_scene"), &Main::get_mnchar_scene);
ClassDB::bind_method(D_METHOD("set_mnchar_scene", "mnchar_scene"),
&Main::set_mnchar_scene);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "packed_scene",
PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"),
"set_mnchar_scene", "get_mnchar_scene");
</div></code></pre>
<p>(Reference 8)</p>
</li>
<li>
<p>In addition, add the following code below <code>Main::~Main() {}</code>:</p>
<pre class="hljs"><code><div>Ref<PackedScene> Main::get_mnchar_scene() { return mnchar_scene; }
void Main::set_mnchar_scene(Ref<PackedScene> packed_scene) {
mnchar_scene = packed_scene;
}
</div></code></pre>
<p>(Reference 8)</p>
<p>We're defining a scene (<code>mnchar_scene</code>) that we'll be able to access within main.cpp. Adding it as a property (like we did with movement_speed) will allow us to specify, within the editor, the exact scene (in this case, mnchar.tscn) that we want Godot to interpret as <code>mnchar_scene</code>.</p>
</li>
<li>
<p>Next, add the following to the bottom of <code>Main::_ready()</code>:</p>
<pre class="hljs"><code><div>auto new_mnchar =
reinterpret_cast<Mnchar *>(get_mnchar_scene()->instantiate());
add_child(new_mnchar);
</div></code></pre>
<p>(Reference 8)</p>
<p>This code retrieves our mnchar_scene; reinterprets it as a Mnchar object; and then adds it to our scene tree.</p>
</li>
<li>
<p>Note: When I was putting this code together, I initially forgot to define <code>get_mnchar_scene()</code> and <code>set_mnchar_scene()</code> within main.cpp. Although my code compiled successfully, these omissions caused Godot to fail to locate both my Main and Mnchar classes within the editor:</p>
<p><img src="/tutorial_screenshots/red_xs_due_to_missing_functions.png" alt=""></p>
<p>(I knew this was the cause because I've made similar mistakes more often than I'd like to admit!)</p>
<p>Adding these functions in, then closing and relaunching the editor resolved this issue.</p>
<p>I actually managed to make a similar mistake later on for <code>get_rotation_speed()</code> and <code>set_rotation_speed()</code>. Error messages within the console helped me figure out the issue. (Note the <code>get_rotation_speed</code> reference within the third-to-last error.)</p>
<p><img src="/tutorial_screenshots/scary_but_helpful_error_messages.png" alt=""></p>
</li>
<li>
<p>Go ahead and compile your code, then relaunch the editor. After you open main.tscn and click on the Main node within your scene tree, you should now see a new Packed Scene entry right below Main in the inspector. Click the 'empty' text; select 'Load'; and then choose your mnchar.tscn scene.</p>
</li>
<li>
<p>When you try playing your project, you should now see a little gray box (your Mnchar) in the middle of your game area:</p>
<p><img src="/tutorial_screenshots/mnchar_added_via_code.png" alt=""></p>
</li>
<li>
<p>You'll notice, though, that the main character is partially sunk in the ground. We could fix this by changing its transform within Mnchar.tscn; however, because we'll need to be able to move Mnchars around later on when setting up multiplayer games, we may as well add some initial movement code now.</p>
<p>Declare a new <code>start()</code> function for the Mnchar class by adding the following code right before the final closing bracket of mnchar.h:</p>
<pre class="hljs"><code><div>void start(Vector3 mnchar_translate_arg);
</div></code></pre>
<p>Next, add the following code to the bottom of mnchar.cpp:</p>
<pre class="hljs"><code><div>void Mnchar::start(Vector3 mnchar_translate_arg)
{translate(mnchar_translate_arg);}
</div></code></pre>
<p>This function will ultimately allow us to configure each player's starting color, location, rotation, and ID. For now, though, we'll just use it to move our player to a better starting location.</p>
<p>(Resource 8)</p>
</li>
<li>
<p>Within Main::_ready(), add the following code right above <code>add_child(new_mnchar)</code>:</p>
<pre class="hljs"><code><div>new_mnchar -> start(Vector3(15, 1, -20));
</div></code></pre>
<p>This will move the Mnchar 15 meters to the left, one meter up (to make its bottom level with the ground), and 20 meters closer to the camera. We'll add in code later to give other Mnchars different starting locations, but all of them will also get moved up one meter.</p>
<p>(Resource 8)</p>
<p>Note: I had originally tried this code:</p>
<p><code>get_node<Node3D>("Pivot") -> translate(translate_val);</code></p>
<p>However, this caused issues when multiple characters were added to the scene--possibly because the transforms of the actual Mnchar class weren't getting changed and were thus overlapping with one another. (This caused them to shoot up in the sky, which was both frustrating and hilarious!)</p>
</li>
<li>
<p>Compile your code, then rerun your main scene. You should now see the full Mnchar closer to the bottom left of the window:</p>
<p><img src="/tutorial_screenshots/repositioned_mnchar.png" alt=""></p>
</li>
</ol>
<h2 id="part-7-moving-the-mnchar">Part 7: Moving the Mnchar</h2>
<p>Because we're creating a multiplayer game, we'll want to set up our movement code in a way that allows each player to move his or her own Mnchar (and no one else's). The approach we'll take for this task will be as follows:</p>
<ul>
<li>
<p>We'll first create a new String variable (<code>mnchar_id</code>) that will store a unique ID for each Mnchar. (These IDs will range from "0" to "7" in order to support up to eight different players.) This variable will also be useful for setting character-specific locations, rotations, and colors.</p>
</li>
<li>
<p>We'll also add each of these same IDs to the end of all action names within one particular group of movement commands that we'll create. Thus, one group's action names will end in "0", others will end in "1", and so forth. (This is why we added "_0" to our first set of movement names.) We'll also specify that each set of commands will only work for one particular device. (The device IDs recognized by Godot range from 0 through 7, thus matching our own list of possible IDs.)</p>
</li>
<li>
<p>In our movement code, we'll check for movements that match the given Mnchar's ID. For instance, here's what our left/right movement code will look like:</p>
<pre class="hljs"><code><div>x_direction = input->get_axis("move_left_"+mnchar_id,
"move_right_"+mnchar_id);
</div></code></pre>
</li>
<li>
<p>In summary, by having Mnchar IDs match movement-name suffixes, and by having all movements for a particular suffix match only one particular device, we can create a functioning multiplayer control setup without too much extra work. (This approach was based on references 11 through 15.)</p>
</li>
</ul>
<ol>
<li>
<p>The first step here will be to add a <code>mnchar_id</code> value to our Mnchar class. Under <code>double movement_speed = 14;</code> within the <code>private</code> section of Mnchar.h, add:</p>
<pre class="hljs"><code><div>String mnchar_id = "";
</div></code></pre>
<p>Next, in the <code>public</code> section, add the following setter and getter function declarations under your <code>get_movement_speed()</code> function declaration:</p>
<pre class="hljs"><code><div>void set_mnchar_id(const String mnchar_id);
String get_mnchar_id() const;
</div></code></pre>
<p>Finally, add a <code>mnchar_id_arg</code> argument before your <code>mnchar_translate_arg</code> within <code>start()</code> so that the declaration matches the following line:</p>
<pre class="hljs"><code><div>void start(String mnchar_id_arg, Vector3 mnchar_translate_arg);
</div></code></pre>
</li>
<li>
<p>Within mnchar.cpp, add the following below your <code>get_movement_speed()</code> function definition:</p>
<pre class="hljs"><code><div>void Mnchar::set_mnchar_id(const String p_mnchar_id) {
mnchar_id = p_mnchar_id;
}
String Mnchar::get_mnchar_id() const { return mnchar_id;}
</div></code></pre>
<p>Next, add <code>String mnchar_id_arg</code> before <code>Vector3 mnchar_translate_arg</code> within the arguments in this file's <code>Mnchar::start()</code> function definition. In addition, right before <code>translate(mnchar_translate_arg);</code> in the function body, add: <code>set_mnchar_id(mnchar_id_arg);</code>.</p>
</li>
<li>
<p>Finally, within main.cpp, update your <code>new_mnchar -> start()</code> command within <code>Main::_ready()</code> so that it reads as follows:</p>
<pre class="hljs"><code><div>new_mnchar -> start("0", Vector3(15, 1, -20));
</div></code></pre>
<p>(This will assign <code>new_mnchar</code> an ID of 0, thus allowing all movement commands ending in "0" to get registered by this Mnchar.)</p>
<p>If you want to confirm that this change has taken effect, you can also add the following code following <code>set_mnchar_id(mnchar_id_arg)</code> within <code>Mnchar::start()</code>:</p>
<pre class="hljs"><code><div>UtilityFunctions::print("Mnchar's ID is ", get_mnchar_id(), ".");
</div></code></pre>
<p>(Printing out values is incredibly helpful for debugging work.)</p>
<p>We could also have added <code>bind_method()</code> and <code>ADD_PROPERTY()</code> calls for our <code>mnchar_id</code> value and its corresponding setter and getter functions (as we did with the <code>movement_speed</code> variable). However, that won't be necessary in this case, as we won't need to access or modify those values directly within the editor.</p>
</li>
<li>
<p>Now that we have code in place for assigning <code>mnchar_id</code>s, we can utilize that ID within our input code. First, add the following code after your <code>start()</code> function declaration within mnchar.h:</p>
<pre class="hljs"><code><div>void _physics_process(double delta) override;
</div></code></pre>
<p>(See Reference 4 for the use of <code>_physics_process</code> here rather than <code>_process</code>.)</p>
<p>In addition, add the following three include statements after <code>#include <godot_cpp/variant/utility_functions.hpp></code>:</p>
<pre class="hljs"><code><div>#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/input_map.hpp>
</div></code></pre>
</li>
<li>
<p>Next, add the following code to the end of mnchar.cpp. We'll start each <code>_physics_process</code> call by retrieving input data that we can then parse. (The lack of a closing bracket is intentional, since we'll be filling out the rest of this function below.)</p>
<pre class="hljs"><code><div>void Mnchar::_physics_process(double delta) {
auto input = Input::get_singleton();
</div></code></pre>
<p>(Reference 4)</p>
</li>
<li>
<p>Extend this function by adding the following code, which uses our left, right, forward, and back movements to determine the player's movement along the x (left/right) and z (forward/back) axes. This approach will allow different movement speeds depending on exactly how far a controller joystick is moved down, but it also works fine for keyboard input.</p>
<p>(Note: I've found that I sometimes need to put 'move_right' before 'move_left', and 'move_forward' before 'move_back', in order to get my movement code to work correctly.)</p>
<pre class="hljs"><code><div>float x_direction =
input->get_axis("move_left_" + mnchar_id, "move_right_" + mnchar_id);
float z_direction =
input->get_axis("move_forward_" + mnchar_id, "move_back_" + mnchar_id);
</div></code></pre>
<p>Also note the inclusion of mnchar_id, which I discussed at length earlier.</p>
<p>(References 16 and 17)</p>
</li>
<li>
<p>Next, we'll rotate the player in response to any rotation commands sent by the player's controller (or, if <code>mnchar_id</code> is 0, the keyboard). Add the following code to the end of the function:</p>
<pre class="hljs"><code><div>get_node<Node3D>("Pivot")->rotate_object_local(
Vector3(0, 1, 0),
rotation_speed * input->get_axis("rotate_right_" + mnchar_id,
"rotate_left_" + mnchar_id));
</div></code></pre>
<p>(References 16, 18, and 19)</p>
</li>
<li>
<p>We'll now retrieve information about the Mnchar's basis that we'll use to update its velocity. Add the following code to the end of <code>_physics_process()</code>:</p>
<p>Note: I'm not sure why, but multiplying the x and z components
of the x and z bases, respectively, by -1 was critical for getting
the movement code to work. This may be due to an issue with my setup. I imagine that there's a way to update my code such that at least one of these multiplication commands won't be necessary.</p>
<pre class="hljs"><code><div>auto player_transform_basis_z =
get_node<Node3D>("Pivot")->get_transform().get_basis()[2];
auto player_transform_basis_x =
get_node<Node3D>("Pivot")->get_transform().get_basis()[0];
player_transform_basis_x.x *= -1;
player_transform_basis_z.z *= -1;
</div></code></pre>
</li>
<li>
<p>Extend the function by adding the following code, which initializes, then updates, the player's target velocity.</p>
<pre class="hljs"><code><div>Vector3 target_velocity = Vector3(0, 0, 0);
float abs_z_direction = std::abs(z_direction);
float abs_x_direction = std::abs(x_direction);
if (abs_z_direction >= abs_x_direction) {
target_velocity +=
-1 * player_transform_basis_z * z_direction * movement_speed;
} else {
target_velocity +=
-1 * player_transform_basis_x * x_direction * movement_speed;
}
</div></code></pre>
<p>I would like Mnchar to be able to move <em>only</em> forward, back, left, or right relative to its current position (i.e. not diagonally). Therefore, the code above finds the absolute value of the x and z directions, then moves the player along the axis with the greatest absolute value. This isn't strictly necessary, but I find it makes the Mnchar's movement somewhat more intuitive.</p>
<p>(Note that I'm using the standard library's abs() function rather than Godot's, as the latter appeared to truncate values down to the nearest int.)</p>
<p>(References 4, 17, 20, and 21)</p>
</li>
<li>
<p>Finally, close out this function with the following code:</p>
<pre class="hljs"><code><div>set_velocity(target_velocity);
move_and_slide();
}
</div></code></pre>
<p>(References 4 and 21)</p>
</li>
<li>
<p>Reopen your editor, launch the scene, and test out the controls. Make sure that no directions are the inverse of what you'd expect--and that the player cannot move diagonally.</p>
<p>By the way: if the game crashes right when you launch it, make sure that your mnchar.tscn scene is still present within Main's Packed Scene attribute. (It sometimes disappears on my end, but thankfully, it's easy to add back in.)</p>
</li>
</ol>
<h2 id="part-8-creating-a-projectile">Part 8: Creating a projectile</h2>
<p>It's neat to move our Mnchar around with code, but the game won't be too much fun without anything for it to fire (or be hit by). Therefore, let's go ahead and add a Projectile class to our game. This class will have many similarities to the Mnchar class (on which its code will be based).</p>
<ol>
<li>
<p>Within your src/ folder, create two new files, 'projectile.h' and 'projectile.cpp'. Add the following text to projectile.h:</p>
<pre class="hljs"><code><div>#pragma once