![]() |
Forum Index : Microcontroller and PC projects : Game programming
Author | Message | ||||
Plasmamac![]() Guru ![]() Joined: 31/01/2019 Location: GermanyPosts: 579 |
hi, I am currently trying to develop a game for the cmm2. I have to move many graphics of different sizes as quickly as possible. does someone have a tip how to program it the fastest? I use 1023/768 mode 9. I am currently using sprites with .png. Plasma |
||||
William Leue Guru ![]() Joined: 03/07/2020 Location: United StatesPosts: 405 |
It all depends on what you want to do. If you are moving a lot of things around on the screen and having to do collision detection, then sprites are your friend. On the other hand, stuff like scrolling the screen or just animating a few things without flicker, then just do offscreen drawing to a page other than page 0, then copy the offscreen page to page 0 using the B option to sync up with the refresh rate. Both these things can be combined of course. Read Peter Mather's document on advanced graphics, it pretty much tells the story. -Bill |
||||
thwill![]() Guru ![]() Joined: 16/09/2019 Location: United KingdomPosts: 4311 |
You may also be in the wrong mode. I'm no expert on games but from other posts most people are using a lower-res mode, possibly because in BASIC the CMM2 just can't move enough pixels around fast enough in the hi-res modes, and/or you get more pages to play with in the low-res modes. Tom MMBasic for Linux, Game*Mite, CMM2 Welcome Tape, Creaky old text adventures |
||||
RetroJoe![]() Senior Member ![]() Joined: 06/08/2020 Location: CanadaPosts: 290 |
Indeed, the fundamental technique appears to be "compositing" the graphic images on a hidden page (typically Page 1), then copying them to the display page (Page 0) in the "right way, at the right time". Per Tom's comment, using display modes with lower resolution and bit depth will dramatically increase performance and give you more offscreen pages to work with. From what I've seen, sprites are not as widely used as yet, but have the obvious advantages of living "on top" of Page 0 i.e. taking care of preserving the underlying pixels as they move around, as well as being more object-oriented e.g. knowing where they are in the X,Y plane and if / what they collided with. Strongly recommend you read the CMM2 Graphics Manual (included in the firmware ZiP file on Geoff's home page), as well giving this blog post a browse (and ideally adding to it!) Happy Maxi-miting!! Edited 2021-01-29 02:27 by RetroJoe Enjoy Every Sandwich / Joe P. |
||||
MauroXavier Guru ![]() Joined: 06/03/2016 Location: BrazilPosts: 303 |
For a very fast action game, I recommend 320x200 or 320x240, with last firmware version, using 8 or 16-bit colour will not give too much speed difference, but lower colour depth will give more video pages if you need. For normal to a fast action game, you can use 640x400 or 640x480 mode, but in this case, the colour depth will make some difference in speed. Games too much demanding could get be hard to develop in this mode, like shooting up bullet hell style with 300 entities on the screen at the same time, for example. Modes like 800x600 or even 1024x768 you can make games that don't have the scroll, like Lode Runner, or you can try make some scroll, but in this case with not be so smooth to keep the game running at the same time. Games that scroll the full screen only when you go to the screen limits is a good approach, like King's Valley (MSX). Use the 1024x768 mode to animate too many objects at the same time on the screen can be a hard task to the CPU, as this video mode consumes a lot of the bus. And remember, not only the video pages but the command FRAMEBUFFER always is your friend to make things easier when programming games. |
||||
Nelno![]() Regular Member ![]() Joined: 22/01/2021 Location: United StatesPosts: 59 |
It really depends what you're trying to do, which is, I think, a large part of what MauroXavier is saying above. Despite CMM2 being fast, you're still going to have to make some decisions up front to achieve best results, and spend some time optimizing. Despite the draw of having "easy mode" sprite programming, etc., I think it might get boring if there weren't any optimization challenges. Let me caveat all of the following with the fact that I've been messing around with CMM2 for a bit less than two weeks, on and off. I've spent a lot of that time investigating how to reliably hit vsync, what a decent game loop will look like, what my performance margins are like (and why my SD card wasn't working), but I definitely do not know everything there is to know. Feel free to call out my mistakes or challenge my assumptions. My goal here is to make some games and make them well. page copy First thing to know is the page copy command. In general, if you're in one of the 8-bit modes you can probably get away with this at up to 800x600. In the 16-bit and especially 12-bit modes, it's going to require a lower resolution. From my tests, 1024x768, 8-bit mode is already too slow to use the page copy technique. If you run the following commands from the prompt: mode 9,8:timer=0:page copy 1 to 0:? timer You'll see the time it will take to do a single page copy from hidden page 1 to page 0. For me this is 14.714 ms. In mode 9,16 it is 45.401 ms! 12-bit mode isn't available in mode 9. Since mode 9 is 60 Hz, and assuming you're okay with 8-bit mode, that means you have a total of ~16.6 ms per frame to do all of your game logic and rendering. If you want to avoid any screen tearing, you cannot render for almost 15 of 16.6 ms per frame! Your only real option then is to drop down to 30Hz, at which point you'll have 33.3 ms per frame. Note that with a 45 ms copy in 16 bit mode, you're not going to be able to manage even 30 FPS, but you could get away with 15 FPS. Some games are probably fine at 15 FPS. For instance, if you wanted to do an Ultima-like RPG, that would work fine. I hope we can get the option to swap the visible page back in MMBASIC, vs. always having to copy. Copy is certainly the easier method to use and probably should be used in most cases, but I can imagine how if you really wanted to tweak performance out of the higher res modes, page swapping might be your only hope, and even then it would probably not be as generally applicable as page copy is. sprite load Second thing, and this is really more of a technique that wasn't entirely obvious to me how I should do it from the get go, is loading your sprite data onto the hidden graphics pages. You do this as follows: page write <page you want to store sprites on> load png <filename>,x,y page write <page you want to draw on> ' when you want to set a sprite to an image: sprite read,<sprite num>,x,y,width,height,<sprite page> Of course, sprite show, sprite next, etc. still need to happen as necessary. How you update your sprites depends on your games needs. If you absolutely must update all sprites each frame, and you're also modifying your background, then you probably want to do: sprite hide all ' change background as you need ' position your sprites with sprite next sprite restore ' show and update all of the sprites That's the simple loop. It may vary for you based on what you're trying to do. Optimize I'm still learning what works on the CMM2. There are things specific to BASIC that throw some of my tried-and-true optimization knowledge out the window. First, time everything. I recommend having some timers visible (frame rate, game logic time, render time, etc.) each time you run the game. This way, if you add something and suddenly a time goes up significantly or you're not hitting frame rate, you'll have a better chance of catching it sooner rather than later. I haven't tried the new profiling capabilities yet, but these should be tremendously helpful. Second, remember you're writing code in BASIC and it's interpreted. Despite the fact that CMM2 is very fast at executing BASIC, this can and does have significant effects. In my tests, calling functions from a loop, even one that just iterated 32-64 times, was much slower than just inlining the code in the loop, but it's going to vary depending on what's in your function as to how much overhead it's costing you. Again, timing is your friend. Third, don't take what you know for granted. I tried some techniques like packing data into arrays that on most modern architectures would pay off, but which backfired. For instance, if you have 4096 objects which you're tracking the position of, you might start off by storing their positions in two arrays, one array for x values, one for y like this: const MAX_STARS = 4096 dim integer stars_x(MAX_STARS) dim integer stars_y(MAX_STARS) Note that trying to update 4096 stars a frame is going to be a bad idea, but if you can amortize the cost and go through some number of them each frame, that may be okay. I recommend NOT skipping frames and doing all 4096 every n frames. Consistent framerates are important for action / arcade games. Better to run a little slower every frame then have spikey / erratic frame rates. Anyway, the issue with two arrays like this is normally cache locality. You need to access both x and y values for star i at the same time, but star_x(i) and star_y(i) are separated by at least 4096 * 8 bytes in memory. Since I'm still learning about the CMM2 architecture, I can't say exactly how this relates to the cache / cache lines, but often, you'll get a speedup by putting values that are accessed together, in memory together. So, in this example: const MAX_STARS = 4096 dim integer stars_pos(MAX_STARS) local integer i = 0 for i = 0 to MAX_STARS step 1 local integer x = stars_pos(i * 2) local integer y = stars_pos(i * 2 + 1) ' do something with x and y next i Here you have achieved better cache locality for the cost of 2 multiplies and an addition, which are generally very fast operations. However (and to be clear, this is just an example and not exactly my test, so timing this might surprise), this did not work out. Not only did I find this didn't speed anything up, it was slower in MMBASIC. A bit more testing indicated that the main cost is probably the interpreter. As you add lines and operations into the loop you pay a high cost in performance -- much more than you would in a compiled language, i.e. C/C++. To caveat this, 4096 * 8 is only 32KB, so this wasn't a great test for cache locality, but it did once again highlight the cost of extra interpreted lines in a loop. If you think about what an interpreter is doing for each line, it's probably just invalidating most of the cache lines anyway and there's probably not a whole lot of benefit to optimizing array access. Again, this is just an example -- don't take this for gospel, and go back and reference the first point under this heading: time everything (at least when you suspect it can / should / needs to be faster). I'm sure there is a lot more to learn! Edited 2021-01-29 14:51 by Nelno |
||||
RetroJoe![]() Senior Member ![]() Joined: 06/08/2020 Location: CanadaPosts: 290 |
Wow, another "winner" thread for 2021 ! I will remind everyone about Peter's recently added INC (INCrement) command, which optimizes the interpreter for simple variable increments / decrements like game scorekeeping and "shots fired" :) Mostly off-topic, but it's been nagging me for a while, and this seems like as good a place and time to ask. Why did it become a convention to declare subroutines and functions at the end of your code listing, rather than the beginning? We routinely declare everything else at the top of our code listings: variables, includes, directives, options, headers, libraries, object definitions, etc. etc. This convention is logical and expository - it's telling the interpreter or compiler, but also the human reader, "Here is all the stuff I will be using in my program". It's like the overture in an opera, introducing you to the musical themes and motifs you will be hearing. Yet, when it comes to the most important declarations and "preview" of how your code is structured i.e. subroutines and functions, they are hidden away at the tail end of the code like a dirty secret, and you have to make an explicit "trip" to read them. Any code appearing inside subroutine and function declarations is skipped unless your main code loop invokes them, so where did this convention originate? Is it from the days when procedural code referenced literal line numbers, and appending lines to the end of a code listing was much easier than inserting them? Edited 2021-01-29 15:51 by RetroJoe Enjoy Every Sandwich / Joe P. |
||||
lizby Guru ![]() Joined: 17/05/2016 Location: United StatesPosts: 3378 |
Personally, I like to have my main loop at the very top, after variable definitions. I vaguely recall some language or implementation which required that functions be placed before the main loop so that the compiler/interpreter would know where to go when it encountered a call to the routine later, but this always seemed awkward to me, harking back to the day when programmers catered to the hardware, rather than having software (compilers and interpreters) which catered to the programmer. PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on fruitoftheshed |
||||
thwill![]() Guru ![]() Joined: 16/09/2019 Location: United KingdomPosts: 4311 |
Subroutines/functions at the end is the result of the common "top-down" practice for reading/writing code, IMHO it makes it easier to just focus on what you are interested in rather than having to wade through all the low-level code. For strict adherance a sub/func is declared "immediately" after it is first required. @RetroJoe looks like you might be an adherent of "bottom-up" practice, personally I think of that as a legacy of languages like "C" which expect at least declarations before usage, but YMMV. Of course as soon as you start using #Include you end up with a mix of both ... that is if you favour the "standard" of putting all the #Include's at the top rather than sprinkling them like magic stardust throughout the code. Best wishes, Tom MMBasic for Linux, Game*Mite, CMM2 Welcome Tape, Creaky old text adventures |
||||
jirsoft![]() Guru ![]() Joined: 18/09/2020 Location: Czech RepublicPosts: 533 |
I'm also using the structure: INCludes CONSTANTs and DIMs main program (loop) END FUNCTIONs and SUBs The main reason is that I'm usually working on method (SUB/FUNCTION), which I have on total end for quick access- fastest way of jump to is on begin or end of the file (HOME/END keys) and on begin are still needed some decalarations etc., so end is the next best choice. When I'm done, I move next method to the end... Jiri Napoleon Commander and SimplEd for CMM2 (GitHub), CMM2.fun |
||||
Nelno![]() Regular Member ![]() Joined: 22/01/2021 Location: United StatesPosts: 59 |
Why did it become a convention to declare subroutines and functions at the end of your code listing, rather than the beginning? I don't know if it's convention for me yet, but I have been tending to do this in MMBASIC for some reason. It also occurred to me that it was a little weird because the code that is calling the subroutines is doing it before they're declared. However, if the interpreter knows enough to know the subroutine is declared without interpreting it first, this is fine with me. One way of looking at this is that the main loop is generally much closer to the entry point of the program, so in a logical flow sense, it's usually executed first. I have been putting my subroutines generally in order of execution, so, for instance, my swapPages routine that is referenced from my mode call before I get into the game loop, is in front of the game loop (in some of my programs). But I think there's a more practical reason for me. I seem to be spending more time up at the top, messing around in the game loop, only occasionally paging down to add or modify a subroutine. But... I haven't written many large subroutines in my testing so far, so this may change. I am finding the paging around to be a bit annoying already. A "Browse to Subroutine" function like QBasic had could be nice. |
||||
RetroJoe![]() Senior Member ![]() Joined: 06/08/2020 Location: CanadaPosts: 290 |
Thanks for the thoughtful replies. I see merit in both top-down and bottom-up design approaches, and I imagine you always end up using a combination of both, as both techniques force you to deconstruct the problem and structure your solution in robust & elegant ways. "Spaghetti Zero" is always the goal :) Peter M, if you are reading this, the mouse-enabled Editor is a joy to work with, and the page up / page down right-click gestures work extremely well. Extending that concept, and pursuant to this modular coding discussion and the "Browse to Subroutine" capability mentioned by Neino, it would be awesome if: * double-clicking on a sub or function or variable name jumped you to the next occurrence of that name * a (modifier key + doubleclick) gesture jumped you to the previous occurrence * a (modifier key + doubleclick) jumped you to the declaration of the name (or "first" / "last" occurrence, which would be functionally equivalent) * there would be keyboard-only versions of these gestures Hopefully you will consider something along these lines for the CMM2 roadmap. Enjoy Every Sandwich / Joe P. |
||||
![]() |
![]() |
The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |