• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Code Bank - share useful code!

Bart

WiseBart
It is possible in GameMaker to get the uvs for just about everything on a texture page but not for particles. The following function fixes that. It gets the uvs on the texture page for a given particle type shape (pt_shape_).
It requires a very basic shader that is used to output the uv info to a surface, which is then read into a buffer using buffer_get_surface.

Use as follows:
GML:
var _uv_info = part_type_get_uvs(pt_shape_cloud);
The function returns an array with the following information:

0. left (u)
1. top (v)
2. right (u)
3. bottom (v)
4. image width on the texture page (in pixels)
5. image height on the texture page (in pixels)
6. texel width
7. texel height

GML:
function part_type_get_uvs(_type, _sf_size = 512) {

    #region Functions to keep code a bit tidy

    function pixel_read(_buffer) {
        var _r = buffer_read(_buffer, buffer_f32);
        var _g = buffer_read(_buffer, buffer_f32);
        var _b = buffer_read(_buffer, buffer_f32);
        var _a = buffer_read(_buffer, buffer_f32);

        return [_r, _g, _b, _a];
    }

    function pixel_is_valid(_pixel) {
        // Define "some" combination of b and a to define whether a pixel is valid or not
        return (_pixel[2] == .5 && _pixel[3] == 1);
    }

    #endregion

    #region Create "dummy" particle system, type and particle

    var _ps = part_system_create();
    part_system_automatic_draw(_ps, false);
    var _pt = part_type_create();
    part_type_shape(_pt, _type);
    part_particles_create(_ps, _sf_size/2, _sf_size/2, _pt, 1);
    part_system_update(_ps);    // Update ONCE so we have a particle

    #endregion

    #region Create buffer

    var _component_size = buffer_sizeof(buffer_f32);
    var _components_per_pixel = 4;
    var _bytes_per_pixel = _components_per_pixel * _component_size;
    var _num_pixels = _sf_size * _sf_size;
    var _buffer_bytesize = _num_pixels * _components_per_pixel * _component_size;

    var _buff = buffer_create(_buffer_bytesize, buffer_grow, 1);
    buffer_fill(_buff, 0, buffer_f32, 0, _buffer_bytesize);

    #endregion

    #region Perform "drawing", i.e. get the data out

    // Create a surface of a size big enough to accommodate the sprite/texture
    var _sf = surface_create(_sf_size, _sf_size, surface_rgba32float);

    surface_set_target(_sf);

        shader_set(sha_output_uv);
    
        part_system_drawit(_ps);

        shader_reset();

    surface_reset_target();

    buffer_get_surface(_buff, _sf, 0);

    #endregion

    #region Get the values out of the buffer

    var _uvs = [0, 0, 0, 0, 0, 0, 0, 0];

    var _tw = 0, _th = 0;

    // Brute-force loop through buffer data to find start and width
    buffer_seek(_buff, buffer_seek_start, 0);
    var _pixel = -1, _topleft = -1, _width = -1;
    for(var i = 0;i < _num_pixels;i++) {
        _pixel = pixel_read(_buff);

        if (pixel_is_valid(_pixel)) {
            // This is a valid pixel written by the shader!
        
            // Process it
            if (_topleft == -1) {
                // Get texel dimensions by moving a single pixel
                var _pixel_next = pixel_read(_buff);
                _tw = _pixel_next[0] - _pixel[0];
                buffer_seek(_buff, buffer_seek_relative, (_sf_size - 1) * _bytes_per_pixel);
                var _pixel_below = pixel_read(_buff);
                _th = _pixel_below[1] - _pixel[1];
                buffer_seek(_buff, buffer_seek_relative, -(1 + _sf_size) * _bytes_per_pixel);
            
                // The first one we encounter is the top-left pixel
                var _u = _pixel[0]; _uvs[0] = floor(_u/_tw) * _tw;
                var _v = _pixel[1]; _uvs[1] = floor(_v/_th) * _th;
                _topleft = i;
            }
        }

        if ((_pixel[2] == 0 && _pixel[3] == 0) && _topleft >= 0 && _width == -1) {
            // This is the first pixel after the first row
            _width = i - _topleft;
            break;
        }
    }

    // Now that we have the width, we can find the height more efficiently
    var _height = 0;
    buffer_seek(_buff, buffer_seek_start, _topleft * _bytes_per_pixel);
    do {
        _pixel = pixel_read(_buff);
        buffer_seek(_buff, buffer_seek_relative, (_sf_size - 1) * _bytes_per_pixel);
        _height++;
    } until(!pixel_is_valid(_pixel));
    _height--;

    buffer_seek(_buff, buffer_seek_start, (_topleft+(_height-1)*_sf_size+_width-1) *_bytes_per_pixel);
    var _px = pixel_read(_buff);
    _uvs[2] = (ceil(_px[0]/_tw)) * _tw;
    _uvs[3] = (ceil(_px[1]/_th)) * _th;

    _uvs[4] = _width;
    _uvs[5] = _height;

    _uvs[6] = _tw;
    _uvs[7] = _th;

    #endregion

    #region Cleanup

    buffer_delete(_buff);
    surface_free(_sf);

    part_type_destroy(_pt);
    part_system_destroy(_ps);

    #endregion

    return _uvs;
}
C:
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;

void main()
{
    vec4 object_space_pos = vec4( in_Position, 1.0);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;

    v_vTexcoord = in_TextureCoord;
}
C:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;

void main()
{
    vec4 temp = texture2D(gm_BaseTexture, v_vTexcoord);     // FORCE a lookup!
    gl_FragColor = vec4(v_vTexcoord, .5, max(1., temp.a));
}

Note that pt_shape_pixel gives mostly wrong results. Though you can still get this info out by using the information from other shapes (texel dimensions are identical as the images are on the same texture page).
 
Last edited:
In cases where you need to find the instance ID of an object with the lowest depth among objects stacked on top of each other in a single point, I've created a function that does just that. For instance (pun intended), if you need to mouse click on an object that overlaps another object, the function will return the object with the lowest depth (i.e. the object on top) rather than both objects at once. This can be useful especially for selecting objects that are stacked on top of each other. I have dubbed this function "instance_top". Works with precise collisions and the "all" keyword.
GML:
///@arg x
///@arg y
///@arg obj
/*
    - Obtains the instance ID of the object with the lowest depth
    among objects stacked on top of each other in a given collision
    point
*/
function instance_top(argument0,argument1,argument2) {
    var _xx = argument0;
    var _yy = argument1;
    var _obj = argument2;
    var _id = noone;
    
    //Get the instance with the lowest depth
    var target_list = ds_list_create();
    var target_num = collision_point_list(_xx,_yy,_obj,true,true,target_list,true);
    if target_num > 0 {
        var target_depth = 9999;//Can be any number greater than or equal to the object with the highest depth among all object assets
        for(var i=0;i<target_num;i++) {
            var target_obj = target_list[| i];
            if target_obj.depth < target_depth {
                target_depth = target_obj.depth;
                _id = target_obj;
            }
        }
    }
    ds_list_destroy(target_list);
    
    return _id;
}
To use, simply store the return value of the function in a variable. Then if desired, you can use the instance ID to do whatever you need to do.
GML:
var top_inst = instance_top(mouse_x,mouse_y,all);
if top_inst != noone {
    //do stuff
}
 

Simon Gust

Member
I made a script for blender. It's my first experience with python, so I would appreciate critique.
Python:
import bpy
import bmesh
from mathutils import Matrix, Vector, Euler


def main(context):
    
    print("main")

    # put all selected objects in an array
    # context.view_layer.objects.active is the yellow one, add it to the array too
    selected_objects = [o for o in context.selected_objects if not o is context.view_layer.objects.active]
    selected_objects.append(context.view_layer.objects.active)

    # must selected at least 2 objects, if more -> unintended behaviour
    size = len(selected_objects)
    if size < 2:
        raise Exception("must select 2 objects")

    # static objects are the ones from 0 to len-2, active one is always the last one
    static_object = selected_objects[0]
    active_object = selected_objects[len(selected_objects)-1]

    # predefine some data, stage vertices from the vertex group
    static_group = static_object.vertex_groups["Snapper"]
    static_snappers = [vert for vert in static_object.data.vertices if static_group.index in [i.group for i in vert.groups]]
    static_location = static_object.location
    static_matrix = static_object.matrix_world

    active_group = active_object.vertex_groups["Snapper"]
    active_snappers = [vert for vert in active_object.data.vertices if active_group.index in [i.group for i in vert.groups]]
    active_location = active_object.location
    active_matrix = active_object.matrix_world

    shortestActive = None
    shortestStatic = None
    shortestDist = 1000000
    
    # go throught all vertices of both groups
    for u in active_snappers: 
        uco = active_matrix @ u.co
        for v in static_snappers:
            vco = static_matrix @ v.co
            dist = (uco - vco).length
            if dist < shortestDist :
                shortestActive = u
                shortestStatic = v
                shortestDist = dist

    # if this happens, you have probably selected a third object somewhere in the distance
    if shortestActive is None or shortestStatic is None:
        raise Exception("no close vertices found")

    # snap active to static
    shortestStaticLoc = static_matrix @ shortestStatic.co
    shortestActiveLoc = active_matrix @ shortestActive.co
    active_object.location += (shortestStaticLoc - shortestActiveLoc)


