Code Bank - share useful code!

rytan451

Member
Quicksort for sorting by some property of elements of arrays
GML:
function shuffle = function(array) {
  for (var i = array_length(array) - 1; i >= 0; i++) {
    array[@ i] = array[irandom(i)];
  }
}

function array_sort_by(array, map) {
  static _swap = function(array, a, b) {
    var c = array[a];
    array[@ a] = array[b];
    array[@ b] = array[a];
  };
  static _quicksort = function(array, map, start, end) {
    // Base case
    if (start == end) {
      return;
    } else if (start == end - 1) {
      if (map(array[start]) > map(array[end])) {
        _swap(array, start, end);
      }
    }
    // Partition
    var end_of_lesser = start;
    for (var i = end_of_lesser; i < end - 1; i++) {
      if (map(array[i]) < map(array[end])) {
        _swap(array, i, end_of_lesser);
        ++end_of_lesser;
      }
    }
    _swap(array, end, end_of_lesser);
    // Recurse
    _quicksort(start, end_of_lesser - 1);
    _quicksort(end_of_lesser + 1, end);
  };
  shuffle(array);
  _quicksort(array, map, 0, array_length(array) - 1);
}
 

Joe Ellis

Member
Alphabetized "Maps" (maybe could be called alphamaps)
Searching for things by name usually is a linear scan through a list, but with alphamapsā„¢ you can add them to an array structure based on what letter they begin with, and end with, so in most cases it narrows the amount of names down so that each name has it's own slot, with only a few others in the same slot, which reduces search iterations by about 90%
So instead of iterating through a list 50 times before it gets to the name it's searching for, it simply gets 2 grid coordinates, the cell with the ord value of the first character on the x axis and the ord of the last character on the y axis, then the cell will only contain a few things and will only have to iterate 3 or so times.

So, the search function is this:

GML:
///map_search(map, name)

var m = argument0, name = argument1;

var
i = wrap(ord(string_char_at(name, 1)), 0, 26),
j = wrap(ord(string_char_at(name, string_length(name))), 0, 26);
var c = m[i, j], n;
if c != 0
{
n = c[_mapcell.names]
i = 0
repeat n[0]
{
if n[++i] = name
{
n = c[_mapcell.nodes]
return n[i]
}
}
}
return 0
To create a map to begin with:

GML:
///map_create()

var m, i = -1;

repeat 26
{m[++i, 25] = 0}

return m
Then to add things to the map:

GML:
///map_add(map, thing)

var m = argument0, n = argument1;

var name = n[_node.name];
var
i = wrap(ord(string_char_at(name, 1)), 0, 26),
j = wrap(ord(string_char_at(name, string_length(name))), 0, 26);

var c = m[i, j];
if c = 0
{
c[_node.type] = _type.mapcell
c[_mapcell.map] = m
c[_mapcell.i] = i
c[_mapcell.j] = j
c[_mapcell.names] = float(0)
c[_mapcell.nodes] = float(0)
m[@ i, j] = c
}

var l = c[_mapcell.nodes];
l[@ ++l[@ 0]] = n
l = c[_mapcell.names]
l[@ ++l[@ 0]] = n[_node.name]
and the wrap function is this:
GML:
///wrap(val, min, max)

var val = argument0;
var min_val = argument1;
var max_val = argument2;
var delta = abs(min_val - max_val);

if min_val = max_val
{return min_val}

if val < min_val
{
while val < min_val
{val += delta}
}

if val >= max_val
{
while val >= max_val
{val -= delta}
}

return val
I did make a newer version of wrap using frac, but I kind of made it then forgot to update my project with it so it's still this at the moment, it's hardly any slower though cus in this case it's just used to make numbers wrap and become part of the alphabet, so things like "Thing 1" "Thing 2" will be classed as "Thing A" "Thing B" and have different slots in the map.

And "float(0)" is just a quick way I use of making a blank array with only 1 (0) index:

GML:
var a;
a[0] = argument0
return a
But you may aswell just use this:

GML:
///array_init()
var a;
a[0] = 0
return a
I also always use the [0] index of arrays to hold how many things are in the array\list, instead of using array_length, so that's what the "c[0]" is used for. You can of course change this and use array_length and start the values at 0. I just do it this way cus I like the 1st thing in a list to have the index of 1, and the 39th to have the index of 39, instead of 38. It just seems ridiculous to always have to subtract 1. If it was up to me the 0 index wouldn't exist and it would start at 1, but hay ho :D
 
Last edited:

Samuel Venable

Time Killer
run the application.
click a window to capture a screenshot of it.
paste into mspaint.

C++:
#include <thread>
#include <chrono>
#include <iostream>
#include <windows.h>

using std::string;
using std::cout;
using std::endl;

int main() {
  RECT rc;
  std::this_thread::sleep_for (std::chrono::seconds(1));
  while (true) {
    if ((GetKeyState(VK_LBUTTON) & 0x100) != 0) {
      break;
    }
  }
  std::this_thread::sleep_for (std::chrono::seconds(1));
  HWND hwnd = GetForegroundWindow();
  GetWindowRect(hwnd, &rc);
  HDC hdcScreen = GetDC(NULL);
  HDC hdc = CreateCompatibleDC(hdcScreen);
  HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, rc.right - rc.left, rc.bottom - rc.top);
  SelectObject(hdc, hbmp);
  PrintWindow(hwnd, hdc, 0);
  OpenClipboard(NULL);
  EmptyClipboard();
  SetClipboardData(CF_BITMAP, hbmp);
  CloseClipboard();
  DeleteDC(hdc);
  DeleteObject(hbmp);
  ReleaseDC(NULL, hdcScreen);
  cout << "successfully copied to clipboard, please paste it into 'mspaint'" << endl;
  return 0;
}
Based on this stackoverflow answer: https://stackoverflow.com/a/7292773/4821390
 
Last edited:

EvanSki

Raccoon Jam Host
GML:
// check for numbers
var _string = "123";

for (var i = 0; i < 10; i++)
{
    //show_message(i) //DEBUG
    if (string_pos((i),_string))
    {
        _string = real(string(_string));
    }
    //other wise the string will stay a string
}
Simple loop checking for numbers,
its not fool proof in any means but its a start, if anyone wants to improve this they are more then encouraged to do so, this is the same amount of a duct tape fix as depth = -y
 

ZeDuval

Member
GML:
// check for numbers
var _string = "123";

for (var i = 0; i < 10; i++)
{
    //show_message(i) //DEBUG
    if (string_pos((i),_string))
    {
        _string = real(string(_string));
    }
    //other wise the string will stay a string
}
Simple loop checking for numbers,
its not fool proof in any means but its a start, if anyone wants to improve this they are more then encouraged to do so, this is the same amount of a duct tape fix as depth = -y
GML:
if( string_digits( str ) == str ) str = real( str )
 

Joe Ellis

Member
-EDIT-
Ignore this, I got confused and thought that ZeDuval had written the original loop version and didn't realize the code under it was his solution to it, which is all that's needed.
 
Last edited:

Samuel Venable

Time Killer
I wrote a quick API that can extract values from a string representing screen coordinates in the form of WidthxHeight+X+Y as well as put those values back into that originally formatted string. I might make this into an extension, but it will need a lot of other stuff to go with it otherwise I don't see people actually using it. The code may be edited and tested online here as well - http://cpp.sh/4smsc

C++:
#include <iostream>
#include <string>
#include <cerrno>

typedef std::string rectangle_t;

static const char ldgrs[256] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0,
  0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
};

std::string string_digits(std::string str) {
  std::string ret;
  for (const char *c = str.c_str(); *c; c++)
    if (ldgrs[(unsigned char)*c]&4) ret += *c;
  return ret;
}

std::size_t string_count(std::string substr, std::string str) {
  std::size_t pos = 0, occ = 0;
  const std::size_t sublen = substr.length();
  while ((pos = str.find(substr, pos)) != std::string::npos)
    occ++, pos += sublen;
  return occ;
}

