moved from Ask, and it shall be given.
Question (07 July 2003)
Recently I've looked at a gaming system called Sphere which is built using JavaScript as the internal scripting language. I starting thinking doing something similar, particularly using a starkit for distribution, might make for some interesting old-graphics (i.e. 8/16 bit) games like the old Nintendo games. I've searched for details on doing sprites under Tcl/Tk and haven't found much information. I've reviewed games like tk_bugz which do some of the work, but basically is there a means of doing a true sprite environment in native TCL or must a 2D/3D gaming library be linked in?
Any pointers to docs/examples would be appreciated.
schlenk For the basic 2D stuff you don't need a library, the Tk canvas is all you need. For 3D work i would recommend something like OpenGL. There also is a binding to SDL available, called Oil.
FW: Actually, the Tk canvas is severely limited. It only allows for basic shape drawing and placing images onscreen, and is slow. I have yet to find a non-restricting use of it.
(ZB 2009-04-21 Surely the above has been written in 2003 - it doesn't look valid for Tk 8.5.x anymore) Well, the above text was added to this page in July 2003. It is tough from the few words provided to tell whether the 21st century version of the canvas would provide the functionality FW wanted.
JSB 2009-04-26 With a bit of cleanup and optimization the below code would be fine for low-impacted games like Sokoban or others with little to no animation and fast object movement. To do something with a lot of sprites and lot's of animation frames, you will need to double buffer. Double buffering is a common practice in gaming (modern and old). In double buffering, you do all your changes to a hidden "bit map" and once you are done you "commit" the changes by bliting the bit map to the display. This keeps display updates from being so flashy. In real display terms it also helped speed up operations by using non-video memory (I'm not sure how much that applies anymore).
JSB I do think a person could write a double buffering game in pure tcl. You would just create an image of your game display, set to the proper size with your backkground if you have one. After this is done, just do image copies to it of your all your sprites. Once you are finished, then display this image on your canvas. Many Tcl game writers may do this already. Here is a small simple example of Double Buffering
ZB I'm afraid, we don't understand eachother; I'm not sure, what is the point. Your demo - even, when I lowered the amount of sprites ("shapes", more exactly) from 100 to just 10, required almost entire power of Pentium II 400, while giving average optical effect. I'm not sure, is the proposed technology really best suitable for Tk canvas.
I'm getting different approach: instead of forcing any particular method, I'm trying rather to use the ones, that are "native" to canvas; trying to look "what can it offer", then find out "what can be done using canvas' commands/methods" - of course, not all can be done. So IMHO to achieve quite nice effect of animated sprites, it's quite enough to use - yes, I'll write it again - swapping the formerly prepared images using "itemconfigure". Did you run that MAXTEROIDS little game? Of course, not much animation there (but still) - but you could see, that it's running quite smoothly, with low demanding for CPU. So are Asteroids.
ZB With Tk, there's no need to provide any buffers - you just "itemconfigure" existing canvas object using previously prepared image. The (rather big) disadvantage - which isn't fault of Tk - is, that we have no possibility to change the screen contents exactly at the time of "ray's return". To allow this, raster-interrupt would be needed, like that one in C-64.
JSB Without any double buffering you will get flashing, I see it playing with the demo code below. This will not be as noticeable with just a few objects/animation, but worse as more are added, displayed & moved. On most of the older systems you had a "raster" type interrupt. On the PC it was the Horizontal Retrace/Refresh interrupt. I think the canvas is great for many real world uses and even a few game genres but the users mileage will vary.
ZB ...and so I wrote: you don't need to use any special "buffering" - since you can prepare in advance that "changed" graphics (next phase of animation) using ordinary canvas commands. The same with replacing the "original" images (".c itemconfigure..."). Double buffering (and even triple) gives nothing more in such situation, when it all has been buffered already. The problem is, that even replacing the graphics via "itemconfigure" can occur in the moment, when next frame is drawn - but without raster-interrupt it's unavoidable, because there's no control.
JSB Please look at the below code and maybe run it before you comment again. The code uses place to set and display the images with. I do understand what you are saying though about the redraw interrupt. This possibly might be handled by the windowing system already since we are not talking about writing to real video memory with a canvas widget.
ZB The code below is using itcl - and most probably this makes it slow (all OO extensions are making TCL scripts slower). How animated simple "canvas sprites" can look like - made using "pure TCL", and without any buffering - you can check out testing another Asteroids - please, look at the code and maybe run it before you comment again. No flickering at all even on my old Pentium II 400.
I forgot: have a look at another demo: Bouncing Balls - of course, there's no animation, but the demo listing here doesn't contain any animation as well. One unpleasant effect, that easily could be fixed, is poorly made movement, that results in effect of "short jump". But look, how little code it took... and compare the result. Pay attention, how little CPU time it requires.
JSB Okay, my last on this, as I do not care to argue. Both are examples of very nice canvas code. Nether use bit maps. Sprites are usually bitmaps of some type. Thank you for the information and examples.
ZB OK, have another one then, with animated bitmaps: MAXTEROIDS - just run, and take a look: is it flashing?
LV Tcl itself has nothing about sprites, due to the fact that it has nothing to do with graphics. Tk, the basic graphics interface for Tcl, does not document any of its features and functions as being sprite related. Check out ODIE or some of the interesting experiments on RS's pages for some possible examples though.
AM If the canvas is too limited, then you might want to check zinc, which uses OpenGL for rendering. As for sprites: you can do that by creatively applying the after command. I will try to come up with a more enlightening example, but check for instance all those pages with moving stuff - falling marbles for instance. The simple example is a little hail storm - for its appearance.
wdb In my understanding, sprites are an imaging system where the "true" pixels are allocated somewhere in memory, and the appearance is realized by "linking" them to the screen. (My ancient C64 provided 8 sprites.) In Tk, image create photo ... returns a string, e.g. image1, naming such a pixel allocation, and the canvas command .c create image -image imgage1 links to the allocated pixels. This is a sprite system per definitionem. So, yes, Tk natively has sprites!
ZB What you've described, are hardware sprites (BTW: using relatively simple tricks with raster-interrupt C-64 was able to use 32 or more sprites, for example: "noborder graphic" demo used even 112 sprites). Tk has software sprites.
Not so big difference, since using Tk we are operating all the time in (as it was called in 8-bit times) "high-resolution" graphics. There are both advantages (like no size limit) - and disadvantages (like no collision detection using interrupts).
The greatest advantage is, that it doesn't depend on the hardware.
LV My apologies. Only after seeing someone refer to Tk's images as sprites did I go off and look up the definition of sprites. I had always thought that sprites were more than just images - that they were were a type of almost threaded object that you programmed with look and action and then let it go off to do its own thing so to speak (ZB such "complex sprite-bots" you can have using canvas items for graphics, and coroutines for holding data/operating sprite). I had never really thought about how one might use the canvas widget images, tags, etc. to do a similar thing.
I appreciate the correction. I have now made a bit of a correction to my original statement, to better reflect the reality. The reality is that Tk reference documentation does not use the term sprite in relationship to any of its features, and so a user who wants to write some games using sprites needs to think out what specific functionality they are wanting, then seek how best to implement that functionality using tcl based GUI toolkits.
ZB Sprites are hardware controlled graphical objects, that can be displayed and moved around on a layer above the usual graphics or text(!) display (! - only hardware sprites could appear in the "pure text mode" too). Some more info:
Of course, other 8-bitters also had sprites: for example all MSX-ers had 32 sprites, and Atari had sprite-like "player-missile" graphics. Tk currently has almost everything, what sprite graphics need, except ready-to-use collision detection. To be more precise: such "software sprites" were at that time (second halve of 80-ties) called "shapes". And perhaps it would be more appropriate term for what Tk offer in area "sprite graphics" - there won't be any confusion with "real", hardware sprite.
JAG I too once tried to create a pure tcl, object-based sprite library (in my case, using Snit), but found the overhead imposed by Snit to be too severely limiting in terms of speed. I have since moved away from that approach to one based more on "brute force", in order to approach the speeds necessary for sprite-based game animation. While the results have been mostly satisfactory (on reasonable hardware), it would sure be nice to have a library that could handle all the gory details of sprite creation/management *and* provide the speed necessary for a usable end product. Maybe the current core oo work (and Will's SNIT rewrite experiments based on that work) will lead to something better suited for this task.
slebetman 29 April 2009: Here's my experiment in "benchmarking" canvas's ability to do sprites: sprites on canvas test. Sorry about the messy code, the sprite logic itself was taken directly from my javascript projects (http://slebetman.homeip.net/rts_test/tank3.html , http://slebetman.homeip.net/rts_test/unitdemo.html ).
(From the original author of this question) I'm updating this to present my first attempt at creating some Sprite type code. I'd appreciate lots of feedback. Jere
Ok folks, here's my first attempt at creating a Sprite type class (using Itcl). Please critique it mercilessly.
Jere
# ----------------------------------------------------------------- # # Mobile Objects # # ----------------------------------------------------------------- # # author: Jere McDevitt # # License: LGPL # # ----------------------------------------------------------------- # # The Sprite allows the creation of a Sprite object that may have # attached to it a sequence of images that simulate animation. # # The basic idea is that each Sprite object creates an internal # canvas for the specific size of the sprite. For each image provided, # an image item is created on the canvas. # # The sprites expect to operate within a parent component that is using # the place manager, so it can move cleanly. The sprites do not move outside # the borders of the parent. # # If a sprite is to be animated, it must be added to the animation list # using # # mob::animate mob # # To start animation, you need to call # # mob::updateAnimation # # # ----------------------------------------------------------------- # # ToDo # # Collision Detection # Stop animation for a given sprite # # ----------------------------------------------------------------- # # This code was written using tclkit-win32.upx.exe # Appreciate any feedback # ----------------------------------------------------------------- package provide app-mob 1.0 package require Itcl package require Tk namespace eval mob { itcl::class Sprite { #-------------------------------- #Common variables for all Sprites #-------------------------------- #gives each sprite a unique index private common _SpriteCount 0 #used to support movement public common DIRECTIONS { north south east west northeast southeast northwest southwest} #holds the sprites to be animated private common animationList {} #User supplied name of sprite. Combined with #parent ($parent.$name) when creating the canvas object. private variable name #Keyed array holding details of parent. Keys are: # parent(name) -> parent path # parent(width) -> [winfo reqwidth $parent(name)] # parent(height) -> [winfo reqheight $parent(name)] # private variable parent #Will hold images for this sprite. Animation in in list order private variable images {} #Keeps track of visible image private variable curImgIdx 0 #Size and location of the sprite private variable xpos 0 private variable ypos 0 private variable height 0 private variable width 0 #Holds the created canvas object private variable cnvs #The direction the sprite is moving private variable dir north #The id of the sprite private variable idx #--------------------------------------------- # constructs a sprite and sets the internal # data. # # @param w width # @param h height # @param nm name for the sprite # @param p parent object #--------------------------------------------- constructor { w h nm p} { set height $h set width $w set name $nm set parent(name) $p set parent(width) [winfo reqwidth $p] set parent(height) [winfo reqheight $p] set cnvs [ canvas $parent(name).$name \ -width $width \ -height $height \ -bg black \ -highlightthickness 0 \ -borderwidth 0 ] set idx $_SpriteCount incr _SpriteCount $cnvs create text 0 10 -text $idx -tag "label" -fill yellow -anchor nw -font { Tahoma 8 bold } set images {} } #--------------------------------------------- # sets the list of images. This method will # read count images from the map (really a # large image) using the row variable as the # default upper y coordinate. # # @param imgMap list of images to use # @param row upper y to read from # @param count number of image frames #--------------------------------------------- public method setImages { imgMap row count } { $cnvs delete "label" set ypos [expr $row * $height] for { set i 0 } { $i < $count } { incr i } { set sox [expr $i * $width] set soy $ypos set sdx [ expr $sox + $width ] set sdy [ expr $soy + $height ] set img [ image create photo -height $height -width $width ] $img copy $imgMap -from $sox $soy $sdx $sdy set tn "" append tn $name "_" $i lappend images [ $cnvs create image 0 0 -image $img -tag $tn -state hidden -anchor nw] } } #--------------------------------------------- # Hides the current visible image, and shows # the next image. If at end of image list, # rotates back to first. #--------------------------------------------- public method nextImage { } { if { [llength $images] != 0 } { $cnvs itemconfigure all -state hidden incr curImgIdx if {$curImgIdx >= [llength $images]} { set curImgIdx 0 } append tn $name "_" $curImgIdx $cnvs itemconfigure $tn -state normal } } #--------------------------------------------- # Attempts to move the sprite to the specified # x,y position within the parent (uses place # command). This returns a 2 element array # indicating whether there was a failure moving # in either direction {xresult yresult}. The # elements are boolean (0 or 1), 0 indicates # success. # # @param x x position to move to # @param y y position to move to # @return 2-element list {xresult yresult} #--------------------------------------------- public method moveTo { x y } { set res {0 0} place forget $parent(name).$name if { $x >= 0 && [expr $x + $width] <= $parent(width) } { set xpos $x } else { lset res 0 1 } if { $y >= 0 && [expr $y + $height <= $parent(height)] } { set ypos $y } else { lset res 1 1 } place $parent(name).$name -x $xpos -y $ypos return $res } #--------------------------------------------- # This method is called to have the sprite # move in the direction it is currently facing. # # The default deltas are 1 for both x and y # direction, but others can be provided. This # calls through to the moveTo method so it # returns the same return (2-element list) # as moveTo does # # @param sx x step to move by # @param sy y step to move by # @return 2-element list {xresult yresult} #--------------------------------------------- public method move { {sx 1} {sy 1} } { set dx 0 set dy 0 switch $dir { north { set dy [expr -$sx] } northwest { set dy [expr -$sy] set dx [expr -$sx] } northeast { set dy [expr -$sy] set dx $sx } south { set dy $sy } southwest { set dy $sy set dx [expr -$sx] } southeast { set dy $sy set dx $sx } east { set dx $sx } west { set dx [expr -$sx] } default { set dir north } } return [$this moveTo [expr $xpos + $dx] [expr $ypos + $dy] ] } #--------------------------------------------- # Changes the direction the sprite is moving. # # @param d Direction to change to #--------------------------------------------- public method setDir { d } { set dir $d } #--------------------------------------------- # This method reverses the direction of the # sprite. There are three choices for # determining the direction to reverse, as # defined by the 'which' argument: # # which = both , do a full reverse # which = x , only reverse x direction # which = y , only reverse y direction # # Note that the diagonal movements are the # ones really affected by using either 'x' or # 'y' for which. # # @param which which axes to reverse # @return new direction #--------------------------------------------- public method reverseDir { {which both} } { switch $which { both { switch $dir { north { set dir south } northwest { set dir southeast } northeast { set dir southwest } south { set dir north } southwest { set dir northeast } southeast { set dir northwest } east { set dir west } west { set dir east } } } x { switch $dir { northwest { set dir northeast } northeast { set dir northwest } southwest { set dir southeast } southeast { set dir southwest } east { set dir west } west {set dir east } } } y { switch $dir { north { set dir south } northwest { set dir southwest } northeast { set dir southeast } south { set dir north } southwest { set dir northwest } southeast { set dir northeast } } } } return $dir } #--------------------------------------------- # This common procedure is called by the after # command and it loops through the list of # of sprites calling the nextImage. Animation # is fixed a twice per second. #--------------------------------------------- public proc updateAnimation { } { foreach x $Sprite::animationList { $x nextImage } after 500 mob::Sprite::updateAnimation } #--------------------------------------------- # This method is called to add a sprite to # the animation list. A sprite is only # added once. # # @param ai sprite to add #--------------------------------------------- public proc animate { ai } { set idx [ lsearch $Sprite::animationList $ai ] if { $idx == -1 } { lappend Sprite::animationList $ai } } #--------------------------------------------- # Simple method to generate a random # direction parameter. #--------------------------------------------- public proc randDir { } { lindex $Sprite::DIRECTIONS [ expr {int(rand() * [llength $Sprite::DIRECTIONS])}] } } }
Simple test program to drive the object.
#Create a simple canvas to test the sprites on set blocks {} wm title . "Sprite Test" #Frame works just as well canvas .c1 -width 640 -height 640 -bg white -highlightthickness 0 -borderwidth 0 pack .c1 -expand 1 -fill both #If you want to have animated images rotating, create #a collection of images X by X in size (match the size with the #size of the block. Can have as many frames in a row as wish. Load #the image and apply. For example, assume a strip of 4 32x32 images #call blocks.gif # #set path [file dirname [info script]] #set imgMap [ image create photo -file [file join $path blocks.gif]] for { set i 0 } { $i < 100 } { incr i } { set x [ mob::Sprite block$i 32 32 "block$i" .c1 ] # If have images, can use them # $x setImages $imgMap 0 4 # mob::Sprite::animate $x $x moveTo [expr rand() * 640 ] [expr rand() * 640] $x setDir [ mob::Sprite::randDir ] #Add to an array for later access lappend blocks $x } #mob::Sprite::updateAnimation #Simple method to manage movement of blocks proc moveBlocks { ms } { global blocks set dx 1 set dy 1 foreach s $blocks { set dx [ expr rand() * 4] set dy [ expr rand() * 4] set block [lindex $s 0] foreach {x y} [ $block move $dx $dy] { if { $x == 1 || $y == 1 } { $block setDir [ mob::Sprite::randDir ] } } } after $ms moveBlocks $ms } #Must adjust movement with number of blocks. If goes to #fast, canvas can't keep up after 100 { moveBlocks 100 } #if there is any animation, call this to kickstart it bind .c1 <KeyPress-q> {exit} focus .c1
ZB 2011-07-26 Although I asked on c.l.t. already, but maybe those, who don't read the group, will take a look here: I'm pondering about possible ways to create "vectored sprites" with "neon" effect (like on this screenshot: http://tinyurl.com/6gucpa6 ). Of course, the obvious way is to create blurry background separately, and move it along with vector canvas object - but maybe someone will have another idea?