class Snapper(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.snapto"
    bl_label = "Snap Active Object to Static Object"
    
    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        main(context)
        return {'FINISHED'}


def menu_func(self, context):
    self.layout.operator(Snapper.bl_idname, text=Snapper.bl_label)


# Register and add to the "object" menu (required to also use F3 search "Snap Active Object to Static Object" for quick access).
def register():
    bpy.utils.register_class(Snapper)
    bpy.types.VIEW3D_MT_object.append(menu_func)


def unregister():
    bpy.utils.unregister_class(Snapper)
    bpy.types.VIEW3D_MT_object.remove(menu_func)


if __name__ == "__main__":
    register()
What does it do?
- it snaps the active object (the one with yellow outline) to another object (one with a red outline)
- red object is static
- yellow object is dynamic and snaps to red object
- snapping is done on the two nearest* vertices

It requires some setup:
- The objects must have *vertices in a Vertex Group called "Snapper"
- The rotation of the active object must be correct beforehand

I want to add automatic rotation based on the vertex normals
And definetly some automation, so you don't need the strict vertex group name

Here are some examples of when to use it
1683626218296.png1683626226605.png
The vertices in the Snapper group I put in the middle of the corridors, but anywhere on that plane works as long as they are consistent between objects.
Alternatively you could use the built-in vertex snapping, but it's finicky and snaps to any vertex.
 

Evanski

Raccoon Lord
Forum Staff
Moderator
useful scripts that I made while the forum is broken

Evanski's Structify!
GML:
/// @desc Converts a Struct to a Json file
/// @param {struct} struct_index Index of the struct to use
/// @param {string} json_name Name of Json file to create
/// @return {real} 0 if success, or -1 if it fails
function struct_to_json(struct_index,json_name){
    if file_exists(json_name) file_delete(json_name);
    var json_string = json_stringify(struct_index);
    var buffer = buffer_create(string_byte_length(json_string)+1, buffer_fixed, 1);
    var output = buffer_write(buffer, buffer_text, json_string);
    buffer_save(buffer,string(json_name));
    buffer_delete(buffer);
    return output;
}

/// @desc Converts a Json file to a Struct
/// @param {string} json The Json file to load.
/// @return {any} struct or -1 on fail
function json_to_struct(json){
    if !(file_exists(json)) return(-1);
    var file = buffer_load(json);
    var json_string = buffer_read(file, buffer_text);
    buffer_delete(file);
    return (json_parse(json_string));
}

/// @desc Combines structs together
/// @param {array} array of structs to combine
/// @return {struct} Combined struct
function struct_combine(structs){
    var output_struct = {};
    var struct_array_len = array_length(structs);
    for(var i = 0; i <= struct_array_len-1; ++i){
        var struct_value_name_array = struct_get_names(structs[i]);
        var len = array_length(struct_value_name_array);
        for (var j = 0; j <= len-1; ++j){
            var struct_value_values = variable_struct_get(structs[i],struct_value_name_array[j]);
            struct_set(output_struct, structs[i], struct_value_values);
        }
    }
    return output_struct;
}
QUICKSORT
This is adapted from The coding train
GML:
function array_swap(arr, a, b){
    var temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;  
}

function QuickSort(arr, start, _end){
    if (start >= _end){ return; }
    var partition = function(arr, start, _end){
        var pivotIndex = start;
        var pivotValue = arr[_end];
        for (var i = start; i < _end; ++i){
            if (arr[i] < pivotValue){
                array_swap(arr, i, pivotIndex);
                ++pivotIndex;
            }
        }
        array_swap(arr, pivotIndex, _end);
        return pivotIndex;
    }
    var index = partition(arr, start, _end);
    QuickSort(arr, start, index-1);
    QuickSort(arr, index +1, _end);
}
EDIT:
For future reference, the quickswap returns nothing, it modifies the existing array to be sorted
 
Last edited:

Joe Ellis

Member
Here's a very simple but useful function using angle_difference
If you've ever tried to lerp an angle, you'll know it has big problems on it's own cus of it wrapping around 0-360
This function makes an angle lerp in the same way as a normal number
and it doesn't matter if the input values are negative\positive, it simply lerps towards the actual direction. Perfect for smoothing turning and rotations.

GML:
function angle_lerp(val1, val2, amount)
{return val1 - (angle_difference(val1, val2) * amount)}
example usage:

GML:
//Turning towards an object

target_angle = point_direction(x, y, obj.x, obj.y)
angle = angle_lerp(angle, target_angle, 0.2)
GML:
//Smoothing turning (in a topdown game)

turn_speed = pi
turn_smooth = 0.1

input_h = keyboard_check(vk_left) - keyboard_check(vk_right)

target_angle += input_h * turn_speed

angle = angle_lerp(angle, target_angle, turn_smooth)
 
Last edited:

Bart

WiseBart
Whe working with vertex formats it can be more convenient to store the function names you'd normally use to add attributes (vertex_format_add_*) in an array and then consider that array as the "vertex format". It allows for some extra functions to get more info out, such as the format size (in bytes) and individual attribute sizes (also in bytes). Getting correct values out of GM where possible is always better than hard-coding them yourself.
(These two functions are basically the continuation of an earlier post on this)

vertex_attribute_sizes
Given an array containing any of the vertex_format_add_* function identifiers, returns an array with the number of bytes taken by the respective vertex attributes (all components):
GML:
function vertex_attribute_sizes(_arr_format) {
    static buff = buffer_create(256, buffer_fixed, 1);      // Fairly arbitrary size, static so we don't have to recreate every time
    var _len = array_length(_arr_format);
    var _arr_sizes = array_create(_len);
    for(var _i = 0; _i < _len;_i++) {
        vertex_format_begin(); _arr_format[_i]();
        var _fmt = vertex_format_end();
        var _vb = vertex_create_buffer_from_buffer_ext(
            buff, _fmt, 0, 1                                // A single vertex takes up the number of bytes of the format
        );
        _arr_sizes[_i] = vertex_get_buffer_size(_vb);       // How many bytes did we get?
        vertex_format_delete(_fmt);
        vertex_delete_buffer(_vb);
    }
    return _arr_sizes;
}
vertex_format_from_array
Creates a vertex format from an array containing any of the vertex_format_add_* functions:
GML:
function vertex_format_from_array(_arr_format) {
    vertex_format_begin();
    array_foreach(_arr_format, script_execute);
    return vertex_format_end();
}

You can then use these in your code as follows:
GML:
// "Define" the format
arr_format_description = [
    vertex_format_add_position_3d,
    vertex_format_add_normal,
    vertex_format_add_color,
    vertex_format_add_texcoord
];

// Get the number of bytes per attribute
arr_sizes = vertex_attribute_sizes(arr_format_description);
show_debug_message(arr_sizes);    // Outputs [12, 12, 4, 8] (i.e. [3*4, 3*4, 4*1, 2*4])

// Create the actual vertex format from the array
format = vertex_format_from_array(arr_format_description);

// Use the vertex format
vb = vertex_create_buffer();
vertex_begin(vb, format);
// vertex_position_3d(), etc. here
vertex_end(vb);
 

Evanski

Raccoon Lord
Forum Staff
Moderator
Useful array functions to make dealing with them a bit easier

GML:
///@desc returns if an index is in bounds of an array
///@param {real} Value index to check
///@param {array} Array array to check in
function array_inbounds(val, array){
    var len = array_length(array) - 1 ;
    
    return ( (val > -1) && !(val > len) );
}

///@desc returns the 0 indexed based length of an array
///@param {array} Array array to get the "length - 1" of
function array_length_actual(array){
    return (array_length(array) - 1);
}
 

Evanski

Raccoon Lord
Forum Staff
Moderator
ds_grid functionality but using 2D grids
If anyone wants a certain ds_grid function ported, I'll look into it

Added to FUR

GML:
/// @desc Creates and 2D array to work as a ds_grid
/// @param {real} rows the amount of rows in the grid
/// @param {real} columns the amount of columns in the grid
/// @param {any} [value] the default value to fill the grid
/// @returns {Array<>}
function es_grid_create(rows,columns,val=undefined){
    if (rows <= 0) show_error(string("<Can not create grid with row size of {0}>",rows),0);
    if (columns <= 0) show_error(string("<Can not create grid with column size of {0}>",columns),0);
  
    var array = undefined;
    for (var i = 0; i < rows; ++i){
        for (var j = 0; j < columns; ++j){
        array[i][j] = val;
        }
    }
    return array;
}

/// @desc Returns the amount of rows in a grid assuming it is square
/// @param {Array<>} es_grid index of the grid
/// @returns {Real}
function es_grid_rows(es_grid){
    return array_length(es_grid);
}

/// @desc Returns the amount of columns in a grid assuming it is square
/// @param {Array<>} es_grid index of the grid
/// @returns {Real}
function es_grid_columns(es_grid){
    return array_length(es_grid[0]);
}

/// @desc Returns the value at the given index
/// @param {Array<>} es_grid index of the grid
/// @param {real} row the row index
/// @param {real} column the column index
/// @returns {Any}
function es_grid_get(es_grid,r,c){
    return es_grid[r][c]; 
}

/// @desc Sets the value at the given index
/// @param {Array<>} es_grid index of the grid
/// @param {real} row the row index
/// @param {real} column the column index
/// @param {any} val the value to set at the index
/// @returns {Undefined} N/A
function es_grid_set(es_grid,r,c, val){
    es_grid[r][c] = val; 
}

/// @desc clears the gird to a specific value
/// @param {Array<>} es_grid index of the grid
/// @param {any} val new value for all grid cells
/// @returns {Undefined} N/A
function es_grid_clear(es_grid, val){
    var r = es_grid_rows(es_grid);
    var c = es_grid_columns(es_grid);
  
    for (var i = 0; i < r; ++i){
        for (var j = 0; j < c; ++j){
        es_grid[i][j] = val;
        }
    } 
}
GML:
array = es_grid_create(10,10,0);

show_debug_message( es_grid_rows(array) );
show_debug_message( es_grid_columns(array) );


for (var i = 0; i < 8; ++i){
    array[i][i] = 1;
}

show_debug_message(array);

es_grid_set(array,3,3,7);

show_message(es_grid_get(array, 3, 3));

show_debug_message(array);

es_grid_clear(array,9);

show_debug_message(array);
 

Evanski

Raccoon Lord
Forum Staff
Moderator
Here is a UTF8 list

I have no idea why you would be insane enough to need it, but here
GML:
enum utf_8 {
// Ord converts the char to UTF8 with decimal encoding   
//  KEY                                Dec            symbol   
    
    null=                            0,
    backspace=                        8,
    tab=                            9,
    shift=                            16,
    esc=                            27,
    space=                            32,
    exclamation=                    33,            // !
    quotation=                        34,            // "
    number_sign=                    35,            // #s
    dollar=                            36,            // $
    percent=                        37,            // %
    ampersand=                        38,            // &
    apostrophe=                        39,            // '
    left_parenthesis=                40,            // (
    right_parenthesis=                41,            // )
    asterisk=                        42,            // *
    plus_sign=                        43,            // +
    comma=                            44,            // ,
    hyphen_minus=                    45,            // -
    full_stop=                        46,            // .
    solidus=                        47,            // /
    digit_zero=                        48,            // 0
    digit_one=                        49,            // 1
    digit_two=                        50,            // 2
    digit_three=                    51,            // 3
    digit_four=                        52,            // 4
    digit_five=                        53,            // 5
    digit_six=                        54,            // 6
    digit_seven=                    55,            // 7
    digit_eight=                    56,            // 8
    digit_nine=                        57,            // 9
    colon=                            58,            // :
    semicolon=                        59,            // ;
    less_than_sign=                    60,            // <
    equals_sign=                    61,            // =
    greater_than_sign=                62,            // >
    question_mark=                    63,            // ?
    commercial_at=                    64,            // @
    A=                                65,            // A
    B=                                66,            // B
    C=                                67,            // C
    D=                                68,            // D
    E=                                69,            // E
    F=                                70,            // F
    G=                                71,            // G
    H=                                72,            // H
    I=                                73,            // I
    J=                                74,            // J
    K=                                75,            // K
    L=                                76,            // L
    M=                                77,            // M
    N=                                78,            // N
    O=                                79,            // O
    P=                                80,            // P
    Q=                                81,            // Q
    R=                                82,            // R
    S=                                83,            // S
    T=                                84,            // T
    U=                                85,            // U
    V=                                86,            // V
    W=                                87,            // W
    X=                                88,            // X
    Y=                                89,            // Y
    Z=                                90,            // Z
    left_square_bracket=            91,            // [
    reverse_solidus=                92,            // \
    right_square_bracket=            93,            // ]
    circumflex_accent=                94,            // ^
    low_line=                        95,            // _
    grave_accent=                    96,            // `
    a=                                97,            // a
    b=                                98,            // b
    c=                                99,            // c
    d=                                100,        // d
    e=                                101,        // e
    f=                                102,        // f
    g=                                103,        // g
    h=                                104,        // h
    i=                                105,        // i
    j=                                106,        // j
    k=                                107,        // k
    l=                                108,        // l
    m=                                109,        // m
    n=                                110,        // n
    o=                                111,        // o
    p=                                112,        // p
    q=                                113,        // q
    r=                                114,        // r
    s=                                115,        // s
    t=                                116,        // t
    u=                                117,        // u
    v=                                118,        // v
    w=                                119,        // w
    x=                                120,        // x
    y=                                121,        // y
    z=                                122,        // z
    left_curly_bracket=                123,        // {
    vertical_line=                    124,        // |
    right_curly_bracket=            125,        // }
    tilde=                            126,        // ~
}
 

Simon Gust

Member
A short floodfill using bfs (Breadth First Search) algo implementation for a 256x256 grid (which is internally a 1d array).

GML:
function bfs256x256(point_count=10)
{
    static bfs_neighbours = [-256, -1, +256, +1];
   
    var result = array_create(65536, 0);
    var points = array_create(65536, 0);
    var first = 0;
   
    // set starting points
    for (var n = 0; n < point_count; n++) {
        var pos = irandom(256) | irandom(256) << 8;
        var key = (irandom(255) + 1) << 16;
        result[pos] = key;
        points[n] = pos | key;
    }
   
    while (first < point_count)
    {
        // get first entry (that hasn't been already checked)
        var vertex = points[first++];
        var pos = vertex & 65535;
        var key = vertex & 4294901760;
       
        // go through neighbours
        for (var n = 0; n < 4; n++)
        {
            // is it inside the grid
            var npos = pos + bfs_neighbours[n];
            if (npos < 0 or npos > 65535) {
                continue;   
            }

            // is it not yet registered
            if (result[npos] == 0) {
                result[npos] = key;
                points[point_count++] = npos | key;
            }
        }
    }
   
    return result;
}
performance (point count does not make a difference)
- vm: ~116ms
- yyc: ~15ms

A result
1703951496419.png
bit difficult to see the edges, I'll leave this for you to fix.
 
Last edited:

lost

Member
Pseudorandom and random functions I write and use (except the last two):

GML:
// get a fuzzy version of a number
function fuzzy(a,r) {
    return (a+(random(r*2)-r));
}

// get a random 1 or -1
function randomsign() {
    return (random(50000) % 2 == 1 ? -1 : 1);
}

// get any sprite index back from a sprite
function s_any(sprite) {
    return floor(random(sprite_get_number(sprite)));
}

// get a random number back from m to n
function number_range( m, n ) {
    return m+random(n-m);
}


// plant a seed, get a pseudo random number back (fractional)
function pseud(seed) { return fmod((seed*1234.567+34567.89)*0.34567,1.0); }

// get a positive-only sin (oscillating) psuedo random value from a seed
function prabsin(seed) { return abs(sin(pi*(seed*1234.567+34567.89)*0.34567)); }

// get a positive or negative 1 from a seed
function psign(seed) { return prabsin(seed)*2.0-1.0 < 0 ? -1 : 1; }

// get a range back using prabsin instead of prab
function prab_range( seed, n, m ) { return prabsin(seed)*abs(n-m)+n; }

// get an integer back from a seed
function pseudoint(seed,_max=32767) { return floor((_max+1)*prabsin(seed)); }

// get a number from n to m from a seed
function pseudo_range( seed, n, m ) { return pseudo(seed)*abs(n-m)+n; }

// fuzzy numbers are similar to their original number
function pfuzz(seed,value,intensity) {
    return value + prabsin(seed)*intensity*psign(seed+prabsin(seed*12345));
}

// clamp an rgba struct to reasonable values (0-255,0-255,0-255,0.0-1.0)
function clamprgba01( rgba ) {
    return { r: min(1.0,max(0.0,rgba.r)), g: min(1.0,max(0.0,rgba.g)), b: min(1.0,max(0.0,rgba.b)), a: min(1.0,max(0.0,rgba.a)) };
}

// get an rgb struct back that has a value near an input color based on a seed
function fuzzrgb( seed, rgba, intensity) {
    return clamprgba01(
    {
        r: pfuzz(seed,rgba.r,intensity),
        g: pfuzz(seed+99,rgba.g,intensity),
        b: pfuzz(seed+66,rgba.b,intensity)
    });
}

// get a fuzzy {r:,g:,b:} value near an input color, where input color is rgba struct
function fuzzrgb_( seed, rgba, intensity) {
    return clamprgba01(
    {
        r: pfuzz(seed,rgba.r,intensity),
        g: pfuzz(seed+99,rgba.g,intensity),
        b: pfuzz(seed+66,rgba.b,intensity),
        a: rgba.a
    });
}

// get a color near another color using fuzz, including alpha, as {r:,g:,b:,a:} (rgba struct)
function fuzzrgba( seed, rgba, intensity) {
    return clamprgba01(
    {
        r: pfuzz(seed,rgba.r,intensity),
        g: pfuzz(seed+99,rgba.g,intensity),
        b: pfuzz(seed+66,rgba.b,intensity),
        a: pfuzz(seed+33,rgba.a,intensity)
    });
}

// get a color near another color using fuzz
function fuzzrgb_a( seed, rgba, intensity, a1, a2) {
    return clamprgba01(
    {
        r: pfuzz(seed,rgba.r,intensity),
        g: pfuzz(seed+99,rgba.g,intensity),
        b: pfuzz(seed+66,rgba.b,intensity),
        a: prab_range(seed+33,a1,a2)
    });
}

// get a pseudorandom fraction from a seed, or scale it to a _max
function pseudo(seed, _max=1) {
    if ( !variable_global_exists("pseudorandom_number") ) global.pseudorandom_number=0;
    global.pseudorandom_number=abs(global.pseudorandom_number+max(1,_max));
    return pseud(seed+global.pseudorandom_number)*_max;
}


// get a bright color from a seed
function pseudo_bright(seed) {
    var r=prabsin(seed+17218) * 255.0;
    var g=prabsin(seed+57843) * 255.0;
    var b=prabsin(seed+34231) * 255.0; 
    if ( r+g+b > 127*3 ) return make_color_rgb(r,g,b); 
    if ( r+b > 127*2 or g+b > 127*2 or r+g > 127*2 ) return make_color_rgb(r,g,b);
    if ( r < g and r < b ) {
        r=127+prabsin(seed+1234)*127.0;
        return make_color_rgb(r,g,b);
    }
    if ( b < g and b < r ) {
        b=127+prabsin(seed+1234)*127.0;
        return make_color_rgb(r,g,b);
    }
    if ( g < r and g < b ) {
        g=127+prabsin(seed+1234)*127.0;
        return make_color_rgb(r,g,b);
    }
    return make_color_rgb(r,g,b);
}

// get a bright color from a seed in { r:r, g:g, b:b } form
function pseudo_bright_rgb(seed) {
    var r=prabsin(seed+17218) * 255.0;
    var g=prabsin(seed+57843) * 255.0;
    var b=prabsin(seed+34231) * 255.0; 
    if ( r+g+b > 127*3 )
        return {r:r,g:g,b:b};
    if ( r+b > 127*2 or g+b > 127*2 or r+g > 127*2 )
        return {r:r,g:g,b:b};
    if ( r < g and r < b ) {
        r=127+prabsin(seed+1234)*127.0;
        return {r:r,g:g,b:b};
    }
    if ( b < g and b < r ) {
        b=127+prabsin(seed+1234)*127.0;
        return {r:r,g:g,b:b};
    }
    if ( g < r and g < b ) {
        g=127+prabsin(seed+1234)*127.0;
        return {r:r,g:g,b:b};
    }
    return {r:r,g:g,b:b};
}

// get a random color back from a seed using HSV method, as {r:,g:,b:,made:c}
function pseudo_hsv_rgb(seed) {
    var h=prabsin(seed)*255.0;
    var c=make_color_hsv(h,255,255);
    return { r: color_get_red(c), g: color_get_green(c), b: color_get_blue(c), made: c };
}

// get an rgb color and its assembled version
function get_rgb( r, g, b ) {
    return { r: r, g: g, b: b, made: make_color_rgb(r,g,b) };
}

// get an hsv color
function get_hsv( h, s=255, v=255 ) {
    var c=make_color_hsv(h,s,v);
    return { r: color_get_red(c), g: color_get_green(c), b: color_get_blue(c), made: c }; 
}

// get multiple seeds based on a seed
function seed_splitter ( seed ) {

return [
   seed,
   seed+123,
   seed+456,
   seed+789,
   abs(seed-seed*3),
   seed*3,
   seed*5,
   floor(seed/2),
   ceil(seed/2),
   abs(seed-123)
];

}

/// @func   gauss(m, sd)
///
/// @desc   Returns a pseudo-random number with an exact Gaussian distribution.
///
/// @param  {real}      m           mean value of the distribution
/// @param  {real}      sd          standard deviation of distribution
///
/// @return {real}      random number with Gaussian distribution
///
/// GMLscripts.com/license
function gauss(m, sd)
{
    var x1, x2, w;
    do {
        x1 = random(2) - 1;
        x2 = random(2) - 1;
        w = x1 * x1 + x2 * x2;
    } until (0 < w && w < 1);

    w = sqrt(-2 * ln(w) / w);
    return m + sd * x1 * w;
}

/// @func   roll_dice(num, sides)
///
/// @desc   Returns the sum of a number of die rolls using dice with a given
///         number of sides. For example, roll_dice(3, 6) will produce a range
///         of values from 3 to 18 with an average value of 10.5.
///
/// @param  {real}      num         number of dice to roll
/// @param  {real}      sides       number of sides on each die
///
/// @return {real}      sum of dice rolls
///
/// GMLscripts.com/license
function roll_dice(num, sides)
{
    var sum = 0;
    repeat (num) sum += floor(random(sides))+1;
    return sum;
}
 

lost

Member
Here are useful "top down shooter" utilities I find I need quite often:

GML:
// is v from A to B or A or B?

function BETWEENInclusive(v, A, B) {
    if ( A<B ) {
     if ( v>=A && v<=B ) return true;
    } else {
     if ( v<=A && v>=B ) return true;
    }
    return false;
}

// is v from A to B but not A or B?
function BETWEEN(v, A, B) {
    if ( A<B ) {
     if ( v>A && v<B ) return true;
    } else {
     if ( v<A && v>B ) return true;
    }
    return false;
}

// Clamp b between a and c
function CLAMPTORANGE(a,b,c) {
    if ( b < a ) return a;
    if ( b > c ) return c;
    return b;
}

// is v an angle that is within t-d and t+d?  if so, which is closer? d should be the "delta turn" or turn speed
// don't turn if its within the delta
function TOWARDS(v, t, d) {
    if ( BETWEEN(v,(t)-(d),(t)+(d)) ) return 0;
    else if( (t)>v ) return d;
    else if( (t)<v ) return -d;
    return 0;
}

// Is tx,ty "in front of" x,y pointing a
function IS_IN_FRONT_OF( tx, ty, x, y, a, halffov=45 ) {
    var ta=point_direction(x,y,tx,ty);
    if ( BETWEEN(ta,a-halffov,a+halffov) ) return true;
    return false;
}

// Return true if the target tx,ty is "behind" x,y facing a
function IS_BEHIND( tx, ty, x, y, a, halffov=90 ) {
    if ( not IS_IN_FRONT_OF(tx,ty,x,y,a,halffov) ) return true;
    return false;
}

// Return the angular difference, for the way we are pointing versus the way we wish to point
function TURN_DISTANCE( tx, ty, x, y, a ) {
    var ta=point_direction(x,y,tx,ty);
    return ta-a;
}
 

lost

Member
Here's a useful shader function I use all the time:

GML:
#macro s_float 1
#macro s_int 2
#macro s_sampler2D 3
#macro s_vec2 4
#macro s_vec3 5
#macro s_vec4 6
#macro s_rgb 7
#macro s_rgba 8
#macro s_mat 9
#macro s_mat_array 10
#macro s_f_array 11
#macro s_f_buffer 12

function ApplyToUniforms( shdr, d, v ) {
    var keys=variable_struct_get_names(d);
    for ( var i=0; i<array_length(keys); i++ ) {
        var k=keys[i];
        var u;
        var t=variable_struct_get(d,k);
        if ( t == s_sampler2D ) u=shader_get_sampler_index(shdr, k);
        else u=shader_get_uniform( shdr, k );
        var a=variable_struct_get(v,k);
        switch ( t ) {
            case s_float:        shader_set_uniform_f( u, a );            break;
            case s_int:            shader_set_uniform_i( u, a );            break;
            case s_sampler2D:    texture_set_stage( u, a );                break;
            case s_vec2:        shader_set_uniform_f( u, a.x, a.y );    break;
            case s_vec3:        shader_set_uniform_f( u, a.x, a.y, a.z );    break;
            case s_vec4:        shader_set_uniform_f( u, a.x, a.y, a.z, a.w ); break;
            case s_rgb:            shader_set_uniform_f( u, a.r, a.g, a.b );    break;
            case s_rgba:        shader_set_uniform_f( u, a.r, a.g, a.b, a.a );    break;
            case s_mat:
            case s_mat_array:
            case s_f_array:
            case s_f_buffer:
            break;
        }
    }
}
The above function accepts a struct which can be directly mapped to uniform variable names. Compare the following simpleStarfield shader struct to the shader provided in the example:

Example:

GML:
// during a Create or init procedure:
simpleStarfield = {
    shdr: PixelSpace_SimpleStars,
    d:{
         time: s_float,
         resolution: s_vec2,
         position: s_vec2,
         tint: s_rgb
    },
    v:{
         time: 0.0,
         resolution: res,
         position: { x: 0.0, y: 0.0 },
         tint: { r: 0.3, g:0.5, b:1.0 }
    }
};

// during the draw call:
    ApplyToUniforms( simpleStarfield.shdr, simpleStarfield.d, simpleStarfield.v );

You can update the struct in a Step and it will apply the uniforms in the Draw.

Shader from example:

Vert:
GML:
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

varying vec2 v_UV;

void main()
{
    vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
   
    v_vColour = in_Colour;
    v_vTexcoord = in_TextureCoord;
   
    vec2 CornerID = vec2( mod(in_Colour.r * 255.0, 2.0 ), mod(in_Colour.b * 255.0, 2.0 ) );
    v_UV = vec2( abs(CornerID.y-CornerID.x), CornerID.y );
}
Frag:
Code:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

varying vec2 v_UV;

uniform float time;
uniform vec2 resolution;
uniform vec2 position;
uniform vec3 tint;

float rand (in vec2 uv) { return fract(sin(dot(uv,vec2(12.4124,48.4124)))*48512.41241); }
const vec2 O = vec2(0.,1.);
float noise (in vec2 uv) {
    vec2 b = floor(uv);
    return mix(mix(rand(b),rand(b+O.yx),.5),mix(rand(b+O),rand(b+O.yy),.5),.5);
}

float draw_layer( vec2 uv, float layer, float layers, float stars ) {
        float fl = (layer);
        float s = (400.-fl*20.);
        float timeFactor = 1.0;
        if ( time < 3.14519/2.0 ) timeFactor = sin(time);
        return stars + step(.1,
            pow(noise(mod(timeFactor*vec2(uv.x*s + position.x*resolution.x - fl*100.,uv.y*s +position.y*resolution.x),resolution.x)),18.)
          )
        * (fl/layers);
}

vec3 fragment() {
    vec2 uv = v_UV;  
   
    float stars = 0.;
   
    stars = draw_layer( uv, 0.0, 4.0, stars );
    stars = draw_layer( uv, 1.0, 4.0, stars );
    stars = draw_layer( uv, 2.0, 4.0, stars );
    stars = draw_layer( uv, 3.0, 4.0, stars );
   
    return vec3(stars)*tint;
}

void main() {
gl_FragColor = v_vColour * vec4(fragment(),1.0);
}
 
Last edited:

lost

Member
Here is a mixed bag of object, color, sprite, file/io, array and structural manipulators. I wrote most of them, except some came from "Mimpy" (probably posted here or on reddit, or GameMaker Helpers discord).

GML:
// like c:> dir, returns an array of filenames in the path
function dir(glep) {
    var results=[];
    var i=0;
    var f=file_find_first(working_directory+glep, 0);
    while ( f != "" ) {
        results[i]=f;
        f=file_find_next();
        i++;
    }
    file_find_close();
    return results;
}

// useful for if you are in a web browser or not
function isHTML5() {
    return not ( os_browser == browser_not_a_browser );
}

// test if a file _can_ exist...
function FileCanExist(filename) {
    if ( isHTML5() ) return true;
    if ( file_exists(filename) ) return true;
    var _buffer = buffer_create(string_byte_length("TEST") + 1, buffer_fixed, 1);
    buffer_write(_buffer, buffer_string, "TEST");
    buffer_save(_buffer, filename);
    buffer_delete(_buffer);
    var result = file_exists(filename);
    file_delete(filename);
    return result;
}

/// Saving a string as a buffer
function StringAsFile(_filename,_string) {
    if ( not FileCanExist(_filename) ) return false;
    var _buffer = buffer_create(string_byte_length(_string) + 1, buffer_fixed, 1);
    buffer_write(_buffer, buffer_string, _string);
    buffer_save(_buffer, _filename);
    buffer_delete(_buffer);
    return true;  // TODO: Test if the buffer can be saved, return false if it cannot.
}

/// Loading a string from a buffer
function FileAsString(_filename) {
    var _buffer = buffer_load(_filename);
    if ( _buffer <0 ) return false;
    var _string = buffer_read(_buffer, buffer_string);
    buffer_delete(_buffer);
    return _string;
}

// Saves JSON data as a file
function JSONAsFile(fname,data) {
    return StringAsFile(fname,json_stringify(data));
}

// Loads JSON data from a file
function FileAsJSON(fname) {
    var s=FileAsString(fname);
    if ( s==false ) {
        if ( debug_mode ) show_debug_message(fname+" couldn't be loaded");
        return {};
    }
    return json_parse(s);
}

// Test if something exists -- generally heavy test
function exists( something ) {
    if ( is_undefined(something) ) return false;
    if ( something == undefined ) return false;
    if ( something == none ) return false;
    if ( something == noone ) return false;
    return (
            instance_exists(something)
         or sprite_exists(something)  
         or audio_exists(something)
         or is_bool(something)
         or is_array(something)
         or is_struct(something)
         or is_method(something)
         or is_numeric(something)
         or is_string(something)
         or is_ptr(something)
         or is_int32(something)
         or is_int64(something)
         or is_vec3(something)
         or is_vec4(something)
         or is_matrix(something)
    );
}

// Imprints an instance with variables from a struct
/*
function imprint_instance(id,b) {
    var k = variable_struct_get_names(b);
    var len= array_length(k);
    for (var i = 0; i < len; i++) {
        var val=variable_struct_get(b, k[i]);
        variable_instance_set(id,k[i],val);
    }
}
*/
// Imprints an instance with variables from a struct
// similar to object_merge but for applying a struct to an instance as instance variables
function imprint_instance(instance, struct) {
    var names = variable_struct_get_names(struct);
    var length = array_length(names);
    for (var i = 0; i < length; i++) {
        var name = names[i];

        var value = struct[$ name];
        if (is_method(value))
            instance[$ name] = method(instance, value);
        else
        if ( is_struct(value) ) {
            if ( debug_mode ) show_debug_message("....>"+name+" is struct: "+json_stringify(value));
            instance[$ name] = object_merge({},value);
        }
        else
            instance[$ name] = value;
    }
}

// Returns a+b, but not for instances
function object_merge(a,b,assign_dont_recurse=false) {
    var o=a;
    if (assign_dont_recurse) {
        o=a;
        var k = variable_struct_get_names(b);
        var len= array_length(k);
        for (var i = 0; i < len; i++) {
            variable_struct_set(o, k[i], variable_struct_get(b, k[i]));
        }
    } else {
        o={};
        var k = variable_struct_get_names(a);
        var len= array_length(k);
        for (var i = 0; i < len; i++) {
            var key=k[i];
            var val= variable_struct_get(a, key);
            if (is_method(val)) o[$ key] = method(o, val);
            else
            if ( is_struct(val) ) o[$ key] = object_merge({},val);
            else
            variable_struct_set(o, key, val);
        }
        k = variable_struct_get_names(b);
        len= array_length(k);
        for (var i = 0; i < len; i++) {
            var key=k[i];
            var val=variable_struct_get(b, key);
            if (is_method(val)) o[$ key] = method(o, val);
            else
            if ( is_struct(val) ) o[$ key] = object_merge({},val);
            else
            variable_struct_set(o, key, val);
        }
    }
    return o;
}

// Apply local impulse simplified
function impulse( _amount, _angle ) {
physics_apply_local_impulse(0,0,lengthdir_x(_amount,_angle),lengthdir_y(_amount,_angle));
}

// Apply local force simplified
function force( _amount, _angle ) {
physics_apply_local_force(0,0,lengthdir_x(_amount,_angle),lengthdir_y(_amount,_angle));
}


// angle in degrees, rotate x,y by a
function rotate_z( x, y, a ) {
  var c = dcos(a);
  var s = dsin(a);
  var rx= x * c - y * s;
  var ry= x * s + y * c;
//  var r=point_distance(x,y,0,0);
  return { x: rx, y: ry };
}

// angle in radians, rotate x,y by a
function rad_rotate_z( x, y, a ) {
  var c = cos(a);
  var s = sin(a);
  var rx= x * c - y * s;
  var ry= x * s + y * c;
//  var r=point_distance(x,y,0,0);
  return { x: rx, y: ry };
}

// expects degrees, rotate x,y,z by dax,day,daz, originally from Lost Astronaut's apolune github repo
function rotate_point( x_, y_, z_, dax, day, daz ) {
  var tx,ty,tz,px,py,pz;
  var ax=degtorad(dax);
  var ay=degtorad(day);
  var az=degtorad(daz);    
  // Rotate by az around Z axis
  var cz = cos(az);
  var sz = sin(az);
  tx= x_*cz - y_*sz;
  ty= x_*sz + y_*cz;
  tz= z_;
  px=tx;
  py=ty;
  pz=tz;
  // Rotate by ay around Y axis
  var cy = cos(ay);
  var sy = sin(ay);
  tx= pz*sy + px*cy;
  ty= py;
  tz= pz*cy - px*sy;
  px=tx;
  py=ty;
  pz=tz;
  // Rotate by ax around X axis
  var cx = cos(ax);
  var sx = sin(ax);
  tx= px;
  ty= py*cx - pz*sx;
  tz= py*sx + pz*cx;
  return { x: tx, y: ty, z: tz };
}

// expects radians, same as above
function rad_rotate_point( x_, y_, z_, ax, ay, az ) {
  var tx,ty,tz,px,py,pz;
  // Rotate by az around Z axis
  var cz = cos(az);
  var sz = sin(az);
  tx= x_*cz - y_*sz;
  ty= x_*sz + y_*cz;
  tz= z_;
  px=tx;
  py=ty;
  pz=tz;
  // Rotate by ay around Y axis
  var cy = cos(ay);
  var sy = sin(ay);
  tx= pz*sy + px*cy;
  ty= py;
  tz= pz*cy - px*sy;
  px=tx;
  py=ty;
  pz=tz;
  // Rotate by ax around X axis
  var cx = cos(ax);
  var sx = sin(ax);
  tx= px;
  ty= py*cx - pz*sx;
  tz= py*sx + pz*cx;
  return { x: tx, y: ty, z: tz };
}

// angles in degrees, rotates x,y,z by angles ax,ay,az around x2_,y2_,z2_
function rotate_point_around( x_, y_, z_, ax, ay, az, x2_, y2_, z2_ ) {
  var tx,ty,tz,px,py,pz;
  px=x_-x2_;
  py=y_-y2_;
  pz=z_-z2_;
  ax=degtorad(ax);
  ay=degtorad(ay);
  az=degtorad(az);    
  // Rotate by az around Z axis
  var cz = cos(az);
  var sz = sin(az);
  tx= px*cz - py*sz;
  ty= px*sz + py*cz;
  tz= pz;
  px=tx;
  py=ty;
  pz=tz;
  // Rotate by ay around Y axis
  var cy = cos(ay);
  var sy = sin(ay);
  tx= pz*sy + px*cy;
  ty= py;
  tz= pz*cy - px*sy;
  px=tx;
  py=ty;
  pz=tz;
  // Rotate by ax around X axis
  var cx = cos(ax);
  var sx = sin(ax);
  tx= px + x2_;
  ty= py*cx - pz*sx + y2_;
  tz= py*sx + pz*cx + z2_;
  return { x: tx, y: ty, z: tz };
}

// angles in radians, same as above
function rad_rotate_point_around( x_, y_, z_, ax, ay, az, x2_, y2_, z2_ ) {
  var tx,ty,tz,px,py,pz;
  px=x_-x2_;
  py=y_-y2_;
  pz=z_-z2_;
  // Rotate by az around Z axis
  var cz = cos(az);
  var sz = sin(az);
  tx= px*cz - py*sz;
  ty= px*sz + py*cz;
  tz= pz;
  px=tx;
  py=ty;
  pz=tz;
  // Rotate by ay around Y axis
  var cy = cos(ay);
  var sy = sin(ay);
  tx= pz*sy + px*cy;
  ty= py;
  tz= pz*cy - px*sy;
  px=tx;
  py=ty;
  pz=tz;
  // Rotate by ax around X axis
  var cx = cos(ax);
  var sx = sin(ax);
  tx= px + x2_;
  ty= py*cx - pz*sx + y2_;
  tz= py*sx + pz*cx + z2_;
  return { x: tx, y: ty, z: tz };
}

// mix a and b together with a mix value of v (similar to mix(a,b,0.5) in GLSL)
function color_mix(a,b,v) {
    var ra=color_get_red(a);
    var ga=color_get_green(a);
    var ba=color_get_blue(a);
    var rb=color_get_red(b);
    var gb=color_get_green(b);
    var bb=color_get_blue(b);
    return make_color_rgb(lerp(ra,rb,v),lerp(ga,gb,v),lerp(ba,bb,v));
}

// give us a square waveform from 0-1 based on value v
function squarewave( v ) {
    return v< 0.5 ? 0 : 1;
}

// lerp a-b by two values where v is the first half and va is the second half
function lerp2( a, b, v, va ) {
    return (v < va) ? lerp(a,b,v/va) : lerp(a,b,1.0-(v-va)/va);
}

// lerp a-b-c by three values
function lerp3( a, b, v, va, vb, vc ) {
    return (v < va) ? lerp(a,b,v/va) :
    ( (v < vb) ? lerp(a,b,1.0-(v-va)/(vb-va))
    : lerp(a,b,1.0-(v-vb)/(vc-vb)) );
}

// lerp2 but reversed
function lerp2_no_invert( a, b, v, va ) {  
    return (v < va) ? lerp(a,b,v/va) : lerp(a,b,(v-va)/va);
}

// lerp3 but reversed
function lerp3_no_invert( a, b, v, va, vb, vc ) {
    return (v < va) ? lerp(a,b,v/va)
    : ( (v < vb) ? lerp(a,b,(v-va)/(vb-va))
      : lerp(a,b,(v-vb)/(vc-vb)) );
}

// clamp a value within a range
function in_range( min_, max_, value ) {
    return max( min_, min( max_, value ) );
}

// startIndex is 1
function string_section( input, startIndex=1, endIndex=-1 ) {
    if ( endIndex == -1 ) endIndex=string_length(input);
    return string_copy(str,startIndex,endIndex);
}


// From Mimpy:  alternative to object_merge
function merge_structs(first, second) {
    var result = { };
    copy_struct(first, result);
    copy_struct(second, result);
    return result;
}
function copy_struct(source, destination) {
    var names = variable_struct_get_names(source);
    for (var i = 0; i < array_length(names); i++) {
        var name = names[i];
        var value = source[$ name];
        if (is_method(value))
            destination[$ name] = method(destination, value);
        else if (is_struct(value)) {
            destination[$ name] = { };
            copy_struct(value, destination[$ name]);
        }
        else if (is_array(value)) {
            destination[$ name] = [ ];
            copy_array(value, destination[$ name]);
        }
        else {
            destination[$ name] = value;
        }
    }
}
function copy_array(source, destination) {
    for (var i = 0; i < array_length(source); i++) {
        var value = source[i];
        if (is_method(value))
            destination[i] = method(destination, value);
        else if (is_struct(value)) {
            destination[i] = { };
            copy_struct(value, destination[i]);
        }
        else if (is_array(value)) {
            destination[i] = [ ];
            copy_array(value, destination[i]);
        }
        else {
            destination[i] = value;
        }
    }
}


// append two arrays (concatenation)
function array_append( array1, array2 ) {
    for(var i = 0; i < array_length(array2); i++) array1[array_length(array1)] = array2[i];
    return array1;
}




// fit text to a width
function trunc_fit(txt, w) {
    var len=string_length(txt);
    var drawn=string_width(string_hash_to_newline(txt));
    if ( drawn <= w ) return txt;
    while ( drawn > w ) {
     txt=string_copy(txt,0,len-1);
     len-=1;
     drawn=string_width(string_hash_to_newline(txt));
    }
    txt=string_copy(txt,0,len-3)+"..";
    return txt;
}

// just get the first instance in a group
function GetFirst(otype) {
    return instance_find(otype,0);
}


// normalized degree values between -180 and 180
function norm_deg( a ) {
    a=a%360;
    if ( a < -180 ) a += 360;
    if ( a > 180 ) a-=360;
    return a;
}

// Angle_to represents a line angle from a to b, facing represents how a is facing, return how much and what direction b must change to face a
function angle_diff( angle_to, facing ) {
    return norm_deg(angle_to-facing);
}

// convert a sprite to an array of color values (best used on small sprites)
function sprite_to_array( s, sindex ) {
    var out=[];
    var w=sprite_get_width(s);
    var h=sprite_get_height(s);
    var surf=surface_create(w,h);
    if ( !surf ) return false;
    surface_set_target(surf);
    draw_clear_alpha(c_black,0.0);
    draw_sprite(s,sindex,0,0);
    surface_reset_target();
    for ( var i=0; i<w; i++ ) {
     out[i]=[];
     for ( var j=0; j<h; j++ ) {
        var col = surface_getpixel_ext(surf,i,j);
        var alpha = (col >> 24) & 255;
        var blue = (col >> 16) & 255;
        var green = (col >> 8) & 255;
        var red = col & 255;
        out[i][j]={ r: red, g: green, b: blue, a: alpha };      
     }
    }
    surface_free(surf);
    return { w: w, h: h, map: out };
}

// generate a 2d struct array containing a grid of default struct (struct_value)
function gen_2d_structarray( w, h, struct_value ) {
    var m=[];
    for ( var a=0; a<w; a++ ) {
        m[a]=[];
        for ( var b=0; b<h; b++ ) m[a][b]=object_merge({ loc: {x:a,y:b} },struct_value);
    }
    return m;
}

// is the integer i in the array of ints?
function int_in( i, arr ) {
    var alen=array_length(arr);
    for ( var j=0; j<alen; j++ ) if ( arr[j] == i ) return true;
    return false;
}
 
Could you tell me the advantage to using...
GML:
// get a random 1 or -1
function randomsign() {
    return (random(50000) % 2 == 1 ? -1 : 1);
}

// get a random number back from m to n
function number_range( m, n ) {
    return m+random(n-m);
}
...over using...
GML:
choose(-1, 1);

random_range(m, n);
...?

Your fuzzy() version of a number gave me nice EarthBound battle nostalgia. $:^ ]
 

