Introducing AGS 3.4.0 (read-only explanation topic)

Started by Crimson Wizard, Fri 31/07/2015 11:51:15

Previous topic - Next topic

Crimson Wizard

AGS 3.4.0
Introductory topic



AGS 3.4.0 is the next version of Adventure Game Studio Editor and Engine, being in development since mid-2014. Currently it is in relatively stable stage (you actually can make games with it), but its main planned features are not yet fully implemented, therefore we call it Alpha version.

As previously, we try to keep backwards compatibility with previous versions, meaning that:
1. You should be able to load your game project made with previous versions of AGS Editor, and continue to work on it, possibly with only minor changes to script (or enabling compatibility mode in General Settings).
2. You should be able to run your previously compiled game with new 3.4.0 engine without recompilation.

This thread is meant to serve for preliminary acquaintance with the new version.


The notable features of AGS 3.4.0 are:

* Custom resolutions. Since AGS 3.4.0 you will be able to create games with any size, and run it in any display resolution using existing scaling filters.
* Extended build system with support to build game for multiple platforms. AGS 3.4.0 supports extensions that let it compile games for additional platforms (e.g. Linux). They are optional, and require installing extra files to AGS folder, as well as enable them in project's General Settings.
* Custom properties are now mutable. Custom properties may now be changed at runtime.
* Extended AGS Script. A number of new commands and constructions were added to AGS script language, such as user-defined managed structs, "for" and "switch" blocks. Few existing features were improved, for example now it is possible to define static extender functions and have dynamic arrays in structs.
* Extended Dialog Options customization. You may now easily create custom rendered dialog options that animate and react to key presses.

TO BE UPDATED

Crimson Wizard

#1
Custom Resolutions

AGS 3.4.0 lets you choose literally any resolution (width and height) for your game. It does not mean, that every variant will work well, but reasonable ones should.
There is no restriction to width/height relation, you could even make "portrait orientation" games with height greater than width.

An example of 180x400 game made in AGS (mockup scenery used)
[imgzoom]http://i.imgur.com/EWJ91vX.png[/imgzoom]

Inside your project, resolution is now set not with drop-down list, but with a slightly more advanced dialog:



Select "Resolution" option and click on "..." button



In the dialog either choose one of the presets, or type in your own width & height, then press OK.


Free display resolutions

To accompany custom game resolutions, AGS engine is now capable to run the game in any display mode supported by your monitor and graphics card.

The WinSetup utility was modified with different graphics options added. At the moment we are in the process of testing this design out. It may be simplified in future updates to make it easier for players to quickly set it up the way they want.



Currently you may choose the fullscreen display mode by setting following parameters:
* Mode: this determines the screen size your monitor will set to run the game;
* Filter: this determines which algorithm will be used to scale things up or down;
* Scaling (multiplier): this determines how much the game will be scaled up or down inside the screen;

For better results I recommend using following combinations:
1. If you feel selecting exact resolution more suitable for you, then choose wanted Mode and set scaling multiplier to "Max fit". This will scale the game by the maximal integer multiplier that fits in the Mode.
2. If it is rather convenient for you to select scaling (e.g. if you wish exactly x2 or x3 size of the game), then choose wanted scaling multiplier, and set "Bind to game scaling (force desktop ratio)" in the Modes list.
"Force desktop ratio" means that the game will refer to your desktop aspect ratio to choose gfx mode that looks better on your monitor.


With some of the display modes your game won't take all screen space if scaled up by an integer multiplier. In such case the black borders will appear around the game to fill the extra space. If you do not like to have these borders, you may enable couple of extra options on "Advanced" panel:



* "Stretch to fit screen" will make your game stretch more until it fills whole screen.
* "Keep aspect ratio" will ensure that the game's aspect ratio will be kept when stretching, so that image will still look proportional.
Latter option may cause some of the black borders to remain.

On thing to remember when using "stretch" options: the random stretching of the low-resolution game may cause image look slightly skewed (the higher resolution is, the less noticeable this effect will be).


