Repository navigation
GML Extended
- The Audit Update!
Time for the yearly major release of this library.
This release is the result of a full audit of every .gml script in the library. On top of 33 new functions that I have been commonly using on other projects, and a new optional library dedicated only to Data Structures.
This release fixes a long list of silent bugs (several of which meant a function had never worked as documented) and rebuilds the compatibility system, which turned out to be doing the exact opposite of its job on older runtimes.
Breaking changes
Warning
Read this section before updating. Most of these are bug fixes, but they change behaviour you may have worked around.
int()now truncates towards zero instead of flooring.int(-10.5)returns-10, not-11. This matches the documented "removes all decimals". Use the uilt-in functionfloor()if you relied on the old behaviour.uuid_v4()now returns a real RFC 4122 UUID (36chars, dashed, with version and variant nibbles) instead of a raw 32 char MD5 string.unix_timestamp()andunix_to_datetime()were rebuilt on integer calendar math. A date now produces the same timestamp on every machine regardless of timezone or daylight saving.now()reads the clock in the timezone GameMaker is set to. You will need to calldate_set_timezone(timezone_utc)at game start if you need it to match a server.struct_merge()no longer modifies its arguments. It returns a deep copy that shares no struct or array reference with either source.instance_create()andshader_set_ext()no longerdeletethe params struct you pass in. The struct stays usable after the call.instance_get_if()now returns the instances that match the callback (it returned the ones that did not match).instance_get_all()now returns every instance (it silently truncated the result).random_range_linear()now returns a value inside the given range.rng()andchoice_weighted()now use a uniform roll.rng(0.5)fires 50% of the time, not 25%.Surface.exists()now returnsfalsefor a freed surface (it always returnedtrue).- Removed the internal
__gml_ext_comp_set_gamemaker_version()and__gml_ext_comp_is_gamemaker_major_version(), replaced by the version core described below. - Removed the
Project-2.3.7folder from the repository. It had not been updated since February 2025 (Before the compatibility update), and the main compatibility layer now genuinely supports GameMaker 2.3.
Major changes
Optional > Data Structures
A new library for saving and loading data structures as compressed base64 strings, ready to drop straight into a JSON save file.
ds_grid_write_buffer/ds_grid_read_buffer: Serialize a numeric grid. Cells are stored as 64-bit floats so every value survives the round trip, and reading resizes the destination grid automatically.ds_list_write_buffer/ds_list_read_buffer: Serialize a list, keeping every value type including nested lists and maps.ds_map_write_buffer/ds_map_read_buffer: Serialize a map, keeping key types and nested data structures.struct_write_buffer/struct_read_buffer: Serialize a struct or an array, nested values included. (included this one here instead inside the "structs" lib since it has dependency on the ds_map* functions)_
Compatibility system rewrite
Important
The old system did not work on every case. Because GameMaker 2.3 reported a major version of 2.3, every check like GM_VERSION_IS_2022 && ... evaluated to false, so on a real 2.3 runtime every fallback was skipped and the library called functions that did not exist. Turning on GML_EXT_FORCE_COMPATIBILITY_MODE did the same thing, so it disabled the fallbacks instead of forcing them.
Every GameMaker version number is now in one table, and each fallback asks for a feature by name instead of comparing versions itself.
- New macros:
GM_HAS,GM_VERSION_AT_LEAST,GM_VERSION_IS_2_3,GM_VERSION_IS_2025,GM_VERSION_IS_2026. - Version parsing now splits on
.instead of reading fixed character offsets, so it no longer breaks on unexpected version strings, andGM_CURRENT_VERSIONis parsed once and cached instead of on every reference. - Asking for a feature that is not in the table logs an error and returns
false, so a typo surfaces immediately.
Core > Strings
string_truncate: Cuts a string to a maximum length and adds a suffix, useful for UI labels.string_remove_duplicate_chars: Collapses every run of a character into a single one, so double spaces become one space and..becomes..string_to_snake: Converts a string tosnake_case, splitting on spaces, dashes, underscores and capital letters.string_to_camel: Converts a string tocamelCase.string_slugify: Returns a lowercase URL friendly version of a string, keeping any special characters you allow.
Core > Arrays
array_chunk: Splits an array into an array of smaller arrays.array_flatten: Unwraps nested arrays, up to an optional depth.array_group_by: Groups the elements into a struct using a callback for the key.array_sum,array_mean,array_median: Statistics over the numeric elements of an array.array_sample: Takes N random elements, without repeats by default.array_swap: Swaps two elements in place.
Core > Math
approach: Moves a value towards a target by a fixed step without overshooting it.lerp_angle: Interpolates between two angles the short way around the circle.normalize: Converts a value from one range to another. With the default arguments it normalizes between 0 and 1.snap: Rounds a value to the closest point of a grid. The grid starts at 0 unless anoffsetis given, sosnap(x, 16, 8)snaps to 8, 24, 40 and so on.
Core > Structs
struct_filter,struct_map,struct_reduce: The array iteration functions, for structs.struct_get_path: Reads a nested value from a dot separated path such as"player.stats.hp", with a default when any step is missing. Numeric steps walk into arrays, so"items.0.name"works too. Safe to use on parsed JSON.struct_flatten: Turns nested keys into paths, so{a: {b: 1}}becomes{"a.b": 1}.
Core > Types
trunc: Removes the decimals of a value without rounding. GameMaker has no built-in for this, andfloorgets negatives wrong.
Core > Snippets
trace_once: Prints a message the first time only, so a Step event does not spam the log.get_sizeandcontainsnow work on data structures when you pass the matchingds_type_*constant, which is what the documentation had been promising since v1.5.0. (I promise I thought I already implemented it! Sorry!)
Optional > Instances & Events
is_inside_room: Returns whether an instance is inside the room boundaries, with an option to require the whole bounding box. Safe to call on a destroyed instance.instance_in_roomis now an alias for it.
Bug Fixes
Core > Arrays
sort_elem_size_desccompared an element against itself, and both size comparators returned a boolean wherearray_sortneeds a signed number, so sorting by size never worked.
Core > Strings
string_titlelost the capital letter on any word that followed two or more spaces.
Core > Math
rngandchoice_weightedused a skewed random roll, so the odds never matched the value you passed.choice_weightedno longer reads past the end when both arrays have a different length.rangewith a step of0or less looped forever and hung the game.wrapdivided by zero whenminandmaxwere equal.random_range_linearreturned the square root of the range, landing far outside it, and returnedNaNfor negative ranges.percentageraised a divide by zero error when the total was0.uuid_v4crashed when the platform did not report a UDID, and could collapse its entropy to zero.dec2hexproduced garbage for negative values, which are now clamped to0, and lost a dead code branch.
Core > Structs
struct_mergewrote straight into the first struct, treated a storedundefinedas a missing key, and shared array and struct references with the second struct.struct_mergenow merges arrays index by index, and merges the structs found on the same index, so an array of structs keeps the keys of both sides.struct_equalcompared nested structs and arrays by reference, so two identical structs were reported as different.
Core > Types
is_typecompared a string against an array when given a list of types with no match.
Core > Snippets
containsraised a type error when looking for a number inside a string.
Optional > Time
%DDand%Dprinted the month instead of the day.- The weekday tables started on Monday while GameMaker counts from Sunday, so every weekday name was off by one and Sundays read past the start of the array.
- Format tokens were sorted shortest first, so
%YYYYcould be partly replaced by%YY. %YYcalledstring_copyon a number, and several tokens passed numbers tostring_replace_all.unix_timestampwas anchored on an epoch built in the local timezone and then floored to UTC midnight, shifting every result by the timezone offset, losing a second to floating point truncation, and drifting by an hour across a daylight saving change. It is now built on integer calendar math and is exact.- Fixed the "Thrusday" typo and a stray
}in thetime_performancelog line.
Optional > Instances & Events
instance_get_allused the digit count of the instance total as its loop bound, so 12 instances returned 2.instance_get_ifreturned the instances that did not match the callback. (my bad sorry :P)
Optional > Layers
layer_get_backgroundandlayer_get_tilesetchecked for failure with!id, which never catches the-1thatlayer_get_idreturns.
Optional > Draw / GUI
draw_create_profileanddraw_set_profileguarded on a global that nothing ever wrote to.mouse_is_movingreturnedtruewhen the mouse had not moved on older runtimes, and the fallback variables it needs were not always created.
Optional > Ease
anim_easeInElastic,anim_easeOutElasticandanim_easeInOutElasticdid not match the easings.net curves they are documented to follow.anim_easeInElastic(1)returned0instead of1.anim_easeInBounceandanim_easeInOutBounceapplied thefactortwice.
Experimental
Surface.exists()compared a boolean against-1, so it always returnedtrue.shader_set_extused the outer loop counter to index a struct when setting a texture uniform.Logger.warn()andLogger.error()wrote the file handle into the log line as if it were part of the message.http_request_sendleaked ads_mapon every call.
Compatibility
__gml_ext_comp_array_containsincremented its step variable instead of its index, looping forever, and computed the wrong end position.- The
string_splitfallback dropped an empty trailing chunk, so it returned[]where the built-in returns[""], and it never matched a delimiter longer than one character.
Minor changes
No minor changes in this update.
Documentation
- Added pages for every new function, plus a new
Optional > Data Structurespage. Compatibility.mdnow documents the feature table, theGM_HASpattern and how to add a new fallback.- Fixed the
random_linear_range/random_range_linearname mismatch inCore/Math.md. - Corrected the
containsreturn type, which was documented as returning an index and-1when it returns a boolean. - Corrected
instance_create_unique, documented as returning-1when it returns the instance that already exists. - Documented that
inttruncates towards zero and thatdec2hexclamps negative values.
Repository
- Removed the unmaintained
Project-2.3.7folder.
If you have any feature request, issue, comment or suggestion, please let me know in the issues tab!
Full Changelog: v1.5.3...v1.6.0