Latest  | Search | Go
Edit this page   |   Attach file 

  Home | Tutorials | Technical Reference | Runtime | API Documentation | ScriptingSystem  

Reality Scripting System Overview

Author:Jeremy Stieglitz

What is the Reality API Scripting System?

The Reality API Scripting System, contained within the ScriptingSystem project, is a memory-managed wrapper for writing Scripts that have full control over Reality in any .NET language; they are quickly compiled at runtime, or can be pre-compiled by the developer should if you don't want to ship with mod-ready code. Scripted "Managed Actors" (or, as they are called, MActors) also have the capability to expose custom instance Properties for serialization (saving into a level xml file) and adjustment within RealityBuilder. With a few low-level exceptions, the entire Reality API is wrapped for your manipulation via Script. This provides immense power, effectively allowing you to create an entire game through C# or VB.NET Script. For purposes of this Game Sample, everything was programmed in C# and that's what we'll be discussing here. Let's examine the pros and cons of C# Script programming over C++ programming in Visual Studio .NET, this from a C++ programmer of many years:

  • Pros
    • Easy editability; for instance, artists can open and tweak hard-coded visual values in any text editor (or you can expose these for editability in RealityBuilder), or end-users can modify your game to whatever extent you wish to allow them.
    • Automatic saving of an MActor's Properties into a level file, and Description-supporting public exposure for instance-configurability within RealityBuilder
    • VS.NET 2005 IDE integration that blows C++'s away. You get:
      • perfect intellisense within the Scripts and from the Reality API Wrapper that immediately updates the moment you add something new, and gives you smart listings based on where you're currently typing (for instance, if you're in an enumeration param, it'll pop up to the appropriate enums)
      • infallible editable watch values and automatic debugger type casting
      • compile times (which are optional, only used for precompilation or pre-runtime compilability testing) that are seconds (for an entire set of scripts) where C++ is minutes
      • hiding of intellisense listings based on public-private-protected access levels from your current coding location, allows for a more natural approach to usage of public & protected interfaces in your programming
      • automatic, aggressive code formatting that's extremely helpful in ensuring easy-to-read code
      • XML comment mark-up system that allows for automatic documentation generation via nDoc
      • unfailing scope denotation and collapsability; where C++'s was unreliable or non-existant, C#'s is a life-saver for complex scope nestings!
      • errors notification that can appear as you code and clear as you fix, and automatic capitalization error fixing! Errors that would traditionally be caught at runtime in C++ (such as null variable exceptions) are caught in the C# compilation process, and C# runtime errors are distinct and descriptive from within and outside the IDE, rather than generic "Unhandled Exceptions".
      • warnings that notify you of all sorts of structural coding lapses, from un-used variables to incorrect overrides
    • Robust helper classes to perform all sorts of common math & file operations
    • Superior object-oriented and private-public structure of C# ensures more intelligent
    • Syntax similarity to C++ ensures quick & painless transition for longtime C++ programmers
    • Simple type-cast checking makes elegant programming of complex type-dependent game logic
    • Inline initial value assignments simplify the constructor and give you one place to look for the initial value of a class' variable
    • Each Enumerations is considered to be its own Type, for rigorous usage of enumeration types in function parameters
    • Garbage collection system written by Microsoft that is extremely intelligent and efficient, working in the background and automatically ensuring your script code has no memory leaks
    • Completely wrapped PhysicsSystems and NetworkingSystem, allowing you to create complex Rigid Body dynamics & Networking entirely within script
    • Just use a Class in a Script and it'll automatically find that Type in the assembly, no more worrying about inclusion files
    • Your scripted game code will remain separate from your core binaries making distribution of targeted code updates easier (don't have to recompile the hefty binaries for an adjustment in the scripted game).

  • Cons
    • Speed is below that of C++, but not by much. Repetitive low-level math operations can still be done in C++, as they are in the ScriptingSystem Reality API Wrapper

Consider that the bulk of CPU usage in today's games is consumed by rendering complex environments, and physics & collision calculations, all of which are handled outside of the ScriptingSystem. We have also wrapped the common 3d math operations involving Matrices, Quaternions, and Vectors. Additionally, C# supports an optimized low-level sorting interface called IComparable, useful for everything from Player rankings to z-sorted Particle FX. Thus my own conclusion is that while complex low-level operations are still best done in C++, the ~15% difference in speed between C++ and C# on most operations is not worth the increased development time, lack of structural enforcement, and memory-leak watchdog mentality of C++.

Furthermore, considering that the open-source Mono Compiler also provides arbritrary cross-platform compilability, the anticipated innate Xbox2 IL assembly support, and the incredible mod-ability of a C# game if you desire that functionality, C# emerges as the hands-down winner for mid-to-high level game programming. That's why we chose to write the Reality API wrapper, and develop the Eval Kit sample game in C#. I hope you'll see the benefits of C# to your technical development process, and benefit from its elegance and the, well, beauty of its code. Now then, let's get on to examining how to create game objects in C# with the ScriptingSystem Reality API Wrapper!

The Managed Actor

The most important class in the managed Reality API is the Managed Actor, named MActor. MActor (documented in the Reality Wrapper API) wraps all the functionality associated with Actor, and is bound to an Actor in its C++ representation. By inheriting from MActor and using the functions in the other wrapped classes, you can effectively create your game in C# script.

Note:        Actors that are created purely on the C++ side are generally not wrapped (though you can extend this by wrapping your own Actor-derived classes). Unwrapped Actors are treated as arbritrary World geometry by the ScriptingSystem, with the exception of Light being wrapped as MLight.

The main things to keep in mind are that the MActor wrapper interface closely mirrors that of the C++ Actor -- if there's a variable or function in MActor you want to access in C#, use its MActor version. You can't directly delete an Object in C# (the garbage collector will automatically free the memory of the Object when all its references are null), but if you call MActor.Destroy() (for immediate destruction) or set MActor.LifeTime = 0 (for post-tick or frame-delayed destruction), the C++ Actor bound to this MActor will be deleted, and the MActor will be freed. Until you do one of these two things (or call MWorld.Unload() to unload the entire World), the MActor will remain in existance due to a static presence on the MActors hash within the ScriptingSystem, so you don't need to worry about it being freed by the garbage collector, even if no references (other than the hash) exist.

Keep in mind that MActors (or MNetworkActors, discussed in NetworkingSystem) are the most convenient way to tie any kind of functionality (and networking) to a given World, but they don't actually have to be physical objects. In the Eval Kit game sample, a GameState MActor is used to store (and synchronize over the network) information such as team scores, even though it doesn't actually have a Model in the World. For such Actors, it is best to set them to IsHidden = true, GhostObject = true, and StaticObject = true to ensure they receive no physics or collision updates.

You can, of course, create any non-MActor classes you wish but they will traditionally piggyback in relation to a given MActor, since MActors will form the crux of what your game World is. The sole exception to this in the game sample as FXSystems, which is a non-MActor base class implemented in the game sample. An FXSystem is not an MActor due to efficiency reasons, they are created so quickly and so often that it's best for rapid-fire objects like these to not add overhead to the Actor system. FXSystems usually just draw a billboard or manage a ParticleSystem, so it's easy for them to simply adjust these values when the C# FXManager updates them every frame. This leads us to the next topic, which stems from the question "How can my game have global logic when I only have separate MActors to be updated each frame?". The answer is that you can indeed have custom global logic, even without modifying the ScriptingSystem. Such global functionality is handled by the Game's LogicCore class.

The Game LogicCore

The LogicCore class is defined within ScriptingSystem to allow creation of static application behavior entirely within script. It's created as a singleton instance upon app startup by the ScriptingSystem, and Ticked once per World, passed World loading notification, appropriate rendering event notifications, and a shutdown notification. With those overridable functions, a user can create his own implementation of the LogicCore to define how the application behaves to loading Worlds, updating Worlds, and rendering Worlds. This gives you the power to create a game that does very different things than just what what is defined in the compiled binary game code. The Eval Kit contains a GameCore implementation of LogicCore which, Ticks all its MActors (they will not be Ticked otherwise, not even their base C++ Actor representation!), PostRenders its non-MActor FXSystems, creates a single GameState per World, and updates user input and camera on what it calls the LocalAvatar (described in greater detail in DemoPlayer).

Note:        The LogicCore class also can determine what is done when, in a multiplayer game or once in a singleplayer game, a new Client joins, by overriding NetworkClientJoined. This allows the LogicCore to create a custom avatar for the Client, or provide the Client with some other information.

Additional ScriptingSystem Classes

In addition to MActor, MNetworkActor, and MLight, there are plenty of other wrapped classes & wrapped interfaces to provide you with robust Reality functionality in your scripts. Let's examine a few of them:

  • MMatrix: Wraps Reality's Matrix and associated operations, keeping all of the heavy Matrix calculations in C++ for maximum speed.
  • MVector: Wraps Reality's Vector and associated operations, ''.
  • MQuaterion: Wraps Quaternion functionality and keeps all of the calculation in C++.
  • MHelpers: Contains a variety of useful helper functions to convert between different color formats, get file paths, and other misc operations.
  • MPrecacher: Provides functionality to pre-load resources and retain them as static resources linked to a particular class, deleted only upon app shutdown or manual purging.
  • MCanvas: Provides a variety of functions to draw graphics onto the screen.
  • MTexture, MModel, MSound: Media resources!

These classes are just a few of the extensive Reality API that is wrapped in the ScriptingSystem. For more information, read the Wrapper API docs, and examine Wrappers.h in the ScriptingSystem project.

Note:       

Some syntax to keep in mind when programming within the C++ ScriptingSystem Wrappers Interface:

  • All objects, except primitive types such as floats ints bools etc, that are passed from the C# script to the ScriptingSystem Wrappers functions are passed as managed pointers, using the following syntax in the C++ code: function(Object^ object). You can also pass the original object reference itself for direct manipulation by using the following syntax: function(Object% object), which also works for passing primitive references such as function(float% val).

  • To create a new Managed Object that will be used in the C# system, you must use 'gcnew' (rather than 'new') to allocate the object as managed memory. It will then return a managed pointer, such as MActor^, that can be used in the scripts.

  • To convert String^ passed from the scripts into STL strings, use Helpers::ToCppString(String). To convert STL strings to String^ returned to the scripts, use Helpers::ToCLIString(string).

Putting it all together

Now that you know what an MActor is, how the inheritable LogicCore can control the global application's behavior through overrides, and what some of the additional important wrapper classes do, you're ready to create some game scripts! Any .cs scripts that you place in the "Scripts\Spawnable" subdirectory will be available for insertion in the RealityBuilder class list (note that when doing this, the .cs file should have the same proper name as the class name to spawn, such as DemoPlayer.cs corresponding to DemoPlayer). Any .cs scripts that you place in the "Scripts\Game" subdirectory will not be directly spawnable in RealityBuilder, and are thus generally more internal game logic MActors (such as GameState), abstract MActor classes that will be derived from (such as Pawn or Vehicle), or non-MActor classes that can not be directly spawned by the user in RealityBuilder (such as FXSystems, though you can contain an FXSystem within an MActor if you want spawning functionality, as depicted in ParticleSystems).

A simple MActor class that handles user input and updates the camera, for a basic controllable avatar within the World, is DemoPlayer. Use it as a starting point for your script development, and remember to place your MActor script in "Scripts\Spawnable" if you want to insert it in RealityBuilder (handy for quickly testing a script). If you create any low-level objects in C++ that you want your C# scripts to have access to, wrap them! You'll soon find that with extremely short compile times, unrivaled IDE capability, and user editability, programming your game through script will provide the most long-term benefit and dramatically shorten development & debugging time.

ScriptingSystem   Edit | Attach | Ref-By | Printable | Diffs | r1.5 | > | r1.4 | > | r1.3 | More