On letterboxed games

AGS supports making your game letterboxed (I call it "letterbox-by-design"), implicitly increasing its height and automatically adding black borders to all rooms at runtime. This mode is enabled by setting "(Setup) -> Enable letterbox mode" property in General Settings.
This property had always had a limited functionality and worked only for 320x200 and 640x400 games. It was kind of a hack really, therefore we do not extend it on custom resolutions. Currently it remains only for backwards compatibility (and still works only with two mentioned resolutions).

In the future we may add better support for automatic letterboxing (if there would be demand for it); for now I recommend making explicit black borders in your game rooms and modify GUI appropriately instead, if you wish to achieve this effect for other resolutions.


On High and Low resolution type

In older versions of AGS there was a distinction between "High" and "Low" resolutions. 320x200 and 320x240 were considered "low", 640x400, 640x480, 800x600 and 1024x768 were considered "high" ones.
Such project items as Sprites, Fonts and Room backgrounds depended on this algorithm to know whether they should be automatically scaled.

For example, Sprites have "Resolution" property which let you set whether they were created for "High" or "Low" one. If the Sprite's resolution type does not match the game's, the sprite will be scaled up or down, respectively.
Similarly, there is a global "Text output -> Fonts designed for 640x480" setting in game project. Normally, for the High-res game the fonts will be scaled up. Enabling this option will stop them being stretched.

To support backwards-compatibility, following rule is applied for aforementioned properties:
* The resolution which has width equal or lower than 320 AND height equal or lower than 240 is considered to be "low-res".
* The resolution which has any of the sides higher; e.g width higher than 320 OR height higher than 240 - is considered to be "hi-res".

As you may realize, this distinction is pretty forced. I should underline, that it exists only to support old behavior for standard resolutions. It is possible that in future we will change how these properties work in the new game projects.




TO BE CONTINUED...

Crimson Wizard

#2
Building for multiple platforms

AGS 3.4.0 can build your game for more than one operating system. This is configured with this new option in General Settings (Compiler -> Build target platforms):



Select the systems you like to build the game for by pressing on "drop down" button to the right and checking or unchecking items in the list:



The contents of this list depends on what Editor components are installed. Editor has Windows build component by default, but you may also install Linux component to create Linux versions of your game.
"DataFile" option cannot be unchecked. It builds main game data file, which is used when making game for other systems.
Other available build targets may be selected in any combination.

This selection only affects doing full build (Build -> Build EXE, or Build -> Rebuild all files). When running in test mode only Windows version is built always.


The "Compiled" folder has changed. Now each build target has its own subdirectory: "Windows", "Linux", etc. To distribute your game - pack/copy contents of corresponding folder(s).

Example of "Compiled" folder contents:


Example of "Compiled/Windows" folder contents




Any extra files put into Compiled folder will be copied to every build subfolder, when the game is compiled. This way you may distribute any additional files your game needs.

You may also use the contents of "Compiled" folder itself (without subdirectories) to distribute game data files alone, without game executable. Then you may use stand-alone AGS engine on any system to run this game data.


This is important to mention, that when you run setup program from the Editor (Build -> Run game setup...), you will be modifying Windows config only. Same config will be used when you are test-running your game from the Editor (Build -> Run).
For other build configs (Linux, etc) you will have to modify acsetup.cfg file yourself right now (use plain text editor for that).


NOTE: for now you may still use old building system if you go to Editor's Preferences (File -> Preferences) and check "Use legacy compiler" option. As soon as we make sure that the new building system works well, this check will be removed forever though.



TO BE CONTINUED...

Crimson Wizard

#3
Dynamic Custom Properties

Custom properties were convenient way to bind additional descriptions to game objects, such as Characters, Room Objects and Inventory Items, which may then be used in script. Unfortunately, they had two limitations: there could only be 30 of them for the whole project, and they could not be changed at runtime.

AGS 3.4.0 removes both of these restrictions. This lets you to extend named object types with any number of extra attributes.

Changed system limits