lost

Member
Could you tell me the advantage to using...

...over using...
GML:
choose(-1, 1);

random_range(m, n);
...?
You get to pick the granularity? We don't know how choose() chooses that. But to be hoenst it's probably there for backward compatibility with my C++ engine :
 

lost

Member
My contributions:

Also the content of these filtered subreddits:
And these are the GML related projects I've forked:

Well, it would be nice to see some centralized way of capturing the itch.io, github.com, marketplace and other sources for useful tools, since not everything can be captured in a single function. Also, the tickler that brought me here that appears in the forum title area was not as specific. It was only after I posted that I realized your scope is only single functions.

Single functions I've posted on r/GML are here and are either my contribution or they are credited:


Also, my "OpenGL Library" is just Objects-Wrapping-Functions, I'm not sure why you are focused on functions since we can make complex objects now. The era of GMScripts predates the ease with which we can created objects-with-methods (aka classes).

https://github.com/LAGameStudio/GMOpenGL
Implements a VBO and FBO class for treating surfaces and vertex buffers as they are in OpenGL/C++

Specifically https://github.com/LAGameStudio/GMOpenGL/blob/main/FBO.gml
 

Evanski

Raccoon Lord
Forum Staff
Moderator
Twin stick shooter Controller axis code!

image_angle = gamepad_axis_direction(0, 0, 0.05, 0.2);


