Introduction
I've spent the last 18 months writing a new game engine, in C, using a variety of different versions of Visual Studio. I've been looking at DirectX demo code and reading up on DirectX. I started with DX9, since that`s what my early demos were using. As I adopted Windows 10 on my PCs I realised that DX12 was now built into the delivery and I would probably have to use that. My game engine (not graphics engine) wasn`t really allied to any particular DX version, or any other system, but then I wasn't picking up how DX12 worked either. Reading articles on what is different from DX11 didn`t help because I didn`t have any DX11 code anyway.
I struggled on, testing my code by producing log files of information, or just using breakpoints to check the results. It was a frustrating time as being able to plot even a few dots on the screen would give me some visual clues as to what was happening. Even the variety of simple Windows loops was baffling: some mains take wide characters, some 8-bit, I just wanted something simple to start with.
DirectX also has a rival: Open GL. went as far as downloading the SDK for that, but was of the opinion that it would be no less complex than DirectX. The issue is that the graphics are no longer drawn by your code using the CPU. You have to ask the graphics card (or on-board graphics) to do your rendering. You don't even get to see the screen memory, Being "old-school", I am used to having control over the graphics chip, and maybe even a sprite chip.
DirectX also has a rival: Open GL. went as far as downloading the SDK for that, but was of the opinion that it would be no less complex than DirectX. The issue is that the graphics are no longer drawn by your code using the CPU. You have to ask the graphics card (or on-board graphics) to do your rendering. You don't even get to see the screen memory, Being "old-school", I am used to having control over the graphics chip, and maybe even a sprite chip.
Graphics cards
One thing you learn early on with PCs is: you won't be plotting a pixel on the screen by accident. I still don't really know exactly how it`s done. I'm getting recollections of the Sega Saturn, that was pretty baffling too! So many chips.
Modern graphics systems are much more powerful since they put many multiple graphics processors, or GPUs, onto the graphics card. My GTX 970 card has about 1500 of them, and each one gets given a program and a pixel to render, just the one. Thus there is a mass of parallel processing capability all done while the CPU carries on. The downside is that the processing architecture diagram is a nightmare, even though much of it is handled by your chosen rendering system. Since you can write one or many pixel rendering programs, called shaders, you need to be able to deliver those to the card and have the right one executed. These programs are infinitely flexible, and that`s a problem.
Modern graphics systems are much more powerful since they put many multiple graphics processors, or GPUs, onto the graphics card. My GTX 970 card has about 1500 of them, and each one gets given a program and a pixel to render, just the one. Thus there is a mass of parallel processing capability all done while the CPU carries on. The downside is that the processing architecture diagram is a nightmare, even though much of it is handled by your chosen rendering system. Since you can write one or many pixel rendering programs, called shaders, you need to be able to deliver those to the card and have the right one executed. These programs are infinitely flexible, and that`s a problem.
I never found a set of default rendering calls, if indeed there are any, nor any good code examples. I tired of finding demos containing 1000 lines of code to render one triangle on the screen, with comments in the code like: this is sloppy, do not do it this way. Firstly, it`s not helpful being shown the wrong way to do something, and not knowing which bit is sloppy, and how to make it not sloppy. Secondly, the demos all only work for 1 triangle at 2,000 frames per second and have no easy way of expanding the model because they have no mechanism to support multiple items.
I get the impression that Open GL on it`s own is just as fiddly. I`ve seen what people can achieve with Unity, or other full game engines. I've watched a few youtube videos showing how to create effects, such as fire. This all looked more familiar as we used multiple objects to produce effects on the Amiga. Unity also appears to have a complete game engine in there, which is great, but I`ve got one of those too. The clincher though was when I found out that Unity only uses C#. Now I`m sure there`s nothing wrong with C#, but my code is written in C, it`s quite complex, and may not fully translate. I found this line of glory yesterday, as an example:
*(NewAMPObjectInLongs + ((*NewAMP)&RemoveIndicatorBits)) = *(ParentAMPObjectInLongs + ((*NewAMP)&RemoveIndicatorBits)) + (*(NewAMP + 1));
Obviously with no context, that line is meaningless, but there are a number of advanced addressing features in there, which would have taken quite a few 68000 assembler instructions. Incidentally, that line is too long for me to tweet! There's a worse one to do the floating point fields. These lines could probably be broken down a bit, but actually all it's doing is copying a long from one object to another, with a modifier being added, which is zero most of the time. To break it up would probably require an additional variable, and that would slow it down.
My game engine began where my old Amiga assembler system finished: there`s lots of packing multiple features into one variable, lots of bitwise operations, things that assembler likes, and you need to do to save space on the Amiga. That`s the way I still think and code. It's all very well having the CPU welly to do all the maths and lighting properly, if you know how to code it properly. Usually you can get similar effects much more cheaply, and therefore do more of them.
*(NewAMPObjectInLongs + ((*NewAMP)&RemoveIndicatorBits)) = *(ParentAMPObjectInLongs + ((*NewAMP)&RemoveIndicatorBits)) + (*(NewAMP + 1));
Obviously with no context, that line is meaningless, but there are a number of advanced addressing features in there, which would have taken quite a few 68000 assembler instructions. Incidentally, that line is too long for me to tweet! There's a worse one to do the floating point fields. These lines could probably be broken down a bit, but actually all it's doing is copying a long from one object to another, with a modifier being added, which is zero most of the time. To break it up would probably require an additional variable, and that would slow it down.
My game engine began where my old Amiga assembler system finished: there`s lots of packing multiple features into one variable, lots of bitwise operations, things that assembler likes, and you need to do to save space on the Amiga. That`s the way I still think and code. It's all very well having the CPU welly to do all the maths and lighting properly, if you know how to code it properly. Usually you can get similar effects much more cheaply, and therefore do more of them.
In fact, since we are now in a 32-bit world, going on 64, then my game system has twice as many bits than on the Amiga, giving me plenty of opportunities for improvement. My coding style is still to save space and reduce instructions as much as possible, even though the machine is a thousand times faster, and I`m writing in C. I figure that'll ensure that I don`t have to go foraging for time-saving opportunities later. Write as efficiently as possible now, but don`t worry about saving space quite as much.
SFML
The breakthrough came in early April 2018 when it was suggested I take a look at SFML. This is a layer of code running over OpenGL that provides all those useful functions that I needed to start with. Things such as: display a rectangular sprite of any size you like on the screen - what a good idea. The documentation looked pretty simple too. I was probably suffering from too much DirectX documentation on too many versions in too many places.
To get a sprite on the screen you just get one call to load a graphics sheet, containing one or more images, then a bit of setup for a sprite to tell it where on the sheet the image is, and where you want it displayed on the screen. You also have rotation, scaling, tinting and transparency options.
Linking my code in with SFML was a bit of a task. Not its fault, mind, the compiler and linker options don`t get any simpler in Visual Studio, and I regularly spend some time searching for options and settings, and working out where you can change them since there are solution files, project files, user templates and custom templates. I want to keep my AMP (which stands for Alien Manoeuvre Programs, BTW) game library pure from the graphics implementation, so it knows nothing of SFML. The AMP system additionally doesn't know anything about a specific game, it handles things like animation, collisions, movement and general object management. The game is managed by a layer that sits between my AMP system, SFML, and the Windows main loop. Incidentally, traditionally I would run multiple control loops on the Amiga and C64, since there are things you want to do differently in the game, or a demo, or the titles sequence. There's no reason why you can`t do that on Windows, but because of the Events system traditionally people seem to use one loop with a load of event handlers, and an inverted controller routine trying to martial the correct things to happen. I have done that too, and it`s horrible!
I set about writing some loading systems so I could just list out which graphics files to load, and then which sprite images are contained on each. I did the same for fonts. This is all simple data handling, just what I`m used too. We used to generate sprite lists with size information of images with our sprite-cutter software at Graftgold. It produced a header file with image numbers to be assembled in, and a data file of image sizes and the actual graphics. We also used it to pack as many images onto a single sheet for the PSX, as it was then.
Now we are getting the graphics from PNG image files, and I have to type in the sizes and positions of the images, and give them names. The beauty of assembler macros or a separate loader is that you could add things to multiple lists. C, being a one-pass compiler job, has less flexibility to work out things forwards, and no ability to add to multiple sections. The macro capability is also less good than assembler`s as it is also single-pass, so again can`t reference anything forwards. I tend to write code upside-down to avoid needing local function prototypes, but that means you have to call a function above where you are rather than below, which is how you would naturally write code. Maybe we could have a compiler that starts at the end of the source code?
Now we are getting the graphics from PNG image files, and I have to type in the sizes and positions of the images, and give them names. The beauty of assembler macros or a separate loader is that you could add things to multiple lists. C, being a one-pass compiler job, has less flexibility to work out things forwards, and no ability to add to multiple sections. The macro capability is also less good than assembler`s as it is also single-pass, so again can`t reference anything forwards. I tend to write code upside-down to avoid needing local function prototypes, but that means you have to call a function above where you are rather than below, which is how you would naturally write code. Maybe we could have a compiler that starts at the end of the source code?
I then hooked in the calls to SFML to plot a sprite. In any application you need to think about ensuring that your sprites get plotted in the correct sequence, back to front on the screen. Since your objects may be processed in a more arbitrary sequence, you tend to update them as listed, then need to sort them into plot sequence. I do this by specifying a "layer" on each object. As the object finishes its movement cycle it then adds itself to one of the layer lists for plotting. Once all of the objects are done we can start rendering the sprites, starting with the lowest layer. The SFML clear screen process is actually initiated before the objects are being processed, so it gets done in parallel, not that it takes long.
When I come to do some character map tiled layers then it will be important to ensure that the sprites interact with the layers in the correct sequence. SFML also provides font support, which saved me from writing my usual first routine on a new platform, and has also saved me from drawing a font. I never did find a TTF file editor, so was trying to create a font as graphics, but even 32x32 pixel images are pretty small and it took me all night to do 5 numbers in 3 colours at 32x48. The number of pixels you need to work has increased dramatically since the Amiga days, and even my PC DOS days. And don`t even get me started on the number of colours. Instead of a palette of 32, or even 256 colours, we now have 8 bits of red, green and blue, about 16 million colours to choose from. Fortunately SFML provides a tinting option for the sprite rendering colours, which means all the old tricks like flashing objects to show they've been hit, or changing them to orange when they're about to blow, or glowing, are all easily controllable. The way it seems to work is you draw, or capture images in greyscale and then tint them at run-time. I have a random tint routine already to give me different coloured rocks.
Which Brings Me To...
In only 2 weeks of graft then I have constructed most of a game. There is still some work to do, and unfortunately I won`t be able to release it. I wanted to just test the rendering routines, develop some more code, debug what`s already there, and get a feel for just how much I can render on the screen at a decent 60 frames per second rate. I'm working mostly on my new laptop, which has an Intel i7 dual core processor and is using it for the graphics rendering too. There is an AMD graphics chip in there, but by default it`s switched off. Until I have any issues with speed it can stay off. The i7 Skylark is allegedly about a 10th as graphically powerful as my desktop: Azumi, she is armed with an NVidia GTX970. As long as everything is smooth on my laptop; we should be OK. It's an exploration of discovery for me to see how much the hardware has moved on since I left gaming in 1998. Now I`m not going to be competing with Destiny, or World of Tanks, just producing some retro games that give me an opportunity to create without the old restrictions.
So What Have I Created?
I have done some screenshots, but they don`t convey the movement of all the objects on the screen. I've coded elastic collisions into the rocks and they all bounce off each other. I was finding that sometimes they can get locked together, less so since I improved the starting position algorithm, but they will blow up after a short while if that happens.
I've allowed now for 2000 objects to be running, each of which might well get plotted on screen. I put a running count of objects into the high-score display and it has hit the maximum. I've therefore limited its production of less-important fragments once it has hit 90% capacity. This ensures that it never gets overloaded and always has some capacity for essentials like player bullets. It`s quite merrily running and plotting those using less than a quarter of the GPU and CPU time. One of the plots is the background picture, taken from a photo I took in 2016 of the night sky above my house. I set the window size to 1280x768, which should work on most laptops. I may ultimately make that user-definable, though the background picture tops out at 1920x1200, I won`t be going bigger than that or the graphics will be tiny. Remember that this is all pixel-based, not 3D, so I draw a pixel and it gets rendered as a pixel. Currently it does let me grab the window and resize it, but it then just stretches the original picture to the new window size. It`s doing a final copy of my whole rendered screen to the window every frame, which with today`s technology hardly takes any time at all.
I'm quite astonished at how little graphics I've needed to do so far. There`s just one sheet of graphics, which keeps everything fast for the graphics engine. I bought PyxelEdit on recommendation, and I`m getting used to the basics. It should do everything I need for sprites, animations and tiled backgrounds.
Sure, I need some more images of rocks to add some variety. Technically, since they are all spinning majestically, they need to be top lit. I couldn't find any asteroids to photograph, so I'll need to construct some images from graphics I can forage for on the Interweb. I did find a sheet of graphics from another Asteroids game, but I mustn`t use those.
I`ve changed some of the game features in order to improve the experience. If a big rock is coming towards the player then shooting it will cause it to split and the fragments will go off at about 45 degrees either side, you won`t get a rock down the throat for your trouble. I`ve added a shield instead of the dodgy hyperspace feature. The original hyperspace had a chance of blowing up, or appearing in front of a rock, not cool. The rocks all bounce off each other, as I mentioned above, which is something else to think about when trying to avoid them.
I`m pleased to say it`s playing pretty well already. I have 3 space bus types, one stupid one that nearly always misses with its shots, a slightly smaller faster one that has a bit more shot accuracy, and a deadly little chap that heads for the player guns-blazing. With the number of rocks this thing can generate, they don`t tend to last long, so they need to be mean and devious.
Did I mention I`ve coded in a 4-player option? Players can play one after the other, or all on screen at once! That should cause some chaos. I've disallowed players killing each other for now, though they will stop each other`s bullets. Only got two controllers at the moment, and one of those doesn't seem to be very reliable, so will need to buy some more.
Did I mention I`ve coded in a 4-player option? Players can play one after the other, or all on screen at once! That should cause some chaos. I've disallowed players killing each other for now, though they will stop each other`s bullets. Only got two controllers at the moment, and one of those doesn't seem to be very reliable, so will need to buy some more.
Next job
Some sounds.
And Finally
So pleased to be able to use SFML to do the rendering work. I'm quite happy to let someone else`s code do the plotting. It does more than I expected and is fairly easy to use, given that it`s that Object-Oriented stuff, which is C++ and has baffling syntax. Despite that, I am able to bludgeon it into doing my will. I haven`t found any bugs in SFML, nor do I expect to, the guys appear to have done a great job.
I`ve had a frustrating time trying to figure out how to get DirectX to do anything more than poll some controllers, XInput was quite nice. But now it is gone! I've come to realise that I can`t do all the coding on my own, but someone else has already done what I need and made it publicly available, so cheers SFML!
I feel back in control.
0 Yorumlar