'''The brick engine''' is a cross-platform game-creation system, in the style of the old 8-bit systems, that uses [Tcl] for all game logic. It supports unlimited sprites (both image and filter sprite types) and text strings, scrollable tile-based maps, songs, sounds, keyboard and joystick input. It is under active development by [hat0] as of October 2008. The v3.3 version is available as of October 12, 2008. The brick engine includes a number of features that should make visually dazzling and richly interactive video games possible to the tcl programmer, including a variety of sprite-rendering modes (such as a brightness/contrast adjustment frame, a convolution kernel, saturation/desaturation, etc), a simple motion-control system to give sprites a degree of autonomy and to create simple particle systems, and more. Multi-player gaming is a breeze, with an input system that supports any combination of keyboard and joysticks, and of course network play is no problem thanks to [socket] and friends. The engine basically prepares the hardware and sets up a Tcl interpreter with many extra commands, so that you can load image data, set up sprites and maps, and let them all interact with each other. The engine can be built as a standalone executable with Tcl embedded, or as a loadable module. The web site is here: http://rs.tc/br/ There are two fully playable game demos on the brick engine site now: * Zeml'a kroshechnykh mashin - The Land of Tiny Machines. This is a side-scroller feat. multiple weapons, enemies with different behaviors, music and sound, all in <2000 lines of Tcl. Controls are: left ctrl/alt to jump and shoot. Z and X to switch weapons. Arrow keys to move. Backspace to quit out of the game immediately. Linux kit is at http://rs.tc/br/misc/zkm and Windows kit is at http://rs.tc/br/misc/zkm.exe (no OS X release yet). * Escape. An overhead-view maze game, with multiple levels and some nice visual effects. Arrow keys to move, escape to quit out of the game immediately. Linux kit is at http://rs.tc/br/misc/maze and Windows kit is at http://rs.tc/br/misc/maze.exe (no OS X release yet). For both games, if you have a joystick, you can use that instead of the keyboard controls. And, of course, to view the source for either came, a simple '''sdx unwrap''' will do. ''Note that you must have SDL and SDL_mixer installed, and an 8.5-based tclkit somewhere in your path.'' Uses [SDL]. ---- [AMG]: Is this related to Five Thousand Italians? [http://yellow5.com/pokey/pcs/5000.zip] [http://yellow5.com/pokey/pcs/5000.tar.gz] [http://yellow5.com/pokey/pcs/5000_osx.tar.gz] [hat0]: Yes, that project uses an early version of the brick engine! ---- [Googie] - 13.10.2008 - Amaizing! I was working on something similar months ago but I've dropped it. Brick looks like mature, powerful and as easy to use as Tcl itself :) I can't find information about GL acceleration - is it supported? I don't mean 3D graphics, but 2D GL-accelerated (like [TkZinc] does). [hat0] - Thank you for the kind words - if you make a project I'd love to hear about it! As for GL acceleration, brick uses it only for the final blit to screen, to give fast performance on the various platforms, and to allow for a resized display window. SDL's native blit is, of course, terribly slow, and does no resizing at all. I have done a little work on a fully-OpenGL rendering pathway, using various ARB extensions to reproduce the sprite filters (such as convolution, available in the Imaging extension, and the colorspace manipulations), but haven't gone too far in this. To be fully honest about it, I only discovered recently that I was compiling the brick engine without any compiler optimization. After turning the optimizations on (-O3), I myself was very surprised at its performance.. (and this is when I lost some interest in the OpenGL work!) ---- [hat0] - And here is a short game, my first attempt at making the smallest playable game (both for fun, and to see where the API could be improved). sloccount counts it at 164 lines of code, and including comments and whitespace, I count 344 lines. To try it out, you can save this into a directory (say, as ''game.tcl'') with the brick executable for your platform, and then run the engine like so: ./br game.tcl [ZB] Just tried the game, and I noticed, it eats power of my weak processor (Pentium II 400) completely. Is it the brick engine itself, that needs so much power - or perhaps the code of the game (didn't make any analysis yet) can be somewhat improved (loops most probably)? ---- ====== namespace import br::* # # first set up the graphics and audio # graphics open accel off 640 480 audio open speaker # # then create the layers and store some layer values for convenience # foreach l { bg main } { set layers($l) [layer add] set layers($l.spr-list) [lindex [layer info $layers($l)] 0] set layers($l.map) [lindex [layer info $layers($l)] 1] set layers($l.str-list) [lindex [layer info $layers($l)] 2] } # # create our maps - first, the background # map tile-size $layers(bg.map) 8 8 map add-tile $layers(bg.map) 1 [binary format H384 AAAAAAAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAAACCCCCCCCCCCCCCCCCC] map set-data $layers(bg.map) 40 30 [binary format H4800 [string repeat 0001 1200]] # and the playfield map tile-size $layers(main.map) 8 8 map add-tile $layers(main.map) 0 [binary format H384 DDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCC] map add-tile $layers(main.map) 1 [binary format H384 CCCCCCCCCCCCCCCCCCAAAAAACCCCCCCCCCCCCCCCCCAAAAAACCCCCC888888888888444444CCCCCC888888888888444444CCCCCC888888888888444444CCCCCC888888888888444444AAAAAA444444444444444444AAAAAA444444444444444444CCCCCCCCCCCCCCCCCCAAAAAACCCCCCCCCCCCCCCCCCAAAAAACCCCCC888888888888444444CCCCCC888888888888444444CCCCCC888888888888444444CCCCCC888888888888444444AAAAAA444444444444444444AAAAAA444444444444444444] map set-data $layers(main.map) 46 32 [binary format H5888 [string map {"-" 0000 "1" 0001} 11111111111111111111111111111111111111111111111----------------------------111-------------11--111111111-----1-----------111-------------11--1------------1------------111-------------11--1-1111111-------------1---111-------------11--1-1-1------1----1---------111-------------11--1---1------1----1---------111-------------11--11111------1-------1------111-------------11----1----------------1111111111-------------11----1----111----------------111-------------11----1---11------------------111-------------11----1---11-----11-----------11--------------11----1-----------------------111-----11------11--------11----------------1111111111111-----11--------11111111111------1111---------------11--------------------------1111111111111-----11---11-----------------------111-------------11--------1------11---1-------11-------------111----1--------1-------1------1-----------1--111--1------------------1------------------1--111---1----111----------1----------1-------1--111--------1-----------1---1111------------1--111-------11------1--------1-------1--1----1--111---------------------1111---------------1--111-------------1-------1--1111------1-----1--111--1111----------1-------1-------1----1--1--111--1111----1----11-------1------------1--1--111--1111---------------1111111-------111--1--111--1111--------111----1--1-------1111----1--111---------------------1--1-------1-----111--111----------------1-------1111---------------111111111111111111111111111111111111111111111111]] # # create some sprites to use as prototypes for the game # set proto(player) [sprite create] sprite collides $proto(player) box sprite add-frame $proto(player) rgb 3 3 [binary format H54 0000FFFF00FF0000FFFF00FFFF00FFFF00FF0000FF0000FF0000FF] 255 0 255 sprite bound $proto(player) 0 0 0 2 2 set proto(enemy) [sprite create] sprite collides $proto(enemy) box sprite add-frame $proto(enemy) rgb 3 3 [binary format H54 FF0000FF00FFFF0000FF00FFFF00FFFF00FFFF0000FF0000FF0000] 255 0 255 sprite bound $proto(enemy) 0 0 0 2 2 sprite mcc $proto(enemy) { add xpos, xvel add ypos, yvel } set proto(bullet) [sprite create] sprite collides $proto(bullet) box sprite add-frame $proto(bullet) rgb 1 1 [binary format H6 000000] sprite bound $proto(bullet) 0 0 0 1 1 sprite mcc $proto(bullet) { add xpos, xvel add ypos, yvel } # # and load the sound effect for the gunshot # sound load-raw [binary format H4428 807d81877c7f96948492827e938687868a8576777d8e796f7e7b797e7d72717e857467707f7a7677787f7a82887d7f7b787a7786827a848a8c8b8d8a888381878a8b80838a84878384867f848e888683879080828888897c757d7f7778767672727b7d7f808483828c9185797b84847b7a7f81868a8a847f8687827e8285878a7e7772766a7280757e6f718575757f7d7d76757a7d7d7676756d7375717a787d8184877d7f7d7a7375798181777e7d847e76838f8a7675797d868b857e79787c7c827f787c7f7b75747d7d787d808380888f8d888382828580797873717e757074727d8283837f81888275797b757e898181827b7a857d747a7d817a7c7a747b7d71717e7b79828d8479727581838b8680857c7d81817b77807c7e7c7b7c757974818d8a88868686828288888b8b85878a87858d8a8283878b8b92968d88908f8d867b878c8b908a89877f81828184878a8c837c818280828684807d7a7f858683878787837e8685817f84887d7e817b79787d7b787f7f7d7b7e807c7f7c787e7984928a8680838985837a787a74757d817c7f7d777b7378787a7f7576797d7e7f7c7a7e807a787d7b7c7b7d7b80847d7b7c7c7d7e7c7a7a7f7d766d6c75777a7e81817f7d7b7c7e7c7f7f8183838383847879837c81827b807a7a827c787d7d797779818580797c797578767776727272787a77797776757576747a7d8084837f82828189888080827f807c7b7f7c7f7f7b797a79797a7779787e817c7c7b787979787b81828281807e7e81807f7d7c7c797c7e7c8286817f807e80827f82828082807f837c7e817f847e7d7c7b7e7f8180827d7c7e7879797a7a7b7f8184838689858383858180848886807e7d7e8183838284847b7b7c7e817f808185827e7f7b7b7c7c7f7f7d7d7e7e81837e7e7e7e7c7a7b7f85828385827d7d7b767978777c82837e8183807e7e7d797b7c7c7c7a7b787b7e817f787a7d7f7d7b7d7c7a7b7c7b7c7d7e7f7f7b7b7e7f807f818383837f7d7e7f818586878580818380818381807f8081817f7f8182828080807f7f82818081807c7d7e7c7d7e7f8181807f7d7d7d7e7d7d7e7c7c7b7c7c7d7f7d7c7c7d7c7b7c7b7c7e7f7e7e7e7f7f80807d7e7d7d7c7c7d7d7d7d7c7b7d7f7f7d7c7e7f808283807e7e7d7c7d7c7d807f7e7e8080818181807f8284848586838082818081807e808080818080807f7e7f7e7f7e807f7e7e7d7e7e7d7d7d7c7d7d7d7d7c7d7e7d7a79797c7a797c7e7e7d7e7e7d7e7e7d7d7c7c7e7d7b7d7e7e7e7e7f7e7e7f7e7a7b7d7c7d7e7f7d7f807e7d7d818180807e7f7e7e7f7f7f8081828281807e7e7c7d7f808180808081838181838482828282827f7f807f80807f7f81808081807f7e8181818381818183848383838283838282828183848283838283858483848482817f7f80807e7f80807f8082828180818182848382828181828281808082807e7e7d7d7c7a7b7a7b7c7c7b7c7d7d7f808080808283828283848383848282838381808080818282807f7f81807f808182817f7f7f807f7f8180808080808181807e7d7d7d7c7b7d7e7e7f7f7f7f8081807e7e7f7e7f8182818080818181818080808181808080808181807f8080818181808182807e7f7f80807e7f7f7f80807f807e7c7c7b7c7c7f8080807f80818181818080808080808182817f7e7d7e7e7f807f80818181807f7f80807f80807f807f7f7f7f80807f7e80807f7f8180807e7d7e7e7d7f7f7f8080808181807f7f807e7c7c7c7d7d7c7c7d7e7e7d7e7e7e7e7e7f7e7f807e7d7d7e7e7d7d7e7e7f7e7d7e7f7f81818080807f7f7f7e7d7d7e7d7d7d7d7e7e7e7f7f7e7f7f7e7f80807f7f7f7f807f7f808081818080807f7f7f80818181807f7f7f7f8080808080807f7e7f7f81818181818181818182838383818181818180818180808081828281818182838280808181808080818181828282828181807f8182818383818181807f8080807f7f7f7f7f807e7f7e7f808181807f7f7f7e7d7d7d7f7e7d7d7c7d7c7e7e7d7f7f7f7f7f7f7e80817f80818181817f80818181808081818281808080807f80807f7e7e7e7f7e7e7d7c7c7e7f80807f7f7f7e7e7e7f80807f7f7f818282808081807f7f7f7f7f7f7d7c7d7f807f7f7f7e7f7f7f7f7f7f7f7f7f7f7f807f8080808080807f807f7f80807f7f7f7f7e80807e7e7f7f7e7e7e7e7e7d7c7c7c7c7d7c7d7e7f7f7f7f7f7f80807f7f7e7e7f7f7f7f7f7f807f7f7e7f7f807f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7e7f8080818181818180808080808080807f7e7e7e7e7f7f7f7f8080818180807f7f7f7f7f7f7f7f7f8080808080807f7f80818180807f807f807f7f8080807f7f7f7f7e7e7f8080808080807f7f7f8080808081818181808080807f8080808081807f7f7f807f80807f7f7e7f7f7f7f7f7f7f7f7e7f80807f7f8081808181818080807f8080807f7f7f7f7f7f7f7f7f8080808080807f7f7f7f7f7f7f7f7e7e7e7f7f7f7f7e7f80808080808080807f7f7f7f80808080807f807f7f7f7f7f7f7f7f807f7f7f7f7f808081818181808181808080808080808181818281818181818180808080807f7f7f7f7f7f7f7f7f807f7f7f7f7f7f7e7f7f7e7e7f7f7f7f7f8080807f7f7f7f808080808081808080808080807f80808181807f7f7f7f808080807f7f807f80807f7f7f7f7f7f7f8080807f7f7f7f808080818181818181818080808080808080808080808081818181818081808080807f7f7f7f80808080808080808080808080808080808080818180808080807f7f7f7f7f7f80807f7f7f7f7f7f7f7f7f80807f7f80807f7f7f7f7f7f7f7f7f80807f7f7f7f7f8080808080808080807f80807f7f7f7f7f80808080808080807f7f7f7f7f8080807f8080807f7f7f808080807f7f7f7f7f7f7f7f7f7f8080807f7f7f7f7f7f7f808080808080807f80807f7f7f7f7f7f7f7f7f7f7f807f807f8080807f7f7f7f7f7f7f7f7f7f7f7f7f7f807f80807f7f7f80807f7f7f807f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f807f7f7f7f7f7f7f7f7f7f7f7f7f8080807f7f808080807f7f8080808080808080] gunshot # # the sprite control procs - first, the player # proc run_player { id } { global proto sdata layers # fetch input set io [io fetch 0] set horiz [lindex $io 0 0] set vert [lindex $io 0 1] if { [lindex $io 7] || [io quit] } { exit } # get player movement set vx [expr { $horiz < 0 ? -1 : ($horiz > 0 ? 1 : 0) }] set vy [expr { $vert < 0 ? -1 : ($vert > 0 ? 1 : 0) }] sprite vel $id $vx $vy # check for collision with walls set colls [collisions map $id $layers(main.map) 1] set vx [expr {[lindex $colls 1] + [lindex $colls 3]}] set vy [expr {[lindex $colls 2] + [lindex $colls 4]}] # set new position incr sdata($id.px) $vx incr sdata($id.py) $vy sprite pos $id $sdata($id.px) $sdata($id.py) # if there is any movement, save the direction for possible shooting if { $horiz || $vert } { set sdata($id.gx) $vx set sdata($id.gy) $vy } # and if a shot is fired .. if { [lindex $io 2 0] } { # and the trigger hasn't been held down if { !$sdata($id.shot) && ($sdata($id.gx) || $sdata($id.gy)) } { # create the bullet set bullet [sprite clone $proto(bullet)] sprite pos $bullet [expr {$sdata($id.px)+1}] [expr {$sdata($id.py)+1}] sprite vel $bullet [expr {$sdata($id.gx)*2}] [expr {$sdata($id.gy)*2}] # and add it to the lists brlist add $layers(main.spr-list) $bullet set sdata($bullet.) run_bullet set sdata($id.shot) 1 # and play the sound sound play gunshot } } else { # reset the trigger for the next shot set sdata($id.shot) 0 } # track player with camera layer camera $layers(main) [expr {$sdata($id.px) - 160}] [expr {$sdata($id.py) - 120}] } # the bullet control proc proc run_bullet { id } { global sdata layers # run the motion program to move the bullet motion single $id # check for collisions with enemies foreach tgt [collisions sprites $id $layers(main.spr-list)] { set tgt_id [lindex $tgt 1] if { $sdata($tgt_id.) eq "run_enemy" } { # enemy hit! update score and remove enemy incr sdata(score) brlist remove $layers(main.spr-list) $tgt_id sprite destroy $tgt_id array unset sdata $tgt_id.* # and flag this bullet for removal set remove_bullet YES } } # and check for bullet removal or collisions with walls if { [info exists remove_bullet] || [lindex [collisions map $id $layers(main.map)] 0] } { brlist remove $layers(main.spr-list) $id sprite destroy $id array unset sdata $id.* } } # the enemy control proc proc run_enemy { id } { global sdata layers # increment the enemy counter incr sdata($id.ct) # and move the enemy once every five ticks if { !($sdata($id.ct) % 5) } { switch $sdata($id.dir) { 0 { set vx 0; set vy -1 } 1 { set vx 1; set vy 0 } 2 { set vx 0; set vy 1 } 3 { set vx -1; set vy 0 } } sprite vel $id $vx $vy # check to see if we've hit a wall or if we are changing direction at random if { [lindex [collisions map $id $layers(main.map)] 0] == 1 || rand() > .99 } { # yes? ok! pick a new direction at random set sdata($id.dir) [expr {int(rand()*4)}] } else { # no change in direction? run the motion-program to move the enemy sprite motion single $id } } # check for collision with the player foreach tgt [collisions sprites $id $layers(main.spr-list)] { set tgt_id [lindex $tgt 1] if { $sdata($tgt_id.) eq "run_player" } { # player hit! decrement health incr sdata(health) -1 } } } # and the proc to generate new enemies at random proc new_enemies {} { global proto sdata layers # only seldom create a new enemy if { rand() > .994 } { # create and initialize the enemy set enemy [sprite clone $proto(enemy)] array set sdata [list $enemy. run_enemy $enemy.ct 0 $enemy.dir [expr {int(rand()*4)}]] brlist add $layers(main.spr-list) $enemy # and position it, with a guarantee that it isn't in any walls while 1 { sprite pos $enemy [expr {int(rand() * (46*8))}] [expr {int(rand() * (32*8))}] if { ![lindex [collisions map $enemy $layers(main.map)] 0] } { break } } } } # # initialize the player sprite # set player [sprite clone $proto(player)] array set sdata [list $player. run_player $player.px 10 $player.py 10 $player.gx 0 $player.gy 0 $player.shot 0] brlist add $layers(main.spr-list) $player # # set up the heads-up display # set stg_x 10 set stg_y 10 foreach stg { time health score } { set sdata(stg.$stg) [brstring create] brstring position $sdata(stg.$stg) $stg_x $stg_y brlist add $layers(main.str-list) $sdata(stg.$stg) incr stg_y 8 } # and a proc to show a message and wait for keypress proc show_msg { text } { global layers set stg [brstring create] brstring position $stg 80 80 brstring text $stg $text brlist add $layers(main.str-list) $stg while { ![lindex [io fetch 0] 5] } { graphics render ; after 25 } brlist remove $layers(main.str-list) $stg brstring destroy $stg } # set traces to keep heads-up display updated trace add variable sdata(time) write {apply {{a1 a2 op} { upvar 1 $a1 a ; brstring text $a(stg.time) "Time |[clock format $a(time) -format %M:%S]" }}} trace add variable sdata(health) write {apply {{a1 a2 op} { upvar 1 $a1 a ; brstring text $a(stg.health) "Health|$a(health)" }}} trace add variable sdata(score) write {apply {{a1 a2 op} { upvar 1 $a1 a ; brstring text $a(stg.score) "Score |$a(score)" }}} # # the main game loop itself - first, show the greeting msg # show_msg "Welcome to hell!\r\nUse arrow keys\r\nto move, ctrl to\r\nshoot, esc to\r\nquit.\r\n\nPress enter to begin." # and set these data (which will fire the traces and set up the heads-up display) set sdata(health) 100 set sdata(score) 0 set sdata(start_time) [clock seconds] # the main loop while { $sdata(health) > 0 } { # run the callbacks for the sprites foreach { id callback } [array get sdata *.] { # make sure that sprites that are removed from play mid-loop are not called if { [info exists sdata($id)] } { $callback [string trim $id .] } } # and take care of accessory business new_enemies set sdata(time) [expr {[clock seconds] - $sdata(start_time)}] graphics render delay 50 } # the game-over message show_msg "You have perished" ====== ---- !!!!!! %| [Category Games] | [Toys and Games] |% !!!!!!