GML:
function gamepad_axis_direction(device, stick, deadzone, lerp_amnt){
    
    static controller_stick_last_angle = 0;
    
    if (stick == 0){
        gp_axis_h = gp_axisrh;
        gp_axis_v = gp_axisrv;
    }else{
        gp_axis_h = gp_axislh;
        gp_axis_v = gp_axislv;    
    }
    
    var gp_h = gamepad_axis_value(device, gp_axis_h);
    var gp_v = gamepad_axis_value(device, gp_axis_v);
        
    if (abs(gp_h) <= deadzone) {
        gp_h = 0;    
    }
    if (abs(gp_v) <= deadzone) {
        gp_v = 0;    
    }
        
    if (abs(gp_h) <= deadzone && abs(gp_v) <= deadzone) {
        controller_stick_angle = controller_stick_last_angle;    
    } else {
        var angle = point_direction(0, 0, gp_h,gp_v);
        
        
        var max_angle = 360;
        var new_angle = ((angle - controller_stick_last_angle + max_angle + 180) % max_angle) - 180;
        controller_stick_angle = (controller_stick_last_angle + new_angle * lerp_amnt + max_angle) % max_angle;
    }

    controller_stick_last_angle = controller_stick_angle;
    
    return controller_stick_angle;
}
 
Last edited:

