Cute Animation with Primitive Shapes


I wanted to share the animation process for creating Harley the Hare, the main character from my latest game Hops and Harmony


Harley, along with all the characters in the game are just primitive shapes drawn onto the screen

You can especially see this in the aptly named Sheepa below when a light is cast from above


Let's draw Harley the Hare!

I used the Godot Engine but you can use any engine or tool as long as there is a way to do custom drawing in 2D

Start with a basic circle

export var body_radius = 40
func _draw():
    var screen_center = get_viewport().size / 2
    draw_circle(screen_center, body_radius, Color.pink)

Convert to 2 semicircles

Drawing a circle is great and all but what we really want is 2 adjustable semicircles. We can use this arc function to help us define our own custom method for creating semicircles supported by custom parameters such as radius, angle, and curviness. The white circles are the points that the circular arc function generates (the nb_points parameter determines how many points each arc should take)


export var body_radius = 40
func _draw():
    var screen_center = get_viewport().size / 2
    draw_circular_arc(screen_center, body_radius, 0, 180, Color.pink, 1.0, 6)
    draw_circular_arc(Vector2(screen_center.x - 20, screen_center.y), body_radius, 180, 360, Color.pink, 1.0, 6)
#
func draw_circular_arc(center, radius, angle_from, angle_to, color, curviness, nb_points):
    var points_arc = PoolVector2Array()
    for i in range(nb_points + 1):
        var angle_point = deg2rad(angle_from + i * (angle_to-angle_from) / nb_points - 90)
        var next_point = Vector2(cos(angle_point) * 1 / curviness, \
            sin(angle_point) * curviness) * radius
        points_arc.push_back(center + next_point)
        draw_circle(center + next_point, 5, Color.white)
    draw_colored_polygon(points_arc, color)

By modifying the curviness parameter, we can alter the curviness of the circle


Update curviness dynamically every frame

I created curviness, curviness_rate, and is_curving_up variables to facilitate the curviness variable oscillating from 0.8 - 1.2

It's breathing!

var curviness = 1.0
var curviness_rate = .1
var is_curving_up = true
func _process(delta):
    if is_curving_up:
        curviness += curviness_rate * delta
    else:
        curviness -= curviness_rate * delta
    if curviness < .9:
        is_curving_up = true
    elif curviness > 1.1:
        is_curving_up = false
    update()

Add the head

For the head, we'll just bob it up and down using the same curviness parameter


func draw_head():
    var screen_center = get_viewport().size / 2
    var head_offset_y = 50 * curviness
    var head_center = Vector2(screen_center.x, screen_center.y - head_offset_y)
    draw_circular_arc(head_center, head_radius, \
        0, 180, Color('c06c84'), 1.0, 8)
    draw_circular_arc(head_center, head_radius, \
        180, 360, Color('6c5b7b'), 1.0, 8)

Add eyes and nose

The eyes and nose are just circles with offsets. Use the head center position as a base for drawing the eyes and nose


func draw_face(head_vec, head_offset_x):
    var eye_height := 5.0
    var eye_between_width := 9.0
    var eye_radius := 3.5
    var left_eye = Vector2(head_vec.x - eye_between_width + head_offset_x, head_vec.y - eye_height)
    var right_eye = Vector2(head_vec.x + eye_between_width + head_offset_x, head_vec.y - eye_height)
    draw_circle_custom(left_eye, eye_radius, eye_color)
    draw_circle_custom(right_eye, eye_radius, eye_color)
    
    var nose_radius := 4.0
    var nose_offset_y := 2.0
    draw_circle_custom(Vector2(head_vec.x + head_offset_x, head_vec.y + nose_offset_y), nose_radius, pink_color)

Since we have a good understanding of the semicircles, let's go back to just pink


Add left/right offsets based on direction

var face_offset_x = 0
func draw_head():
    var screen_center = get_viewport().size / 2
    var head_offset_y = 50 * curviness
    var head_center = Vector2(screen_center.x + face_offset_x / 2, screen_center.y - head_offset_y)
    draw_circular_arc(head_center, head_radius, \
        0, 180, primary_color, 1.0, 8)
    draw_circular_arc(head_center, head_radius, \
        180, 360, primary_color, 1.0, 8)
    if is_walking_right():
        face_offset_x = 5
    if is_walking_left():
        face_offset_x = -5
    draw_face(head_center, face_offset_x)

Add ears

There are circles for the outer ear, smaller circles for the pink inner ear, and a primitive shape from the radius of the outer ear down to a little above the eyes


That's it!

To finish up, just add a collider for physics and stretch the body whenever you jump/land. I added some little particle effects on landing as well

For more devlogs, updates, and tutorials feel free to follow me on itch.io or twitter

Cheers!

---

You can check out Hops and Harmony at https://pyrecraft.itch.io/hops-and-harmony

Hops and Harmony
when in doubt, hop about!

Get Hops and Harmony

Download NowName your own price

Comments

Log in with itch.io to leave a comment.

how would one follow this in game maker?

(1 edit)

Hi - looks like there is a way to draw primitives in gamemaker: https://docs2.yoyogames.com/source/_build/3_scripting/4_gml_reference/drawing/primitives/draw_primitive_begin.html

You can try and translate my godot code to gamemaker code

An alternative to the drawing primitive approach altogether would be using sprites + scaling the sprite x/y values to stretch the circle upward and sideways

Let me know if you have any questions about these approaches