----- Update 2019-10-24
New release:
Download latest release at:
https://github.com/lvcabral/brs-emu/releases/tag/v0.6.0-emu
The emulator code is available on the the repository: https://github.com/lvcabral/brs-emu
The demonstration site has the details about the project and several examples of games and channels running: https://lvcabral.com/brs
----- Original Post
I had this idea for a long time, but to start from scratch was always a big effort that I was not willing to put. Then recently I learned about this cool project: https://github.com/lvcabral/brs under the branch called "brsLib" and you can test it online at this link: https://lvcabral.com/brs/
I'm far from a TypeScript/Javascript expert, learning as I go, if you would be interested to help, let me know!
I will keep updating this thread with the progress.
The code below was extracted from Roku Draw 2D API example (Simple2D), you can save it to a brs file and load in the tool to see the animation above in your browser. You can change it or create your own animations with BrightScript.
sub main()
screen=CreateObject("roScreen", true, 854, 480)
screen.SetAlphaEnable(true)
screen.Clear(&HFF)
port = CreateObject("roMessagePort")
screen.SetMessagePort(port)
compositor=CreateObject("roCompositor")
compositor.SetDrawTo(screen, 0)
scaleblit(screen, port, 0, 0, 854, 480, 1)
end sub
sub scaleblit(screenFull as object, msgport as object, topx, topy, w, h, par)
print "Scale Boing"
screen = screenFull
red = 255*256*256*256+255
green = 255*256*256+255
blue = 255*256+255
clr = int(255*.55)
background = &h8c8c8cff
sidebarcolor = green
screen.Clear(background)
screenFull.SwapBuffers()
' create a red sprite '
ballsize = h/4
ballsizey = int(ballsize)
ballsizex = int(ballsize*par)
tmpballbitmap = createobject("robitmap","pkg:/img/AmigaBoingBall.png")
scaley = ballsizey/tmpballbitmap.getheight()
scalex = scaley*par
ballbitmap = createobject("robitmap",{width:ballsizex,height:ballsizey,alphaenable:true})
ballbitmap.drawscaledobject(0,0,scalex*1.0,scaley*1.0,tmpballbitmap)
ballregion = createobject("roregion",ballbitmap,0,0,ballsizex,ballsizey)
ballcenterX = int(ballsizex/2)
ballcenterY = int(ballsizey/2)
ballregion.setpretranslation(-ballcenterX, -ballcenterY)
ballregion.setscalemode(0)
' construct ball shadow '
tmpballbitmap = createobject("robitmap","pkg:/img/BallShadow.png")
ballshadow = createobject("robitmap",{width:ballsizex,height:ballsizey,alphaenable:true})
ballshadow.drawscaledobject(0,0,ballsizex/tmpballbitmap.getwidth(),ballsizey/tmpballbitmap.getheight(),tmpballbitmap)
shadowregion = createobject("roregion",ballshadow,0,0,ballsizex,ballsizey)
shadowregion.setpretranslation(-ballcenterX, -ballcenterY)
shadowregion.setscalemode(0)
' calculate starting position and motion dynamics '
x = w/10 + ballcenterX
y = h/10 + ballcenterY
dx = 2
dy = 1
ay = 1
framecount = 0
timestamp = createobject("rotimespan")
swapbuff_timestamp = createobject("rotimespan")
start = timestamp.totalmilliseconds()
swapbuff_time = 0
shadow_dx = int(ballsizex/4)
shadow_dy = int(ballsizey/10)
w_over_10 = w/10
rightedge = int(ballcenterx + (w*9)/10)
bottomedge = int(ballcentery + (h*9)/10)
running = true
' codes = bslUniversalControlEventCodes() '
grid = createobject("robitmap", {width:screen.getWidth(),height:screen.getheight(),alphaenable:false})
regiondrawgrid(grid, background)
grid.finish()
while true
screen.drawobject(0, 0, grid)
screen.SetAlphaEnable(true)
scalex = x/rightedge
scaley = y/bottomedge
screen.drawscaledobject(toInt(x+shadow_dx),toInt(y+shadow_dy),scalex*1.0,scaley*1.0,shadowregion)
screen.drawscaledobject(toInt(x),toInt(y),scalex*1.0,scaley*1.0,ballregion)
screen.SetAlphaEnable(false)
screen.drawrect(toInt(x-2),toInt(y-2),5,5,green)
swapbuff_timestamp.mark()
screenFull.SwapBuffers()
swapbuff_time = swapbuff_time + swapbuff_timestamp.totalmilliseconds()
pullingmsgs = true
while pullingmsgs
deltatime = timestamp.totalmilliseconds() - start
msg = msgport.getmessage()
if msg = invalid and deltatime > 16 'aprox 60fps '
timestamp.mark()
start = timestamp.totalmilliseconds()
pullingmsgs = false
else
if type(msg) = "roUniversalControlEvent"
button = msg.getint()
print "button=";button
'if button=codes.BUTTON_BACK_PRESSED '
return
'endif '
endif
endif
end while
x = x + dx
y = y + dy
dy = dy + ay
if x<w_over_10
x = w_over_10+(w_over_10-x)
dx = -dx
endif
if y<0
y = -y
dy = -dy
endif
if x+ballsizex > rightedge
x = 2*rightedge - x - 2*ballsizex
dx = -dx
endif
if y+ballsizey > bottomedge
y = 2*y - y
dy = -dy + ay
endif
end while
print "Exiting APP"
End Sub
sub drawline(screen, x0,y0,x1,y1,width,color)
if (width = 1) and (y0 <> y1) and (x0 <> x1)
screen.drawline(x0, y0, toInt(x1), y1, color)
return
end if
if (x0=x1)
' vertical line '
h = y1-y0
if h<0 ' upside down? '
y0=y1
h = -h
endif
screen.drawrect(toInt(x0),y0,toInt(width),h+1,color)
else if (y0=y1)
w = x1-x0
if w<0
x0=x1
w = -w
endif
screen.drawrect(toInt(x0),y0,toInt(w+1),width,color)
end if
end sub
function toInt(value) as integer
if type(value) = "Integer" then return value
return int(value)
end function
sub regiondrawgrid(screen, background)
' only draw into primary surface area now - do not touch sidebars '
screen.clear(background)
w = screen.getWidth()
h = screen.getHeight()
left = int(w/10)
right = w-left
top= int(h/10)
bottom = h-top
color = &hff00ffff
' draw vertical lines '
i = 0
x = left
deltax = int(left/2)
deltay = deltax
lineheight = bottom - top
bottom = top + deltay*int(lineheight/deltay)
bottomXdelta = (deltax/3)
bottomXdeltainit = bottomXdelta
deltax_over_20 = int(deltax/20)
deltay_over_2 = int(deltay/2)
while x<=right
drawline(screen,x,top,x,bottom,1,color)
drawline(screen,x,bottom,x-bottomXdelta,bottom+deltay_over_2,1,color)
x = x + deltax
bottomXdelta =bottomXdelta - deltax_over_20
end while
' correct for actual right edge '
right = x - deltax
y = top
'draw horizontal lines '
while y<=bottom
drawline(screen,left,y,right,y,1,color)
y = y + deltay
end while
' draw floor '
drawline(screen, left-bottomXdeltainit, bottom+deltay_over_2,right-bottomXdelta-deltax_over_20 , bottom+deltay_over_2, 1, color)
end sub
"Komag" wrote:
This is incredible, something I've wished for since 2014! I haven't tried it yet, but will soon. I really hope you continue to work on it, as it will be a quite valuable tool for anyone making Roku games now and in the future.
"Komag" wrote:
I tried selecting my main brs file, but I have many brs files in the project, and all the graphics files and xml files. I didn't see anything. In the example, the code shows the ball and shadow as png files in the pkg, but the emulator is only taking the brs file, so how can that work anyway? Do I need to run the emulator locally somehow, so it can have access to my folders and files?
Library "v30/bslDefender.brs"
Function Main() as void
screen = CreateObject("roScreen", true)
port = CreateObject("roMessagePort")
bitmapset = dfNewBitmapSet(ReadAsciiFile("pkg:/assets/bitmapset.xml"))
ballsize = bitmapset.extrainfo.ballsize.ToInt()
compositor = CreateObject("roCompositor")
compositor.SetDrawTo(screen, &h000000FF)
screenWidth = screen.GetWidth()
screenHeight= screen.GetHeight()
clock = CreateObject("roTimespan")
clock.Mark()
screen.SetMessagePort(port)
screen.SetAlphaEnable(true)
codes = bslUniversalControlEventCodes()
sprites = []
balls = []
balls[0] = bitmapset.animations.animated_3ball
balls[1] = bitmapset.animations.animated_4ball
balls[2] = bitmapset.animations.animated_5ball
balls[3] = bitmapset.animations.animated_6ball
balls[4] = bitmapset.animations.animated_8ball
spriteCount = 0
sprites[0] = compositor.NewAnimatedSprite(Rnd(screenWidth-ballSize), Rnd(screenHeight-ballSize), balls[0])
sprites[0].SetData( {dx: Rnd(20)+10, dy: Rnd(20)+10, index: spriteCount} )
timestamp = createobject("rotimespan")
start = timestamp.totalmilliseconds()
speed = 100
while true
for each sprite in sprites
sprite.SetMemberFlags(1)
end for
event = port.GetMessage()
deltatime = timestamp.totalmilliseconds() - start
if (type(event) = "roUniversalControlEvent" or deltatime > 5000)
id = 0 'event.GetInt() '
if spriteCount < 3 'Add a sprite '
spriteCount = spriteCount + 1
sprites[spriteCount] = compositor.NewAnimatedSprite(Rnd(screenWidth-ballSize), Rnd(screenHeight-ballSize), balls[spriteCount])
sprites[spriteCount].SetData( {dx: Rnd(20)+10, dy: Rnd(20)+10, index: spriteCount} )
else if (id = codes.BUTTON_LEFT_PRESSED)
wait(0, port)
endif
timestamp.mark()
start = timestamp.totalmilliseconds()
else if (event = invalid)
ticks = clock.TotalMilliseconds()
if (ticks > speed)
for each sprite in sprites
dx = sprite.GetData().dx
dy = sprite.GetData().dy
sprite.MoveTo( (sprite.GetX() + dx), (sprite.GetY() + dy) )
collidingSprite = sprite.CheckCollision()
if (collidingSprite <> invalid)
doCollision(sprite, collidingSprite, sprites)
endif
end for
sprites = checkEdgeCollisions(sprites, screenWidth, screenHeight, ballsize)
compositor.AnimationTick(ticks)
compositor.DrawAll()
screen.SwapBuffers()
clock.Mark()
endif
endif
end while
End Function
Function checkEdgeCollisions(sprites as object, screenWidth as integer, screenHeight as integer, ballsize as integer) as object
for each sprite in sprites
x = sprite.GetX()
y = sprite.GetY()
i = sprite.GetData().index
deltaX = sprite.GetData().dx
deltaY = sprite.GetData().dy
if ((x + ballsize) > screenWidth)
x = screenWidth - ballsize
if (deltaX > 0)
deltaX = -deltaX
endif
sprite.SetData( {dx: deltaX, dy: deltaY, index: i} )
endif
if (x < 0)
x = 0
if (deltaX < 0)
deltaX = -deltaX
endif
sprite.SetData( {dx: deltaX, dy: deltaY, index: i} )
endif
if ((y + ballsize) > screenHeight)
y = screenHeight - ballSize
if (deltaY > 0)
deltaY = -deltaY
endif
sprite.SetData( {dx: deltaX, dy: deltaY, index: i} )
endif
if (y < 0)
y = 0
if (deltaY < 0)
deltaY = -deltaY
endif
sprite.SetData( {dx: deltaX, dy: deltaY, index: i} )
endif
sprite.MoveOffset(deltaX, deltaY)
end for
return sprites
End Function
Function doCollision(sprite0 as object, sprite1 as object, sprites as object) as object
index0 = sprite0.GetData().index
index1 = sprite1.GetData().index
dx = sprite1.GetX() - sprite0.GetX()
dy = sprite1.GetY() - sprite0.GetY()
if (dx > 0)
angle = atn(dy / dx)
else
angle = 1.5708
endif
fSin = sin(angle)
fCos = cos(angle)
pos0 = {}
pos0.x = 0
pos0.y = 0
pos1 = rotate(dx, dy, fSin, fCos, true)
vel0 = rotate(sprite0.GetData().dx, sprite0.GetData().dy, fSin, fCos, true)
vel1 = rotate(sprite1.GetData().dx, sprite1.GetData().dy, fSin, fCos, true)
'collision reaction'
vxTotal = vel0.x - vel1.x
vel0.x = vel1.x
vel1.x = vxTotal + vel0.x
'update position'
pos0.x = pos0.x + vel0.x
pos1.x = pos1.x + vel1.x
'rotate positions back'
pos0F = rotate(pos0.x, pos0.y, fSin, fCos, false)
pos1F = rotate(pos1.x, pos1.y, fSin, fCos, false)
'adjust positions to actual screen positions'
sprites[index0].MoveOffset(pos0F.x, pos0F.y)
sprites[index1].MoveOffset(pos1F.x, pos1F.y)
'rotate velocities back'
vel0F = rotate(vel0.x, vel0.y, fSin, fCos, false)
vel1F = rotate(vel1.x, vel1.y, fSin, fCos, false)
sprites[index0].SetData( {dx: vel0F.x, dy: vel0F.y, index: index0} )
sprites[index1].SetData( {dx: vel1F.x, dy: vel1F.y, index: index1} )
sprites[index0].SetMemberFlags(0)
sprites[index1].SetMemberFlags(0)
return sprites
End Function
Function rotate(x as float, y as float, fSin as float, fCos as float, reverse as boolean) as object
res = {}
if (reverse)
res.x = (x * fCos + y * fSin)
res.y = (y * fCos - x * fSin)
else
res.x = (x * fCos - y * fSin)
res.y = (y * fCos + x * fSin)
endif
return res
End Function