Evanski

Raccoon Lord
Forum Staff
Moderator
Also, the tickler that brought me here that appears in the forum title area was not as specific. It was only after I posted that I realized your scope is only single functions.
If you are talking about the github, thats a limitation of my current approach to making an index, Im looking into better ways to do it
 

Simon Gust

Member
An improvement to @Dragonite 's obj model load script. This one uses the new string_split functionality and supports objs exported as animations.
GML:
function load_obj(basename)
{
    #macro ANIMATION_FILES_MAX 256  
  
    // Open an obj file using a base name
    // so instead of myModel.obj, the basename would be "myModel"
    // and the function will find myModel.obj for a single frame
    // or myModel_00000.obj to myModel_99999.obj if it finds any starting with that _00000 notation
  
    // returns an array with the same amount of vertex buffers as files it found
    // for non-animated models, it's just [0] of the returned array.
  
    // make sure your model has triangulated faces, otherwise the 'f' section has 4 entries per line
    // this does not include materials, so i recommend color palette or texture atlas
  
    var time = get_timer();
    var filequeue = ds_queue_create();
    var filename = basename + ".obj";
  
    // enqueue file
    if (file_exists(filename)) {
        ds_queue_enqueue(filequeue, filename);
    }
      
    var cnt = 0;
    var filename = basename + "_000000.obj";
    while (file_exists(filename) and cnt < ANIMATION_FILES_MAX)
    {
        // enqueue file
        ds_queue_enqueue(filequeue, filename);
          
        // update filename
        var str = string(cnt);
        var len = string_length(str);
        var filename = basename + "_";
        repeat (6 - len) {
            filename += "0";
        } filename += str + ".obj";
        cnt++;
    }
  
    // iterate over filename queue
    var vertex_buffers = [];
    while (!ds_queue_empty(filequeue))
    {
        // dequeue found filename
        var filename = ds_queue_dequeue(filequeue);
        var obj_file = file_text_open_read(filename);
        var vb = vertex_create_buffer();
      
        // start writing to vertex buffer
        vertex_begin(vb, global.model_format);

        // create the lists of position/normal/texture data
        var vertex_x = ds_list_create();
        var vertex_y = ds_list_create();
        var vertex_z = ds_list_create();
        var vertex_nx = ds_list_create();
        var vertex_ny = ds_list_create();
        var vertex_nz = ds_list_create();
        var vertex_xtex = ds_list_create();
        var vertex_ytex = ds_list_create();

        // read each line in the file
        while (not file_text_eof(obj_file))
        {
            var line = file_text_read_string(obj_file);
            file_text_readln(obj_file);
      
            // split each line around the space character into an array
            var contents = string_split(line, " ");
            switch (contents[0])
            {
                case "v": // 3d position
                    ds_list_add(vertex_x, real(contents[1]));
                    ds_list_add(vertex_y, real(contents[2]));
                    ds_list_add(vertex_z, real(contents[3]));
                break;
                case "vt": // 3d normal
                    ds_list_add(vertex_xtex, real(contents[1]));
                    ds_list_add(vertex_ytex, real(contents[2]));
                break;
                case "vn": // 2d vertex coordinate
                    ds_list_add(vertex_nx, real(contents[1]));
                    ds_list_add(vertex_ny, real(contents[2]));
                    ds_list_add(vertex_nz, real(contents[3]));
                break;
                case "f": // faces, these are indices to reduce file size i presume
                    for (var n = 1; n <= 3; n++)
                    {
                        var data = string_split(contents[n], "/");
                        var v = real(data[0]) - 1;
                        var vt = real(data[1]) - 1;
                        var vn = real(data[2]) - 1;
                  
                        // look up the x, y, z, normal x, y, z and texture x, y in the already-created lists
                        var xx = ds_list_find_value(vertex_x, v);
                        var yy = ds_list_find_value(vertex_y, v);
                        var zz = ds_list_find_value(vertex_z, v);
                        var xtex = ds_list_find_value(vertex_xtex, vt);
                        var ytex = 1 - ds_list_find_value(vertex_ytex, vt);
                        var nx = ds_list_find_value(vertex_nx, vn);
                        var ny = ds_list_find_value(vertex_ny, vn);
                        var nz = ds_list_find_value(vertex_nz, vn);
              
                        // optional: swap the y and z positions (useful if you used the default Blender export settings)
                        // if you do that you'll also need to swap the normals
                        var t = yy; yy = zz; zz = t;
                        var t = ny; ny = nz; nz = t;
              
                        // add the data to the vertex buffers
                        vertex_position_3d(vb, xx, yy, zz);
                        vertex_normal(vb, nx, ny, nz);
                        vertex_texcoord(vb, xtex, ytex);
                        vertex_colour(vb, c_white, 1);
                    }
                break;
                default: break;
            }
        }

        // end the vertex buffer, destroy the lists, close the text file and return the vertex buffer
        vertex_end(vb);
      
        // there is little reason not to freeze this
        // especially for animated objs
        vertex_freeze(vb);

        ds_list_destroy(vertex_x);
        ds_list_destroy(vertex_y);
        ds_list_destroy(vertex_z);
        ds_list_destroy(vertex_nx);
        ds_list_destroy(vertex_ny);
        ds_list_destroy(vertex_nz);
        ds_list_destroy(vertex_xtex);
        ds_list_destroy(vertex_ytex);
      
        // push to array
        file_text_close(obj_file);
        array_push(vertex_buffers, vb);
    }
    var time = get_timer() - time;
    show_debug_message("obj load took: " + string(time/1000)  + "ms");
  
    return vertex_buffers;
}
 
Last edited:
Top