A downloadable asset pack

Download NowName your own price

This tutorial resumes where I left off on Tutorial #1 of the series (Here)

(includes downloadable code for drawing texturable body segments)

Prologue:

I spent a lot more time working on this part because the movement was very rigid and I thought about making it dynamic so it would interact with the environment. Then I realized that I would have to change almost everything I had written. I then figured out how to make the body move more organically and now I can finish this tutorial without having to re-invent my creature from scratch. (Besides, dynamic would drastically lower performance)

Part 1: Expanding the Body Part Class

Not only do we need to add appendages, but the body needs to be able to move in response to the motion of the appendages. I ended up adding in a lot more variables and found out the set_position and initial position values were broken.

function body_part_class(_parent, _x, _y, _radius, _color, _range=[0,0]) constructor{
    self.parent = _parent;
    self.x = _x;
    self.y = _y;
    self.radius = _radius;
    self.color = _color;
    self.angle = 0;
    self.direction = 0;
    self.distance = 0;
    self.type = "body";
    self.reflect = 0;
    self.range = _range;
    self.angle_start = 0;
    self.rotate = 0;
    
    
    if(is_struct(self.parent)){
        self.parent.update(-1);
        self.angle = point_direction(self.parent.x,self.parent.y,self.x,self.y);
        self.distance = point_distance(self.parent.x,self.parent.y,self.x,self.y);
        self.angle_start = self.parent.direction;
    }
    
    static update = function(ratio = -1){
        if(self.type == "leg" && ratio > -1){
            /// rotate between range0 and range1 depending on the ratio given
            /// used for animation so we can get exact position
            var sig = self.reflect ? -1 : 1;
            self.rotate = lerp(self.range[0], self.range[1], 1-ratio) * sig;
        }
        if(self.type == "body" && ratio > -1){
            self.rotate = lerp(self.range[0], self.range[1], 1-ratio)
        }
        self.direction = self.angle + self.parent.direction - self.angle_start + self.rotate;
        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){
        self.x = xp;
        self.y = yp;
        self.angle = point_direction(self.parent.x,self.parent.y,self.x,self.y);
        self.distance = point_distance(self.parent.x,self.parent.y,self.x,self.y);
    }
} 

This adds a lot more variables, most of them just so I can keep track of the rotation relative to the parent, an array called range so I can rotate between two angles when I pass in the ratio (between 0 and 1) into the step function. I then added reflect so I can change the direction of rotation of the limbs so they don't all rotate the same direction. (I could probably get rid of this and just change the start and end angles).

Part 2: Adding an appendage class

There are some variables I didn't want to have to add in to the parent class each time I called it, so I extended it with an appendage class so I can give it the type "leg," and change the sign of reflect. It is very simple but still useful.

 function appendage_class(_parent, _x, _y, _radius, _color, _reflect=1, _range=[-20,45]) : body_part_class(_parent, _x, _y, _radius, _color) constructor{
    self.type = "leg";
    self.reflect = _reflect;
    self.range = _range;
}

Part 3: Adding in the appendages

It got a little crazy adding in all the appendages because I needed a lot more local variables to keep track of where I was adding in the appendages, the probability, and how to place them.

