|
Forum Index : Microcontroller and PC projects : MMX5.04.11Beta: Towards Sprites
| Author | Message | ||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10568 |
Please find attached MMX5.04.11Beta for the 100/144 pin chips 2018-01-22_034106_MMX5.04.11Beta.zip and the 64-pin chips 2018-01-22_034205_MMX645.04.11Beta.zip This release plays with some ideas for Sprites building on the existing BLIT functionality which has also been slightly simplified/improved. Sprites are full colour and of any size and shape. SPRITE/BLIT images can be displayed partially off the screen, this was not previously the case for BLIT on the MMX, and I believe the MM+, where partially off screen images were corrupted. The commands and function SPRITE and BLIT can be used interchangeably NEW FUNCTIONALITY SPRITE LOAD A new option in BLIT/SPRITE is added to directly load a .PNG file from the SDcard. PNG files are the standard for sprites in CSS and it seemed to make sense to allow the Micromite to load them directly 2018-01-22_034650_apple.zip The syntax is: SPRITE LOAD #n,"filename" The # can be omitted and "n" is the number of the buffer to hold the image. The maximum number of buffers is currently set to 50 but you will be limited by available memory when using a TFT display (unlikely to be an issue with a VGA display). The system will automatically append ".png" to the filename if omitted. Valid png file formats are RGB888 and RGBA888. The size of the loaded image can be interrogated by the function SPRITE - see below. To minimize memory usage the sprite image should be image-edited to have the active part (non-background) tightly fitting into the rectangular image outline. Sprites can also be loaded from .BMP files by using LOAD IMAGE and SPRITE READ. SPRITE SHOW This command displays a sprite on the display. All types of display which currently support BLIT will work with this command (SSD1963, ILI9341, VGA). The syntax is SPRITE SHOW #n, X, Y [,sprite background colour] The # can be omitted and "n" is the number of the buffer holding the sprite image. This command displays the SPRITE on the screen with the top left of the sprite image at X,Y. The image is overlayed on the screen such that only pixels which are not equal to the sprite background colour will overwrite the display (the sprite background colour defaults to 0). The command will automatically save the display background before overwriting it. If the SPRITE is already displayed the command will automatically replace the background, move the sprite to the new location and re-display it. Thus the SPRITE SHOW command is also the "SPRITE MOVE" command. SPRITE HIDE This command removes a sprite from the display and restores the display background The syntax is SPRITE HIDE #n The # can be omitted and "n" is the number of the buffer holding the sprite image. The sprite image is not deleted but the memory saving the display background is released SPRITE() The sprite function allows you to keep track of the sprite and will be expanded to deal with collisions The syntax is: SPRITE(#n, FUNC) The # can be omitted and "n" is the number of the buffer holding the sprite image Valid values of FUNC are W - returns the width of the sprite H - returns the height of the sprite X - if the sprite is currently displayed returns the X coordinate of the top left of the sprite - otherwise returns -1 Y - if the sprite is currently displayed returns the Y coordinate of the top left of the sprite - otherwise returns -1 CHANGES TO BLIT BLIT READ The BLIT READ command does not change in format but the functionality is slightly different. BLIT READ now stores the width and height of the region in the blit-buffer. This allows BLIT WRITE (or SPRITE SHOW) to be used without specifying the width and height of the buffer. BLIT READ will now re-use the buffer if a new read is the same width and height. This allows the user to re-use the memory without having to close the buffer first. BLIT WRITE The BLIT WRITE command now no longer takes a width and height parameter. The new syntax is BLIT WRITE #n, X, Y The # can be omitted and "n" is the number of the buffer holding the image. This command displays the image on the screen with the top left of the sprite image at X,Y. The image overwrites the screen so all pixels within the rectangular image boundary are replaced. The command uses the width and height of the image taken from BLIT READ or BLIT LOAD. These can no longer be specified. The code for a very simple demo is attached Option explicit option default NONE dim integer i DIM INTEGER iw, ih ' load sprite BLIT LOAD #1,"apple" BLIT LOAD #2,"apple" iw=BLIT(#1, W) ih=BLIT(#1, H) 'create a background load image "tiger800" dim n%,m%, x1%=rnd()*(mm.hres-iw), y1%=rnd()*(mm.vres-ih), x2%=rnd()*(mm.hres-iw), y2%=rnd()*(mm.vres-ih) dim x1o%=-1, x2o%=1, y1o%=-1, y2o%=1 dim s%=8 blit show 1, x1%,y1% blit show 2, x2%,y2% do do x1%=x1%+CINT(rnd()*s%-s%\2+s%\2*x1o%) y1%=y1%+CINT(rnd()*s%-s%\2+s%\2*y1o%) if x1%>=mm.hres then x1%=mm.hres x1o%=-1 endif if y1%>=mm.vres then y1%=mm.vres y1o%=-1 endif if x1%<=0-iw then x1%=0-iw x1o%=1 endif if y1%<=0-ih then y1%=0-ih y1o%=1 endif loop until collision%()=0 blit show 1, x1%,y1% do x2%=x2%+CINT(rnd()*s%-s%\2+s%\2*x2o%) y2%=y2%+CINT(rnd()*s%-s%\2+s%\2*y2o%) if x2%>=mm.hres then x2%=mm.hres x2o%=-1 endif if y2%>=mm.vres then y2%=mm.vres y2o%=-1 endif if x2%<=0-iw then x2%=0-iw x2o%=1 endif if y2%<=0-ih then y2%=0-ih y2o%=1 endif loop until collision%()=0 blit show 2, x2%,y2% pause 250 loop function collision%() collision%=1 if x1%+iw<x2% OR x1%>x2%+iw OR y1%+ih<y2% OR y1%>y2%+ih then collision%=0 end function |
||||
| Geoffg Guru Joined: 06/06/2011 Location: AustraliaPosts: 3308 |
Excellent job. I have not thought this through fully but how would this work with overlapping sprites? Do we need some sort of layering system (easy to say but hard to do)? I look forward to collision detection. Geoff Geoff Graham - http://geoffg.net |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10568 |
My thinking tends to evolve as I code because the limitations of the art of the possible/performance often constrain the solution but my current thinking is as follows and I would greatly appreciate feedback/comment. My first assumption is that a collision will always be reported against the sprite that creates it i.e. we won't try and trigger interrupts on a stationary sprite when it is "bumped into" This then leads to the consequence that all actions resulting from the collision will be programmed by the MMBasic coder from the point of view of the sprite that they just moved. The basic collision processing is assumed to be as follows: Sprite A is moved to a new location. This happens without any checking. The sprite is simply redrawn in the new location which may overlap another sprite. Following all moves, a fast sweep is performed in the firmware to check if the containing rectangle of sprite A intersects with the containing rectangle of any other sprites or the display limits. If it doesn't then there is nothing else to do If it does then a detailed check is performed of the sprite pixels to see if any of them overlap, if they don't, again there is nothing else to do. If they do then a list is created of each item that it overlaps. The function SPRITE should be called by the MMBasic programmer and will indicate to the programmer that a collision has occured - number_of_collisions=SPRITE(#A) Each individual collision can then be interrogated using sprite_which_I_hit=SPRITE(#A, integer_value) ' integer_value in range 0 to number_of_collisions-1 This gives the programmer information about which sprites were hit. Display edges will be coded as pseudo sprites: 1000,1001,1002,1003 or something similar. Now it is up to the programmer to code the result of the collision. If he wants more information about where the hit occurred then he can use the SPRITE function to read off the coordinates of the two sprites if he isn't already tracking that information in Basic. Suppose the result is that sprite_which_I_hit disappears. This will use a new sub-command SPRITE REPLACE #A, #sprite_which_I_hit This uses the stored background for sprite_which_I_hit to replace the sprite_which_I_hit pixels WHICH DON'T OVERLAP sprite A Alternatively it could be sprite A which disappears - this is easy just SPRITE HIDE #A, this automatically restores the bits of sprite_which_I_hit which were overwritten. If they both disappear then it is also easy just SPRITE HIDE #A, then SPRITE HIDE #sprite_which_I_hit. NB because we are always coding from the perspective of the sprite we just moved we always know the order to do this. And if there is an explosion then hide SPRITE HIDE #A, then SPRITE HIDE #sprite_which_I_hit in the correct order, display the explosion sprite, and then HIDE it after a suitable period. If appropriate to the game restore one or other of the sprites as required. In order to keep the code tidy each sprite will only keep the details of the collisions following a SHOW, until the next SHOW command. This allows the programmer to deal with all collisions in any way he wants but the "clock" will be reset when the instigating sprite is moved. So in summary, we need one extra sub-command "SPRITE REPLACE" which needs some pretty sneaky coding and a COLLISION function (as part of the SPRITE function) which indicates if a collision has taken place after a move (SPRITE SHOW) and provides a list (that can be interrogated one element at a time) of each and every COLLISION resulting from the move which resets after any subsequent move. The big challenge will be performance. At the moment the sprite code works pixel by pixel but, for VGA, it can be optimized by treating a line of pixels in the X axis as a set before a byte boundary, those that are covered by whole bytes, and a set after the last byte boundary. This is something I do in various display drivers but haven't yet implemented in the sprite code. |
||||
| Geoffg Guru Joined: 06/06/2011 Location: AustraliaPosts: 3308 |
I am not a games programmer but from discussions that I have had there seems be two things missing. The first is that ideally the collision mask should be different from the actual pixels of the sprite. I cannot see how a mask could be embedded in a PNG file so perhaps loading a separate "collision mask" PNG with just one colour might work. The next issue is that you would have to maintain four bitmaps in RAM (red, blue, green and collision mask). That seems like a lot of work (and uses precious RAM) just to have an independent collision mask. Could some game programmer (WW, @Heidelberg, anyone) comment on how important it is to have a collision mask which is different from the coloured pixels? The second point is that the SPRITE REPLACE idea seems to be a substitute for placing sprites on layers. I was thinking that the higher numbered sprites would overlay the lower ones and if a lower numbered sprite was moved under a higher one the higher numbered sprite would remain intact. Again that is a lot of work and it would not be needed is sprites only occasionally overlapped. So more questions for games programmers. Do sprites often overlap? If they do would the proposed SPRITE REPLACE command provide the required functionality? Full automatic layering of sprites would have a significant performance impact so there is a significant trade off in something so complex. Geoff Geoff Graham - http://geoffg.net |
||||
| CaptainBoing Guru Joined: 07/09/2016 Location: United KingdomPosts: 2171 |
from chatting to friends in the business today; Very. Even so far as it is vital for returnability for the two reasons I mention at the beginning of this thread: 1. It encourages "close shave with survival" which gives a buzz for the palyer 2. It eliminates a perceived lack of contact in a player death which leaves the player feeling cheated and reluctant to return - we've all been there "That was no-where near me!" I was pointed to the following vid (as an arguable pinnacle of sprite based 80's shoot-em up, side-scroller arcade tech; on Konami's "Twin 16" board.), the curiously named "Gradius Gofer" which was released in many English speaking countries as "Vulcan Venture". Drop the speed to 25% and watch from about 1:40 onwards paying close attention around 1:42 to 1:44. If the hit mask had been the same as the image, it would have resulted in a player death. Instead it was a joystick mashing frenzy that got the player hot & sweaty and pouring quarters into the thing. This is Konami - they knew how to rinse money by giving a thrilling game. https://www.youtube.com/watch?v=kUByXzTil1g Why you don't have hitmasks the same as the sprite graphic: ![]() p.s. this machine is dual 16MHz 68000s with a Z80 @4MHz + Yamaha chip just for the soundtrack/SFX. hope this helps. |
||||
MicroBlocks![]() Guru Joined: 12/05/2012 Location: ThailandPosts: 2209 |
Collision can best be done by using simple shapes like circles and rectangles. You save the data of visible sprites/background elements preferably in a structure that holds variables like this: [code] Layer, X, Y, NumberOfImages, AnimationDelay, CurrentImageIndex, SavedBackgroundImage, Images[], Collision[] [/code] Layer, X and Y is to put it on a certain layer and location on the screen. NumberOfImages hold a count of images that can be displayed consecutively to be able to create a animation loop AnimationDelay is the number of milliseconds that have to elapse before showing the next image. Images[] is an array of raw image data that can be directly transferred to the screen Collision[] is an array that holds boundary data of the specific image that is displayed. It can be a collection of circles and rectangles. A sprite can be tested against other sprites or background elements by first checking for the same layer and then use the rectangles/circles to determine if they collided. When a sprite or background element containes an animation you can check for collision with the right 'frame'. When there is a collision both (or more) objects should get a collision event containing the data of the index of itself, the index of the object it collided with. Animation of effected objects should be stopped (as this is probably done by a timer), this will give time for the program to change to another animation. More complicated but ultimately easier to use is to have multiple sets of animations per object so that upon certain times or conditions animations can be changed without the use of a main programming loop. A simple timer could do all the work for displaying everything and checking for collisions. Adding motion to the object could also be done in combination with an animation. Walk left, walk right for instance can start an animation that also includes movement. The main program just waits for collisions and user input. If you test for collisions using pixel data then programs come to a grinding halt as it takes too much time. Using simple mathematical objects that define the border of an object closely are very fast. Even games on super powerfull PC's use mathematical objects as even on those systems testing pixel data is very slow. Microblocks. Build with logic. |
||||
| matherp Guru Joined: 11/12/2012 Location: United KingdomPosts: 10568 |
Agree with this approach and it is what I was intending to do, but see below. The PNG file is converted to a simple RGB memory image by the load program and this can easily be extended to create a fourth plane which would be the collision mask. Thinking more about the performance issues, I think this approach is probably a necessity. It would be easy to develop a "best-fit-rectangle" algorithm which overlays the sprite. It would need to include all the sprite pixels or artifacts would be created when "collisions" occurred with outlying pixels that weren't registered. The functionality for this is covered by my proposal in the second case I mention, but my assumption was that it had to be "user coded". The collision occurs when I move object A. The collision handling reacts by redrawing sprite_which_I_hit on top of object A . The question then is does this need to happen automatically without the MMBasic programmer coding for the collision? If so then the layer approach is needed. Or, even more radically, do collisions only take place between sprites on the same layer (or within a defined "layer_collision_parameter" of each other)? |
||||
| WhiteWizzard Guru Joined: 05/04/2013 Location: United KingdomPosts: 2959 |
My limited understanding is that Sprites can only collide IF on the same layer. |
||||
MicroBlocks![]() Guru Joined: 12/05/2012 Location: ThailandPosts: 2209 |
It is best to let the programmer decide how the circle or rectangle is defined. Imagiine a 'person' with a tall had and a sword. The person can be a sprite, the hat a sprite and the sword a sprite. Or it could all be one bigger sprite. If it is a single sprite you overlay circles and rectangles over it. It would be nice if a single rectangle would suffice but it might need two or three. It could also be a combination like two rectangles and a circle. Maybe the 'person' has a protective suit. So only his head (circle), the sword (rectangle) , his hat (rectangle) and his body (rectangle) all have different 'actions' when they collide with another object. A 'bullet' would bounce of the 'body', it will remove the 'hat', the 'sword' can deflect it and the bullet hitting the 'head' will result in death. A single sprite, with four collision borders can all do that. Layers can make a game more realistic. Imagine there is a battle going on, in the background their is shooting but they are far away and will not effect the 'person' as they are not on the same layer. You can still use the same data structure and manipulation of objects but it is used for 'dressing up' the scene. You could even add a 'debug' flag to each sprites data structure that will display the rectangle/circles. That would be a huge help in developing assets. You can treat everything on screen as a 'sprite'. A better word would be object because sprite already has meaning for people. An object could be an image of a rock. This 'rock' could have a circle as a collision border. If a 'person' object aproaches this it would 'hit' it and this could then be used to stop movement. If the 'rock' and the 'person are not on the same layer then the 'person' can just walk behind or in front of it depending on the layer. With all these 'objects' you can build a stage that can 'run' by itself. A timer function could take that data from each object and apply animation to it. If something has movement like a 'person' it could add not only animation but also movement. The same timer function checks for colisions. The collisions are handled by the program. A datastruture like mentioned could also be used as a base to add for instance a 'physics' engine. Object could have weight and all other kinds of properties like smoothness, springy, hardness etc. The 'world' could have gravity etc. Or no gravity for space games. A stack of rocks can then collapse all by itself. Obviously this is another huge taks but once a base is made lots can be added. If a chip is used with enough memory then rendering the whole scene in memory first would allow for very smooth animations. You would need at least two. While one is displayed the other is rendered. When rendering is finished then that one will be displayed in the next screen refresh. It would require only changing a few pointers. But it does consume twice as much memory. Some pseudo code: [code] if SpriteA collides with SpriteB then call UserFunction(SpriteA,CollisionIndex,SpriteB,CollisionIndex) end [/code] If from above example the 'person' was hit in the head then this function would be called if the circle drawn as a border on the face would be the collisionIndex and if SpriteB is the 'bullet' then then rectangle drawn as border on the bullet would be its Collisionindex. As everything are numbers it can be done very efficiently. The person is index 10 and bullet is index 11) then the function would be called with [code] UserFunction(10,1,11,0) [/code] 10 being the index in the datastructure array to point to the right object (person). 1 would be the definition of the circle collision border 11 would be the index to the bullet object 0 would be the rectangle collision border If the bullet hit the 'Hat' then it would be [code] UserFunction(10,2,11,0) [/code] Where the 2 stand for the rectangle collision border of the hat. Microblocks. Build with logic. |
||||
| CaptainBoing Guru Joined: 07/09/2016 Location: United KingdomPosts: 2171 |
Yep I agree... no need for the hit mask to be a "mini version" of the graphic... but read on... I have been thinking about this. I think you can dispense with the hit mask and use sprite shadowing. If you have two sprites overlaid (one invisible and only trigger a collision event on that), the rest is just down to programming. Really, really, simply... Say you have 64 sprites ( don't panic it is hypothetical), 0-63 and each sprite could have a bit-wise integer where a 1 represents interest (an event to be triggered) for that sprite - e.g. bit 32=1 means generate an event if I collide with sprite 32 etc... So if the player was sprite 1 and the collision-detect sprite was 0, the bit field would have a 0 in the opposite numbers for each sprite and the hit flags would be 1s for all the baddies sprite numbers:sprite zero flags could be 1111111111...111111111100 so it is interested in every other sprite (in this instance) except itself and sprite 1. In turn, sprite 1 would have all 0s - this implements the "squeeze through" - it is effectively immune to hits from anything, but it won't matter because sprite zero is shadowing it and if that gets whacked... when two sprites collide, only the bit field needs to be ORed for the relevant sprites to trigger an event and when you move a sprite and it overlays another you "only" have to OR two integers to work out if an event is triggered. All the baddies could have 0's (unless you wanted some FX when they hit so you could have an event to change it when it hit. The hit with a 1 in the hit mask ensures a collision event. This makes it really easy to implement things like "power pills" etc to make the player invincible for a time, simply set sprite 0's collision integer to 0 then restore it after a time. The collision routine would have to work out which hit which and what to do about it - standard fare, that is the essence of the game. So I think the hit mask could be the same as the sprite - looking at that vid for other hit events, that seems to be the case - watch the baddies and the bullets collide... no close scrapes for them what all of the above comes down to is: 1. A need for a transparent "ink" (this is not the same as background) - or layers 2. trigger collide events based on the "not background" pixels of the sprite when the OR of the two flags is 1 (it isn't necessary for both sprites have to be interested in each other) 3. fine-tuning of any hits is down to the sprites themselves - program not system. this gets rid of the hit mask for each sprite - you just sacrifice a sprite to act as the mask for another and that is just program. h |
||||
| Heidelberg Newbie Joined: 22/01/2018 Location: AustraliaPosts: 13 |
Personally I don't think implementing an additional collision mask for sprites is worth the effort. CaptainBoing makes some interesting points about 'hitboxes' being reduced to make the game more 'forgiving' and addictive. With a coin operated arcade machine which has to deliver a return on investment there probably is some sneaky psychology involved. Computers in the 80's which had hardware that supported sprites would raise a collision which any non-transparent pixel clashed with another sprites non-transparent pixel or the background. It was a 'pixel perfect' test without any other additional complexity. If the programmer wanted to respond to the collision with other additional tests of their own - they could. I reckon if a 'bullet' comes close enough to clash with your sprite, you're dead! In my opinion sprites ordinarily would only overlap as the result of a collision - no doubt I'll probably think of exceptions later. Being able to handle that and always be able to restore the background/handling the layers is a real can of worms. You certainly don't want to introduce anymore complexity than is necessary. -Heidelberg |
||||
MicroBlocks![]() Guru Joined: 12/05/2012 Location: ThailandPosts: 2209 |
Testing on a pixel basis is very time consuming. Just use some simple testing with mathematical objects. It is a 1000 times faster. Defining a circle or rectangle collision boundary requires only a few integers. It can all be done wihtout accessing the video memory which is also a bonus when using other kinds of screens like LCD's that are connected over I2C or SPI. Microblocks. Build with logic. |
||||
| Heidelberg Newbie Joined: 22/01/2018 Location: AustraliaPosts: 13 |
Yep, agreed testing on a per pixel basis takes a lot of processing. However just to be clear I wasn't suggesting one would test all pixels on all sprites all of the time. You would only test for a collision on a sprite you have just moved. At this point consider that sprite and any others to be rectangles of their entire sprite size and test for intersecting of your moved sprite rectangle against the other other sprite rectangles. If they don't intersect then a collision is not possible and further testing is pointless. If two rectangles are found to intersect then you pixel test only the intersecting area. Given that sprites move in a generally smooth manner in a game chances are that at the point of collision the intersecting area would normally be small. -Heidelberg |
||||
| Azure Guru Joined: 09/11/2017 Location: AustraliaPosts: 446 |
Most arcade game hardware provides the ability to flip the sprite in X/Y directions to limit the number of sprite images needed to be loaded. This makes animating sprites and managing direction changes much easier. They also usually have an option to flip the screen in the X/Y direction to allow a second player in what was referred to as upright (no flip) or cocktail table mode (flip for 2nd player). Depending on whether the game was landscape or portrait oriented would change the flip which is why most hardware allowed for both X/Y independantly. Sprite - sprite collision detection is important as well as sprite - tile collision with any tile (character) layers. There should also be flags whether sprites and layers are enabled for collision detection. One thing, I am not sure on, that varies is some games allow the sprite to wrap the screen (as it goes off one side it appears on the other) and some don't (it just disappears as it goes off screen). Maybe this should be another flag for the sprite to be able to cater for both types of games. Sprites are not usually considered as layers, but are prioritised above any tile (character) layers. Sprites can overlap and are usually drawn from the highest number on top of any other sprites (where it is not transparent) down to the bottom sprite. The tile (character) layer(s) are prioritised top down. Layer scrolling allows games with a moving background ("Scramble" - 1 layer 2D horizontal scrolling, "Jungle King" - 3 layers simulated 3D moving background and "Pacman" static). Trying to define actions on collision is trying to prescribe a playing engine not a graphics engine. It gets messy, complex, timehungry and unsuitable very quickly with all of the considerations for any game people conceive. Better to leave that to the actual game code. For sprite - sprite software collision detection, it is usually most efficient to first check by comparing sprite edges (bounding box) and only if they overlap then do a pixel comparison. One thing that may be worth considering (or discarding) is using an alpha channel for each sprite like used in imaging graphics. The alpha channel only needs to be 1 bit deep and set on if any sprite pixel at that point is not the background colour. This can be created quickly when loading a sprite image (if the image does not already have it) and it reduces the comparisons needed for collision detection. If the user creates the alpha channel with their image then they can play with what is detected or not as a collision allowing the case proposed earlier to have some "space" around characters handled as well. If sprites are going to be larger than 16x16 (most classic arcade games) and any more than 64 of them then I would suggest the user declares the size and number. Otherwise this can quickly swamp processing resources as the workload becomes exponential and does not leave enough time for fast playing games with nice sound design to do what they need to do. Most classic games needing larger sprites only need them in a few places so they just stitch multiple sprites next to each other as needed. All of these types of classic arcade hardware functions are able to be emulated and even enhanced on current micro hardware. If using a single frame buffer then this processing should be done during the VBlank time on a VGA monitor so it does not create any undesirable glitches on the screen. Great project, looking forward to where it goes. |
||||
| amitor Newbie Joined: 15/01/2018 Location: United StatesPosts: 4 |
I will definitely be one of the users waiting excitedly for sprites, but would highly support tiles and some form of playfield scrolling. I have good experience with all three from C64, and they are very essential in both memory conservation as well as ease of programming. Having said that though, easy VBLANK synchronization would top my priority list, and should not be too difficult to implement, unless I'm totally off-base. |
||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |