Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
646d9d2
basic sprite implementation
DomDomHaas Feb 9, 2025
06983e0
added the basics for collision;
DomDomHaas Feb 18, 2025
28920b5
added setCenter, getCenter, getCenterPoint
DomDomHaas Feb 20, 2025
3deb898
WIP: rendering the sprite image with "colors" (untested)
Sep 15, 2025
402a947
Merge branch 'main' into feature/sprite
Sep 15, 2025
ae5fae8
fixed zero tiles in a tilemap causing error
Jan 29, 2026
ad1511c
fixed tilemap _height not being initialized from setTiles()
Jan 29, 2026
d3f5571
fixed invalid tile index calculation in getTileAtPosition()
Jan 29, 2026
f3b7b1a
fixed getTileAtPosition() not returning nil when x or y are out of bo…
Jan 29, 2026
94b504c
fixed setTileAtPosition() not working properly
Jan 29, 2026
932beaf
implemented tilemap getSize() and getPixelSize()
Jan 29, 2026
e959ebb
optimize drawing loop
Jan 29, 2026
7bbcab4
fixed tilemap draw() not using x, y
Jan 29, 2026
e382c3d
checked tile is not nil before drawing
Jan 29, 2026
a387a41
fix error when tile index is out of range
Feb 1, 2026
2aa7e89
implemented image:copy() and image:scaledImage()
Feb 2, 2026
5a3f53d
fixed drawing functions to support scaled images
Feb 2, 2026
f4a492f
partial geometry implementation
Feb 3, 2026
a10d9ae
affineTransform implementation
Feb 5, 2026
7253572
polygon implementation
Feb 6, 2026
0b59962
fix arc.pointOnArc
Feb 6, 2026
ee01aa9
animator implementation
Feb 6, 2026
9cd337d
implemented input handlers
Feb 3, 2026
696a354
running playdate.update as a coroutine
Feb 7, 2026
0458bb9
implemented math
Feb 7, 2026
d0d91a2
added graphics stubs
Feb 10, 2026
71a7a29
Merge branch 'feature/input-handlers' into 123
Feb 10, 2026
1d816a8
Merge branch 'bugfix/fix-issue-62' into 123
Feb 10, 2026
ec93571
Merge branch 'feature/scaled-image' into 123
Feb 10, 2026
d6bd071
Merge branch 'bugfix/fix-issue-81' into 123
Feb 10, 2026
b351a41
Merge branch 'feature/math' into 123
Feb 10, 2026
64b9a1f
Merge branch 'feature/graphics-stubs' into 123
Feb 10, 2026
ec99b42
Merge branch 'feature/geometry-api' into 123
Feb 10, 2026
67bcc66
fixed graphics.drawArc()
Feb 1, 2026
7a35fff
add support for geometry objects as parameters in drawing functions
Feb 7, 2026
299da20
fixed image.new() not trying to use provided path as is
Feb 7, 2026
6b8dac2
fixed button functions being case-sensitive
Feb 10, 2026
0b0d568
added playdate stubs
Feb 10, 2026
72ffd7c
fix typo
Feb 10, 2026
5e5e46c
use dpi awareness on windows
Feb 2, 2026
3ba0085
initialize playbit window using initial love.window mode
Feb 11, 2026
291afe6
Merge branch 'bugfix/fix-issue-76'
Feb 11, 2026
f8965f8
Merge branch 'feature/animator'
Feb 11, 2026
6eb24b6
Merge branch 'bugfix/fix-issue-86'
Feb 11, 2026
f15b007
Merge branch 'bugfix/fix-issue-88'
Feb 11, 2026
7c65f7e
Merge branch 'feature/playdate-stubs'
Feb 11, 2026
9770360
added new shaders
Feb 10, 2026
38e3b1c
pass drawing color directly to the color shader
Feb 10, 2026
466423c
added three drawing modes: line, fill, image
Feb 10, 2026
bfdfae7
implemented xor and nxor image drawing modes (poc)
Feb 10, 2026
e1fbab8
Merge branch 'feature/graphics-rework'
Feb 11, 2026
f95c91b
Merge branch 'feature/xor-nxor-modes'
Feb 11, 2026
b65e11c
Merge branch 'bugfix/1-dpi-awareness'
Feb 11, 2026
d0c2f12
Merge branch 'feature/2-love-window-init'
Feb 11, 2026
6c1e058
implemented better pushContext()/popContext()
Feb 10, 2026
ff52e5d
remove updateContext() calls
Feb 10, 2026
6613f21
Merge branch 'bugfix/fix-issue-64-contexts'
Feb 11, 2026
9ae8546
Merge remote-tracking branch 'DomDomHaas/feature/sprite' into feature…
Feb 11, 2026
5793f46
renamed file to match Playdate naming
Feb 11, 2026
80f006c
reformat sprites.lua using 2-space indentation
Feb 11, 2026
f9ee20f
implemented sprites stubs
Feb 11, 2026
6feee15
allow sprite to be inheritable
Feb 11, 2026
81dea03
implemented playdate.graphics.sprite.update()
Feb 11, 2026
6a448d8
call sprite:update() implementation
Feb 11, 2026
778eaa4
implemented setBounds(), getBounds()
Feb 11, 2026
9c8fdde
do not check collisions if collideRect is not set
Feb 11, 2026
5c9c464
cleanup
Feb 11, 2026
7d305d1
allow setting null image
Feb 11, 2026
0e3222f
properly handle if sprite has image set or custom draw() implementation
Feb 11, 2026
29dcc8d
implemented setAnimator(), removeAnimator()
Feb 11, 2026
f53f0d7
implemented setCollisionsEnabled(), collisionsEnabled()
Feb 11, 2026
cc8d97d
fix stack overflow during sprite updates
Feb 12, 2026
27c65e1
fixed sprite being added automatically (self.added not being set)
Feb 12, 2026
2f58825
fixed typo
Feb 12, 2026
9817ccd
implemented sprite.removeAll() and sprite.removeSprites()
Feb 12, 2026
0055b9b
proper implementation of sprite.setBackgroundDrawingCallback()
Feb 12, 2026
4dab26b
implement sprite:setIgnoresDrawOffset()
Feb 11, 2026
8a429a7
sort sprites once per frame at most
Feb 13, 2026
9fc995a
implement sprite:setImageFlip()
Feb 13, 2026
c68e189
prepended _ (underscore) to sprite internal fields
Feb 13, 2026
09aa510
implemented sprite:setImageDrawMode()
Feb 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions conf.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-- conf.lua
if love._os == "Windows" then
local ffi = require "ffi"
ffi.cdef[[ bool SetProcessDPIAware(); ]]
ffi.C.SetProcessDPIAware();
end