function setup_test(){
    randomize();
    segments = irandom_range(3,5);
    body_parts = [];
    var _last_part = -1;
    var _last_leg_data = -1;
    var _leg_count = 0;
    var _reflect = 0;
    var _body_flex = 15;
    
    for(var i = 0; i < segments; i++){
        var col = i % 2 == 0 ? c_red : c_blue;
        var xto = 240-32*i;
        var yto = 128;
        var flex = i%2 == 0 ? -_body_flex : _body_flex;
        flex *= i > 0 ? ((i+2)/(segments+2)) : 0;
        
        /// create body part
        var _body_part = new body_part_class(_last_part, xto, yto, irandom_range(4,12), col, [flex, -flex]);
        
        /// track last body part added
        _last_part = _body_part;
        
        /// add body part to the array
        array_push(body_parts,     _body_part);
        
        /// additional checks for adding appendages
        if(i < 1 || i >= segments-1) continue;
        if(random(1) > (0.3 + (_leg_count < 4) * 0.65)) continue;
        
        _leg_count += 2;
        var _length = irandom_range(20,40);
        var _rad_leg = irandom_range(2,6);
        var _rad_foot = irandom_range(2,6);
        var _range_left = [-20,45];
        var _range_right = [-45, 20];
        
        if(is_struct(_last_leg_data) && random(1) > 0.2){
            _length = _last_leg_data.length;
            _rad_leg = _last_leg_data.rad_leg;
            _rad_foot = _last_leg_data.rad_foot;
        }
        
        var _leg_left = new appendage_class(_body_part, xto, yto-_length*0.5, _rad_leg, c_purple, _reflect, _range_left);
        var _leg_right = new appendage_class(_body_part, xto, yto+_length*0.5, _rad_leg, c_purple, _reflect, _range_right);
        var _foot_left = new appendage_class(_leg_left, xto, yto-_length, _rad_foot, c_orange, _reflect, _range_left);
        var _foot_right = new appendage_class(_leg_right, xto, yto+_length, _rad_foot, c_orange, _reflect, _range_right);
        
        array_push(body_parts,     _leg_left, _leg_right, _foot_left, _foot_right);
        
        /// set last leg data, most likely to re-use leg data
        _last_leg_data = {
            length: _length,
            rad_leg: _rad_leg,
            rad_foot: _rad_foot
        };
        
        /// switch direction
        _reflect = !_reflect;
    }
    sprite = -1;
    sprite_frame = 0;
    sprite_max_frames = 60;
    frame_speed = 1;
}

This one got a way from me a little bit... I decided that I want a very high probability to create the first set of legs. I also didn't want any legs on the head so I only used segments 1 to N for generating appendages. The first leg has a 95% chance of being placed (snakes are pretty boring). After the first leg is placed, any additional legs will have a 30% chance to be placed.  I then store the data I used to create the leg in a struct for later use. there is an 80%  chance of me placing the same exact leg twice. I figured it didn't make any sense for most creatures to have 3 pairs of legs that were completely different since that almost never happens. Once the variables for each appendage are generated I place a a leg in the right facing direction and an identical one in the left facing direction. 

Part 4: Creating a sprite and drawing it

I realized that doing all this drawing can be expensive, why not take all the surfaces (now that I am adding in animation) and pass them into a sprite?

function draw_test(){
    
    if(!sprite_exists(sprite)){
        var surf = surface_create(256, 256);
        var frames = sprite_max_frames;
        for(var n = 0; n < frames; n++){
            /// update
            for(var i = 0; i < array_length(body_parts); i++){
                var dir = (cos(6.2831 * n / frames) + 1) * 0.5;
                body_parts[i].update(dir);
            }
            /// draw to surface
            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();
            /// add to sprite
            if(n == 0){
                sprite = sprite_create_from_surface(surf, 0, 0, 256, 256, 1, 0, 200, 128);
            } else {
                sprite_add_from_surface(sprite, surf, 0, 0, 256, 256, 1, 0);
            }
        }
        /// remove old surface
        surface_free(surf);
    }
    var tex = sprite_get_texture(sprite, 0);
    
    var draw = function(){
        draw_sprite_ext(obj_creature.sprite, floor(sprite_frame), obj_creature.x, obj_creature.y, 1, 1, image_angle, c_white, 1);
        sprite_frame += frame_speed;
        if(sprite_frame > sprite_max_frames-1){
            sprite_frame -= (sprite_max_frames-1);
        }
    }
    
    draw_shd_outline(draw, tex, 2);
}

