Posted: 19th Feb 2014 12:27
Hi,

I just wrote a small library handling rendering of advanced sprites (which could easily be changed to use DBPro sprites, Advanced2D-images (e.g. for particle systems using additive blending) or anything else), including a simple camera system with zoom and rotation. The advantage of AdvancedSprites over native DBPro sprites is that they can be rendered with anti aliasing, which really improves the image quality:



While there are a few artifacts in the AS area as well (I don't really know why, but part of the problem surely is the selection of bad source images, I didn't have anything more fancy at hand), it's probably quite clear that it looks a lot better than the DBP pixel mess on the right side.

Well, and apart from the improved visual quality, the library handles world to screen transformations and stuff like that for you and you don't have to worry about writing your own camera code for 2D games.

In case you don't have Advanced Sprites yet, you can get it here.


And finally, here's the library:

+ Code Snippet
Rem Project: AdvancedSpritesLib
Rem Created: Wednesday, February 19, 2014
rem Author:  Shellfish Games

Rem ***** Main Source File *****



rem Trilinear Filtering
#constant AS__DEFAULT_FILTER = 3

type as_camType
    x as float
    y as float
    zoom as float
    angle as float
    scrw2 as float
    scrh2 as float
    rot_sin as float
    rot_cos as float
    zoom1 as float
endtype

function as_init()
    dxs initialize
    dxs use filter AS__DEFAULT_FILTER
    global as_cam as as_camType
    as_cam.x = 0.0
    as_cam.y = 0.0
    as_cam.zoom = 1.0
    as_cam.angle = 0.0
    global as_outX as float = 0.0
    global as_outY as float = 0.0
    as_updateResolution()
endfunction


rem Loads Sprite Centered
function as_loadSprite(f$)
    spr = as_loadSpriteOff(f$, 0.5, 0.5)
endfunction spr