ItemOld limitNew limit
Custom properties30unlimited
Custom property name lengths20unlimited
Custom property value lengths (for strings)500unlimited

You may now change property value at runtime from script. The related script functions are added to all classes that already supported GetProperty() and GetTextProperty(), that is - Room, InventoryItem, Hotspot, RoomObject and Character:
Code: ags

  /// Sets an integer (or boolean) custom property.
  static bool SetProperty(const string property, int value);
  /// Sets a text custom property.
  static bool SetTextProperty(const string property, const string value);

They return 'true' if the property value was changed, or 'false' if such property does not exist, or property type is incorrect (like setting text value for integer property).


The values set this way will be kept for the duration of the game, unless modified again.
Also, Room, Hotspot and RoomObject property values are reset to defaults when ResetRoom() is called.




TO BE CONTINUED...

Crimson Wizard

#4
Improved Custom Dialog Options rendering

Custom Dialog Options rendering system was extended to give game developers more freedom in setting it up. In the original variant the callback behavior was strictly tied to mouse movement and clicks. It now supports wider range of callbacks, covering all kinds of events that may occur when Dialog Option GUI is on screen.

To achieve this following changes were implemented.

1. Support for two more callbacks added:
Code: ags

  // runs each tick when dialog options are on screen
  void dialog_options_repexec(DialogOptionsRenderingInfo *info);
  // runs when user pressed a key while dialog options are on screen
  void dialog_options_key_press(DialogOptionsRenderingInfo *info, eKeyCode key);


The "dialog_options_get_active" callback is now NOT called, at all. It is supported only for backwards compatibility when running old games.
You will need to slightly change the logic of your script. In most cases it will be enough to simply rename "dialog_options_get_active" to "dialog_options_repexec".

2. Custom dialog options are no longer have automated reaction on mouse click and movement. Instead, following functions are added to the DialogOptionsRenderingInfo struct:
Code: ags

  bool RunActiveOption(); // runs the active dialog option (defined with ActiveOptionID property)
  void Update(); // forces dialog options to redraw itself ("dialog_options_render" callback will be called)


The "dialog_options_mouse_click" is now called always, even if user clicks on the option. In the most common case you should just call RunActiveOption() from there.



In short, scripting custom dialog options requires a bit more work on gamedev part, but is much more versatile now.

To prove this point, two basic examples follow.



Example A. Classic mouse controls
Spoiler

Code: ags

int dlg_opt_color = 14;
int dlg_opt_acolor = 13;
int dlg_opt_ncolor = 4;

function dialog_options_get_dimensions(DialogOptionsRenderingInfo *info)
{
    // Create a 200x200 dialog options area at (50,100)
    info.X = 50;
    info.Y = 100;
    info.Width = 200;
    info.Height = 200;
}

function dialog_options_render(DialogOptionsRenderingInfo *info)
{
    info.Surface.Clear(dlg_opt_color);
    int i = 1,  ypos = 0;
    // Render all the options that are enabled
    while (i <= info.DialogToRender.OptionCount)
    {
        if (info.DialogToRender.GetOptionState(i) == eOptionOn)
        {
            if (info.ActiveOptionID == i)
                info.Surface.DrawingColor = dlg_opt_acolor;
            else
                info.Surface.DrawingColor = dlg_opt_ncolor;
            info.Surface.DrawStringWrapped(5, ypos, info.Width - 10, 
                    eFontFont0, eAlignLeft, info.DialogToRender.GetOptionText(i));
            ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontFont0, info.Width - 10);
        }
        i++;
    }
}

function dialog_options_repexec(DialogOptionsRenderingInfo *info)
{
    info.ActiveOptionID = 0;
    if (mouse.y < info.Y || mouse.y >= info.Y + info.Height ||
        mouse.x < info.X || mouse.x >= info.X + info.Width)
    {
        return; // return if the mouse is outside UI bounds
    }

    int i = 1, ypos = 0;
    // Find the option that corresponds to where the player clicked
    while (i <= info.DialogToRender.OptionCount)
    {
        if (info.DialogToRender.GetOptionState(i) == eOptionOn)
        {
            ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontFont0, info.Width - 10);
            if ((mouse.y - info.Y) < ypos)
            {
                info.ActiveOptionID = i;
                return;
            }
        }
        i++;
    }
}

