Jump to content


  • Posts

  • Joined

  • Last visited

Personal Information

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

JeffR's Achievements

  1. JeffR

    Workblog - Aug 2021

    Hey everyone! So, been a while since the last workblog, but the good news is, I didn’t quite threshold past the one year mark! So that’s… good. Anywho. Predictably there’s a frankly preposterous amount of stuff to go over so lets get getting and dig on in! What’s new? A lot, as said. But for specifics, since the last workblog, we’ve rolled in over 150 pull requests. These range everything from bugfixes, library updates, QOL improvements and all the way up to converting pretty much every class that touches a content file into using the assets system. We even got the site updated, as I’m sure you all had noticed. Lets cover a few of the specifics and big ones. New Site So, if you’re reading this, then you’re at least slightly aware that the site underwent some changes relatively recently. We were using a git-based pages system before which while lightweight and anyone could contribute, the turnaround time ON changes was fairly suboptimal, and because the only way to make any changes at all was via PR’s, it disincentivized people from contributing content. Pushing tutorials or updating documentation on the site had enough of a hurdle that no one really wanted to bother. So we did a lot of digging and landed on a fully integrated CMS platform. This allowed the forums to be integrated into the main site and allowed us to easily manage the content that was on the site itself. Other features like broad site search and a few handy additional boons like being able to post micro-communities for games all tie together to make a better centralized point of community engagement. And as noted prior, it drastically lowers the bar for people pitching in to update the documentation if they have at least the Contributor designation, as someone trusted within the community on helping out and know their business. As we’re in the home stretch on 4.0(as I’ll get into more below), I’m starting to be able to split my time from that as we wait on rounds of testing to begin filling out the documentation section. The plan is to consolidate all the disparate docs from the various sites torque’s found itself on, from the old GG docs to the old TDN, to the wiki and so on, and fold it all back here in the docs section. This way we have a concrete point people KNOW they can find what they’re looking for, instead of having to guess what random site the info they need is on. Major, MAJOR props to Tony and Lukas for helping get this thing where it is now. It's definitely a big deal! PBR It’s been a bit since I’ve covered PBR stuffs, but honestly not TOO much has changed since I’d last covered it, so this can be fairly quick. The main points to note is we spent a lot of time refining and stabilizing the math so you can have pretty high confidence that whatever is exported out from your art tools like blender or substance, if they use the standard Metalness/Roughness PBR formatting, will look Tools Correct. Also, as noted previously, we’d shifted up to utilize the more industry-standard ORM Configuration map, which further emphasizes the Tools-Correct approach. Of course, you can still use sliders or individual maps to fill in the PBR properties too. That hadn’t gone anywhere. Other things were cleaning up and stabilizing probes and IBL and improving performance with them by adding some nice convenience functionality with controlling active probe counts and the like. All in all, PBR should give stable, quick results that look quite nice. Assets Alright, now we’re getting into the meat of things. So, assets. I’ve talked about them a good bit before, so if you’ve been following my workblogs, you’ve got at least a general notion of them. Suffice it to say, they’ve gotten a LOT of work done in the past months. So, as of this moment, outside of single-digit numbering exceptions, every class that ingests a normal “content” file type - that is, Images, Shapes, Materials - now utilizes assets. While a lot of work to get right, we did everything we could to ensure that the process is stable and doesn’t impact the functionality of the classes or objects themselves. Part of that is we made some… liberal use of macros to fill in a lot of duplicated and common code pertaining to these content file types. Whereas before each class would have to implement the entire file ingest, load and bind logic each time - which as with all duplicate and repeated code, leads to points of possible failure - they now use a common set of macros which have been aggressively and thoroughly tested, making everything a bit leaner and less prone to issues sneaking in. In this shot, we see an example of defining our asset macro in the class header file: And here's how you use the asset macros to set up the init persist fields to expose them to script: There's even macros for the packing and unpacking of the network data: And finally, an important bit, is that it doesn't just generate function info or anything, but actually configures the internal variables and the like off the parameters you pass in. This allows everything to be VERY consistent and predictable, making usage in the classes once you actually want to utilize the data set much more comprehensible, like so: This means that pretty much every class has a very similar content-file flow, which allows us to standardize object fields more(less guessing if it was shapeName, or shapeFile, or shape, etc). This also standardizes accessor functions. Want the gui’s bitmap? %bitmap = %gui.getBitmap(); And you can be sure it’ll get it, no questions asked. Part of these macros is retaining the legacy fields too, so if you load up a project and some stuff doesn’t get processed successfully via the Project Importer(will get to that here shortly), then the old fields are held onto so you can retry, or debug it. So those accessor functions are smart enough to figure out where your bitmap data is and return it back. Using an asset? Returns the asset info. It still using the legacy bitmap filename field? Returns that instead. This should alleviate a good chunk of pain for existing projects that are looking to port up into 4.0 without utterly snapping all your stuff over its knee. Ultimately, this all ends out with a standardized, consistent content flow without having to juggle different means, forms and fields depending on the object and class, which is nice. Asset Browser So, obviously, if we’re gunna be mainlining assets now, the asset workflow needs to be up to snuff as well. First up, we’ve got the Asset Browser. I’ve shown and talked about this thing before, but now it’s the primary access point for all content in your game. Previous, I’d gone over the improvements to searches in the AB with stuff like complex searches and collection sets. This is obviously all still in, but I’ve added some further tweaks to improve reliability here. A search isn’t much help if it doesn’t find what you need it to. Additionally, I’d covered how you could do stuff like drag-n-drop datablocks to spawn the vehicle or the like. This too has been improved and expanded on, so that pretty much anything that is generally spawnable via the Library tab can be spawned via the Asset Browser. The plan here is to ultimately replace the Library tab with the AB as the centralized one-stop-shop for all your content needs. To that end, most of the ‘Creator’ entries have been brought over to the AB as well, so spawning lights, probes, precipitation, skies and so on can be done via the AB too. (Of course, the current class icons are a bit low res and need some sprucing) Beyond that, improvements to the tooltips for when you hover over an asset to present where the asset is located in the file structure and other meta info should help tracking down where those assets you knew you had but forgot where you put. We’ll also be looking into some ‘Go To Asset’ navigation option/actions as well to further facilitate tracking down these lost sheep, er, assets. Now, if anyone threw some big images, or a lot of shapes at the AB previously(Catographer, for example, uses like a dozen 4k images for material atlases), you’d find there’d be a load hitch every time it’d try and populate the asset previews in the AB. Obviously, this is a minor annoyance that, when repeated enough times(which it would, now that the AB is the primary content nexus) it would become grating fast, and hurt workflow. So I added a system where it’ll take preview images and create a scaled down version, ensuring that no preview is so large as to cause hitching, allowing fast loading of directories even if they have a crazy number of assets visible. Whenever the AB loads, it’ll generate a preview image based off the source content - a shape or image - and generate a scaled down preview image to a given size, such as a 512x512 preview. This keeps them reasonably sized, which keeps loading in the AB fast, but doesn’t hurt the quick-glance recognition of what you’re looking for either. Additionally, I added logic to compare the file modified timestamps whenever the AB is opened, so if an asset’s file saw changes newer than the preview image, it’ll regenerate it. This ensure that if you recently re-exported an image or a model, the previews will always show the correct version. Asset Importing So with everything actually for-realsies using assets now, we want to ensure the importing process is as sexy-smooth as possible, so this saw improvements as well. Various sanity checks, safeguards and issue-resolution methods were added to the import configs. For example, a new Issue Resolution step “FolderPrefix” was added. In the event of an importing asset happening to have the same name as an existing asset, like say, both have “grass” for the AssetName. In this case, with the FolderPrefix resolution, it’ll then find the folder the asset’s file is in, and then just prefix it. So if our conflicting importing file is in the ‘Foliage’ folder, it’d resolve the conflict via turning the asset name into Foliage_grass. It’s also smart enough to try and find non-conflicting folders in case THAT was already taken too. Additionally, options for force-adding type suffixes onto the asset name help prevent collisions too. Like say we had a ‘Player’ model, that has a Player material and uses the Player image for the diffuse. These 3 would normally conflict, being all named player. With the options on to add the type suffixes, though, they become Player_shape, Player_mat and Player_image. This prevents collisions AND becomes easier to search for/spot at a glance which-is-which. So several birds with one stone there! And remember, this doesn’t affect the actual file the asset uses, just the assetName the engine builds for the AssetId, so you don’t have to worry about it doing spooky rename voodoo on your png file so you can’t find it again later. Asset Loading/Error Tracking A big addition that Az pushed for was better tracking of the explicit load status of an asset. Before, you effectively had two states for an asset. It either loaded, or it didn’t. Obviously, depending on what’s going on, this could be wildly unhelpful. For example, if the asset definition loaded ok, but something was wrong with the associated file(like lets say the model file was corrupted), we can report the error while still continuing to load the asset, instead presenting a fallback shape. This avoids the old situation where if a model failed to load on a TSStatic, the TSStatic just wholesale doesn’t load, disappearing from your level and some point later down the line you realize a bunch of your objects are missing. Beyond that, naturally having more specific loading error codes lets you pin down exactly what’s wrong. From bad file references, to invalid formats, being able to note the problem is the first step to fixing it, which can save on development time without the engine just throwing its hands up and failing to load everything. Modules Also important to assets are the modules as well. Per previous updates, we’ve been refining how modules handle loading of scripts that may overlap or override via the new queueExec() function. We’ve been shoring up problems that’d come up and dialing it in more, but overall modules haven’t seen any big functionality shifts, which is good, as that means even with people wrenching on it, it’s proven to be rather stable. Legacy Project Importer So this has been a big one. When we were implementing the conversion process of existing projects to work with 4.0(since obviously there’s a number of people with existing projects that would be keen just update update to latest to get the sweet new benefits) we originally tried a sort of “in line” import process. As part of the above mentioned asset macros, we would integrate in the original filename fields, that when a file is fed into, would run the Asset Importer in-place, get the results, and update the field definitions. And in a lot of cases, this worked! The problem was there was also a lot of cases where we started seeing some really weird and funky behavior, timing issues, order of operations shenanigans, and needing to close and open things multiple times to process everything. The sexy, seamless system unfortunately didn’t play out quite as smoothly as the theory indicated. So, I opted to pivot on it and build it as a dedicated importer process. There were several advantages to this approach. Sure, we lost the ‘pure automagic’ of the inline approach, but you could start up a new 4.0 project, open the importer, point at your old game, and it’d just import everything in and process it into a valid module for you. So maybe not PURE automagic, but like, 56.5% automagic. So I did the initial pass of it and with Az helping crunch a lot of the logical refinements, we got it to a spot where we could get others throwing their projects or old content kits at it. While there’s still some bugs to be fully chased down yet, the results are looking really good, and should make it MUCH less painful for anyone and everyone with an existing torque game(in theory all the way back to TGE games, though that’s not a dedicated focus at the moment) can get rolled over to 4.0 in an afternoon. Nice, How’s It Work? As mentioned, the process involves you pointing at your old torque project, and getting a nice, 4.0 compliant module in your new 4.0 project as a result. But lets get into the specifics of it. This: Is the Project Importer. It’s formatted as a classical wizard utility that walks one through the process in pretty plain terms. It’s pretty straightforward, but we can do a quick overview of how it works here. If you continue past the welcome page, you select what version you're importing from(though currently everything is pre-4.0, this gives us options to have by-version import rules for people upgrading from 4.0 to 4.1 and so on later) it goes on to prompt you about what content you’re going to process in with the importer, as well as it’s location, as seen here: Depending on which you pick dictates some supplemental behavior. As you can see your options are: A folder that is inside the current project already - useful for re-importing something if required A folder that is outside the current project. This is the normal option and would be what you pick to select your old project’s main directory The Core and Tools folder. This is primarily for getting the BaseGame template up to speed, but if you’ve got some custom tools or the like you’ve copied into place already, you could run that. Normally, this is where you would select the Outside folder option. Then you click the locate button, and navigate to your project you want to import. Once you’ve done that, you can continue. It’ll next prompt what the new module being created will be called. This is defaulted to whatever folder you pointed to’s name, but it can be named anything like ‘MyCoolGame’. Continuing on will see the files be copied into the new module, then processed. It’ll run all content files(shapes, images, etc) through the Asset Importer, and then it’ll process all editable/code files looking for legacy fields. If it finds one, it’ll then convert that field into the associated asset field, and find the new asset associated to the filepath it had originally. So you go from something like this: Bitmap = “./background.png”; To: bitmapAsset = “MyCoolGame:background_image”; It’ll only process lines and objects that actually can successfully convert, so if a file doesn’t import and generate an asset, the legacy line will stay as-is. You can re-run the importer again on the existing module if needbe, but tests have so far proven it’s generally a one-and-done process. Once the import process has completed, the only real thing left is to integrate in the scripts to the module. You’d crack open the module script file, and then slap in your exec calls for the client, server, etc in the appropriate place and there ya go. Your project’s pretty well converted up into a 4.0 module! In example, this we can see the FPSGameplay content imported in as a module now here: Naturally, we’ll be fully fleshing out the process and details of this in the documentation, but that’s the broadstrokes. Currently it’s part of the tools suite, but the main plan is it being part of the new project manager, so you can create a project, launch the LPI, and convert in an old project into the 4.0 one all in rapid succession in the PM. Terrain Improvements Up next, going to do a quick go-over on the terrain updates. No major overhauls here, but we did bring in Lukas’ improvements to the terrain material blending, as well as toggles for if it should utilize the height data in the terrain materials to blend materials or not, as seen here: This naturally allows not only nicer looking terrain materials blending, but the toggle gives more control if it fits your style. Additionally, Lukas did some nice wizardry to streamline the rendering of the terrain a lot, utilizing texture arrays to remove a LOT of the drawcalls and render overhead that came with the older way we rendered terrain. So looks better and runs better. Not too shabby TScript Conversion This one has been discussed for quite a while, but we went ahead and pulled the trigger on it a little while back. Namely, shifting of the default torquescript extension to ‘tscript’ from ‘cs’. A number of reasons for this, but the big ones being that a lot of tools treat cs as a C# extension, which can have some weird results when dealing with code IDEs. Another reason tying into that was the expansion of the cinterface and support for more script languages Lukas spearheaded, namely, C#. With that soon to become a fully viable possible route for people’s T3D games, dealing with confusing extension mix-n-matches - especially for project generation and asset/module installs not being able to tell a torquescript and a c# version apart - all lead to the lock-in on swapping the default extension. That said, we know people liked having that cs extension, so while the default may have changed, we added a new project setting for CMAKE as well as a global var that sets the extension used. So if you wanted to keep with cs, you can just change that in your project settings and presto! And to better facilitate the extension swapping, we went in and adjusted a number of filename handlings to check for both the default and the ‘other options’. Meaning that doing an exec() call can just take the path and name, leaving off the file extension and the engine’ll figure it out. Additionally, we added the isScriptFile() console function to specifically check valid script extensions if you're trying to test if a filename is a valid script file, rather than trying to bruteforce your way through with isFile(). Further, If you wanted to go ahead and use the tscript extension and were importing in an old project, fear not, because the Project Importer will check what the extension global variable is, and update any scripts to utilize that extension instead(including filename references in files, in cases where it doesn't just drop the extension as unneeded entirely); So while it is a change and I know some of you may not be fond of it, we’ve done everything possible to make it painless to swap, or just keep to the classical if you so desire. TorqueScript Interpreter Update While not merged in juuuuust yet, we’re in the final testing pass for some big updates to the TS Interpreter(lovingly dubbed as ‘TSNeo’). Hutch has been doing some solid work not only simplifying the interpreter, making it easier to maintain and expand, but also some biiiig performance gains. How big? Lets have a gander. He drafted up a number of tests to check perf of various things, from crunching math right in TS, to handling objects and variables, to invoking up into the engine via console methods. All metrics are for his i9-9900K @ 4.8ghz. All numbers are in milliseconds. So here’s stock: As you can see, some stuff isn’t too bad, but function calls in particular are really painful. Now let’s look at the TSNeo benchmarks: As we can see, biiiiiiig gains in basically everything except the string pressure tests. Some benchmarks in fact have an order of magnitude improvement. And even though the string pressure tests show a dip in performance, it’s pretty slight. So suffice it to say, big, big gains here. Beyond that, he fixed several bugs that were lurking around, in particular with the telnet debugger(which was causing some weird behaviors in Torsion), as well as added a pretty large list of unit tests. So any future changes to the interpreter can come easier because we can validate the crap out of it with standardized testing. Mind, part of the improvements do come with the consequence of some relatively minor changes to how the interpreter handles things. For example, you can’t have local variables be in the global scope anymore. Aka, if you have a %localVar it HAS to be within a function or namespace. The good news is, if something is unsupported full stop by the interpreter, it’ll error and provide a specific message including the offending file, so correcting these problems is pretty simple. I’m also planning to try and make the Project Importer catch as many of these as possible, so *in theory* it should be a seamless transition unless you got REAL freaky with your torquescript. And in the end, I think the performance gains are so good it’s worth it. Project Manager I’ve been working on this bit by bit for a while now, so most of you are aware that there’ll be a new PM for 4.0 to work with. I’ll be doing a follow-up workblog soon with more deets about it, but I had tossed it out to those on the discord for an initial peek and slap around and got a lot of excellent feedback on its shortcomings. So with all the real big changes out of the way and shifting into the polish-up phase for the 4.0 release, I can get some grind time on this very soon and get the first real Release Candidate build out to work in conjunction with the upcoming 4.0 RC. And Now for Some Other Bits So that’s it for the big standalone update bits, but there’s definitely a lot of other smaller things to note still, so lets go over some of those, shall we? Base UI Updates A number of small fixes went in, but an important one was further improvements and fixes to handling of the window state, like with borderless mode. These options should be quite robust and stable to work with. Multiplatform Fixes Ragora, Az, HiGuy, Hutch and TRON all got some really good grid time recently to fix a number of lingering issues for our non-windows platforms. Between a litany of file handling fixes, compiler shenanigans fixes(GCC, as ever, being very strict) and crash fixes, the multiplat situation is coming along nicely. Hutch even confirmed that 4.0’ll run on Apple’s new M1 chips, so we needn’t worry about people upgrading their machines causing problems on the mac side of things, which is exciting Library Updates As part of the chasedown for multiplat shenanigans, we’ve made sure that OpenAL, SDL and TinyXML are up to date as well. Zip Loading Mars helped a lot in ensuring that the zip handling was ironed out and brought back up to working. This means you can package your modules or games into zips again and it won’t get all freaky-deaky when trying to load or save stuff. This also opens up some options for the future as well, such as collapsing assets(and their associated files) into singular archive *.asset files or the like, keeping file counts down and potentially reduced disk footprints, which is pretty cool. So there we are! With all the big stuff settled, or in final testing for roll-in, the plan for the next month or so is bug chasing, polishing and shoring up the new Project Manager, and just generally refining 4.0 to make it locked and prepped. So keep your eyes out and ears to the ground for the RC builds so we can make sure 4.0 is good to go sooner rather than later, because we’ve all definitely been waiting long enough Until next time! -JeffR
  2. There is a difference between discussing what would make a game jam work, and ideas to improve it. Simply saying "game jams are stupid and never work" isn't useful or conducive to improving the conversation. In fact, there's a line in the CoC about leaving constructive criticism to improve the thing being discussed and not just saying it's bad. If you wanted to suggest 'Hey 48 hours is too short, a week would be better' or 'We need something to ensure people can focus on the GAME part of the game jam' then those help dial in on potential pratfalls. Your post, instead was "They're stupid because games take time" which isn't constructive at all. Thus, you received a warning. And to that end, I would remind you that you have run aground the rules several times prior. If all your going to post is negativity without any constructive feedback, you are free to not post at all. And if you continue to do so, then you will face more permanent moderation action.
  3. Yeah, the entire idea of a jam is that the restrictions require scoping to get it done in the timeframe. The reduced scope and restrictions means a) it's acceptable to take a weekend off whatever else is going on to just smash something out b) it's a good breather to shift off an ongoing project and c) getting something out and completed, even if it's an itty bitty babu game is always gratifying Plus, limitations is often the thing that yields the most creative results
  4. Oh, yeah fair enough then, I'd be fine with that.
  5. I'd say yes, but Marauder also asked me to be a judge if we end up doing it, so I think that'd automatically recuse me from competing
  6. Also for what it's worth, you can just add the extension to Torsion and it'll work fine. I still use it. I'm sorting out some bugs with a PR to make the generated torsion project file play nice, but in the meantime if you go Tools > Preferences and edit the Script Extensions field, you can add tscript in there. Then right click on the project in the explorer tree, open it's properties and add tscript to the 'script scanner extensions'(and if it hasn't already been swapped, change the main.cs to main.tscript) You do that, and torsion'll continue to work fine with the extension swap.
  7. JeffR


  8. JeffR


    Objects The most complex aspect of TorqueScript involves dealing with game objects. Much of your object creation will be performed in the World Editor, but you should still know how to manipulate objects at a script level. One thing to remember is that everything in TorqueScript is an object: players, vehicles, items, etc. Every object added in the level is saved to a mission file, which is written entirely in TorqueScript. This also means every game object is accessible from script. Syntax Even though objects are originally created in C++, they are exposed to script in a way that allows them to be declared using the following syntax: %objectID = new ObjectType(Name : CopySource, arg0, ..., argn) { <datablock = DatablockIdentifier;> [existing_field0 = InitialValue0;] ... [existing_fieldN = InitialValueN;] [dynamic_field0 = InitialValue0;] ... [dynamic_fieldN = InitialValueN;] }; %objectID Is the variable where the object’s handle will be stored. new Is a key word telling the engine to create an instance of the following ObjectType. ObjectType Is any class declared in the engine or in script that has been derived from SimObject or a subclass of SimObject. SimObject-derived objects are what we were calling “game world objects” above. Name (optional) Is any expression evaluating to a string, which will be used as the object’s name. CopySource (optional) The name of an object which is previously defined somewhere in script. Existing field values will be copied from CopySource to the new object being created. Any dynamic fields defined in CopySource will also be defined in the new object, and their values will be copied. Note: If CopySource is of a different ObjectType than the object being created, only CopySource’s dynamic fields will be copied. arg0, ..., argn (optional) Is a comma separated list of arguments to the class constructor (if it takes any). datablock Many objects (those derived from GameBase, or children of GameBase) require datablocks to initialize specific attributes of the new object. Datablocks are discussed below. existing_fieldN In addition to initializing values with a datablock, you may also initialize existing class members (fields) here. In order to modify a member of a C++-defined class, the member must be exposed to the Console. dynamic_fieldN Lastly, you may create new fields (which will exist only in Script) for your new object. These will show up as dynamic fields in the World Editor Inspector. The main object variants you can create are SimObjects without a datablock, and game objects which require a datablock. The most basic SimObject can be created in a single line of code: // Create a SimObject without any name, argument, or fields. $exampleSimObject = new SimObject(); The $exampleSimObject variable now has access to all the properties and functions of a basic SimObject. Usually, when you are creating a SimObject you will want custom fields to define features: // Create a SimObject with a custom field $exampleSimObject = new SimObject() { catchPhrase = "Hello world!"; }; As with the previous example, the above code creates a SimObject without a name which can be referenced by the global variable $exampleSimObject. This time, we have added a user defined field called “catchPhrase.” There is not a single stock Torque 3D object that has a field called “catchPhrase.” However, by adding this field to the SimObject it is now stored as long as that object exists. The other game object variant mentioned previously involves the usage of datablocks. Datablocks contain static information used by a game object with a similar purpose. Datablocks are transmitted from a server to client, which means they cannot be modified while the game is running. We will cover datablocks in more detail later, but the following syntax shows how to create a game object using a datablock: // create a StaticShape using a datablock datablock StaticShapeData(ceiling_fan) { category = "Misc"; shapeFile = "art/shapes/undercity/cfan.dts"; isInvincible = true; }; new StaticShape(CistFan) { dataBlock = "ceiling_fan"; position = "12.5693 35.5857 59.5747"; rotation = "1 0 0 0"; scale = "1 1 1"; }; Once you have learned about datablocks, the process is quite simple: Create a datablock in script, or using the datablock editor Add a shape to the scene from script or using the World Editor Assign the new object a datablock Handles vs Names Every game object added to a level can be accessed by two parameters: Handle A unique numeric ID generated when the object is created Name This is an optional parameter given to an object when it is created. You can assign a name to an object from the World Editor, or do so in TorqueScript. Example: // In this example, CistFan is the name of the object new StaticShape(CistFan) { dataBlock = "ceiling_fan"; position = "12.5693 35.5857 59.5747"; rotation = "1 0 0 0"; scale = "1 1 1"; }; While in the World Editor, you will not be allowed to assign the same name to multiple, separate objects. The editor will ignore the attempt. If you manually name two objects the same thing in script, the game will only load the first object and ignore the second. Singletons If you need a global script object with only a single instance, you can use the singleton keyword. Singletons, in TorqueScript, are mostly used for unique shaders, materials, and other client-side only objects. For example, SSAO (screen space ambient occlusion) is a post-processing effect. The game will only ever need a single instance of the shader, but it needs to be globally accessible on the client. The declaration of the SSAO shader in TorqueScript can be shown below: singleton ShaderData( SSAOShader ) { DXVertexShaderFile = "shaders/common/postFx/postFxV.hlsl"; DXPixelShaderFile = "shaders/common/postFx/ssao/SSAO_P.hlsl"; pixVersion = 3.0; }; Fields Objects instantiated via script may have data members, referred to as Fields. Methods In addition to the creation of stand-alone functions, TorqueScript allows you to create and call methods attached to objects. Some of the more important Console Methods are already written in C++, then exposed to script. You can call these methods by using the dot . notation: objHandle.function_name(); objName.function_name(); Example: new StaticShape(CistFan) { dataBlock = "ceiling_fan"; position = "12.5693 35.5857 59.5747"; rotation = "1 0 0 0"; scale = "1 1 1"; }; // Write all the objects methods to the console log CistFan.dump(); // Get the ID of an object, using the object's name $objID = CistFan.getID(); // Print the ID to the console echo("Object ID: ", $objID); // Get the object's position, using the object's handle %position = $objID.getPosition(); // Print the position to the console echo("Object Position: ", %position); The above example shows how you can call an object’s method by using its name or a variable containing its handle (unique ID number). Additionally, TorqueScript supports the creation of methods that have no associated C++ counterpart: // function - Is a keyword telling TorqueScript we are defining a new function. // ClassName::- Is the class type this function is supposed to work with. // function_name - Is the name of the function we are creating. // ... - Is any number of additional arguments. // statements - Your custom logic executed when function is called // %this- Is a variable that will contain the handle of the 'calling object'. // return val - The value the function will give back after it has completed. Optional. function Classname::func_name(%this, [arg0],...,[argn]) { statements; [return val;] } At a minimum, Console Methods require that you pass them an object handle. You will often see the first argument named %this. People use this as a hint, but you can name it anything you want. As with Console Functions any number of additional arguments can be specified separated by commas. As a simple example, let’s say there is an object called Samurai, derived from the Player class. It is likely that a specific appearance and play style will be given to the samurai, so custom ConsoleMethods can be written. Here is a sample: function Samurai::sheatheSword(%this) { echo("Katana sheathed"); } When you add a Samurai object to your level via the World Editor, it will be given an ID. Let’s pretend the handle (ID number) is 1042. We can call its ConsoleMethod once it is defined, using the period syntax: 1042.sheatheSword(); OUTPUT: "Katana sheathed" Notice that no parameters were passed into the function. The %this parameter is inherent, and the original function did not require any other parameters.
  9. JeffR


    Functions Much of your TorqueScript experience will come down to calling existing Console Functions and writing your own. Functions are a blocks of code that only execute when you call them by name. Basic functions in TorqueScript are defined as follows: // function - Is a keyword telling TorqueScript we are defining a new function. // function_name - Is the name of the function we are creating. // ... - Is any number of additional arguments. // statements - Your custom logic executed when function is called // return val - The value the function will give back after it has completed. Optional. function function_name([arg0],...,[argn]) { statements; [return val;] } The function keyword, like other TorqueScript keywords, is case sensitive. You must type it exactly as shown above. The following is an example of a custom function that takes in two parameters, then executes code based on those arguments. TorqueScript can take any number of arguments, as long as they are comma separated. If you call a function and pass fewer parameters than the function’s definition specifies, the un-passed parameters will be given an empty string as their default value: function echoRepeat (%echoString, %repeatCount) { for (%count = 0; %count < %repeatCount; %count++) { echo(%echoString); } } You can cause this function to execute by calling it in the console, or in another function: echoRepeat("hello!", 5); OUTPUT: "hello!" "hello!" "hello!" "hello!" "hello!" If you define a function and give it the same name as a previously defined function, TorqueScript will completely override the old function. This still applies even if you change the number of parameters used; the older function will still be overridden. Torque 3D also contain Console Functions written in C++, then exposed to TorqueScript. These are global functions you can call at any time, and are usually very helpful or important. E.g. throughout this document, we have been using the Console Function echo(...)
  10. Control Structures TorqueScript provides basic branching structures that will be familiar to programmers that have used other languages. If you are completely new to programming, you use branching structures to control your game’s flow and logic. This section builds on everything you have learned about TorqueScript so far. if, else This type of structure is used to test a condition, then perform certain actions if the condition passes or fails. You do not always have to use the full structure, but the following syntax shows the extent of the conditional: if(<boolean expression>) { pass logic } else { alternative logic } Remember how boolean values work? Essentially, a bool can either be true (1) or false (0). The condition (boolean) is always typed into the parenthesis after the “if” syntax. Your logic will be typed within the brackets {}. The following example uses specific variable names and conditions to show how this can be used: // Global variable that controls lighting $lightsShouldBeOn = true; // Check to see if lights should be on or off if($lightsShouldBeOn) { // True. Call turn on lights function turnOnLights(); echo("Lights have been turned on"); } else { // False. Turn off the lights turnOffLights(); echo("Lights have been turned off"); } Brackets for single line statements are optional. If you are thinking about adding additional logic to the code, then you should use the brackets anyway. If you know you will only use one logic statement, you can use the following syntax: // Global variable that controls lighting $lightsShouldBeOn = true; // Check to see if lights should be on or off if($lightsShouldBeOn) turnOnLights(); // True. Call turn on lights function else turnOffLights(); // False. Turn off the lights switch, switch$ If your code is using several cascading if-then-else statements based on a single value, you might want to use a switch statement instead. Switch statements are easier to manage and read. There are two types of switch statements, based on data type: numeric (switch) and string (switch$ switch(<numeric expression>) { case value0: statements; case value1: statements; case value3: statements; default: statements; } As the above code demonstrates, start by declaring the switch statement by passing in a value to the switch(...) line. Inside of the brackets {}, you will list out all the possible cases that will execute based on what value being tested. It is wise to always use the default case, anticipating rogue values being passed in: switch($ammoCount) { case 0: echo("Out of ammo, time to reload"); reloadWeapon(); case 1: echo("Almost out of ammo, warn user"); lowAmmoWarning(); case 100: echo("Full ammo count"); playFullAmmoSound(); default: doNothing(); } switch only properly evaluates numerical values. If you need a switch statement to handle a string value, you will want to use switch$. The switch$ syntax is similar to what you just learned: switch$ (<string expression>) { case "string value 0": statements; case "string value 1": statements; case "string value N": statements; default: statements; } Appending the $ sign to switch will immediately cause the parameter passed in to be parsed as a string. The following code applies this logic: // Print out specialties switch($userName) { case "Heather": echo("Sniper"); case "Nikki": echo("Demolition"); case Mich: echo("Meat shield"); default: echo("Unknown user"); } for As the name implies, this structure type is used to repeat logic in a loop based on an expression. The expression is usually a set of variables that increase by count, or a constant variable changed once a loop has hit a specific point: for(expression0; expression1; expression2) { statement(s); } One way to label the expressions in this syntax are (startExpression; testExpression; countExpression). Each expression is separated by a semi-colon: for(%count = 0; %count < 3; %count++) { echo(%count); } OUTPUT: 0 1 2 The first expression creates the local variable %count and initializing it to 0. In the second expression determines when to stop looping, which is when the %count is no longer less than 3. Finally, the third expression increases the count the loop relies on. foreach Simplify the iteration over sets of objects and string vectors. To loop over each object in a SimSet, use the foreach statement: foreach( %obj in %set ) /* do something with %obj */; To loop over each element in a string vector, use the foreach$ statement: foreach$( %str in "a b c" ) /* do something with %str */; while A while loop is a much simpler looping structure compared to a for loop. while(expression) { statements; } As soon as the expression is met, the while loop will terminate: %countLimit = 0; while(%countLimit <= 5) { echo("Still in loop"); %count++; } echo("Loop was terminated");
  11. JeffR


    Operators Operators in TorqueScript behave very similarly to operators in real world math and other programming languages. You should recognize quite a few of these from math classes you took in school, but with small syntactical changes. The rest of this section will explain the syntax and show a brief example, but we will cover these in depth in later guides. Arithmetic Operators These are your basic math ops. Operator Name Example Explanation * multiplication $a * $b Multiply $a and $b. / division $a / $b Divide $a by $b. % modulo $a % $b Remainder of $a divided by $b. + addition $a + $b Add $a and $b. - subtraction $a - $b Subtract $b from $a. ++ auto-increment (post-fix only) $a++ Increment $a. -- auto-decrement (post-fix only) $b-- Decrement $b. Relational Operators Used in comparing values and variables against each other. Operator Name Example Explanation < Less than $a < $b 1 if $a is less than $b > More than $a > $b 1 if $a is greater than $b <= Less than or Equal to $a <= $b 1 if $a is less than or equal to $b >= More than or Equal to $a >= $b 1 if $a is greater than or equal to $b == Equal to $a == $b 1 if $a is equal to $b != Not equal to $a != $b 1 if $a is not equal to $b ! Logical NOT !$a 1 if $a is 0 && Logical AND $a && $b 1 if $a and $b are both non-zero || Logical OR $a || $b 1 if either $a or $b is non-zero $= String equal to $c $= $d 1 if $c equal to $d. !$= String not equal to $c !$= $d 1 if $c not equal to $d. Bitwise Operators Used for comparing and shifting bits. Operator Name Example Explanation ~ Bitwise complement ~$a flip bits 1 to 0 and 0 to 1 & Bitwise AND $a & $b composite of elements where bits in same position are 1 | Bitwise OR $a | $b composite of elements where bits 1 in either of the two elements ^ Bitwise XOR $a ^ $b composite of elements where bits in same position are opposite << Left Shift $a << 3 element shifted left by 3 and padded with zeros >> Right Shift $a >> 3 element shifted right by 3 and padded with zeros Assignment Operators Used for setting the value of variables. Operator Name Example Explanation = Assignment $a = $b; Assign value of $b to $a op= Assignment Operators $a op= $b; Equivalent to $a = $a op $b, where op can be any of: * / % + - & | ^ << >> String Operators There are special values you can use to concatenate strings and variables. Concatenation refers to the joining of multiple values into a single variable. The following is the basic syntax: "string 1" operation "string 2" You can use string operators similarly to how you use mathematical operators (=, +, -, *). You have four operators at your disposal: Operator Name Example Explanation @ String concatenation $c @ $d Concatenates strings $c and $d into a single string. Numeric literals/variables convert to strings. NL New Line $c NL $d Concatenates strings $c and $d into a single string separated by new-line. Such a string can be decomposed with getRecord() TAB Tab $c TAB $d Concatenates strings $c and $d into a single string separated by tab. Such a string can be decomposed with getField() SPC Space $c SPC $d Concatenates strings $c and $d into a single string separated by space. Such a string can be decomposed with getWord() Miscellaneous Operators General programming operators. Operator Name Example Explanation ? : Conditional x ? y : z Evaluates to y if x equal to 1, else evaluates to z [] Array element $a[5] Synonymous with $a5 ( ) Delimiting, Grouping t2dGetMin(%a, %b) if ( $a == $b ) ($a+$b)*($c-$d) Argument list for function call Used with if, for, while, switch keywords Control associativity in expressions {} Compound statement if (1) {$a = 1; $b = 2;} function foo() {$a = 1;} Delimit multiple statements, optional for if, else, for, while Required for switch, datablock, new, function , Listing t2dGetMin(%a, %b) %M[1,2] Delimiter for arguments :: Namespace Item::onCollision() This definition of the onCollision() function is in the Item namespace . Field/Method selection %obj.field %obj.method() Select a console method or field // Single-line comment // This is a comment Used to comment out a single line of code /* */ Multi-line comment /*This is a a multi-line comment*/ Used to comment out multiple consecutive lines /* opens the comment, and */ closes it
  12. JeffR


    Types TorqueScript implicitly supports several variable data-types: numbers, strings, booleans, arrays and vectors. If you wish to test the various data types, you can use the echo(...) command. For example: %meaningOfLife = 42; echo(%meaningOfLife); $name = "Heather"; echo($name); The echo will post the results in the console, which can be accessed by pressing the tilde key ~ while in game. Numbers TorqueScript handles standard numeric types: 123 (Integer) 1.234 (floating point) 1234e-3 (scientific notation) 0xc001 (hexadecimal) Strings Text, such as names or phrases, are supported as strings. Numbers can also be stored in string format. Standard strings are stored in double-quotes: "abcd" (string) Example: $UserName = "Heather"; Strings with single quotes are called “tagged strings”: 'abcd' (tagged string) Tagged strings are special in that they contain string data, but also have a special numeric tag associated with them. Tagged strings are used for sending string data across a network. The value of a tagged string is only sent once, regardless of how many times you actually do the sending. On subsequent sends, only the tag value is sent. Tagged values must be de-tagged when printing. You will not need to use a tagged string often unless you are in need of sending strings across a network often, like a chat system: $a = 'This is a tagged string'; echo(" Tagged string: ", $a); echo("Detagged string: ", detag($a)); The output will be similar to this: Tagged string: 24 Detagged string: The second echo will be blank unless the string has been passed to you over a network. Booleans Like most programming languages, TorqueScript also supports booleans. Boolean numbers have only two values- true or false: true (1) false (0) Again, as in many programming languages the constant “true” evaluates to the number 1 in TorqueScript, and the constant “false” evaluates to the number zero. However, non-zero values are also considered true. Think of booleans as “on/off” switches, often used in conditional statements: $lightsOn = true; if($lightsOn) echo("Lights are turned on"); Arrays Arrays are data structures used to store consecutive values of the same data type: $TestArray[n] (Single-dimension) $TestArray[m,n] (Multidimensional) $TestArray[m_n] (Multidimensional) If you have a list of similar variables you wish to store together, try using an array to save time and create cleaner code. The syntax displayed above uses the letters n and m to represent where you will input the number of elements in an array. The following example shows code that could benefit from an array: $firstUser = "Heather"; $secondUser = "Nikki"; $thirdUser = "Mich"; echo($firstUser); echo($secondUser); echo($thirdUser); Instead of using a global variable for each user name, we can put those values into a single array: $userNames[0] = "Heather"; $userNames[1] = "Nikki"; $userNames[2] = "Mich"; echo($userNames[0]); echo($userNames[1]); echo($userNames[2]); Now, let’s break the code down. Like any other variable declaration, you can create an array by giving it a name and value: $userNames[0] = "Heather"; What separates an array declaration from a standard variable is the use of brackets []. The number you put between the brackets is called the index. The index will access a specific element in an array, allowing you to view or manipulate the data. All the array values are stored in consecutive order. If you were able to see an array on paper, it would look something like this: [0] [1] [2] In our example, the data looks like this: ["Heather"] ["Nikki"] ["Mich"] Like other programming languages, the index is always a numerical value and the starting index is always 0. Just remember, index 0 is always the first element in an array. As you can see in the above example, we create the array by assigning the first index (0) a string value (“Heather”). The next two lines continue filling out the array, progressing through the index consecutively: $userNames[1] = "Nikki"; $userNames[2] = "Mich"; The second array element (index 1) is assigned a different string value (“Nikki”), as is the third (index 2). At this point, we still have a single array structure, but it is holding three separate values we can access. Excellent for organization. The last section of code shows how you can access the data that has been stored in the array. Again, you use a numerical index to point to an element in the array. If you want to access the first element, use 0: echo($userNames[0]); In a later section, you will learn about looping structures that make using arrays a lot simpler. Before moving on, you should know that an array does not have to be a single, ordered list. TorqueScript also support multidimensional arrays. An single-dimensional array contains a single row of values. A multidimensional array is essentially an array of arrays, which introduces columns as well. The following is a visual of what a multidimensional looks like with three rows and three columns: [x] [x] [x] [x] [x] [x] [x] [x] [x] Defining this kind of array in TorqueScript is simple. The following creates an array with 3 rows and 3 columns: $testArray[0,0] = "a"; $testArray[0,1] = "b"; $testArray[0,2] = "c"; $testArray[1,0] = "d"; $testArray[1,1] = "e"; $testArray[1,2] = "f"; $testArray[2,0] = "g"; $testArray[2,1] = "h"; $testArray[2,2] = "i"; Notice that we are are now using two indices, both starting at 0 and stopping at 2. We can use these as coordinates to determine which array element we are accessing: [0,0] [0,1] [0,2] [1,0] [1,1] [1,2] [2,0] [2,1] [2,2] In our example, which progresses through the alphabet, you can visualize the data in the same way: [a] [b] [c] [d] [e] [f] [g] [h] [i] The first element [0,0] points to the letter ‘a’. The last element [2,2] points to the letter ‘i’. Vectors Vectors are a helpful data-type which are used throughout Torque 3D. For example, many fields in the World Editor take numeric values in sets of 3 or 4. These are stored as strings and interpreted as “vectors”: "1.0 1.0 1.0" (3 element vector) The most common example of a vector would be a world position. Like most 3D coordinate systems, an object’s position is stored as (X Y Z). You can use a three element vector to hold this data: %position = "25.0 32 42.5"; You can separate the values using spaces or tabs (both are acceptable whitespace). Another example is storing color data in a four element vector. The values that make up a color are “Red Blue Green Alpha,” which are all numbers. You can create a vector for color using hard numbers, or variables: %firstColor = "100 100 100 255"; echo(%firstColor); %red = 128; %blue = 255; %green = 64; %alpha = 255; %secondColor = %red SPC %blue SPC %green SPC %alpha; echo(%secondColor);
  13. JeffR


    Variables A variable is a letter, word, or phrase linked to a value stored in your game’s memory and used during operations. Creating a variable is a one line process. The following code creates a variable by naming it and assigning a value: %localVariable = 3; You can assign any type value to the variable you want. This is referred to as a language being type-insensitive. TorqueScript does not care (insensitive) what you put in a variable, even after you have created it. The following code is completely valid: %localVariable = 27; %localVariable = "Heather"; %localVariable = "7 7 7"; The main purpose of the code is to show that TorqueScript treats all data types the same way. It will interpret and convert the values internally, so you do not have to worry about typecasting. That may seem a little confusing. After all, when would you want a variable that can store a number, a string, or a vector? You will rarely need to, which is why you want to start practicing good programming habits. An important practice is proper variable naming. The following code will make a lot more sense, considering how the variables are named: %userName = "Heather"; %userAge = 27; %userScores = "7 7 7"; TorqueScript is more forgiving than low level programming languages. While it expects you to obey the basic syntax rules, it will allow you to get away with small mistakes or inconsistency. The best example is variable case sensitivity. With variables, TorqueScript is not case sensitive. You can create a variable and refer to it during operations without adhering to case rules: %userName = "Heather"; echo(%Username); In the above code, %userName and %Username are the same variable, even though they are using different capitalization. You should still try to remain consistent in your variable naming and usage, but you will not be punished if you slip up occasionally. There are two types of variables you can declare and use in TorqueScript: local and global. Both are created and referenced similarly: %localVariable = 1; $globalVariable = 2; As you can see, local variable names are preceded by the percent sign %. Global variables are preceded by the dollar sign $. Both types can be used in the same manner: operations, functions, equations, etc. The main difference has to do with how they are scoped. In programming, scoping refers to where in memory a variable exists during its life. A local variable is meant to only exist in specific blocks of code, and its value is discarded when you leave that block. Global variables are meant to exist and hold their value during your entire programs execution. Look at the following code to see an example of a local variable: function test() { %userName = "Heather"; echo(%userName); } We will cover functions a little later, but you should know that functions are blocks of code that only execute when you call them by name. This means the variable, %userName, does not exist until the test() function is called. When the function has finished all of its logic, the %userName variable will no longer exist. If you were to try to access the %userName variable outside of the function, you will get nothing. Most variables you will work with are local, but you will eventually want a variables that last for your entire game. These are extremely important values used throughout the project. This is when global variables become useful. For the most part, you can declare global variables whenever you want: $PlayerName = "Heather"; function printPlayerName() { echo($PlayerName); } function setPlayerName() { $PlayerName = "Nikki"; } The above code makes full use of a global variable that holds a player’s name. The first declaration of the variable happens outside of the functions, written anywhere in your script. Because it is global, you can reference it in other locations, including separate script files. Once declared, your game will hold on to the variable until shutdown.
  • Create New...