rem Loads Sprite with given offset (should be between 0.0 and 1.0)
function as_loadSpriteOff(f$,ox#,oy#)
    spr = dxs create sprite(f$)
    dxs set sprite center spr, dxs get sprite width(spr)*ox#, dxs get sprite height(spr)*oy#
    dxs set sprite scaling center spr, 0,0
    dxs set sprite filter spr, AS__DEFAULT_FILTER
    dxs update sprite spr
endfunction spr

rem Creates Sprite from Image Centered
function as_createSprite(img)
    spr = as_createSpriteOff(img,0.5,0.5)
endfunction spr

rem Creates Sprite from Image with given Offset
function as_createSpriteOff(img,ox#,oy#)
    spr = dxs create sprite from image(img)
    dxs set sprite center spr, dxs get sprite width(spr)*ox#, dxs get sprite height(spr)*oy#
    dxs set sprite scaling center spr, 0,0
    dxs set sprite filter spr, AS__DEFAULT_FILTER
    dxs update sprite spr
endfunction spr

rem Paste Sprite to Screen with the exact given parameters
function as_pasteSpriteRaw(spr, x#, y#, scale#, rotation#)
    rem Scaling
    scale# = 100*scale#
    dxs set sprite scale spr, scale#,scale#
    rem Rotation
    dxs set sprite rotation center spr, x#, y#
    dxs set sprite angle spr, rotation#
    rem Apply Changes
    dxs update sprite spr
    rem Render
    dxs begin sprite render spr
        dxs draw sprite spr, x#, y#
    dxs end sprite render spr
endfunction


rem Paste Sprite to World (coordinates, zoom and rotation will be chanced depending on camera)
function as_pasteSprite(spr, x#, y#, scale#, rotation#)
    as_worldToScreen(x#,y#)
    as_pasteSpriteRaw(spr, as_outX, as_outY, scale#*as_cam.zoom, rotation#-as_cam.angle)
endfunction


function as_setCam(x#,y#,zoom#,angle#)
    as_cam.x = x#
    as_cam.y = y#
    as_cam.zoom = zoom#
    as_cam.angle = angle#
    as_cam.rot_sin = sin(-angle#)
    as_cam.rot_cos = cos(-angle#)
    as_cam.zoom1 = 1.0/zoom#
endfunction

function as_moveCam(fw#,sw#)
    inc as_cam.x, sw#*as_cam.rot_cos + fw#*as_cam.rot_sin
    inc as_cam.y, sw#*as_cam.rot_sin - fw#*as_cam.rot_cos
endfunction

rem Call this each time after you change your resolution within the code
function as_updateResolution()
    as_cam.scrw2 = screen width()*0.5
    as_cam.scrh2 = screen height()*0.5
endfunction

rem This is called when renderen a world sprite in order to get its screen coordinate
function as_worldToScreen(wx#,wy#)
    rem Camera Position and Zoom
    dx# = as_cam.zoom * (wx# - as_cam.x)
    dy# = as_cam.zoom * (wy# - as_cam.y)
    rem Rotation
    as_OutX = as_cam.scrw2 + as_cam.rot_cos * dx#   +   as_cam.rot_sin * dy#
    as_OutY = as_cam.scrh2 + as_cam.rot_sin * dx#   -   as_cam.rot_cos * dy#
endfunction

rem You may call this to transform screen cordinates (i.e. the mouse) to world coordinates
function as_screenToWorld(sx#,sy#)    
    rem De-Center
    dec sx#, as_cam.scrw2
    dec sy#, as_cam.scrh2 
    rem Inverse Rotation
    x# =  as_cam.rot_cos * sx#   +   as_cam.rot_sin * sy#
    y# =  as_cam.rot_sin * sx#   -   as_cam.rot_cos * sy#
    rem Zoom and Position
    as_OutX = as_cam.x + x# * as_cam.zoom1
    as_OutY = as_cam.y + y# * as_cam.zoom1
endfunction


And the complete code that resulted in the screenshot above (without the comparison though, I did that in paint), no media required:

+ Code Snippet
Rem Project: AdvancedSpritesLib
Rem Created: Wednesday, February 19, 2014
rem Author:  Shellfish Games

Rem ***** Main Source File *****


#constant scrw = 1280
#constant scrh = 960

start:

set display mode scrw, scrh, 32, 0
set window on
sync on
sync rate 0
sync


as_init()

rem Create a few Images
set image colorkey 254,0,0
cls rgb(254,0,0)
ink 0xFFFF0000,0
box 20,20,80,80
get image 1, 0,0,100,100, 1

set image colorkey 0,254,0
cls rgb(0,254,0)
ink 0xFF00FF00,0
box 40,20,60,80
box 20,40,80,60
get image 2, 0,0,100,100, 1

set image colorkey 0,0,254
cls rgb(0,0,254)
ink 0xFF0000FF,0
set text font "Helvetica"
set text to bold
set text size 80
center text 256, 20, "HELLO WORLD"
get image 3, 0,0,512,128, 1

rem Make Text Readable again for Debug Output
set text size 24


rem Create Sprites
dim spr(3)
for i = 1 to 3
    spr(i) = as_createSprite(i)
next


rem Draw
do
    cls 0x808080
    
    rem Camera
    fac# = (0.3 + 2.7*shiftkey())
    rl# = (rightkey() - leftkey())*fac#
    ud# = (downkey() - upkey())*fac#
    z# = 1.0 + 0.003*fac#*(keystate(17)-keystate(31))
    ang# = 0.2*fac#*(keystate(32)-keystate(30))
    as_setCam( as_cam.x, as_cam.y, as_cam.zoom*z#, as_cam.angle+ang# )
    as_moveCam( ud#, rl# )
    
    time = timer()
    time = 121009
    
    rem Draw Bunch of Sprites
    for i = 1 to 100
        randomize i
        rem Initial Position
        x# = 500-rnd(1000)
        y# = 500-rnd(1000)
        ang# = rnd(359)
        scale# = 1.0 + 0.01*(rnd(100)-rnd(50))
        rem Make Some Sprite Static and some Moving
        if rnd(1) = 0
            rem Moving Sprite
            rad = 5+rnd(300)
            spd# = rnd(100)*0.002
            inc x#, rad*sin(time*spd#)
            inc y#, rad*cos(time*spd#)
            angspd# = rnd(100)*0.001
            inc ang#, time*angspd# 
        endif
        rem Render
        sprID = 1 + rnd(2)
        if sprID = 3 then scale#=0.5*scale#
        as_pasteSprite(spr(sprID), x#, y#, scale#, ang#)
    next
    
    as_pasteSpriteRaw(spr(2), mousex(), mousey(), 2.0, time*0.1)
    
    
    rem Output
    ink 0xA0FFFFFF,0
    print "FPS: ", screen fps()
    print
    print "  Arrowkeys to Move"
    print "  W/S to Zoom"
    print "  A/D to Rotate"
    print "  Shift to Speed Up Movement"
    
    sync    
loop






rem Trilinear Filtering
#constant AS__DEFAULT_FILTER = 3

type as_camType
    x as float
    y as float
    zoom as float
    angle as float
    scrw2 as float
    scrh2 as float
    rot_sin as float
    rot_cos as float
    zoom1 as float
endtype

function as_init()
    dxs initialize
    dxs use filter AS__DEFAULT_FILTER
    global as_cam as as_camType
    as_cam.x = 0.0
    as_cam.y = 0.0
    as_cam.zoom = 1.0
    as_cam.angle = 0.0
    global as_outX as float = 0.0
    global as_outY as float = 0.0
    as_updateResolution()
endfunction


rem Loads Sprite Centered
function as_loadSprite(f$)
    spr = as_loadSpriteOff(f$, 0.5, 0.5)
endfunction spr

rem Loads Sprite with given offset (should be between 0.0 and 1.0)
function as_loadSpriteOff(f$,ox#,oy#)
    spr = dxs create sprite(f$)
    dxs set sprite center spr, dxs get sprite width(spr)*ox#, dxs get sprite height(spr)*oy#
    dxs set sprite scaling center spr, 0,0
    dxs set sprite filter spr, AS__DEFAULT_FILTER
    dxs update sprite spr
endfunction spr

rem Creates Sprite from Image Centered
function as_createSprite(img)
    spr = as_createSpriteOff(img,0.5,0.5)
endfunction spr

rem Creates Sprite from Image with given Offset
function as_createSpriteOff(img,ox#,oy#)
    spr = dxs create sprite from image(img)
    dxs set sprite center spr, dxs get sprite width(spr)*ox#, dxs get sprite height(spr)*oy#
    dxs set sprite scaling center spr, 0,0
    dxs set sprite filter spr, AS__DEFAULT_FILTER
    dxs update sprite spr
endfunction spr

rem Paste Sprite to Screen with the exact given parameters
function as_pasteSpriteRaw(spr, x#, y#, scale#, rotation#)
    rem Scaling
    scale# = 100*scale#
    dxs set sprite scale spr, scale#,scale#
    rem Rotation
    dxs set sprite rotation center spr, x#, y#
    dxs set sprite angle spr, rotation#
    rem Apply Changes
    dxs update sprite spr
    rem Render
    dxs begin sprite render spr
        dxs draw sprite spr, x#, y#
    dxs end sprite render spr
endfunction


rem Paste Sprite to World (coordinates, zoom and rotation will be chanced depending on camera)
function as_pasteSprite(spr, x#, y#, scale#, rotation#)
    as_worldToScreen(x#,y#)
    as_pasteSpriteRaw(spr, as_outX, as_outY, scale#*as_cam.zoom, rotation#-as_cam.angle)
endfunction


function as_setCam(x#,y#,zoom#,angle#)
    as_cam.x = x#
    as_cam.y = y#
    as_cam.zoom = zoom#
    as_cam.angle = angle#
    as_cam.rot_sin = sin(-angle#)
    as_cam.rot_cos = cos(-angle#)
    as_cam.zoom1 = 1.0/zoom#
endfunction

function as_moveCam(fw#,sw#)
    inc as_cam.x, sw#*as_cam.rot_cos + fw#*as_cam.rot_sin
    inc as_cam.y, sw#*as_cam.rot_sin - fw#*as_cam.rot_cos
endfunction

rem Call this each time after you change your resolution within the code
function as_updateResolution()
    as_cam.scrw2 = screen width()*0.5
    as_cam.scrh2 = screen height()*0.5
endfunction

rem This is called when renderen a world sprite in order to get its screen coordinate
function as_worldToScreen(wx#,wy#)
    rem Camera Position and Zoom
    dx# = as_cam.zoom * (wx# - as_cam.x)
    dy# = as_cam.zoom * (wy# - as_cam.y)
    rem Rotation
    as_OutX = as_cam.scrw2 + as_cam.rot_cos * dx#   +   as_cam.rot_sin * dy#
    as_OutY = as_cam.scrh2 + as_cam.rot_sin * dx#   -   as_cam.rot_cos * dy#
endfunction

rem You may call this to transform screen cordinates (i.e. the mouse) to world coordinates
function as_screenToWorld(sx#,sy#)    
    rem De-Center
    dec sx#, as_cam.scrw2
    dec sy#, as_cam.scrh2 
    rem Inverse Rotation
    x# =  as_cam.rot_cos * sx#   +   as_cam.rot_sin * sy#
    y# =  as_cam.rot_sin * sx#   -   as_cam.rot_cos * sy#
    rem Zoom and Position
    as_OutX = as_cam.x + x# * as_cam.zoom1
    as_OutY = as_cam.y + y# * as_cam.zoom1
endfunction



Feel free to use this code for your own projects. Mentioning me in the credits would be nice, but well... I guess that's up to you.

Cheers.


Edit: Maybe I should add a little overview of the available functions and how to use them.

as_init() - should be called once after starting your program
as_loadSprite(f$) - loads a sprite file, centers it and returns handler (store it in a variable to access the sprite later)
as_loadSpriteOff(f$,offx#,offy#) - load sprite with given offset (relative to sprite size) - using 0.5,0.5 will center the sprite
as_createSprite(img) - creates an Advanced Sprite from an image, centers it and returns the handler
as_createSpriteOff(img,offx#,offy#) - you get the point
as_pasteSpriteRaw(spr, x#,y#, scale#, angle#) - pastes the advanced sprite (using the handler you were given when creating the sprite) to the given screen coordinates
as_pasteSprite(spr, x#,y#, scale#, angle#) - pastes the advanced sprite to the given world coordinates
as_setCam(x#,y#,zoom#,angle#) - sets the camera state, x# and y# being the world coordinates of the cam's center, zoom# is 1.0 by default (2.0 meaning everything is twice as big)
as_moveCam(forward#, sideways#) - can be used to make the camera move relative to its angle. Both values can be positive or negative and higher values will make the cam move faster.
as_updateResolution() - should be called if you change the screen resolution while the program is running, to make sure the camera knows what's going on
as_worldToScreen(wx#,wy#) - Transforms world to screen coordinates and stores them in the global variables as_OutX/as_OutY. Usually you don't need to call this function, as as_pasteSprite() takes care of that
as_screenToWorld(sx#,sy#) - Transforms screen to world coordinates and stores them in as_OutX/as_OutY. You can for instance use this function to find out what the woorld coordinate of the mouse is.

And a minimal usage example:

+ Code Snippet
rem Set up Display
set display mode <width>, <height>, 32
sync on : sync rate 0 : sync

rem Initialize Library
as_init()

rem Load some Sprites
spr1 = as_loadSprite("mySprite.png")
spr2 = as_loadSpriteOff("myCharacter.png", 0.5, 1.0) //set sprite center to center of lower edge
spr3 = as_createSprite(someImageThatWasAlreadyLoaded)

rem Main Loop
do
    cls

    rem Set Camera
    as_setCam(0,0, 4.0, timer()*0.01) //Zoomed in, slightly rotating

    rem Draw a few sprites to the World
    as_pasteSprite(spr1, 0,0, 0.5, 0) //Scaled down to 50%
    as_pasteSprite(spr2, 0,300, 1.0, -timer()*0.05) //Rotating
    rem Draw third sprite as cursor
    as_pasteSpriteRaw(spr3, mousex(), mousey(), 1.0, 0.0)

    rem Get World Coordinate of Mouse
    as_screenToWorld(mousex(), mousey())
    print "Coord: ", as_OutX, ",", as_OutY

    sync
loop


Note: Right now the library does not support animated sprites or tilesets. Adding those things wouldn't be a big deal, so just ask if you need it (or add it yourself ).

NoteĀ²: Also, the rendering could be optimized. Right now dxs begin sprite render and dxs end sprite render are called before and after every single sprite drawing operation. I could add some simple optimization, but haven't done it yet since it would make the code and usage (slightly) more complicated.
Posted: 21st Feb 2014 18:40
I'm not entirely sure the left looks better than the right side. Why does every advanced sprite have a border? It's like the AA is picking the wrong colors or something for the alpha pixels
Posted: 21st Feb 2014 19:51
Why does every advanced sprite have a border? It's like the AA is picking the wrong colors or something for the alpha pixels


That's a valid question. I don't really know what it's doing there, and I even tried to counter the effect by making sure the transparent pixels are of the same color as the rest of the sprite, but it didn't make any difference unfortunately...

However, from my experience the AA-effect really pays off in games, and the overall image quality is increased quite a lot, especially in 2D games with a camera that is able to zoom and rotate. Since this is how DBPro renders rotated sprites:



...

In comparison, I replaced the rendering code of Lab Rush (anyone remebers it? Was posted here a few years ago, the creator allowed me to work with the code) yesterday with the library above, using Advanced Sprites.
This is how it looked before (note the extreme artifacts on the red player, the blue shield and the yellow crosshair):



And finally, this is how it looks now, rendered with Advanced Sprites (and the ground tiles replaced, but that's not really the point):



This degree of smoothness would never be possible with native DBPro sprites. As far as I know. And you can't really see the partially wrong color blending that was apparent in the screenshot from the original post.
Posted: 21st Feb 2014 21:04
Have you gotten into AppGameKit yet? I find the sprites look so much better I think.
Posted: 21st Feb 2014 21:28
I've bought and installed it, but haven't worked with it yet. Maybe I should.