function dialog_options_mouse_click(DialogOptionsRenderingInfo *info, MouseButton button)
{
    if (info.ActiveOptionID > 0)
        info.RunActiveOption();
}

[close]



Example B. Keyboard controls
Spoiler

Code: ags

int dlg_opt_color = 14;
int dlg_opt_acolor = 13;
int dlg_opt_ncolor = 4;

function dialog_options_get_dimensions(DialogOptionsRenderingInfo *info)
{
    // Create a 200x200 dialog options area at (50,100)
    info.X = 50;
    info.Y = 100;
    info.Width = 200;
    info.Height = 200;
    info.ActiveOptionID = 1; // set to first option
}

function dialog_options_render(DialogOptionsRenderingInfo *info)
{
    info.Surface.Clear(dlg_opt_color);
    int i = 1,  ypos = 0;
    // Render all the options that are enabled
    while (i <= info.DialogToRender.OptionCount)
    {
        if (info.DialogToRender.GetOptionState(i) == eOptionOn)
        {
            if (info.ActiveOptionID == i)
                info.Surface.DrawingColor = dlg_opt_acolor;
            else
                info.Surface.DrawingColor = dlg_opt_ncolor;
            info.Surface.DrawStringWrapped(5, ypos, info.Width - 10, 
                    eFontFont0, eAlignLeft, info.DialogToRender.GetOptionText(i));
            ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontFont0, info.Width - 10);
        }
        i++;
    }
}

function dialog_options_key_press(DialogOptionsRenderingInfo *info, eKeyCode keycode) 
{
    if (keycode == eKeyUpArrow && info.ActiveOptionID > 1)
        info.ActiveOptionID = info.ActiveOptionID - 1;
    if (keycode == eKeyDownArrow && info.ActiveOptionID < info.DialogToRender.OptionCount)
        info.ActiveOptionID = info.ActiveOptionID + 1;
    if (keycode == eKeyReturn || keycode == eKeySpace)
        info.RunActiveOption();
}

[close]


TO BE CONTINUED...

Crimson Wizard

Scripting improvements

In version 3.4.0 AGS scripting language is enriched with new commands and constructs.



Do...While loops

In addition to the previously existing "while" loop, the "do...while" loop construct is now supported. Unlike "while", "do...while" runs the first iteration *before* evaluating the condition.

Example:
Spoiler

Code: ags

x = 1;
do
{
    x++;
    Display("%d", x);
} while(x < 1);


The loop above will run once.
[close]



For loop

In addition to a "while" loop, you can now use a "for" loop. The syntax is:
Code: ags

for([initialisation];[condition];[increment])
	[single statement to repeat];
	
for([initialisation];[condition];[increment])
{
    [loop body]
}


Examples:
Spoiler

Code: ags

for(player.x = 0; player.x < 100; player.x++)
    Wait(1);


for(int i = 10; i > 0; i--)
{
    Display("i = %d", i);
}

[close]



Break and continue statements

You can now break out of loops (any kind of loops) using the "break" statement.

Example:
Spoiler

Code: ags

Code: Adventure Game Studio
i = length - 1;
while(i >= 0)
{
    if(page[i] == target)
        break;
    i--;
}


This will halt the loop when a match is found or leave i as -1 if there was no match.
[close]

You can also skip the rest of loop and go straight to the next iteration using the "continue" statement.

Example:
Spoiler

Code: ags

for(x = 0; x < 100; x++)
{
    if(x % 2 == 0)
        continue;
    Display("%d", x);
}


This will display only odd numbers between 0 and 100.
[close]


Switch statement

Added support for "switch" statement, which may serve as a replacement for multiple "if/else if" coming in sequence.
Strings and other variable types are allowed to be checked in switch condition. Standard case statement features like fallthrough and break are also supported.

Example:
Spoiler

Code: ags

String x = "a";
 
switch(x)
{
case "a":
    Display("X is A");
case "b": // fall-through
    Display("X is A or B");
    break; // break here
case "c":
    Display("X is C");
case "d": // fall-through
default: // fall-through
    Display("X is C, D, or something except A and B");
}

[close]


Dynamic Arrays in Structs

Dynamic arrays are now permitted inside structs.

Example:
Spoiler

Code: ags

struct DieRoll
{
    int BaseModifier;
    int DieCount;
    int Dice[ ];
    import function GetTotalValueOfRoll();
};
 
function PrepareDice()
{
    DieRoll a;
 
    a.DieCount = 3;
    a.Dice = new int[a.DieCount];
    a.Dice[0] = 6; // d6
    a.Dice[1] = 6; // d6
    a.Dice[2] = 8; // d8
    ...
}


And the dynamic array "Dice" can be initialised and used like any other dynamic array.
[close]


Dynamic arrays returned by the Struct member function.

It is now possible to define a member function of a struct, that returns dynamic array.

Example:
Spoiler

Code: ags

struct MyClass
{
        int Max;
        int Arr[];
        
        import void  InitArray(int max);
        import int[] GetArray();
        import int   GetArrayLength();
};
 
void MyClass::InitArray(int max)
{
        this.Max = max;
        this.Arr = new int[this.Max];
        int i;
        for (i = 0; i < this.Max; i++)
                this.Arr[i] = i;
}
 
int[] MyClass::GetArray()
{
        return this.Arr;
}
 
int MyClass::GetArrayLength()
{
        return this.Max;
}
 
function game_start() 
{
        MyClass my_obj;
        my_obj.InitArray(5);
        int get_arr[] = my_obj.GetArray();
        int i;
        for (i = 0; i < my_obj.GetArrayLength(); i++)
                Display("#%i = %i", i, get_arr[i]);
}

[close]


Static extender functions

It is now possible to create static extender functions, that is extender function that do not require an actual object.

The syntax is:
Code: ags

[return_type] function_name(static [Struct_Name], [parameters]);


Example:
Spoiler

Code: ags

int AbsInt(static Maths, int value)
{
    if (value < 0)
        value = 0 - value;
     
    return value;
}


This works in the same way as the normal extender method syntax (e.g. this Character *) but for static methods. The above code will define a new method in the static Maths called AbsInt. You can then import it in a header:
Code: ags

import int AbsInt(static Maths, int value);


And then use it elsewhere in your code just like it were a built-in Maths function:
Code: ags

int x = Maths.AbsInt(-3);

[close]


More assignment operators

The following C-style assignment operators are now supported in addition to += and -=:

Code: ags

*=   // Multiply by and assign
/=   // Divide by and assign
&=   // Bitwise AND and assign
|=   // Bitwise OR and assign
^=   // Bitwise XOR and assign
<<=  // Bitshift left and assign
>>=  // Bitshift right and assign



Improvements to macros

#define command can now refer to other #define'd constants. Like in VC++, #define symbol expansion only needs to make sense at the time of reference. Also like VC++, the order of previously defined constants isn't important.

Example:
Spoiler

Code: ags

#define RED    GREEN
#define BLUE   456
#define GREEN  BLUE
Display("%d", RED); // Prints 456
#undef BLUE
#define BLUE  123
Display("%d", RED); // Prints 123

[close]

Important: To prevent circular references, a #define cannot refer to itself or anything previously used to expand the #define symbol.


Code Regions

You can now define an arbitrary region for code folding in your script. Then the AGS editor will allow you to fold and unfold this region as though it were a code block. Regions are purely cosmetic feature and have no effect on compiled code.

Example:
Spoiler

Code: ags

#region MyRegion
do stuff;
do stuff;
do stuff;
#endregion MyRegion

[close]


TO BE CONTINUED...

SMF spam blocked by CleanTalk