void print_help(std::string argv0) {
  std::cout << "usage:     " << argv0 << " <flag>           <WxH+X+Y>  "      << std::endl;
  std::cout << "xposition: " << argv0 << " --xpos   (or -x) 640x480+0+0"      << std::endl;
  std::cout << "yposition: " << argv0 << " --ypos   (or -y) 640x480+0+0"      << std::endl;
  std::cout << "width:     " << argv0 << " --width  (or -w) 640x480+0+0"      << std::endl;
  std::cout << "height:    " << argv0 << " --height (or -h) 640x480+0+0"      << std::endl;
  exit(0);
}

void print_error() {
  std::cout << "error parsing or invalid rectangle!" << std::endl;
  exit(1);
}

void print_int(int value) {
  std::cout << value << std::endl;
  exit(0);
}

void print_uint(unsigned value) {
  std::cout << value << std::endl;
  exit(0);
}

void rectangle_getter(rectangle_t rect, int *ret_x, int *ret_y,
  unsigned *ret_w, unsigned *ret_h, bool *ret_e) {
  bool error = false;
  std::size_t wpos = 0;
  std::size_t hpos = rect.find("x") + 1;
  if (hpos == wpos) error = true;
  if (string_count("+", rect) != 2) error = true;
  std::size_t xpos = rect.find_first_of("+") + 1;
  if (xpos == wpos) error = true;
  std::size_t ypos = rect.find_last_of("+") + 1;
  if (ypos == wpos) error = true;
  std::string w = rect.substr(wpos, hpos - wpos - 1);
  if (string_digits(w).empty()) error = true;
  std::string h = rect.substr(hpos, xpos - hpos - 1);
  if (string_digits(h).empty()) error = true;
  std::string x = rect.substr(xpos, ypos - xpos - 1);
  if (string_digits(x).empty()) error = true;
  std::string y = rect.substr(ypos);
  if (string_digits(y).empty()) error = true;
  *ret_x = !error ? (int)strtol(x.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_y = !error ? (int)strtol(y.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_w = !error ? (unsigned)strtoul(w.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_h = !error ? (unsigned)strtoul(h.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_e = error;
}

rectangle_t rectangle_setter(int x, int y, unsigned w, unsigned h) {
  return std::to_string(w) + "x" + std::to_string(h) + "+" +
    std::to_string(x) + "+" + std::to_string(y);
}

unsigned rectangle_get_width(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_w;
}

unsigned rectangle_get_height(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_h;
}

int rectangle_get_x(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_x;
}

int rectangle_get_y(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_y;
}

int main (int argc, char **argv) {
  if (argc == 1) { print_help(argv[0]); }
  if (argc > 3)  { print_error(); }
  std::string flag = argv[1];
  rectangle_t rect = argv[1];
  if (argc == 3) { rect = argv[2]; }
  if (argc == 2 && (flag == "--help" || flag == "-h"))
  { print_help(argv[0]); }
  int x = rectangle_get_x(rect);
  int y = rectangle_get_y(rect);
  unsigned w = rectangle_get_width(rect);
  unsigned h = rectangle_get_height(rect);
  if (flag == "--xpos" || flag == "-x")   { print_int(x); }
  if (flag == "--ypos" || flag == "-y")   { print_int(y); }
  if (flag == "--width" || flag == "-w")  { print_uint(w); }
  if (flag == "--height" || flag == "-h") { print_uint(h); }
  rectangle_t r = rectangle_setter(x, y, w, h);
  std::cout << "unparsed: " << r << std::endl;
  std::cout << "parsed x: " << x << std::endl;
  std::cout << "parsed y: " << y << std::endl;
  std::cout << "parsed w: " << w << std::endl;
  std::cout << "parsed h: " << h << std::endl;
  return 0;
}
It can be done in pure GML I'm sure, but this extension would be more for helping people learn C++ than anything else. Using negative numbers for the width or height of the rectangle will result in those values being treated as UINT_MAX, (which equals 4294967295).

So, entering this into the terminal:
Code:
/path/to/rectparser.exe -1x-1+0+0
...will result in the following output:
Code:
unparsed: 4294967295x4294967295+0+0
parsed x: 0
parsed y: 0
parsed w: 4294967295
parsed h: 4294967295
 
Last edited:

Samuel Venable

Time Killer
I wrote another example of how to use my process information extension. This time I demonstrate how to execute notepad.exe (and the equivalent for other platforms) so that the program stays on top of the game while forcing it to close after an alarm event has triggered.

Create Event:
GML:
globalvar process, directory, command, stayontop;
process[0] = 0;

directory_create(game_save_id);
file_copy(working_directory + "readme.txt", game_save_id + "readme.txt");

if (os_type == os_windows) {
  directory = "C:\\Windows\\System32\\";
  command[0] = "notepad.exe \"" + game_save_id + "readme.txt\"";
} else if (os_type == os_macosx) {
  directory = "/Applications/";
  if (file_exists(directory + "TextEdit.app/Contents/MacOS/TextEdit")) { // older than macOS 10.15 Catalina
    command[0] = directory + "TextEdit.app/Contents/MacOS/TextEdit \"" + game_save_id + "readme.txt\"";
  } else { // macOS 10.15 Catalina or later
    directory = "/System" + directory;
    command[0] = directory + "TextEdit.app/Contents/MacOS/TextEdit \"" + game_save_id + "readme.txt\"";
  }
} else {
  if (os_type == os_linux) {
    directory = "/usr/bin/";
  } else if (os_type == os_freebsd) {
    directory = "/usr/local/bin/";
  }
  if (os_type == os_linux || os_type == os_freebsd) {
    if (file_exists(directory + "gedit")) { // GNOME Text Editor
      command[0] = directory + "gedit \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "pluma")) { // MATE Text Editor
      command[0] = directory + "pluma \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "kate")) { // KDE Advanced Text Editor
      command[0] = directory + "kate \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "kwrite")) { // KDE Text Editor
      command[0] = directory + "kwrite \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "mousepad")) { // XFCE Text Editor
      command[0] = directory + "mousepad \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "leafpad")) { // LXDE Text Editor
      command[0] = directory + "leafpad \"" + game_save_id + "readme.txt\"";
    } else { // if you have anything else, forget about it, kid!
      show_error("Missing Text Editor or Unsupported Desktop Environment!", true);
    }
  } else {
    show_error("Unsupported Operating System!", true);
  }
}

stayontop[0] = true;
alarm[0] = 10;
Alarm 0 Event:
GML:
process_create(process[0], command[0], stayontop[0]);
alarm[1] = 500;
Alarm 1 Event:
GML:
process_destroy(process[0]);
process_create.gml Script:
GML:
function process_create(process, command, stayontop) {
  process_execute_async(int64(process), string(command));
  if (stayontop) {
    var hwnd = wid_from_top(), pid = pid_from_wid(hwnd);
    while (pid == 0 || hwnd == wid_from_window(window_handle()) ||
      (pid != process_current(int64(process)) &&
      pid_exists(process_current(int64(process))))) {
      pid = pid_from_wid(hwnd);
      hwnd = wid_from_top();
    }
    wid_set_pwid(hwnd, wid_from_window(window_handle()));
  }
}
process_destroy.gml Script:
GML:
function process_destroy(process) {
  if (process_exists(process)) {
    pid_kill(process_current(int64(process)))
    process_clear_out(int64(process));
    process_clear_pid(int64(process));
    wid_to_top(wid_from_window(window_handle()));
  }
}
process_exists.gml Script:
GML:
function process_exists(process) {
  var ppid = ppid_from_pid(process_current(int64(process)));
  if (string_copy(cmd_from_pid(ppid), 0, 8) == "/bin/sh ") { ppid = ppid_from_pid(ppid); }
  return (pid_exists(process_current(int64(process))) && pid_from_self() == ppid);
}
You can get an example project here:
Or if you want to try the code snippets in your own project, you'll need this extension:
p.s. os_freebsd is just a macro for os_unknown :p
 
Last edited:

Bart

WiseBart
The following script allows you to draw a texture group's layout while running the game, which might come in handy for debugging purposes.
It basically shows the same information as the texture preview option in the game options.
You can modify it further by adding specific colors for each resource type, by choosing which resource type to draw, etc.

GML:
/// dbg_draw_texgroup(x, y, texgroup_name)
var _x = argument0;
var _y = argument1;
var texgroup_name = argument2;

var arr_sprites = texturegroup_get_sprites(texgroup_name);
var arr_fonts = texturegroup_get_fonts(texgroup_name);
var arr_tilesets = texturegroup_get_tilesets(texgroup_name);

var len_sprites = array_length_1d(arr_sprites);
var len_fonts = array_length_1d(arr_fonts);
var len_tilesets = array_length_1d(arr_tilesets);

if (len_sprites == 0 && len_fonts == 0 && len_tilesets == 0) {
    // Nothing to draw
    return;
}

// Get texture page dimensions from the first resource we can find on the page
var resource, tex;
if (len_sprites > 0) {
    var resource = arr_sprites[0];
    var tex = sprite_get_texture(resource, 0);
} else if (len_fonts > 0) {
    var resource = arr_fonts[0];
    var tex = font_get_texture(resource);
} else if (len_tilesets > 0) {
    var tls = arr_tilesets[0];
    var tex = tileset_get_texture(arr_tilesets[0]);
}
var w = 1/texture_get_texel_width(tex);
var h = 1/texture_get_texel_height(tex);

// Keep things tidy by storing the current draw color
var c = draw_get_color();

// Now change the color
draw_set_color(c_red);
draw_rectangle(_x, _y, _x + w, _y + h, true);
draw_set_color(c_white);

// Loop through all different resources that take up space on a texture
for(var i = 0;i < len_sprites;i++) {
    var spr = arr_sprites[i];
    var arr_uvs = sprite_get_uvs(spr, 0);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, sprite_get_name(spr));
}

for(var i = 0;i < len_fonts;i++) {
    var fnt = arr_fonts[i];
    var arr_uvs = font_get_uvs(fnt);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, font_get_name(fnt));
}

for(var i = 0;i < len_tilesets;i++) {
    var tls = arr_tilesets[i];
    var arr_uvs = tileset_get_uvs(tls);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, tileset_get_name(tls));
}

// Restore draw state
draw_set_color(c);
Using the script:
GML:
/// Draw event
dbg_draw_texgroup(x, y, "Default");
 
Last edited:

angelwire

Member
This is a dumb little thing I do because I'm scared I'll forget to come back to a "//Todo: do something" comment. I make it a function instead:

GML:
todo("Give the player a pair of pants");

///@function todo(arg)
function todo(_arg)
{
    //Show as a debug message
    show_debug_message("You forgot to: " + string(_arg));
    //Or throw an error
    //throw ("You forgot to: " + string(_arg));
}
 

hippyman

Member
With these four functions you use JSON files to generate structs at runtime.
EDIT: I added a few more functions so that you can save structs to a JSON file.

test.json
JSON:
{
    "object": { "innerNum": 50 },
    "array": [1,2,3],
    "string_var": "Howdy!"
}
GML:
json_struct = json_load_from_file("test.json");

// json_struct.object == { "innerNum": 50 }
// json_struct.object.innerNum == 50
// json_struct.array == [5,10,15]
// json_struct.array[0] == 5
// json_struct.array[1] == 10
// json_struct.array[2] == 15
// json_struct.string_var == "Howdy!"

GML:
function struct_save_to_file(struct,path) {
    var json_string = struct_get_string(struct);
    var file = file_text_open_write(path);
    file_text_write_string(file,json_string);
    file_text_close(file);
}
GML:
/// @func json_load_from_file
function json_load_from_file(path) {
    var json_string = json_file_get_string(path);
    var json_map = json_decode(json_string);
    var json_object = ds_map_to_struct(json_map);
    ds_map_destroy(json_map);
    return json_object;
}
GML:
/// @func json_file_get_string
function json_file_get_string(path) {
    if (!file_exists(path)) show_error(path + " doesn't exist. Check the path.",true);
    var file = file_text_open_read(path);
    var json_string = ""
    while (!file_text_eof(file)) {
        json_string += file_text_readln(file);
    }
    file_text_close(file);
    return json_string;
}
GML:
/// @func ds_map_to_struct
function ds_map_to_struct(map) {
    var struct = {};
    var key = ds_map_find_first(map);
    var count = ds_map_size(map);
    for (var i = 0; i < count; ++i) {
       
        if (ds_map_is_map(map,key)) {
            // Value is a ds_map
            variable_struct_set(struct,key,ds_map_to_struct(map[? key ]));
        } else if (ds_map_is_list(map,key)) {
            // Value is a ds_list
            variable_struct_set(struct,key,ds_list_to_array(map[? key ]));
        } else {
            // Value is a real/string/boolean
            variable_struct_set(struct,key,map[? key ]);
        }
       
        key = ds_map_find_next(map,key);
    }
   
    return struct;
}
GML:
/// @func ds_list_to_array
function ds_list_to_array(list) {
    var count = ds_list_size(list);
    var array = array_create(count);
    for (var i = 0; i < count; ++i) {
        //var val = list[| i ];
       
        if (ds_list_is_map(list,i)) {
            // Value is a ds_map
            array[ i ] = ds_map_to_struct(list[| i ]);
        } else if (ds_list_is_list(list,i)) {
            // Value is a ds_list
            array[ i ] = ds_list_to_array(list[| i ]);
        } else {
            // value is a real/string/boolean
            array[ i ] = list[| i ];
        }
    }
    return array;
}
GML:
function array_get_string(array) {
    var array_str = "[";
    var count = array_length(array);
    for (var i = 0; i < count; ++i) {
        var val = array[i];
        if (is_struct(val)) {
            val = struct_get_string(val);
        } else if (is_array(val)) {
            val = array_get_string(val);
        } else if (is_string(val)) {
            val = "\"" + val + "\"";
        } else {
            val = string(val);
        }
        
        array_str += val;
        if (i < count-1) {
            array_str += ",";
        }
    }
    
    array_str += "]";
    
    return array_str;
}
GML:
function struct_get_string(struct) {
    var struct_str = "{";
    var var_count = variable_struct_names_count(struct);
    var var_names = variable_struct_get_names(struct)
    for (var i = 0; i < var_count; ++i) {
        var var_name = var_names[i];
        var var_val = variable_struct_get(struct,var_name);
        if (is_struct(var_val)) {
            var_val = struct_get_string(var_val);
        } else if (is_array(var_val)) {
            var_val = array_get_string(var_val);
        } else if (is_string(var_val)) {
            var_val = "\"" + var_val + "\"";
        } else {
            var_val = string(var_val);
        }
        
        struct_str += "\"" + var_name + "\"" + ":" + var_val;
        if (i < var_count-1) {
            struct_str += ",";
        }
    }
    
    struct_str += "}";
    
    return struct_str;
}
 
Last edited:

rytan451

Member
A basic linked list implementation in pure GML (2.3). Untested.

All methods are O(1) time complexity, O(1) space complexity.

new LinkedList()
Creates a new linked list.

LinkedList#size
The size (number of elements) of the linked list.

LinkedList#insertFirst(value)
Inserts an element into the start of the list.

LinkedList#insertLast(value)
Inserts an element into the end of the list.

LinkedList#deleteFirst()
Deletes the first element in the list; returns the value of that element.

LinkedList#deleteLast()
Deletes the last element in the list; returns the value of that element.

LinkedList#clear()
Clears the list; returns the number of elements destroyed.

LinkedList#empty()
Returns true if the list is empty, and false otherwise.

LinkedList#getViewFirst()
Returns a view (type LLView) of the first element. This allows looping through the list.

LinkedList#getViewLast()
Returns a view (type LLView) of the last element. This allows looping through the list.

LLView#current
Returns the value of the viewed element. Setting does not affect the linked list.

LLView#previous()
Moves the view to the previous element. Returns true if said element exists, and false otherwise.

LLView#insertBefore(value)
Inserts an element before the current (viewed) element.

LLView#insertAfter(value)
Inserts an element after the current (viewed) element.

LLView#destroyBefore()
Destroys the element before the current (viewed) element; returns the value of the destroyed element.

LLView#destroyAfter()
Destroys the element after the current (viewed) element; returns the value of the destroyed element.

LLView#set(value)
Sets the value of the current (viewed) element.

GML:
/// @desc Linked List implementation
/// @func LinkedList()
function LinkedList() constructor {
  first = undefined;
  last = undefined;
  size = 0;
  _size = 0;
  static t_element = function(value, next, previous, ll) {
    v = value;
    n = next;
    p = previous;
    par = ll;
    static insertBefore = function(value) {
      var e = new t_element(value, self, p, par);
      if (!is_undefined(p)) p.n = e;
      else par.first = e;
      p = e;
      par._size += 1;
      par.size = _size;
    }
    static insertAfter = function(value) {
      var e = new t_element(value, n, self, par);
      if (is_undefined(n)) par.last = e;
      else n.p = e;
      n = e;
      par._size += 1;
      par.size = _size;
    }
    static destroy = function() {
      if (is_undefined(p)) par.first = n;
      else p.n = n;
      if (is_undefined(n)) par.last = p;
      else n.p = p;
      par._size -= 1;
      par.size = _size;
    }
  }
  static LLView = function(viewing) {
    _c = viewing;
    current = is_undefined(_c) ? undefined : _c.value;
    static previous = function() {
      if (is_undefined(_c) || is_undefined(_c.p)) {
        return false;
      } else {
        _c = _c.p;
        current = is_undefined(_c) ? undefined : _c.value;
        return true;
      }
    }
    static next = function() {
      if (is_undefined(_c) || is_undefined(_c.n)) {
        return false;
      } else {
        _c = _c.n;
        current = is_undefined(_c) ? undefined : _c.value;
        return true;
      }
    }
    static insertBefore = function(value) {
      if (!is_undefined(_c)) {
        _c.insertBefore(value);
      }
    }
    static insertAfter = function(value) {
      if (!is_undefined(_c)) {
        _c.insertAfter(value);
      }
    }
    static destroyBefore = function(value) {
      if (!is_undefined(_c) && !is_undefined(_c.p)) {
        var v = _c.p.value;
        _c.p.destroy();
        return v;
      }
      return undefined;
    }
    static destroyAfter = function(value) {
      if (!is_undefined(_c) && !is_undefined(_c.n)) {
        var v = _c.n.value;
        _c.n.destroy();
        return v;
      }
      return undefined;
    }
    static set = function(value) {
      if (!is_undefined(_c)) {
        _c.value = value;
      }
    }
    static destroy = function() {};
  }
  
  static empty = function() {
    return _size == 0;
  }
  static clear = function() {
    var s = _size;
    first = undefined;
    last = undefined;
    _size = 0;
    size = _size;
    return s;
  }
  static insertFirst = function(value) {
    var e = new t_element(value, first, undefined, self);
    if (!is_undefined(first)) {
      first.p = e;
    }
    if (is_undefined(last)) {
      last = e;
    }
    first = e;
    _size += 1;
    size = _size;
  }
  static insertLast = function(value) {
    var e = new t_element(value, undefined, last, self);
    if (!is_undefined(last)) {
      last.n = e;
    }
    if (is_undefined(first)) {
      first = e;
    }
    last = e;
    _size += 1;
    size = _size;
  }
  static deleteFirst = function() {
    if (is_undefined(first)) {
      return undefined;
    }
    var v = first.value;
    first = first.n;
    if (is_undefined(first)) {
      last = first;
    } else {
      first.p = undefined;
    }
    _size -= 1;
    size = _size;
    return v;
  }
  static deleteLast = function() {
    if (is_undefined(last)) {
      return undefined;
    }
    var v = last.value;
    last = last.p;
    if (is_undefined(last)) {
      first = last;
    } else {
      last.n = undefined;
    }
    _size -= 1;
    size = _size;
    return v;
  }
  static getViewFirst = function() {
    return new LLView(first);
  }
  static getViewLast = function() {
    return new LLView(last);
  }
  
  static destroy = function() {}
}
 

Joe Ellis

Member
Here are two functions for HSL color handling (hue, sat, lum)
I've always preferred color pickers that use lum instead of val, cus I find it awkward to create pastel colors with hue sat val. You have to adjust the sat and val separately and lum makes it a lot simpler. I generally just prefer the way it works cus it seems more logical and I'm used to it from using paint since I was little.

I got the original code from https://www.easyrgb.com/en/, which is a great resource for anything color related.

I can't actually explain what the maths is doing cus I don't understand it 100%, more like 65% lol
but it is explained on https://www.easyrgb.com/en/

So this is the function for creating a color using hsl: (It's basically the same as make_color_hsv, but with lum instead)

GML:
function make_color_hsl(hue, sat, lum) {

//Same as make_color_hsv only it uses lum instead of val (Like the ms paint color picker)
//and returns a standard gm color value (u32 integer)
//Note: the input ranges are: hue(0 - 1530), sat(0 - 255), lum(0 - 510)
//This means you're able to use all 1530 possible hues instead of just 255 like make_color_hsv

    //Convert input values to 0-1 range
    hue /= 1530
    sat /= 255
    lum /= 510

    var
    r = hue + 0.333,
    g = hue,
    b = hue - 0.333,
    v1, v2, _range;
  
    if lum < 0.5
    {v1 = lum * (1.0 + sat)}
    else
    {v1 = lum + sat - lum * sat}

    v2 = 2 * lum - v1

    _range = v1 - v2

    //Wrap rgb values to 0-1 range
    if r > 1
    || r < 0
    {r -= sign(r)}
  
    if g > 1
    || g < 0
    {g -= sign(g)}
  
    if b > 1
    || b < 0
    {b -= sign(b)}

    //Red
    if 6 * r < 1
    {r = v2 + _range * 6 * r}
    else
    {
    if 2 * r < 1
    {r = v1}
    else
    {
    if 3 * r < 2
    {r = v2 + _range * (0.666 - r) * 6}
    else
    {r = v2}
    }
    }

    //Green
    if 6 * g < 1
    {g = v2 + _range * 6 * g}
    else
    {
    if 2 * g < 1
    {g = v1}
    else
    {
    if 3 * g < 2
    {g = v2 + _range * (0.666 - g) * 6}
    else
    {g = v2}
    }
    }

    //Blue
    if 6 * b < 1
    {b = v2 + _range * 6 * b}
    else
    {
    if 2 * b < 1
    {b = v1}
    else
    {
    if 3 * b < 2
    {b = v2 + _range * (0.666 - b) * 6}
    else
    {b = v2}
    }
    }

    return make_color_rgb(r * 255, g * 255, b * 255)
}

And this is the function for getting the hue, sat and lum values of a color (A standard gm color as the input argument)

GML:
function color_get_hsl(color) {

//Gets the hue, sat and lum of a given color (standard gm color value)
//and returns them in a vec3 array
//Note: the output ranges are: hue(0 - 1530), sat(0 - 255), lum(0 - 510)

    var
    r = colour_get_red(color) / 255,
    g = colour_get_green(color) / 255,
    b = colour_get_blue(color) / 255,
    _min, _max, _range, hue, sat, lum;

    _min = min(r, g, b)
    _max = max(r, g, b)
    _range = _max - _min

    lum = (_max + _min) / 2

    if _range = 0
    {
    hue = 0
    sat = 0
    }
    else
    {

    if lum < 0.5
    {sat = _range / (_max + _min)}
    else
    {sat = _range / (2 - _max - _min)}

    if r = _max
    {
    if g > b
    {hue = (g - b) / _range}
    else
    {hue = 6 - ((b - g) / _range)}
    }
    else
    {
    if g = _max
    {hue = 2 + (b - r) / _range}
    else
    {hue = 4 + (r - g) / _range}
    }
    if h < 0
    {++hue}
    if h > 6
    {--hue}
    }

    return [round(hue * 1530), round(sat * 255), round(lum * 510)]
}

So now y'all guys can make hsl color pickers! šŸ¤— No credit required lol
 

Samuel Venable

Time Killer
Remember from GameMaker 8.1 splash_show_video(fname, loop)?
GML:
// Initialize defaults...
function splash_initialize() {
  globalvar splash_get_main, splash_get_caption, splash_get_fullscreen,
  splash_get_border, splash_get_volume;

  splash_get_main = true;
  splash_get_caption = "";
  splash_get_fullscreen = false;
  splash_get_border = true;
  splash_get_volume = 100;
}

// splash_set_main(main) Indicated whether the splash screen must be
// shown in the main game window (true, default) or in a separate window
// (false).

function splash_set_main(main) {
  splash_get_main = main;
}

// splash_set_caption(cap) Sets the caption for the splash window. This
// only has effect when is a separate splash window is used. Default the
// empty string is used.

function splash_set_caption(cap) {
  splash_get_caption = cap;
}

// splash_set_fullscreen(full) Indicates whether to use a full screen
// window or not. This only has effect when is a separate splash window is
// used. Default a normal window is used.

function splash_set_fullscreen(full) {
  splash_get_fullscreen = full;
}

// splash_set_border(border) Indicates whether the window should have a
// border. This only has effect when is a separate normal splash window is
// used. Default a border is used.

function splash_set_border(border) {
  splash_get_border = border;
}

// splash_set_volume(vol) Indicates the percent of volume the splash
// screen is to have if video or audio. Default the value is 100.

function splash_set_volume(vol) {
  splash_get_volume = vol;
}

// splash_show_video(fname,loop) Shows a video splash screen. fname is the
// name of the video file. loop indicates whether to loop the video.

function splash_show_video(fname, loop) {
  globalvar video;
  var wid, wstr, hstr, xstr, ystr, size, pstn, geom;

  if (splash_get_main) {
    if (os_type == os_macosx) {
      wid = window_get_contentview(string(int64(window_handle())));
    } else { wid = string(int64(window_handle())); }
  } else { wid = "-1"; }

  if (splash_get_fullscreen) splash_get_fullscreen = "yes";
  else splash_get_fullscreen = "no";

  if (splash_get_border) splash_get_border = "yes";
  else splash_get_border = "no";

  if (splash_get_main) splash_get_main = "no";
  else splash_get_main = "yes";

  if (loop) loop = "yes";
  else loop = "no";

  wstr = string(window_get_width());
  hstr = string(window_get_height());
  xstr = string(window_get_x());
  ystr = string(window_get_y());
  size = wstr + "x" + hstr;
  pstn = xstr + "+" + ystr;
  geom = size + "+" + pstn;

  video = video_add(fname);
  video_set_option_string(video, "volume", string(splash_get_volume));
  video_set_option_string(video, "input-default-bindings", "no");
  video_set_option_string(video, "title", splash_get_caption);
  video_set_option_string(video, "fs", splash_get_fullscreen);
  video_set_option_string(video, "border", splash_get_border);
  video_set_option_string(video, "keepaspect-window", "no");
  video_set_option_string(video, "taskbar-progress", "no");
  video_set_option_string(video, "ontop", splash_get_main);
  video_set_option_string(video, "geometry", geom);
  video_set_option_string(video, "config", "no");
  video_set_option_string(video, "loop", loop);
  video_set_option_string(video, "osc", "no");
  video_set_option_string(video, "wid", wid);

  video_play(video);
  var canpause = true;
  if (os_type == os_windows) {
    while (video_is_playing(video)) {
      /* handle video pause and stop actions here */
      /* use keyboard_check_direct for pause/stop */
      /* you can use window_has_focus in if check */
      /* keyboard_check_direct is broken on linux */
      /* use async video playback for linux input */
      // example:
      if (window_has_focus()) {
        if (keyboard_check_direct(vk_escape)) {
          video_stop(video);
        } else if (keyboard_check_direct(vk_space)) {
          if (canpause) {
            video_pause(video);
            canpause = false;
          }
        } else {
          canpause = true;
        }
      }
    }
    video_delete(video);
  }
}

// Process Pause/Stop input on non-Windows
function splash_update() {
  if (os_type != os_windows) {
    if (video_exists(video)) {
      if (keyboard_check_pressed(vk_escape)) {
        video_stop(video);
        video_delete(video);
      } else if (keyboard_check_pressed(vk_space)) {
        video_pause(video);
      } else if (!video_is_playing(video)) {
        video_delete(video);
      }
    }
  }
}
Needs this extension.

Example...

Create Event:
GML:
if (os_type == os_macosx) {
  /* macOS-specific instructions for building & running games */   

  // Backspace the signing identity of your mac app to skip code
  // signing; doing so will allow you to move the necessary libs
  // to the correct place in your mac bundle. After building the
  // game, you will need to move ALL *.dylib files in the *.app,
  // EXCEPT "libmpv.1.dylib" and "libvidplayer.dylib" from *.app
  // YouAppBundle.app/Contents/Resources to Contents/Frameworks.
  // After you have moved these files to your frameworks folder,
  // you may then codesign your app manually, and open your app.

  // install libmpv on Mac OS X
  // look at macOS Game Options
  // disable filesystem sandbox

  var src, dst;
  dst = "/usr/local/opt/mpv";
  directory_create(dst);
  dst = "/usr/local/opt/mpv/lib";
  directory_create(dst);
  src = working_directory;
  file_copy(src + "/libmpv.1.dylib",
    dst + "/libmpv.1.dylib");
}

// requirement: any video file supported by ffmpeg & mpv media player
// see online manual for option strings https://mpv.io/manual/stable/
// video_set_option_string() wraps the mpv_set_option_string function

// Ubuntu packages install needed:
// sudo apt-get install libmpv-dev

// Initialize defaults...
splash_initialize();

// splash_set_main(main) Indicated whether the splash screen must be
// shown in the main game window (true, default) or in a separate window
// (false).

splash_set_main(true);

// splash_set_caption(cap) Sets the caption for the splash window. This
// only has effect when is a separate splash window is used. Default the
// empty string is used.

splash_set_caption(window_get_caption());

// splash_set_fullscreen(full) Indicates whether to use a full screen
// window or not. This only has effect when is a separate splash window is
// used. Default a normal window is used.

splash_set_fullscreen(window_get_fullscreen());

// splash_set_border(border) Indicates whether the window should have a
// border. This only has effect when is a separate normal splash window is
// used. Default a border is used.

splash_set_border(true);

// splash_set_volume(vol) Indicates the percent of volume the splash
// screen is to have if video or audio. Default the value is 100.

splash_set_volume(100);

// splash_show_video(fname,loop) Shows a video splash screen. fname is the
// name of the video file. loop indicates whether to loop the video.

splash_show_video(working_directory + "example.webm", false);
Step Event:
GML:
// Process Pause/Stop input on non-Windows
splash_update();

if (!video_exists(video)) {
  // End the game when video playback is ogre...
  // ...it's never ogre...
  // ...shrek is love...
  // ...shrek is life...
  game_end();
}
 
Last edited:

rytan451

Member
An (untested) implementation of the Union-Find Disjoint Set data structure (GC-safe). This data structure works well for Kruskal's Algorithm (for finding minimum/maximum spanning trees), as well as incremental graph connectivity.

GML:
/// @func UnionFindDisjointSet
/// @desc A Union-Find Disjoint Set implementation
function UnionFindDisjointSet(size) constructor {
  var _set, _rank;
  _rank = array_create(size, 0);
  _set = array_create(size);
  for (var i = 0; i < size; i++) {
    _set[i] = i;
  }

  static find = function(element) {
    if (_set[element] != element && _set[_set[element]] != _set[element]) {
      _set[element] = find(_set[element])
    }
    return _set[element];
  }

  static union = function(e1, e2) {
    e1 = find(e1);
    e2 = find(e2);
    switch (sign(_rank[e1] - _rank[e2])) {
      case 0:
        _rank[e1] += 1;
      case 1:
        _set[e2] = e1;
        break;
      case -1:
        _set[e1] = e2;
    }
  }
}
This implementation uses both path compression and union by rank, so their asymptotic time complexity is O(a(n)), where a(n) is the inverse Ackermann function.

(For some reason, I keep implementing data structures and algorithms in GML, but I never end up using them in any games. Or working on any games in any form of consistency)
 

FoxyOfJungle

Kazan Games
Simple string code generator:

GML:
/// @func generate_code(chars, size)
/// @arg chars
/// @arg size

function generate_code(chars, size)
{
    var _len = string_length(chars);

    var _strfinal = "";
    for (var i = 0; i < size; i+=1)
    {
        _strchars[i] = string_char_at(chars, irandom(_len));
        _strfinal += _strchars[i];
    }

    return string(_strfinal);
}
You define the character combination, the size and the function will generate a code.


Usage:

GML:
var code = generate_code("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);

Generated examples:

Code:
MGQEx7B5PX5s
laV38d2kCQlWjbR
B18BuAhIKcQAeMl9RMGqDbg0J1azpM
 

Yal

šŸ§ *penguin noises*
GMC Elder
Simple string code generator:

GML:
/// @func generate_code(chars, size)
/// @arg chars
/// @arg size

function generate_code(chars, size)
{
    var _len = string_length(chars);

    var _strfinal = "";
    for (var i = 0; i < size; i+=1)
    {
        _strchars[i] = string_char_at(chars, irandom(_len));
        _strfinal += _strchars[i];
    }

    return string(_strfinal);
}
You define the character combination, the size and the function will generate a code.


Usage:

GML:
var code = generate_code("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);

Generated examples:

Code:
MGQEx7B5PX5s
laV38d2kCQlWjbR
B18BuAhIKcQAeMl9RMGqDbg0J1azpM
Improvement suggestion... ideally you should have some sort of checksum as well, so it's possible to tell a valid code from an invalid code without having to store a list of all the codes you've generated (and checking the entire list every time!)
 

FoxyOfJungle

Kazan Games
Advanced print function that allow multiple arguments:

GML:
function print()
{
    /// @func print()

    if (argument_count > 0)
    {
        var _log = "";
  
        for (i = 0; i < argument_count; i += 1;)
        {
            _log += string(argument[i]);
            if (argument_count > 1) _log += " | ";
        }
  
        show_debug_message(_log);
    }
}
It was purposeful to put the comment inside the function, it is my personal way of writing.


Examples:

GML:
print();
print("AAA");
print("AAA", "BBB");
print("AAA", "BBB", "CCC");
print(4524, "ASD", true, false);

GMS 2 Console output:

Code:
AAA
AAA | BBB |
AAA | BBB | CCC |
4524 | ASD | 1 | 0 |
Note: As the first function was empty, so it wasn't even printed on the console.

I needed this function when I had to quickly display multiple variables with the " | " separator on the same line on the console.
 
Last edited:

FoxyOfJungle

Kazan Games
Get the top instance of an object:

GML:
function instance_top_position(mx, my, object)
{
    /// @func instance_top_position(mx, my, object)
    /// @arg mx
    /// @arg my
    /// @arg object

    var _top_instance = -1;

    var _list = ds_list_create();
    var _num = collision_point_list(mx, my, object, false, true, _list, false);
    if (_num > 0)
    {
        for (var i = 0; i < _num; ++i;)
        {
            _top_instance = _list[| ds_list_size(_list)-1]
        }
    }

    ds_list_destroy(_list);

    return _top_instance;
}

Get the top instance on a specific layer:

GML:
function layer_instance_top_position(mx, my, layer_id)
{
    /// @func layer_instance_top_position(mx, my, layer_id)
    /// @arg mx
    /// @arg my
    /// @arg layer_id
  
    var _top_instance = -1;
  
    // create list
    var _list = ds_list_create();
  
    // add layer objects to the list
    var _array = layer_get_all_elements(layer_id);

    var i = array_length(_array)-1;
    repeat (array_length(_array))
    {
        if (layer_get_element_type(_array[i]) == layerelementtype_instance)
        {
            var _inst = layer_instance_get_instance(_array[i]);
              
            if position_meeting(mx, my, _inst) ds_list_add(_list, _inst);
        }
        i -= 1;
    }
  
    // get top instance
    var _num = ds_list_size(_list);
    if (_num > 0)
    {
        for (var i = 0; i < _num; ++i;)
        {
            _top_instance = _list[| ds_list_size(_list)-1]
        }
    }
  
    // destroy list
    ds_list_destroy(_list);
  
    return _top_instance;
}

It would be nice if these were built-in functions of GMS 2.


Utiki - UI Editor for Game Maker
 
Last edited:

EvanSki

Raccoon Jam Host
GML:
function draw_set_default(){
//Sets all Draw options to there default states.

draw_set_alpha(1);
draw_set_circle_precision(24);
draw_set_color(c_black);
draw_set_font(-1);
draw_set_halign(fa_left);
draw_set_lighting(false);
draw_set_valign(fa_top);

}
call at the end of a draw event and never worry about text not being drawn correctly
 

Joe Ellis

Member
I've just gone through the utility functions that I use quite often:
A bit of a mixture, all are quite useful I think

GML:
function string_to_array(str) {

//Creates an array from a string
//The string uses commas to separate each value

    var
    commas = string_count(",", str),
    a, i = -1, comma;
    a[commas] = 0
    repeat commas
    {
    comma = string_pos(",", str)
    a[++i] = real(string_copy(str, 1, comma - 1))
    str = string_delete(str, 1, comma)
    }
    a[++i] = real(str)

    return a
}

function string_format_var(str) {
  
//Formats a string so it's safe to use as a variable name
//ei. with underscores instead of spaces and all lower case
  
    return string_replace_all(string_lower(str), " ", "_")
}

function line_intersect_bbox(lx1, ly1, lz1, lx2, ly2, lz2, bbox_x1, bbox_y1, bbox_z1, bbox_x2, bbox_y2, bbox_z2) {

    //Detects whether a 3d line\ray intersects a 3d bounding box
    //and returns the intersect position if there is one.
    //Useful for bullet collisions
    //Based on the SLAB method (Look it up!)
  
    var
    tmin = -10000000,
    tmax = 10000000,
    nx, ny, nz, d, v1, v2;

    nx = lx2 - lx1
    ny = ly2 - ly1
    nz = lz2 - lz1
    d = 1 / point_distance_3d(0, 0, 0, nx, ny, nz)
    nx *= d
    ny *= d
    nz *= d

    if nx != 0.0
    {
    v1 = (bbox_x1 - lx1) / nx
    v2 = (bbox_x2 - lx1) / nx
    tmin = max(tmin, min(v1, v2))
    tmax = min(tmax, max(v1, v2))
    }

    if ny != 0.0
    {
    v1 = (bbox_y1 - ly1) / ny
    v2 = (bbox_y2 - ly1) / ny
    tmin = max(tmin, min(v1, v2))
    tmax = min(tmax, max(v1, v2))
    }

    if nz != 0.0
    {
    v1 = (bbox_z1 - lz1) / nz
    v2 = (bbox_z2 - lz1) / nz
    tmin = max(tmin, min(v1, v2))
    tmax = min(tmax, max(v1, v2))
    }

    if tmax >= tmin
    {return [lx1 + (nx * tmin), ly1 + (ny * tmin), lz1 + (nz * tmin)]}

    return 0
}

function clamp_vec3(_x, _y, _z, _max) {

//Clamps the length of a vector whilst maintaining the direction

    var d = point_distance_3d(0, 0, 0, _x, _y, _z);

    if d > _max
    {
    d = _max / d
    return [_x * d, _y * d, _z * d]
    }

    return [_x, _y, _z]
}

function pos3d_get_screen_pos(_x, _y, _z) {

//Gets the position on the screen of a 3d coordinate, given the current view & projection matrices
//Requires global.matrix_world_view_projection to be pre calculated before hand,
//Ideally just after the view & projection matrices have been set:
//global.matrix_world_view_projection =
//matrix_multiply(matrix_get(matrix_world), matrix_multiply(matrix_get(matrix_view), matrix_get(matrix_projection)))

    var pos = matrix_transform_vertex(global.matrix_world_view_projection, _x, _y, _z);
  
    _z = pos[2] + 1

    //If the depth is "behind" the view focal point(xyz_from), set to -10000 so it's off the screen
    if _z < 0
    {return [-10000, -10000, _z]}

    //Convert the position from clip space to pixel space:
    pos[0] = (1 + (pos[0] / _z)) * 0.5 * screen_width
    pos[1] = (1 - (pos[1] / _z)) * 0.5 * screen_height
    return pos
}

function decel_val(val, amt) {

//Moves a value (negative or positive) towards zero.
//Useful for deceleration of speed variables
//Has a quicker\sharper effect than lerping or multiplying speed by 0.9 etc.
//Similar effect to older games

    if val = 0
    {return 0}

    var s = sign(val);

    val -= amt * s

    if s != sign(val)
    {return 0}

    return val
}

function yaw_pitch_length(yaw, pitch, length) {

//Creates a 3d vector(3 component array)

    var l = lengthdir_x(length, pitch);

    return [lengthdir_x(l, yaw), lengthdir_y(l, yaw), lengthdir_y(length, pitch)]
}

function vec3_to_yaw_pitch(vx, vy, vz) {

//Gets the yaw, pitch and length of a vector and returns a vec3 array

    return [
    point_direction(0, 0, vx, vy),
    point_direction(0, 0, point_distance(0, 0, vx, vy), vz),
    point_distance_3d(0, 0, 0, vx, vy, vz)]
}
 

Samuel Venable

Time Killer
Load a cylindrical panorama named "panorama.png" and view it in first person 360 degree scrolling perspective (requires lodepng):

C++:
/*

MIT License

Copyright Ā© 2021 Samuel Venable

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

#include <cmath>
#include <cstring>
#include <climits>

#include <string>

#include "lodepng.h"

#include <sys/types.h>
#include <unistd.h>

#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#include <libproc.h>
#else
#include <GL/glut.h>
#endif

void CreateCylinder() {
  const double PI = 3.141592653589793;
  double i, resolution  = 0.3141592653589793;
  double height = 3.5;
  double radius = 0.5;
  glPushMatrix();
  glRotatef(90, 0, 90, 0);
  glTranslatef(0, -1.75, 0);
  glBegin(GL_TRIANGLE_FAN);
  glTexCoord2f(0.5, 0.5);
  glVertex3f(0, height, 0);
  for (i = 2 * PI; i >= 0; i -= resolution) {
    glTexCoord2f(0.5f * cos(i) + 0.5f, 0.5f * sin(i) + 0.5f);
    glVertex3f(radius * cos(i), height, radius * sin(i));
  }
  glTexCoord2f(0.5, 0.5);
  glVertex3f(radius, height, 0);
  glEnd();
  glBegin(GL_TRIANGLE_FAN);
  glTexCoord2f(0.5, 0.5);
  glVertex3f(0, 0, 0);
  for (i = 0; i <= 2 * PI; i += resolution) {
    glTexCoord2f(0.5f * cos(i) + 0.5f, 0.5f * sin(i) + 0.5f);
    glVertex3f(radius * cos(i), 0, radius * sin(i));
  }
  glEnd();
  glBegin(GL_QUAD_STRIP);
  for (i = 0; i <= 2 * PI; i += resolution) {
    const float tc = (i / (float)(2 * PI));
    glTexCoord2f(tc, 0.0);
    glVertex3f(radius * cos(i), 0, radius * sin(i));
    glTexCoord2f(tc, 1.0);
    glVertex3f(radius * cos(i), height, radius * sin(i));
  }
  glTexCoord2f(0.0, 0.0);
  glVertex3f(radius, 0, 0);
  glTexCoord2f(0.0, 1.0);
  glVertex3f(radius, height, 0);
  glEnd();
  glPopMatrix();
}

std::string exeParentDirectory() {
  std::string fname;
  #if defined(__APPLE__) && defined(__MACH__)
  char exe[PROC_PIDPATHINFO_MAXSIZE];
  if (proc_pidpath(getpid(), exe, sizeof(exe)) > 0) {
    fname = exe;
  }
  #elif defined(__linux__) && !defined(__ANDROID__)
  char exe[PATH_MAX];
  std::string symLink = "/proc/self/exe";
  if (realpath(symLink.c_str(), exe)) {
    fname = exe;
  }
  #elif defined(__FreeBSD__)
  int mib[4]; size_t s;
  mib[0] = CTL_KERN;
  mib[1] = KERN_PROC;
  mib[2] = KERN_PROC_PATHNAME;
  mib[3] = -1;
  if (sysctl(mib, 4, nullptr, &s, nullptr, 0) == 0) {
    std::string str; str.resize(s, '\0');
    char *exe = str.data();
    if (sysctl(mib, 4, exe, &s, nullptr, 0) == 0) {
      fname = exe;
    }
  }
  #endif
  size_t fpos = fname.find_last_of("/");
  return fname.substr(0, fpos + 1);
}

GLuint tex;
void CreateTexture(const char *fname) {
  unsigned char *data = nullptr;
  unsigned pngwidth, pngheight;
  unsigned error = lodepng_decode32_file(&data, &pngwidth, &pngheight, fname);
  if (error) return;
  const int size = pngwidth * pngheight * 4;
  unsigned char *buffer = new unsigned char[size]();
  for (unsigned i = 0; i < pngheight; i++) {
    for (unsigned j = 0; j < pngwidth; j++) {
      unsigned oldPos = (pngheight - i - 1) * (pngwidth * 4) + 4 * j;
      unsigned newPos = i * (pngwidth * 4) + 4 * j;
      buffer[newPos + 0] = data[oldPos + 0];
      buffer[newPos + 1] = data[oldPos + 1];
      buffer[newPos + 2] = data[oldPos + 2];
      buffer[newPos + 3] = data[oldPos + 3];
    }
  }
  glGenTextures(1, &tex);
  glBindTexture(GL_TEXTURE_2D, tex);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, pngwidth, pngheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
  delete[] buffer;
  delete[] data;
}

float angle = 0;
void timer(int value) {
  angle += 0.1;
  glutPostRedisplay();
  glutTimerFunc(16, timer, 0);
}

void display() {
  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(90.0f, 16/9, 0.1f, 100.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0, 0, 0);
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  glFrontFace(GL_CW);
  glEnable(GL_DEPTH_TEST);
  glLoadIdentity();
  glRotatef(angle, 0, 1, 0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, tex);
  CreateCylinder();
  glutSwapBuffers();
}

int window = 0;
void keyboard(unsigned char key, int x, int y) {
  switch (key) {
    case 27:
      glutDestroyWindow(window);
      exit(0);
      break;
  }
  glutPostRedisplay();
}

int main(int argc, char **argv) {
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE);
  window = glutCreateWindow("");
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClearDepth(1.0f);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);
  glShadeModel(GL_SMOOTH);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
  CreateTexture("panorama.png");
  glutDisplayFunc(display);
  glutTimerFunc(0, timer, 0);
  glutSetCursor(GLUT_CURSOR_NONE);
  glutKeyboardFunc(keyboard);
  glutFullScreen();
  glutMainLoop();
  return 0;
}

Check out panoview on GitHub, which the above code is based on.

360 degree cylindrical panorama viewer - can be used graphically or from the command line




 
Last edited:

FoxyOfJungle

Kazan Games
Compact tween function that uses the animation curves of GMS 2.3:

GML:
function tween(curve, position, channels_array, channel_index)
{
    /// @func tween(curve, position, channels_array, channel_index)
    /// @arg curve
    /// @arg position
    /// @arg channels_array
    /// @arg channel_index

    var _cv_ch_lenght = array_length(channels_array);
    var _cv_ch = 0;
    var _cv = animcurve_get(curve);
    var _cv_value = 0;

    if (channel_index < _cv_ch_lenght) {
        _cv_ch = animcurve_get_channel(_cv, channels_array[@ channel_index])
        _cv_value = animcurve_channel_evaluate(_cv_ch, position);
    }

    return _cv_value;
}

Example of use:



GML:
// One
curvePosition = approach(curvePosition, 1, 0.02);
var _curveValue = tween(ac_tween_elastic, curvePosition, ["tweenIn"], 0);

// Two
curvePosition = approach(curvePosition, 1, 0.02);
var _curveValue = tween(ac_tween_elastic, curvePosition, ["tweenIn", "tweenOut"], 1);

Result:

 
Last edited:

EvanSki

Raccoon Jam Host
Apparently made this and forgot about it
Its an edit of an existing script

This one draws the grid with color to easily show information
such as a mp_grid to ds_grid



GML:
function ds_grid_draw(grid,sx,sy) {
//
//  Draws the data of a given grid at a screen location.
//
//      grid        grid data structure, id
//      x,y         screen position, real
//
// GMLscripts.com/license
    var w = ds_grid_width(grid);
    var h = ds_grid_height(grid);

    var M = ds_grid_get_max(grid,0,0,w-1,h-1);
    var m = ds_grid_get_min(grid,0,0,w-1,h-1);
    if (M == m) var f = 0 else var f = 1/(M-m);

    for (var i=0; i<w; i++)
    {
        for (var j=0; j<h; j++)
        {

            var value = (ds_grid_get(grid,i,j));
            if (value == -1)
            {
                value = 1;
                draw_text_color(sx+i*20,sy+j*20,string(value),c_red,c_red,c_red,c_red,1);
            }else{
                value = 0;
                draw_text_color(sx+i*20,sy+j*20,string(value),c_lime,c_lime,c_lime,c_lime,1);
            }
         
        }
    }

    return 0;
}
 

Samuel Venable

Time Killer

Quick and easy borderless windows for Linux and Mac:

borderless.cpp (linux)
C++:
#include <X11/Xlib.h>
#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
EXPORTED_FUNCTION double borderless(void *handle) {
  typedef struct {
    unsigned long flags;
    unsigned long functions;
    unsigned long decorations;
    long inputMode;
    unsigned long status;
  } Hints;
  Display *d = XOpenDisplay(nullptr);
  Window w = (Window)handle;
  Hints hints;
  Atom property = XInternAtom(d, "_MOTIF_WM_HINTS", false);
  hints.flags = 2;
  hints.decorations = 0;
  XChangeProperty(d, w, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
  XCloseDisplay(d);
  return 0;
}
borderless.mm (mac)
Objective-C:
#include <Cocoa/Cocoa.h>
#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
@interface NSWindow(subclass)
-(BOOL)canBecomeKeyWindow;
-(BOOL)canBecomeMainWindow;
@end
@implementation NSWindow(subclass)
-(BOOL)canBecomeKeyWindow{return YES;}
-(BOOL)canBecomeMainWindow{return YES;}
@end
EXPORTED_FUNCTION double borderless(void *handle) {
  NSWindow *w = (NSWindow *)handle;
  [w setStyleMask:NSWindowStyleMaskBorderless];
  return 0;
}
build64.sh
Code:
#!/bin/sh
cd "${0%/*}"
if [ $(uname) = "Darwin" ]; then
  clang++ borderless.mm -o libborderless.dylib -std=c++17 -shared -ObjC++ -framework Cocoa -m64
elif [ $(uname) = "Linux" ]; then
  g++ borderless.cpp -o libborderless.so -std=c++17 -shared -static-libgcc -static-libstdc++ -lX11 -m64
fi
...and with borderless mode available under Windows Game Options, all 3 desktops are now supported.





 
Last edited:

EvanSki

Raccoon Jam Host
idk when you'd need it, but heres a script to convert a string to unicode

GML:
function string_unicode(input_string){

    //make input uppercase so we are in 65-90 range
    input_string = string_upper(input_string);
    
    //get length
    input_string_length = string_length(input_string);
    
    //empty unicode string
    unicode_string = " ";
    
    //convert input word to numbers
    for (var i = 1; i < (input_string_length+1); ++i){
        var _n = ord(string_char_at(input_string,i));
        //adds the two numbers on unicode letter to the end of the string using a formula to get the pos it needs
        unicode_string = string_insert(string(_n),unicode_theme_word,string_length(unicode_string)+2);
    }
    
    return(unicode_string);
}
EXAMPLE:
Code:
string_apple = "apple";

unicode_apple = string_unicode(string_apple);

//unicode_apple = 6580807669
 

FoxyOfJungle

Kazan Games
Simple function to draw a sprite with aspect ratio that's fit an area:

GML:
function draw_sprite_aspect(sprite, subimage, x, y, xscale, yscale, rot, col, alpha, areaw, areah) {
    var xsc, ysc;
    if (areaw != 0) {
        xsc = areaw / sprite_get_width(sprite) * xscale;
        ysc = xsc;
    } else {
        ysc = areah / sprite_get_height(sprite) * yscale;
        xsc = ysc;
    }
    draw_sprite_ext(sprite, subimage, x, y, xsc, ysc, rot, col, alpha);
}

Original sprite size: 720 x 320
GUI size: 720 x 1280


Usage one:

GML:
var scale = device_mouse_x_to_gui() / display_get_gui_width();
draw_sprite_aspect(spr_studio_logo, 0, display_get_gui_width()/2, display_get_gui_height()/2, scale, scale, 0, c_white, 1, display_get_gui_width(), 0);



Usage two:

GML:
var scale = device_mouse_y_to_gui() / display_get_gui_height();
draw_sprite_aspect(spr_studio_logo, 0, display_get_gui_width()/2, display_get_gui_height()/2, scale, scale, 0, c_white, 1, 0, display_get_gui_height());
 
Last edited:

Samuel Venable

Time Killer
If you want your GameMaker game to open a file when it is drag-and-dropped onto the game's exe in your file explorer, you may use parameter_count() and parameter_string(). But if you drag two files one at a time onto the exe, you'll notice multiple instances of the game will open. What if you want the files to be opened by the same game instance, instead? Well thanks to my process information extension, this is now possible. For a basic demonstration, here I wrote an example which opens and plays an ogg audio file in your game's included files when you drag it onto your game executable in the file explorer, allowing for more than one audio file being played from the same instance, when dragging one audio file at a time. (make sure you turn off the splash screen so a second window won't open and close briefly).

Create Event:
GML:
exeutable_path = path_from_pid(pid_from_self());
i = 0; for (; i <= 32000; i++) {
  audio[i] = -1;
}

i = 0;
if (parameter_count() == 2) {
  if (filename_ext(parameter_string(1)) == ".ogg") {
    audio[i] = audio_create_stream(parameter_string(1));
    audio_play_sound(audio[i], 0, false);
  }
}
Step Event:
GML:
if (audio[i] != -1) {
  if (!audio_is_playing(audio[i])) {
    audio_destroy_stream(audio[i]);
  }
}

if (exeutable_path != "") {
  var duplicate_instances = pids_from_spec(exeutable_path, spec_both);
  if (duplicate_instances != "") {
    var pos = string_pos(@'|', duplicate_instances);
    if (pos != 0) {
      var dup1 = string_copy(duplicate_instances, 0, pos - 1);
      var dup2 = string_copy(duplicate_instances, pos + 1,
      string_length(duplicate_instances));
      var pid = (int64(dup1) != pid_from_self()) ? dup1 : dup2;
      if (pid_exists(int64(pid))) {
        var cmd = cmd_from_pid(int64(pid));
        pos = 0; var str = cmd;
        var j = 0; for (; j < 3; j++) {
          pos = string_pos(@'"', str); pos++;
          str = string_copy(str, pos, string_length(str));
        }
        var arg = string_copy(str, 0, string_pos(@'"', str) - 1);
        if (filename_ext(arg) == ".ogg") {
          i++; audio[i] = audio_create_stream(arg);
          audio_play_sound(audio[i], 0, false);
        }
        pid_kill(int64(pid));
      }
    }
  }
}
Download the example here:

And for those of you who want a demo executable:

If you need additional explanation on how this code works, please private message me or add me on discord, and I can also help adapt the code to your specific needs. :)
 
Last edited:
Top