function love.conf(t)
-- TODO: only enable in debug mode
t.console = true
Expand Down
38 changes: 25 additions & 13 deletions header.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
!if LOVE2D then
require("playbit.graphics")

--[[ since there is no CoreLibs/playdate, this file should always
--[[ since there is no CoreLibs/playdate, this file should always
be included here so the methods are always available ]]--
require("playdate.playdate")
--[[ not really a way around including this one, but probably doesn't really
Expand All @@ -19,7 +19,14 @@ function import(path)
end

local firstFrame = true
local windowWidth, windowHeight = playbit.graphics.getWindowSize()

-- initialize playbit window using initial love.window mode
local windowWidth, windowHeight, windowFlags = love.window.getMode()
playbit.graphics.setWindowSize(windowWidth, windowHeight)
playbit.graphics.setFullscreen(windowFlags.fullscreen)
-- scale canvas to fit the window
local canvasWidth, canvasHeight = playbit.graphics.getCanvasSize()
playbit.graphics.setCanvasScale(windowWidth / canvasWidth)

playbit.graphics.canvas:setFilter("nearest", "nearest")

Expand All @@ -35,6 +42,8 @@ math.randomseed(os.time())
local font = playdate.graphics.font.new("fonts/Phozon/Phozon")
playdate.graphics.setFont(font)

local updateCoroutine

function love.draw()
-- must be changed at start of frame when canvas is not active
local newCanvasWidth, newCanvasHeight = playbit.graphics.getCanvasSize()
Expand Down Expand Up @@ -66,9 +75,8 @@ function love.draw()

-- render to canvas to allow 2x scaling
love.graphics.setCanvas(playbit.graphics.canvas)
love.graphics.setShader(playbit.graphics.shader)

--[[
--[[
Love2d won't allow a canvas to be set outside of the draw function, so we need to do this on the first frame of draw.
Otherwise setting the bg color outside of playdate.update() won't be consistent with PD.
--]]
Expand All @@ -86,7 +94,14 @@ function love.draw()
love.graphics.translate(playbit.graphics.drawOffset.x, playbit.graphics.drawOffset.y)

-- main update
playdate.update()
if not updateCoroutine or coroutine.status(updateCoroutine) == "dead" then
updateCoroutine = coroutine.create(playdate.update)
end

local ok, err = coroutine.resume(updateCoroutine)
if not ok then
error(err)
end

-- debug draw
if playdate.debugDraw then
Expand All @@ -102,22 +117,19 @@ function love.draw()
love.graphics.setCanvas()

-- clear shader so that canvas is rendered normally
love.graphics.setShader()

-- always render pure white so its not tinted
local r, g, b = love.graphics.getColor()
love.graphics.setColor(1, 1, 1, 1)
local shader = love.graphics.getShader()
love.graphics.setShader(playbit.graphics.shaders.final)

-- draw canvas to screen
local currentCanvasScale = playbit.graphics.getCanvasScale()
local x, y = playbit.graphics.getCanvasPosition()
love.graphics.draw(playbit.graphics.canvas, x, y, 0, currentCanvasScale, currentCanvasScale)

-- reset back to set color
love.graphics.setColor(r, g, b, 1)
-- reset back the shader
love.graphics.setShader(shader)

-- update emulated input
playdate.updateInput()
end

!end
!end
117 changes: 81 additions & 36 deletions playbit/graphics.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,47 @@ module.COLOR_BLACK = { 49 / 255, 47 / 255, 40 / 255, 1 }

module.colorWhite = module.COLOR_WHITE
module.colorBlack = module.COLOR_BLACK
module.shader = love.graphics.newShader("playdate/shader")

module.shaders =
{
final = love.graphics.newShader("playbit/shaders/final.glsl"),
color = love.graphics.newShader("playbit/shaders/color.glsl"),
pattern = love.graphics.newShader("playbit/shaders/pattern.glsl"),
image = { }
}

local shader = love.filesystem.read("playbit/shaders/image.glsl")
for i = 0, 9 do
local src = "#define DRAW_MODE " .. i .. "\n" .. shader
module.shaders.image[i] = love.graphics.newShader(src)
end

module.shader = nil
module.drawOffset = { x = 0, y = 0}
module.drawColorIndex = 1
module.drawColor = module.colorWhite
module.backgroundColorIndex = 0
module.backgroundColor = module.colorBlack
module.activeFont = {}
module.drawMode = "copy"
module.imageDrawMode = 0
module.canvas = love.graphics.newCanvas()
module.contextStack = {}
-- shared quad to reduce gc
module.quad = love.graphics.newQuad(0, 0, 1, 1, 1, 1)
module.lastClearColor = module.colorWhite
module.drawPattern = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
module.drawPattern = nil
module.lineWidth = 1

module.textToImageDrawMode = {
["copy"] = 0,
["inverted"] = 7,
["xor"] = 5,
["nxor"] = 6,
["whitetransparent"] = 1,
["blacktransparent"] = 2,
["fillwhite"] = 3,
["fillblack"] = 4
}

local canvasScale = 1
local canvasWidth = 400
Expand Down Expand Up @@ -107,44 +134,62 @@ end
---@param white table An array of 4 values that correspond to RGBA that range from 0 to 1.
---@param black table An array of 4 values that correspond to RGBA that range from 0 to 1.
function module.setColors(white, black)
if white == nil then
white = module.COLOR_WHITE
end
if black == nil then
black = module.COLOR_BLACK
end

module.colorWhite = white
module.colorBlack = black
module.shader:send("white", white)
module.shader:send("black", black)

if module.backgroundColorIndex == 1 then
module.backgroundColor = module.colorWhite
else
module.backgroundColor = module.colorBlack
end
module.colorWhite = white or module.COLOR_WHITE
module.colorBlack = black or module.COLOR_BLACK
module.shaders.final:send("white", white)
module.shaders.final:send("black", black)
end

local function copyAndSwapCanvases()
local shader = love.graphics.getShader()

if module.drawColorIndex == 1 then
module.drawColor = module.colorWhite
else
module.drawColor = module.colorBlack
-- create second canvas if needed of the same size.
if not module.canvas2 then
local w, h = module.canvas:getWidth(), module.canvas:getHeight()
module.canvas2 = love.graphics.newCanvas(w, h)
end

-- copy original canvas to another one
love.graphics.push()
love.graphics.origin()
love.graphics.setCanvas(module.canvas2)
love.graphics.setShader()
love.graphics.draw(module.canvas)
love.graphics.pop()

-- swap canvases.
module.canvas, module.canvas2 = module.canvas2, module.canvas

-- restore shader and the color
love.graphics.setShader(shader)
end

function module.updateContext()
if #module.contextStack == 0 then
return
end
local function getShader(mode, imageDrawMode)
if mode == "line" then
return module.shaders.color

local activeContext = module.contextStack[#module.contextStack]
elseif mode == "fill" then
if module.drawPattern then
return module.shaders.pattern
else
return module.shaders.color
end

-- love2d doesn't allow calling newImageData() when canvas is active
love.graphics.setCanvas()
local imageData = activeContext._canvas:newImageData()
love.graphics.setCanvas(activeContext._canvas)
elseif mode == "image" then
return module.shaders.image[imageDrawMode]
end
end

-- update image
activeContext.data:replacePixels(imageData)
function module.setDrawMode(mode, imageDrawMode)
local shader = getShader(mode, imageDrawMode or module.imageDrawMode)
if module.shader ~= shader then
module.shader = shader
-- TODO: we have to do this before every drawing call.
if shader:hasUniform("canvas") then
copyAndSwapCanvases()
shader:send("canvas", module.canvas2)
end
love.graphics.setShader(shader)
end
end
!end
!end
11 changes: 11 additions & 0 deletions playbit/shaders/color.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma language glsl3

extern vec4 drawColor;

vec4 effect(vec4 color, Image tex, vec2 tex_coords, vec2 screen_coords)
{
float outColor = drawColor.r;
float outAlpha = drawColor.a;

return vec4(outColor, outColor, outColor, outAlpha);
}
15 changes: 15 additions & 0 deletions playbit/shaders/final.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma language glsl3

extern vec4 white = vec4(176.0f / 255.0f, 174.0f / 255.0f, 167.0f / 255.0f, 1);
extern vec4 black = vec4( 49.0f / 255.0f, 47.0f / 255.0f, 40.0f / 255.0f, 1);

vec4 effect(vec4 color, Image tex, vec2 tex_coords, vec2 screen_coords)
{
vec4 inColor = Texel(tex, tex_coords) * color;

vec4 outColor;
outColor.rgb = mix(black.rgb, white.rgb, inColor.r);
outColor.a = 1;

return outColor;
}
49 changes: 49 additions & 0 deletions playbit/shaders/image.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma language glsl3

extern Image canvas;

vec4 effect(vec4 color, Image tex, vec2 tex_coords, vec2 screen_coords)
{
vec4 texColor = Texel(tex, tex_coords);
float grayscale = dot(texColor.rgb, vec3(0.2126, 0.7152, 0.0722));
float inColor = step(0.5, grayscale);
float inAlpha = step(0.5, texColor.a);

#if DRAW_MODE == 1 // White Transparent
float outColor = inColor;
float outAlpha = inAlpha * (1.0 - inColor);

#elif DRAW_MODE == 2 // Black Transparent
float outColor = inColor;
float outAlpha = inAlpha * inColor;

#elif DRAW_MODE == 3 // Fill White
float outColor = 1;
float outAlpha = inAlpha;

#elif DRAW_MODE == 4 // Fill Black
float outColor = 0;
float outAlpha = inAlpha;

#elif DRAW_MODE == 5 // XOR
vec4 canvasColor = Texel(canvas, screen_coords / love_ScreenSize.xy);
float outColor = abs(canvasColor.r - inColor);
float outAlpha = inAlpha;

#elif DRAW_MODE == 6 // NXOR
vec4 canvasColor = Texel(canvas, screen_coords / love_ScreenSize.xy);
float outColor = 1.0 - abs(canvasColor.r - inColor);
float outAlpha = inAlpha;

#elif DRAW_MODE == 7 // Inverted
float outColor = 1.0 - inColor;
float outAlpha = inAlpha;

#else // Copy
float outColor = inColor;
float outAlpha = inAlpha;

#endif

return vec4(outColor, outColor, outColor, outAlpha);
}
15 changes: 15 additions & 0 deletions playbit/shaders/pattern.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma language glsl3

extern float pattern[64];

vec4 effect(vec4 color, Image tex, vec2 tex_coords, vec2 screen_coords)
{
// Use mod() to get the position of the current pixel within the 8x8 pattern
int x = int(mod(screen_coords.x, 8.0));
int y = int(mod(screen_coords.y, 8.0));

float outColor = pattern[x + y * 8];
float outAlpha = 1;

return vec4(outColor, outColor, outColor, outAlpha);
}
20 changes: 20 additions & 0 deletions playbit/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,24 @@ function module.sign(a)
else
return 0
end
end

function module.clamp01(x)
if x <= 0 then
return 0
elseif x >= 1 then
return 1
else
return x
end
end

function module.clamp(x, min, max)
if x <= min then
return min
elseif x >= max then
return max
else
return x
end
end
Loading