This one is a little less complicated. I basically take the code for drawing to a surface and I put it in a for loop for each of the frames I want to generate. The motion is pinned to the update function and I can pass in a value between 0 and 1 to determine the point on the animation. Therefore, I just need to determine the number of frames, and then pass in a value that goes from 0 to 1 and back to 0 in that amount of frames. I save each one of those frames to the canvas, and then pack them into a sprite. I just need to remember to clean up the surface when I am done and It is good to go.

Part 5: Outline Shader!

The last thing I have to do is add an outline to my creature. (I don't have to, but it looks a lot more nice) Here is the fragment shader portion of the code (The vertex shader is untouched, using the default)

// name: shd_outline
// Outline Shader -- outlines a sprite
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
/// pass in variables
uniform float pixelH;
uniform float pixelW;
void main()
{
    /// new variables
    vec2 offsetX;
    offsetX.xy = vec2(pixelW, 0);
    vec2 offsetY;
    offsetY.xy = vec2(0, pixelH);
    
    /// Find the alpha around the pixel being drawn, and return the highest one
    float maxAlpha = texture2D( gm_BaseTexture, v_vTexcoord ).a;
    maxAlpha += ceil(texture2D( gm_BaseTexture, v_vTexcoord + offsetX).a);
    maxAlpha += ceil(texture2D( gm_BaseTexture, v_vTexcoord - offsetX).a);
    maxAlpha += ceil(texture2D( gm_BaseTexture, v_vTexcoord + offsetY).a);
    maxAlpha += ceil(texture2D( gm_BaseTexture, v_vTexcoord - offsetY).a);
    /// get the alpha at the current pixel drawn
    float minAlpha = texture2D( gm_BaseTexture, v_vTexcoord ).a;
    
    /// Pass information to pixel
    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    
    /// set color to black
    if(maxAlpha >= 1.0 && minAlpha <= .2){
        gl_FragColor.r = 0.0;
        gl_FragColor.g = 0.0;
        gl_FragColor.b = 0.0;
    }
    /// get alpha info
    gl_FragColor.a = maxAlpha;
}

Here is the function we use to call the shader:


function draw_shd_outline(drawFcn, tex, lineWidth){
    var upixelH = shader_get_uniform(shd_outline,"pixelH");
    var upixelW = shader_get_uniform(shd_outline,"pixelW");
    var texelW = 0;
    var texelH = 0;
    texelH = lineWidth*texture_get_texel_height(tex);
    texelW = lineWidth*texture_get_texel_width(tex);
    
    shader_set(shd_outline);
    /// set variables to texel size
    shader_set_uniform_f(upixelW,texelW);
    shader_set_uniform_f(upixelH,texelH);
    /// draw stuff
    drawFcn();
    shader_reset();
}

The above code finds a pixel with zero alpha and turns it to 1 alpha and RGB of 0,0,0 if one of the pixels next to has a higher opacity than 0.2.

(Optional Download for draw_capsule function)

How to Apply Optional Code 


function draw_segment(partA, partB){
    /// call draw capsule
    var sprite = insert_sprite_name_here;    /// sprite to use
    var index = 0;    /// index of sprite to use
    
    draw_capsule(partA.x,partA.y-partA.z,partA.radius,partA.color,partB.x,partB.y-partB.z,partB.radius,partB.color,sprite,index);
}

Conclusion:

With a lot more work, a lot of playing around, and a heck of a lot of determination one can create a pretty interesting creature generator. There are a lot more things we can add to make this really stand out, which is where I leave you to experiment with. If you add in some sprites you can create eyes, feet, and textures (or even armor) for your creatures and really make them look amazing.


StatusReleased
CategoryAssets
Rating
Rated 5.0 out of 5 stars
(1 total ratings)
Authorfrothzon
Code licenseMIT License
Asset licenseCreative Commons Attribution v4.0 International

Download

Download NowName your own price

Click download now to get access to the following files:

draw_capsule.zip 984 bytes

Comments

Log in with itch.io to leave a comment.

Fantastic! I am enjoying your amazing work. I hope you keep bringing more tutorials. Thanks!