Skip to content
Lukas Aldershaab edited this page Oct 9, 2023 · 7 revisions

This is the T3DSharp Framework files designed to be included via CMake

Installation

  1. Install dotnet-sdk 7.0 (Chocolatey: cinst dotnet-sdk)
  2. Download the source
  3. Make a new directory in the "My Projects" folder (name it as you please)
  4. Download https://github.com/lukaspj/T3DSharp and extract into "My Projects//game/data/T3DSharp"
  5. Download https://github.com/lukaspj/T3DSharp-CoinCollection and extract into "My Projects//game/data/CoinCollection"
  6. CMake Configure (set TORQUE_ENTRY_FUNCTION to CSharpEntryFunction)
  7. CMake Generate

The T3DSharp project will be added to the VS solution when you build the source and all of the C# code in the CoinCollection module is added to the T3DSharpGame project.

Then a new solution is created in "My Projects/BaseGame/game/data/T3DSharp" with all the C# projects.

How does it work?

In this repo you can find three template C# projects:

  • T3DSharpGame - The entrypoint for the game, runs Torque3D and loads the C# types
  • T3DSharpFramework - An auto-generated C# layer to interface with T3D through the EngineAPI CInterface
  • T3DSharpGenerator - A generator that can generate the ConsoleObject class hierarchy and other console object definitions

The C# project uses the Console to handle calls from the engine and EngineAPI for interacting with the engine.

What is the Console?

It's kindda vague what the Console in Torque3D actually is. It's a type-system but it can also lookup instances of objects and it might do other stuff as well..

The crux of the matter is that the Console is one of the core components that makes TorqueScript work, and the C# implementation taps into this engine to communicate with the engine. Thus anything that is accessible in the console, should be accessible in C#.

Do I need to figure out how to run the generator?

No, the generator has already been run in this sample. You can find the result under Engine/lib/t3dsharp/T3DSharpFramework/Generated. Feel free to run it again if you want to mess with it though (you might have to do a little bit of manual editing of the output atm. but it should be manageable).

T3DSharp

A quick introduction to T3DSharp, the macros in the C++ code DefineEngineFunction and the like also define a CInterface-based version of that function that can be called by consumers of Torque3D as a .dll. A CInterface version of a function is a compiler-independent simple version of a C++ function based on the C-language. It is limited to C's features as opposed to C++, which means no operator overloading, no class-methods, no virtual methods etc. This significantly simplifies the interface between the C++ code and a potential scripting language.

The CInterface version of these functions are typed, so, as opposed to TorqueScript, C# doesn't have to convert everything to string before it can call the function. It can just call it with the actual parameters that function expects. However, this typing is a one-way street. The console is built up around TorqueScript's stringly-typing where everything is a string, so when the engine wants to call the script it converts everything to string before it sends the command.

TL:DR; almost all calls from C# to C++ is statically typed (variadic functions are an exception atm), calls from C++ to C# will be dynamically typed.

In order to compensate for the dynamic typing that happens when the engine calls a C# function, the T3DSharp layer analyses your C# code, and checks that the number of parameters is correct and converts arguments from string to whatever your function expects.

Annotations

T3DSharp utilizes three annotations to simplify integration with the engine.

  1. [ScriptEntryPoint] - Used in Program.cs to specify the equivalent of the main.cs TorqueScript file.
  2. [ConsoleFunction] - Use on a public static function to register the function (similar to a TorqueScript function definition). The class name will be used as the namespace. For example, the public static void Foo() in the class Bar can be called as both Bar::Foo() and Foo()
  3. [ConsoleClass] - Used to specify that the class represents a class that should be accessible from the console. If you have a C# class Bar you can't write new Bar() in TorqueScript, but you can write new ScriptObject() { Class ="Bar"; };

How-To:

Instantiate an object

// Works mostly like in TorqueScript, except type-safe.
// Also you have to manually "register" the object as you would in C++.
var Foo = new SimObject("Foo") {
    // You can set static properties here
    CanSaveDynamicFields = true,
};
// Atm, you have to set dynamic properties outside of the initializer:
Foo.SetFieldValue("dynamicProperty", "bar");
Foo.RegisterObject();

Find an object

new SimObject("Foo").RegisterObject();
// Find by name
Sim.FindObject<SimObject>("Foo");
// Find by ID
Sim.FindObject<SimObject>(1234);
// If the string can be converted to a number, it will be treated as an ID instead of a name
Sim.FindObject<SimObject>("1234");

Change type of an object

// Since the C# objects are representations of the underlying C++, 
// you might sometimes have an object instance of type A, but 
// you know it should be of type B but C# won't let you cast from A to B.
// Then you can do an unsafe-cast from A to B.
new SimSet("Foo").RegisterObject();
var Foo = Sim.FindObject<SimObject>("Foo");
// Cast from SimObject to SimSet
var FooAsSimSet = Foo.As<SimSet>();

Casting string to type X

// You can use the helper class GenericMarshal to convert from/to string
GenericMarshal.StringTo<int>("123");
GenericMarshal.ToString(123);
GenericMarshal.StringTo<Point3F>("1 2 3");
GenericMarshal.ToString(new Point3F {X = 1.0f, Y = 2.0f, Z = 3.0f});

Tag/Untag strings

// Tagged strings is used extensively in TorqueScript for networking purposes.
// In TorqueScript, tagged strings are specified by using single quotes (').
// In C# you can use string extensions instead:
var taggedStr = "myString".Tag();
var str = taggedStr.DeTag();

Colors in strings

// Colors in TorqueScript are supported by special escaped characters like \c0.
// In order to implement this similarly in C#, we use string extensions:
"\\c0SomeText".ColorEscape();

Call engine functions

// All engine functions are bundled by their scope. Most exist in the "Global" scope.
// Except from the explicit scope, this looks quite like TorqueScript. The only 
// caveat is that they are case-sensitive in C#.
Global.Echo("Some log message here");

Call TorqueScript functions

// You can't call TorqueScript directly, but you can use the engine functions Call or Eval
string arg1 = "foo";
string arg2 = "bar";
Global.Call("someTorqueScriptFunction", foo, bar);
Global.Eval($"someTorqueScriptFunction({foo}, {bar});");

C# code-patterns

Dynamic Field Properties

In the CoinCollection C# module, you might see properties like this here and there:

      public CoinCollectionGameConnection Client {
         get => Sim.FindObject<CoinCollectionGameConnection>(GetFieldValue("client"));
         set => SetFieldValue("client", GenericMarshal.ToString(value));
      }

The purpose here is to formalize a dynamic property, making it type-safe and well-documented. This is quite boiler-plate looking, at some point a better solution should be found for this.