Fruitilities

Table of Contents

Under construction !!!!11!1

Fruitilities are being rewritten. This page is entirely outdated and wrong.

Overview

Fruitilities is a vanilla lua compatible set of libraries made for the LÖVE game engine.

Made to work in harmony

I was a bit upset at how some libraries for LÖVE seemed to not cooperate with each other too well, which ended up forcing compromises to happen. While this was rare and not often an issue, I still wanted to make a set of libraries designed specifically to work together well.

(the real reason for making these is because i was too lazy to learn how to use other peoples' libraries so i just made my own)

Compatibility

For all libraries where it made sense to do so, I've made them vanilla lua compatible. Meaning that while these were made for Love2D, you can use them to aid you in your Lua shenanigans in any context.

Specifically, the libraries were made to work with Lua 5.1 (LuaJIT), but most likely work in newer versions too.

Cocollision

Performs collision calculations.

You start by creating a hitbox and adding shapes to it. The shapes a hitbox can have are an axis aligned bounding box, a convex polygon, a point, a line, a line segment, and a raycast. You then create so-called controllers from these hitboxes, which you can use directly as in-game objects. These controllers are also designed to work in harmony with sprite controllers - more on that later.

Usage example below.


local coco = require("cocollision")

local mouseSquare
local passiveSquare

function love.load()
    local box
    box = coco.newHitbox(50, 50) -- Set the origin to be in the center (50px, 50px)

    -- Any amount of shapes can be added
    -- Only ever adding one increases simplicity
    box:addShape("polygon", 0, 0, 100, 0, 100, 100, 0, 100)

    mouseSquare = box:createController()
    passiveSquare = box:createController() -- Any amount of controllers can exist using a given hitbox

    passiveSquare.x = 200
    passiveSquare.y = 275
end

function love.update()
    local mx, my = love.mouse.getPosition()
    mouseSquare.x = mx
    mouseSquare.y = my
    mouseSquare.angle = mouseSquare.angle + 0.001

    -- We know a pushVector will always be returned on hit
    -- because the only shape we have is a polygon, and that returns a pushVector
    local hit, _, _, pushVector = mouseSquare.intersects(passiveSquare)
    if (hit) then
        passiveSquare.x = passiveSquare.x - pushVector[1]
        passiveSquare.y = passiveSquare.y - pushVector[2]
    end
end

function love.draw()
    mouseSquare.drawHitbox() -- Draw functions for debugging purposes
    passiveSquare.drawHitbox()
end

Animango

Takes care of taking sprites from spritesheets, cropping them out, and animating them. Sprites have the ability to have "animation events", to trigger things to happen once an animation gets to a specific frame. This is useful for, for example, syncing footstep sounds with a walk animation.

This is the only library that is not vanilla lua compatible. This is because it's so graphics-heavy (which vanilla lua doesn't have a way to do by itself), so making this library work in other environments would get rid of the vast majority of features it provides.

Spritesheet used, usage, and result below.

spritesheet

love.graphics.setDefaultFilter("nearest", "nearest") -- Configure LÖVE for pixelart

local mango = require("animango")

local sprite
local controller

function love.load()
    local spriteImage = love.graphics.newImage("symbol.png")

    -- Create sprite from the image,
    -- set the size of the sprite within the spritesheet as 8x8px,
    -- set the origin to X=0.5, Y=0.5 (center),
    -- set the "crop" value to 1px
    -- (usually the cropping would be removing pixels which are there to prevent bleeding)
    sprite = mango.newSprite(spriteImage, 8, 8, 0.5, 0.5, 1)

    -- Add animation at 5fps including all frames in the spritesheet at 
    sprite:addAnimation("cycleSymbols", 5)

    -- Add a similar animation that only includes letters, not numbers (frames 1-26)
    sprite:addAnimation("cycleLetters", 5, 1, 26)

    -- Set up a controller
    controller = sprite:createController()
    controller.setAnimation("cycleLetters")
    controller.setScale(10)
    controller.x = 250
    controller.y = 250
end

function love.update(dt)
    controller.update(dt) -- Necessary for animations
end

function love.draw()
    controller.draw()
end
resulting animation

Animango & Cocollision

Cocollision and animango are designed to work together. Both work by creating controllers that you can then manipulate with and call functions on.

The way these libraries synergise is that when creating a controller with one of them, you can pass in an already existing controller previously made with the other. This way you end up with a single controller controlling both collisions and animations, since those two have many shared values, such as the position or scale of the controller.

So a controller created this way will have both the methods for manipulating its visual and collision aspect, but the values for those two are shared among them. This is best explained with an example, provided below.


love.graphics.setDefaultFilter("nearest", "nearest") -- Configure LÖVE for pixel art

local coco = require("cocollision")
local mango = require("animango")

local sprite
local hitbox
local controllers

function love.load()
    local spriteImage = love.graphics.newImage("symbol.png")

    -- Create sprite
    sprite = mango.newSprite(spriteImage, 8, 8, 0.5, 0.5, 1)
    sprite:addAnimation("cycleSymbols", 5)

    -- Create hitbox
    hitbox = coco.newHitbox(3, 3)
    hitbox:addShape("rectangle", 6, 6) -- 1px smaller than the size set in the sprite because we cropped it

    -- Create a few controllers
    controllers = {}
    controllers[#controllers+1] = hitbox:createController(sprite:createController()) -- Combine them in one line
    controllers[#controllers+1] = hitbox:createController(sprite:createController())
    controllers[#controllers+1] = hitbox:createController(sprite:createController())

    -- Scale them up, set their animations, move them more to the center
    for _, controller in ipairs(controllers) do
        controller.setScale(10)
        controller.setAnimation("cycleSymbols")
        controller.x = 400
        controller.y = 250
    end
end

function love.update(dt)

    -- Update all controllers
    for index, controller in ipairs(controllers) do

        if (index == 1) then
            -- Move first controller with mouse
            local mx, my = love.mouse.getPosition()
            controller.update(dt, mx, my)
        else
            -- Just update the others
            controller.update(dt)
        end

        -- Solve collisions
        for _, otherController in ipairs(controllers) do
            if controller == otherController then break end

            local hit, _, _, pushVector = controller.intersects(otherController)
            if hit then
                controller.x = controller.x + pushVector[1] / 2
                controller.y = controller.y + pushVector[2] / 2

                otherController.x = otherController.x - pushVector[1] / 2
                otherController.y = otherController.y - pushVector[2] / 2
            end
        end
    end
end

function love.draw()
    -- Draw all controllers
    for _, controller in ipairs(controllers) do
        controller.draw()
    end
end
resulting animation

This is quite a bit of a longer example for the sake of showing something slightly more robust. In this setup, the "controllers" table can have any controllers whose hitbox only has 1 shape that is either a rectangle or a polygon. They will all collide together correctly and animate whatever sprite is attached to them.

Each controller's animation can be controlled seperately, although in this example, all are set to the same sprite and animation at the same speed.

Other Libraries

I mainly wanted to cover Animango and Cocollision so far, as those are more or less done. When Fruitilities as a whole are ready, each library will be covered.