Last active
November 27, 2024 05:38
-
-
Save steelx/c6a0847237254e9e02f2f77cf17914ac to your computer and use it in GitHub Desktop.
GameMaker Scripts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Watch demo at : youtube.com/@ajinkyax | |
The classic Behavior Tree for GameMaker 2024+ | |
I customized this to be able to have 2 new methods such as Draw and DrawGUI which also allows to display current node status | |
@example | |
// in your DrawGUI event | |
var _gui_x = room_x_to_gui(x); | |
var _gui_y = room_y_to_gui(y-sprite_height); | |
bt_root.DrawGUI(_gui_x, _gui_y, false); | |
Forked from https://github.com/VitorEstevam/GML-Behaviour-Tree | |
*/ | |
enum BTStates { | |
Running, // Task/sequence is still executing (in progress) | |
Success, // Task/sequence completed successfully | |
Failure, // Task/sequence failed to complete | |
Off // Task/sequence is inactive | |
} | |
///@abstract | |
function BTreeNode() constructor { | |
name = "BT_TREE_NODE_BASE"; | |
status = BTStates.Running; | |
visited = false; | |
children = []; | |
children_arr_len = 0; | |
black_board_ref = noone; | |
static Init = function(){} | |
static Process = function(){ | |
return BTStates.Success; | |
} | |
static ChildAdd = function(_child){ | |
array_push(children, _child); | |
++children_arr_len; | |
} | |
static FindNodeByName = function(_name) { | |
// Check if this node matches the name | |
if (name == _name) return self; | |
// Check children recursively | |
var _i = 0; | |
repeat(children_arr_len) { | |
var _found = children[_i].FindNodeByName(_name); | |
if (_found != noone) return _found; | |
++_i; | |
} | |
return noone; | |
} | |
static NodeProcess = function(_node){ | |
if(_node.visited == false){ // Initial configure | |
_node.black_board_ref = black_board_ref; | |
_node.visited = true; | |
_node.Init(); | |
} | |
var _status = _node.Process(); // Returning State | |
if(_status == BTStates.Running and black_board_ref.running_node == noone){ | |
black_board_ref.running_node = _node | |
} | |
else if( _status != BTStates.Running and black_board_ref.running_node != noone){ | |
black_board_ref.running_node = noone; | |
} | |
return _status | |
} | |
static Draw = function(_instance_id) { | |
// Base Draw method that can be overridden | |
// Call Draw on all children | |
var _i = 0; | |
repeat(children_arr_len) { | |
children[_i].Draw(_instance_id); | |
++_i; | |
} | |
} | |
static DrawGUI = function(_gui_x, _gui_y) { | |
var _color = c_white; | |
switch(status) { | |
case BTStates.Running: _color = c_yellow; break; | |
case BTStates.Success: _color = c_green; break; | |
case BTStates.Failure: _color = c_red; break; | |
case BTStates.Off: _color = c_gray; break; | |
} | |
draw_set_color(_color); | |
draw_set_halign(fa_center); | |
draw_set_valign(fa_bottom); | |
// Draw status above object | |
draw_text(_gui_x, _gui_y - 10, name); | |
if (black_board_ref != noone and black_board_ref.running_node != noone) { | |
if variable_struct_exists(black_board_ref.running_node, "selector_name") { | |
draw_text(_gui_x, _gui_y - 25, "Running: " + black_board_ref.running_node.selector_name); | |
} else if variable_struct_exists(black_board_ref.running_node, "sequence_name") { | |
draw_text(_gui_x, _gui_y - 25, "Running: " + black_board_ref.running_node.sequence_name); | |
} else { | |
draw_text(_gui_x, _gui_y - 25, "Running: " + black_board_ref.running_node.name); | |
} | |
} | |
// Reset draw properties | |
draw_set_color(c_white); | |
draw_set_halign(fa_left); | |
draw_set_valign(fa_top); | |
} | |
} | |
///@abstract | |
function BTreeComposite() : BTreeNode() constructor{} | |
///@abstract | |
function BTreeLeaf() : BTreeNode() constructor{ | |
/// @override | |
static Draw = function(_instance_id){ | |
// Base leaf Draw method | |
// This can be overridden by specific task implementations | |
// For example, GuardianPatrolTask would override this to call DrawWaypoints | |
} | |
} | |
///@abstract | |
function BTreeDecorator() : BTreeNode() constructor{ | |
/// @overwrite | |
static ChildAdd = function(child_node){ | |
children[0] = child_node; | |
children_arr_len = 1; | |
} | |
} | |
/// @param inst_id - Expects an instance id. | |
function BTreeRoot(inst_id): BTreeNode() constructor{ | |
name = "BT_ROOT"; | |
status = BTStates.Off; | |
array_push(children, noone); | |
black_board = { | |
user : inst_id, | |
root_reference : other, | |
running_node: noone, | |
}; | |
black_board_ref = black_board; | |
/// @override | |
static Init = function(){ | |
status = BTStates.Running; | |
} | |
/// @override | |
static Process = function(){ | |
if(black_board.running_node != noone) | |
NodeProcess(black_board.running_node); | |
else if(children[0] != noone){ | |
if(status == BTStates.Running) | |
NodeProcess(children[0]); | |
} | |
} | |
/// @override | |
/// @param child_node - Expects a BTreeNode. | |
static ChildAdd = function(child_node){ | |
children[0] = child_node; | |
children_arr_len = 1; | |
} | |
/// @override | |
static Draw = function(_instance_id){ | |
// Draw children (which will cascade through the tree) | |
if(children[0] != noone){ | |
children[0].Draw(); | |
} | |
// If there's a running node with its own Draw method, call it | |
if(black_board.running_node != noone && | |
variable_struct_exists(black_board.running_node, "Draw")){ | |
black_board.running_node.Draw(_instance_id); | |
} | |
} | |
/// @override | |
static DrawGUI = function(_x = 10, _y = 10, _draw_at_feets = true) { | |
// Draw root status | |
var _color = c_white; | |
switch(status) { | |
case BTStates.Running: _color = c_yellow; break; | |
case BTStates.Success: _color = c_green; break; | |
case BTStates.Failure: _color = c_red; break; | |
case BTStates.Off: _color = c_gray; break; | |
} | |
draw_set_color(_color); | |
draw_set_halign(fa_left); | |
draw_set_valign(fa_top); | |
draw_text(_x, _y, "BT Status: " + name); | |
var _scaler = _draw_at_feets ? 1 : -1; | |
var _margin = 20 * _scaler; | |
// Draw running node if exists | |
if(black_board.running_node != noone) { | |
_y += _margin; | |
draw_text(_x, _y, "Active Node: " + black_board.running_node.name); | |
_y += _margin; | |
// If running node has sequence/selector name, show it | |
var _running_node = black_board.running_node; | |
if(variable_struct_exists(_running_node, "sequence_name")) { | |
draw_text(_x, _y, $"Sequence: {_running_node.sequence_name}"); | |
} | |
else if(variable_struct_exists(_running_node, "selector_name")) { | |
draw_text(_x, _y, $"Selector: {_running_node.selector_name}"); | |
} else { | |
draw_text(_x, _y, $"Node!: {black_board_ref.running_node.name}"); | |
} | |
} | |
draw_set_color(c_white); | |
} | |
} | |
function BTreeSequence(_sequence_name = "") : BTreeComposite() constructor{ | |
name = "BT_SEQUENCE"; | |
sequence_name = _sequence_name; | |
/// @override | |
static Process = function(){ | |
var _i = 0; | |
repeat(children_arr_len){ | |
if(children[_i].status == BTStates.Running){ | |
switch( NodeProcess(children[_i])){ | |
case BTStates.Running: return BTStates.Running; | |
case BTStates.Failure: return BTStates.Failure; | |
} | |
} | |
++_i; | |
} | |
return BTStates.Success; | |
} | |
/// @override | |
static Draw = function(_instance_id){ | |
// Draw all children in the sequence | |
var _i = 0; | |
repeat(children_arr_len){ | |
children[_i].Draw(); | |
++_i; | |
} | |
} | |
} | |
function BTreeSelector(_selector_name = "") : BTreeComposite() constructor{ | |
name = "BT_SELECTOR"; | |
selector_name = _selector_name | |
/// @override | |
static Process = function(){ | |
var _i = 0; | |
repeat(children_arr_len){ | |
if(children[_i].status == BTStates.Running){ | |
switch(NodeProcess(children[_i])){ | |
case BTStates.Running: return BTStates.Running; | |
case BTStates.Success: return BTStates.Success; | |
} | |
} | |
++_i; | |
} | |
return BTStates.Failure; | |
} | |
/// @override | |
static Draw = function(_instance_id){ | |
// Draw all children in the selector | |
var _i = 0; | |
repeat(children_arr_len){ | |
children[_i].Draw(); | |
++_i; | |
} | |
} | |
} | |
function BTreeInverter() : BTreeDecorator() constructor{ | |
name = "BT_Inverter"; | |
/// @override | |
static Process = function(){ | |
var _state = NodeProcess(children[0]); | |
switch(_state){ | |
case BTStates.Failure: return BTStates.Success; | |
case BTStates.Success: return BTStates.Failure; | |
default: return _state; | |
} | |
} | |
} | |
function BTreeSucceeder() : BTreeDecorator() constructor{ | |
name = "BT_Succeeder"; | |
/// @override | |
static Process = function(){ | |
var _state = NodeProcess(children[0]); | |
return BTStates.Success; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @description draw_text_spiral_gradual by oliver @pixelchipcode | |
/// @param text - String to spiral | |
/// @param x - Center X position | |
/// @param y - Center Y position | |
/// @param radius_start - Starting radius | |
/// @param letter_spacing - Desired space between letters in pixels | |
/// @param expansion_rate - How quickly the spiral expands | |
/// DEMO https://bsky.app/profile/pixelchipcode.bsky.social/post/3l7ctn6m2ih23 | |
function draw_text_spiral_gradual(text, center_x, center_y, radius_start, letter_spacing, expansion_rate) { | |
var len = string_length(text); | |
var angle = 0; | |
var radius = radius_start; | |
// Store original text alignment settings | |
var old_halign = draw_get_halign(); | |
var old_valign = draw_get_valign(); | |
// Set text alignment to center | |
draw_set_halign(fa_center); | |
draw_set_valign(fa_middle); | |
// Track total angle for expansion calculation | |
var total_angle = 0; | |
// Draw each character | |
for (var i = 1; i <= len; i++) { | |
var char = string_char_at(text, i); | |
// Calculate position on spiral | |
var xx = center_x + lengthdir_x(radius, angle); | |
var yy = center_y + lengthdir_y(radius, angle); | |
// Draw the character rotated to follow the spiral | |
var char_angle = angle + 90; // Add 90 degrees to make characters face outward | |
draw_text_transformed(xx, yy, char, 1, 1, char_angle); | |
// Calculate next position | |
var circumference = 2 * pi * radius; | |
var angle_change = (360 * letter_spacing) / circumference; | |
// Update angle and total angle | |
angle += angle_change; | |
total_angle += angle_change; | |
// Gradually increase radius based on total rotation | |
// Using a logarithmic increase for smoother expansion | |
radius = radius_start + (expansion_rate * ln(1 + total_angle/360)); | |
} | |
// Restore original alignment settings | |
draw_set_halign(old_halign); | |
draw_set_valign(old_valign); | |
} | |
// Example usage in Draw event: | |
var my_text = "Fools rush in, where wise men never go"; | |
draw_text_spiral_gradual(my_text, room_width/2, room_height/2, 40, 8, 15); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @function draw_textbox(x, y, max_width, text, sprite, bg_color, border_color) | |
/// It uses Scribble's get_box method to calculate the text dimensions, which includes padding, | |
/// The background and border are now drawn based on the dimensions returned by get_box. | |
/// @param {Real} _x The x coordinate of the bottom-left corner of the textbox | |
/// @param {Real} _y The y coordinate of the bottom-left corner of the textbox | |
/// @param {Real} _max_width The maximum width of the textbox | |
/// @param {String} _text The text to display in the textbox | |
/// @param {String} _bg_color The background color in hex format | |
/// @param {String} _border_color The border color in hex format | |
function draw_textbox(_x, _y, _max_width, _text, _bg_color, _border_color) { | |
var _padding = 5; | |
// Create Scribble text element | |
var _text_el = scribble(_text, id) | |
.starting_format("font_tooltip", c_black) | |
.wrap(_max_width - _padding * 2); | |
// Get box dimensions | |
var _text_box = _text_el.get_bbox(); | |
// Calculate total dimensions | |
var _total_width = _text_box.width + _padding * 2; | |
var _total_height = _text_box.height + _padding * 2; | |
// Adjust y coordinate to expand upwards | |
var _box_y = _y - _total_height; | |
// Draw background | |
draw_rectangle_hex(_x, _box_y, _x + _total_width, _y, _bg_color); | |
// Draw border | |
draw_rectangle_hex(_x, _box_y, _x + _total_width, _y, _border_color, true); | |
// Draw text using Scribble | |
_text_el | |
.align(fa_left, fa_top) | |
.draw(_x + _padding, _box_y + _padding); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @function load_next_sequence(sequence_array) | |
/// @description Loads the next sequence in the array or executes a function if encountered | |
/// @param {Array} sequence_array Array of sequences or functions to cycle through | |
function load_next_sequence(sequence_array) { | |
// Check if we've gone through all items in the array | |
if (global.current_sequence_index >= array_length(sequence_array)) { | |
global.current_sequence_index = 0; // Reset to start if we've reached the end | |
return; | |
} | |
// Get the current item in the sequence array | |
var current_item = sequence_array[global.current_sequence_index]; | |
// Check if the current item is a sequence or a function | |
if (is_method(current_item)) { | |
// If it's a method/function, execute it | |
current_item(); | |
// Move to the next item in the array | |
global.current_sequence_index++; | |
// Recursively call to handle the next item | |
load_next_sequence(sequence_array); | |
} else { | |
// If it's a sequence, layer and play it | |
layer_sequence_create("Sequences", 0, 0, current_item); | |
layer_sequence_play(current_item); | |
// Optional: Add an event listener to automatically move to next sequence | |
sequence_instance_override_end_position(current_item, true); | |
sequence_instance_add_listener(current_item, | |
function() { | |
// Increment sequence index | |
global.current_sequence_index++; | |
// Load the next sequence | |
load_next_sequence(sequence_array); | |
} | |
); | |
} | |
} | |
// Initialize the global sequence index before first use | |
global.current_sequence_index = 0; | |
// Example usage in Create event or wherever you want to start sequences | |
function start_sequence_chain() { | |
var sequence_chain = [ | |
seq_1, // First sequence | |
seq_2, // Second sequence | |
function() { // Function to transition to room | |
room_goto(room_1); | |
} | |
]; | |
load_next_sequence(sequence_chain); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// @description obj_camera Create, added in rm_init | |
var _viewport_camera_index = 0; | |
_base_w = camera_get_view_width(view_camera[_viewport_camera_index]); | |
_base_h = camera_get_view_height(view_camera[_viewport_camera_index]); | |
var _x_speed = 5; | |
var _y_speed = 5; | |
var _border = 128; | |
camera = camera_create_view(0, 0, _base_w, _base_h, 0, -1, _x_speed, _y_speed, _border, _border); | |
view_set_camera(_viewport_camera_index, camera); | |
// follow (we will be following o_player, but once we init Room with player is loaded) | |
follow = noone; | |
move_to_x = x; | |
move_to_y = y; | |
// zoom_level = 1 Normal view, zoom_level > 1 zoom in, zoom_level < 1 Zoom out | |
zoom_level = 1; | |
camera_pan_speed_initial = 0.15; | |
camera_pan_speed = 1.6; | |
screen_shake = false; | |
screen_shake_amount_initial = 2; | |
screen_shake_amount = screen_shake_amount_initial; | |
// reset camera to default pan speed CAMERA_RESET_PAN 0 | |
alarm_set(CAMERA_RESET_PAN, 3); | |
function background_parallax_scrolling(_camera) { | |
var _layer_id = layer_get_id("Background"); | |
layer_x(_layer_id, lerp(0, camera_get_view_x(_camera), 0.7)); | |
layer_y(_layer_id, lerp(0, camera_get_view_y(_camera), 0.4)); | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
/// @description camera Step | |
// 1. set viewport position | |
x = lerp(x, move_to_x, camera_pan_speed); | |
y = lerp(y, move_to_y, camera_pan_speed); | |
var _w = camera_get_view_width(camera); | |
var _h = camera_get_view_height(camera); | |
var _center_x = x - _w/2; | |
var _center_y = y - _h/2; | |
camera_set_view_pos(camera, _center_x, _center_y); | |
// 2. follow camera | |
if (follow != noone and instance_exists(follow)) { | |
move_to_x = follow.x; | |
move_to_y = follow.y - global.tile_size/2; | |
} | |
#region Apply zoom | |
// Calculate target dimensions | |
var _target_w = _base_w / zoom_level; | |
var _target_h = _base_h / zoom_level; | |
var _current_w = camera_get_view_width(camera); | |
var _current_h = camera_get_view_height(camera); | |
// Smoothly interpolate to the target zoom | |
var _zoom_speed = 0.1; // Adjust this value to change zoom speed | |
var _new_w = lerp(_current_w, _target_w, _zoom_speed); | |
var _new_h = lerp(_current_h, _target_h, _zoom_speed); | |
// Update camera view size | |
camera_set_view_size(camera, _new_w, _new_h); | |
// Adjust camera position to keep it centered on the follow object | |
if (follow != noone && instance_exists(follow)) { | |
var _new_x = follow.x - _new_w / 2; | |
var _new_y = follow.y - _new_h / 2; | |
camera_set_view_pos(camera, _new_x, _new_y); | |
} else { | |
// If no follow object, center on current position | |
var _new_x = x - _new_w / 2; | |
var _new_y = y - _new_h / 2; | |
camera_set_view_pos(camera, _new_x, _new_y); | |
} | |
// Recalculate variables for other operations | |
_w = camera_get_view_width(camera); | |
_h = camera_get_view_height(camera); | |
_center_x = camera_get_view_x(camera) + _w / 2; | |
_center_y = camera_get_view_y(camera) + _h / 2; | |
#endregion | |
// limit camera movement within room width & height | |
var _xx = clamp(camera_get_view_x(camera), 0, room_width - _w); | |
var _yy = clamp(camera_get_view_y(camera), 0, room_height - _h); | |
if screen_shake { | |
// Generate random offset for shake | |
var _shake_x = choose(-screen_shake_amount, screen_shake_amount); | |
var _shake_y = choose(-screen_shake_amount, screen_shake_amount); | |
var _curr_x = camera_get_view_x(camera); | |
var _curr_y = camera_get_view_y(camera); | |
var _spd = 0.1; | |
// Apply offset to camera position | |
camera_set_view_pos(camera, lerp(_curr_x, _xx, _spd) + _shake_x, lerp(_curr_y, _yy, _spd) + _shake_y); | |
} else { | |
camera_set_view_pos(camera, _xx, _yy); | |
} | |
// BG scroll effect | |
background_parallax_scrolling(camera); | |
//////////////////////////////////////////////////////////////////////////////////////// | |
/// @description Room start | |
view_camera[0] = camera; | |
view_enabled = true; | |
view_visible[0] = true; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment