Skip to content

Script Engine

ZilverBlade edited this page Jul 10, 2025 · 21 revisions

Index

  1. Game Loop
  2. Scripting
    1. Script Classes
    2. Script Attributes
      1. Editor
      2. UI
      3. Network
      4. Controller
    3. Networking
      1. Connection, Relevance and Roles
      2. Field Replication
      3. Method Replication (RPC)

Glossary

  • C# calls static methods in the convention Namespace.Class.Subclass.Method(parameters), however to distinguish namespaces from class scopes, methods and fields, in this documentation it is written as Namespace.Class::Subclass::Method(parameters) (similar to C++ scope style).
  • Certain words are written in bold to stand out, avoiding confusion or error with certain concepts by grabbing your eye’s attention.

1. Game Loop

  • Shard3D runs with a single threaded game loop, everything gets called in a defined order, however it is planned to separate the game logic and physics from the renderer (1 game thread, 1 renderer thread).
  • Renderer calls always get called at the end of the loop
  • Physics runs with a fixed step rate configurable in the engine_settings.ini (default is 120Hz). It may run multiple times per frame or it may even skip a frame! (depending on the framerate)
  • When physics is stepped in a frame, it's done before the C# script step except for LLevel::PrePhysicsTickEvent(float ts) (Shard3D.Scriptable) (this also means that fixed tick methods will be called before their variable tick counterparts)
  • Physics body transform interpolation occurs every frame after physics step (if occurred) and before script engine step (this also means before the fixed step events)

2. Scripting

2.i. Script Classes

  • Scripts derive from 3 base classes: LLevel, LGameMode, AActor (Shard3D.Scriptable)

  • L is the prefix stating it’s a singleton inside the level, thus there may only be one instance.

  • A is the prefix stating it’s an actor, which is a scriptable prefab actor, it may have multiple instances.

  • AActor:

    • Is to be used standalone for any sort of script, whilst AController is used for input management (this distinction is made due to how the networking system is set up):

      • AController (Inherits AActor, IGhostActor, INetworkReplicatesServerOwner, INetworkOwnableActor)
        • Only this class and UIControl should be allowed to poll user inputs and manage renderer data
        • Spawned through new T() (where T is a class derived from AController)
        • Controllers will be automatically destroyed at the end of the level, however you can destroy them earlier using Level.KillController<T>()
    • BeginEvent() (called when level simulation starts, only called once solely for actors that already existed before the level started simulation!)

    • EndEvent() (called when the level simulation ends, only called once solely for actors that survived until the end of the simulation!)

    • SpawnEvent() (called when actor is spawned)

    • KillEvent() (called when actor is destroyed)

    • TickEvent(float dt) (called every game loop frame, is not stable, as it has a variable delta time and should be used when integration can be done with a variable frame time)

    • PhysicsTickEvent(float ts) (called at the same rate as the physics step, may be called several times per frame, or it may be skipped in a frame as it runs at a fixed step rate, should be used for integration that requires a fixed time step. Warning, this does not mean that the values get interpolated like the physics positions do, if interpolation between steps is requires, you should do that in a variable frametime tick event (e.g. AActor::TickEvent(), LLevel::PostTickEvent()))

  • LLevel:

    • Is set in the WorldSceneInfoComponent
    • BeginEvent() (called before any other begin event, it is the first event called)
    • EndEvent() (called after every end event, it is the last event called)
    • PreTickEvent(float dt) (called before any AActor::TickEvent(float dt))
    • PostTickEvent(float dt) (called after every AActor::TickEvent(float dt))
    • PrePhysicsTickEvent(float ts) (called before any AActor::PhysicsTickEvent(float ts) and before regular tick events)
    • PostPhysicsTickEvent(float ts) (called after every AActor::PhysicsTickEvent(float ts) and before regular tick events)
  • LGameMode

    • Is set in the WorldSceneInfoComponent
    • N is the prefix stating it is a networking related method
    • NClientConnectingEvent(AController controller) (client has connected and their respective default controller prefab)
    • NClientDisconnectedEvent(AController controller) (client has disconnected)

2.ii. Script Attributes

  • The script api provides 2 kinds of attributes, attributes are only used by derived classes of AActor and UIControl, one purely flag oriented kind by inheriting an empty interface (so that it gets passed onto derived classes, e.g. public class MyBaseClass : ITestAttribute) and another property oriented kind that work like C# attributes (e.g. [MyAttribute] or [MyOtherAttribute(true)])

2.ii.a. Editor Attributes

  • Shard3D offers the ability for fields to be edited in editor, this allows for multiple instances to have different parameters at edit time, being loaded from the scene file at runtime. It also offers the ability for (exclusively useful for editor) calling methods in the editor (without input parameters or return types!)
  • Attributes (Shard3D.Reflection):
    • ExposeField(bool editable = true)
    • ExposeMethod(bool requirePlayMode = true)
    • NumericFieldEditProperties(double min = -INFINITY, double max = INFINITY, double speed = 1.0)
  • [ExposeField(bool)] may only be applied on top of the following types:
    • (of System) float, double, (u)byte, (u)short, (u)int, (u)long, string
    • (of Shard3D.Library.Types) Float2, Float3, Float4, Float3x3, Float4x4
    • (of Shard3D) Actor (This includes all classes derived from Actor)
  • [NumericFieldEditProperties(double, double, double)] may be applied on numeric fields that have the [ExposeField] attribute with bool editable set to true.

2.ii.b. UI Attributes

  • These attributes are only used on the new RmlDocument class (from Shard3D.UI.Rml)
  • As RmlUi supports scripting through Lua, basic interop is possible between C# and Lua
  • Attributes (Shard3D.UI.Rml.Lua):
    • [RmlLuaAccessible(string accessAs)] defines the name where the Lua script in the document can be accessed as
    • [RmlLuaMethod] exposes the method to the Lua script. Only float, double, (u)byte, (u)short, (u)int, (u)long, string are supported as input parameters
    • [RmlLuaField] exposes the field to the Lua script`)
  • Recent Shard3D Torque versions support RmlUi data bindings. Data bindings are used through use of Data models. To create a data model, create a class that inherits from RmlDataModel (Shard3D.UI.Rml.Data)
    • [RmlDataVariable] is to be placed on a field. This may be a primitive type, RmlColor, Float2,3,4, or string. Data conversion is automatically performed whenever possible. In case of an invalid conversion, the default value is provided. This attribute may also be placed on a field who's type is a class implementing IRmlDataScalar,IRmlDataArray or IRmlDataStruct. XML documentation is provided for those interfaces.

2.ii.c. Network Attributes

  • Networking is enabled through setting attributes on fields, methods and classes.
  • Networking attributes through interfaces: INetworkOwnableActor, INetworkReplicatesEveryone, INetworkReplicatesServerOwner. These attributes may be applied on a base class and their “attribute flag” will be passed down.
  • Networking attributes through System.Attribute: NetworkReplicateField(uint Size, float maxHZ, NetworkReplicationFlags ReplicationFlags = NetworkReplicationFlags.SendEveryone) (for use on fields), NetworkReplicateFieldNotifier(string FieldName) (for use on methods to connect to fields with the [NetworkReplicateField] attribute), NetworkReplicableActor(NetworkReplicationRelevance Relevance = NetworkReplicationRelevance.NotRelevant, float Priority = 1.0f, float CullDistanceMeters = 10000.0F) (for use on classes deriving from AActor), RemoteProcedureCall(RemoteProcedureCallBroadcast Broadcast, RemoteProcedureCallReliability Reliability = RemoteProcedureCallReliability.Unreliable) (for use on methods) (Shard3D.Networking)
  • INetworkOwnableActor signifies an actor that can be owned, e.g. a controller, pawn, or client relative actor. This does not mean that a client has authority over the actor, the server should always have ultimate authority over replicated actors.
  • [NetworkReplicableActor(NetworkReplicationRelevance, float, float)] is an attribute to be applied on classes that inherit AActor. This will enable the actor to make use of the networking replication API.
    • NetworkReplicationRelevance (currently unused) defines the relevance of the actor. The boundary of relevance is determined by the CullDistanceMeters parameter, which is the maximum cull distance between the possessed camera and possessed pawn. NotAlwaysRelevant states that the actor may not be relevant, once out of the cull range it is considered not relevant, and replication priority is low or even disabled. AlwaysRelevant will force the actor to always be replicated. OnlyOwnerRelevant will only force replication on server and owning client, it is treated as NotAlwaysRelevant on the rest of the clients.
    • Priority (currently unused) defines the relative replication priority.
    • CullDistanceMeters (currently unused) defines the bounding radius when relevance alters.
  • [NetworkReplicateField(uint, float, float, NetworkReplicationFlags, NetworkReplicationQuantization)] is an attribute to be applied on fields (of type: float, double, (u)byte, (u)short, (u)int, (u)long, and every unmanaged struct) of actors with the [NetworkReplicableActor] attribute
    • uint Size states the static size of the field. Only the server may replicate variables back to the client(s).
    • float maxHZ states the maximum update rate of that field (note thus that in this case fields only replicate when their value has changed between their respective checks!). Warning: fields may not successfully replicate to clients as the network packets are sent as low priority and unreliable as field replication gets updated relatively frequently, if a client has an unstable connection it may fail to receive the most up-to-date value! For updating values crucially (but infrequently) prefer using a Remote Procedure Call.
    • float minHZ states the constant update rate of the field, the server will periodically replicate the variable according to this parameter, so use it sparingly!
    • NetworkReplicationFlags ReplicationFlags defines to whom the variable may be replicated (SendOwner replicates from server to the client who owns the actor, SendExceptOwner replicates from server to everyone except the client who owns the actor. These 2 flags are combined in SendEveryone, which replicates to everyone).
    • NetworkReplicationQuantization Quantization is used on numeric values, including the FloatN types of Shard3D.Library.Types, each type is used for compressing values to reduce network bandwidth usage (especially useful for variables that change in mass a lot)
  • NetworkReplicateFieldNotifier(string) (currently unused) is an attribute to be used in junction with [NetworkReplicateField] who’s method will be called when the named field is replicated.
    • string FieldName is the name of the field that will trigger the event.
  • [RemoteProcedureCall(RemoteProcedureCallBroadcast, RemoteProcedureCallReliability)] is an attribute to be applied on methods, either static, or non-static inside of classes deriving from AActor with the [NetworkReplicableActor] attribute.
    • RemoteProcedureCallBroadcast sets to who the RPC may be called to (Client replicates from server to the client who owns the actor, Server replicates from the calling client to the server, Universal calls the function locally and replicates the function to all clients (only allowed to be called by server, clients currently do not have the ability to call a Universal RPC).)
    • RemoteProcedureCallReliability configures the packet reliability. By default this is set to Unreliable (UDP) which means the function is not guaranteed to make it to the remote connection. Reliable (TCP) RPC’s will guarantee making it to the remote connection and in defined order, however it is not recommended to make use of too many reliable RPC’s as it can congest the server or client. Examples for when to mark a method as unreliable:
      • It has no gameplay impact
      • It may be called extremely frequently

2.ii.d. Controller Attributes

  • Shard3D now supports mapping input via attributes on fields, properties and methods. When mapped, the engine will deal with the mapping and automatically bind the appropriate calls on the event that an input is triggered.

2.iii. Networking

2.iii.a. Connection, Relevance and Roles

  • Network::ConnectToRemoteHost(AController requestingController, string ipv4, ushort port = 5400) (from Shard3D.Networking) is a method to connect to a remote host:
    • AController requestingController is the client's controller that the server will be able to connect with.
    • string ipv4 is the IPv4 address encoded in a string (it may not be a domain address!) of the host. Set as “127.0.0.1” to connect to your own machine. Local IP addresses (192.168.xxx.xxx) can be connected to if the connecting machine and server are on the exact same network.
    • ushort port is the port of the remote host (defined in serverconfig/server_settings.ini). Default is 5400.
  • NetworkConnectionStatusDelegate ConnectionStatusDelegate (from Shard3D.Networking) is a callback that will be called when changes are made with the host's connection (void NetworkConnectionStatusDelegate(NetworkConnectionStatus status) is the callback's signature)
    • NetworkConnectionStatus status is the new status of the reported connection result
  • NetworkRole Network::Role (from Shard3D.Networking) will return:
    • ILLEGAL if there is no connection
    • Authority if the script is running in the server
    • Client if the script is running in the client and has an active connection with the server

2.iii.b. Field Replication

  • Fields are only replicated by the server through UDP, this means that when a field replication occurs, only when the server sets the variable will it replicate to all clients. This is both due to that clients cannot see other clients (which would encourage bad design choices related to networking security), and to prevent easy exploits where the server is not able to check the values that are set.

2.iii.c. Method Replication (Remote Procedure Calls)

  • RPC’s are extremely useful to reproduce steps across connections.
  • To create a RPC:
    • Define a method (preferably prefixed with RPC or an indicator that it is a remote procedure call, as well as to whom it shall be replicated e.g. RPCToClient_MyMethod(int foo)) and apply the [RemoteProcedureCall] attribute.
    • Create another method with the exact same input parameters, and exact same name, suffixed with _Impl e.g. RPCToClient_MyMethod_Impl(int foo) with no attributes, and implement your code in there. If your remote procedure call is of broadcast type Server, an optional auto generated first input parameter is RemoteConnectionHandle (from Shard3D.Networking) e.g. RPCToServer_Impl(RemoteConnectionHandle who, int test);
    • Inside the RPC defined method (not the implementation method) initialise a variable with the static Network::CreateRPC(AActor instance = null) method (from Shard3D.Networking). (DO NOT CALL THIS THROUGH A HELPER METHOD!! Call it directly inside the RPC defining method! This method uses reflection to automatically determine what class and what method is defining the RPC!). You may provide an AActor instance in case your RPC is defined inside an instantiated actor. Leave as null if your method is static. The builder is to be used as follows:
      • AddParameter<T>(T param) (T of type string or any unmanaged type) adds a parameter in order to the network packet.
      • SetPrefabUUIDSyncCount(uint count) sets the amount of prefab creations needing their UUID’s synchronised within the remote procedure call. Make sure this value is the exact quantity of synchronised UUIDs necessary as any disjunction can cause errors with UUID synchronisation in other methods! This is accompanied with the Network::PopPrefabSyncUUID(bool discard = false) method which synchronises the next spawned prefab’s UUID in order, which should be called right before spawning a prefab!
      • Call() sends the RPC to the remote connection (and automatically calls locally if the RPC is broadcast universally)