Procedural Creature Generation (Tutorial #1)
A downloadable asset pack
In this tutorial we are going to create a random creature generator.
(Tutorial #2 is here)
(11 Randomly generated Monsters with 24 direction movement, 10 frames each - Download included)
Prologue:
Before we begin, this tutorial is geared towards people that understand intermediate programming concepts such as Game Maker Classes, Arrays, and Vectors.
Part 1: Creating a Body Part Class
The body part class helps us keep track of the individual segments of the creature, its parent, the color, radius, and relative positioning. When we begin rotating and moving around the parts in a later tutorial this will lay the foundation
function body_part_class(_parent, _x, _y, _radius) constructor{
self.parent = _parent;
self.x = _x;
self.y = _y;
self.radius = _radius;
self.angle = 0;
self.direction = 0;
self.distance = 0;
if(is_struct(self.parent)){
var dx = _x - self.parent.x;
var dy = _y - self.parent.y;
self.angle = point_direction(dx,dy,0,0);
self.distance = point_distance(dx,dy,0,0);
}
static update = function(){
self.direction = self.angle + self.parent.direction;
self.x = self.parent.x + lengthdir_x(self.distance, self.direction);
self.y = self.parent.y + lengthdir_y(self.distance, self.direction);
}
/// figure out relative position
static set_postion = function(xp, yp){
var dx = xp - self.parent.x;
var dy = yp - self.parent.y;
self.angle = point_direction(dx,dy,0,0);
self.distance = point_distance(dx,dy,0,0);
}
}
The class has a constructor function that takes four arguments: _parent, _x, _y, and _radius. These arguments represent the parent body part (if any), the x and y coordinates of the body part, and the radius of the body part.
The constructor sets the values of these arguments as properties of the class instance (self.parent, self.x, self.y, and self.radius). It also calculates the angle and distance between the body part and its parent (if it has one), and stores these values as self.angle and self.distance.
The class also has two static methods: update and set_position. The update method updates the direction and position of the body part based on the direction and position of its parent. The set_position method updates the angle and distance between the body part and its parent based on new x and y coordinates passed to it as arguments (xp and yp).
Part 2: Drawing Segments
Each body part will connect to another body part through a simple parenting system, for each pair of parts we want to draw a segment that interpolates between the radius, position, and color. Lets create a simple function for drawing each one of these segments.
function draw_segment(partA, partB){
var dist = round(point_distance(partA.x, partA.y, partB.x, partB.y));
for(var i = 0; i < dist; i++){
var ratio = i/dist;
var cc = merge_color(partA.color, partB.color, ratio);
var rr = lerp(partA.radius, partB.radius, ratio);
var xx = lerp(partA.x, partB.x, ratio);
var yy = lerp(partA.y, partB.y, ratio);
draw_circle_color(xx,yy,rr,cc,cc,0);
}
}
This is a function that draws a segment between two body parts (partA and partB) by drawing a series of circles along the line connecting the two parts. The function first calculates the distance between the two parts using the point_distance function. It then iterates over this distance, calculating a color, radius, and position for each circle based on the properties of the two body parts and the current iteration (i).
The color of each circle is calculated using the merge_color function, which blends two colors together based on a ratio (in this case, the ratio of the current iteration to the total distance). The radius of each circle is calculated using the lerp function, which linearly interpolates a value between two other values based on a ratio. Similarly, the x and y positions of each circle are calculated using the lerp function, using the x and y coordinates of the two body parts as the values to interpolate between.
Finally, the draw_circle_color function is called to draw each circle, using the calculated color, radius, and position values. This results in a series of circles being drawn along the line connecting the two body parts, creating the appearance of a solid segment connecting the parts.
Part 3: Putting it all Together
The best way to draw such an inefficient segment function is to only do it once, on a surface, then convert the surface to a sprite (if you want to animate it) or simply draw the surface to the screen. We will now set up the test object for creating and rendering the creature.
function setup_test(){
randomize();
segments = irandom_range(2,4);
body_parts = [];
for(var i = 0; i < segments; i++){
var prnt = i > 0 ? body_parts[i-1] : -1;
var col = i % 2 == 0 ? c_red : c_blue;
array_push(body_parts, new body_part_class(prnt, 13+32*i, 25, irandom_range(4,12), col));
}
surf = -1;
}
This is a function that sets up a test scenario with a series of body parts. It does this by performing the following steps:
- It calls the randomize function to seed the random number generator with a random value.
- It sets the variable "segments" to a random number of segments between 2 and 4 (inclusive).
- It initializes an empty array called "body_parts".
- It enters a loop that iterates over the number of segments.
- For each iteration of the loop:
- It sets a variable "prnt" to the previous body part in the array (if there is one), or to -1 if this is the first body part.
- It sets a variable "col" to either the color c_red or c_blue, depending on the current iteration number.
- It adds a new body part to the end of the "body_parts" array using the body_part_class constructor, passing in the value of "prnt", a position, a random radius between 4 and 12, and the value of "col" as arguments.
- It sets a variable "surf" to -1.
This function effectively creates a series of body parts that are connected in a chain, with each part positioned slightly to the right of the previous one and with alternating colors. The number of body parts in the chain and the radius of each part are randomized.
Now we can draw the creature.
function draw_test(){
if(!surface_exists(surf)){
surf = surface_create(150, 50);
surface_set_target(surf);
draw_clear_alpha(c_white, 0);
for(var i = 0; i < array_length(body_parts); i++){
if(is_struct(body_parts[i].parent)){
draw_segment(body_parts[i].parent, body_parts[i]);
}
}
surface_reset_target();
}
draw_surface(obj_creature.surf, obj_creature.x, obj_creature.y);
}
This is a function that draws a test scenario with a series of body parts. It does this by performing the following steps:
- It checks if a surface (identified by the variable "surf") exists. If not, it creates a new surface with dimensions 150x50 pixels and sets it as the target for drawing.
- It clears the surface with a white color and an alpha value of 0.
- It enters a loop that iterates over the body parts in the "body_parts" array.
- For each iteration of the loop:
- It checks if the current body part has a parent (using the is_struct function).
- If the body part has a parent, it calls the draw_segment function to draw a segment between the parent and the current body part.
- It resets the drawing target to the default surface.
- It draws the surface (identified by the variable "surf") at the position specified by the x and y properties of an object with the name "obj_creature".
This function essentially draws a series of connected segments between the body parts in the "body_parts" array, using the draw_segment function. It then draws the resulting image on a surface, which can be displayed on the screen at a later time by drawing the surface.
Conclusion:
Creating a creature generator can be a little complicated, there are a lot of moving parts and it is pretty easy to get lost. Game Maker Studio 2 has made this a lot easier now that there are added functions for arrays and a struct system that allows us to create the "body parts" of the creature. In the next post I will add in the remaining body parts and animate it, and go over the Shader I am using to draw an outline (above image).
Download of monster pack generated from the alpha version of my creature generator is included.
Status | Released |
Category | Assets |
Author | frothzon |
Tags | 2D, Generator, Monsters, Top Down Adventure, Tutorial |
Code license | MIT License |
Asset license | Creative Commons Attribution v4.0 International |
Download
Click download now to get access to the following files:
Comments
Log in with itch.io to leave a comment.
Oh my gosh,this is simply amazing