QB Express

Issue #25  ~  October 31, 2007

"A magazine by the QB community, for the QB community!"

In This Issue



From The Editor's Desk

Written by Pete

HAPPY HALLOWEEN!

Welcome to a spook-tacular, spine-tingling Halloween issue of QB Express!

Okay, granted, the issue really has nothing to do with Halloween other than this opening letter and it's just a coincidence that this issue is coming out on October 31st (since I was too busy to release it earlier)...but HEY, why not celebrate if we have the chance?

In honor of this special day, I would like to bring your attention to one of the forgotten gems of the QBasic community, that 1994 horror adventure hit, Haunted Halloween by Jason Jackson.

It's an epic and scary adventure, all right. Here's the setup:

IT IS HALLOWEEN 1994, AND YOU AND A FRIEND HAVE BEEN INVITED TO A HALLOWEEN PARTY. WHILE DRIVING DOWN A ROAD AN ANIMAL JUMPS IN FRONT OF YOUR CAR. YOU SWIRVE TO AVOID HITTING IT. IN THE PROCESS YOU GET TWO FLAT TIRES. YOU LOOK IN YOUR TRUNK ONLY TO FIND ONE SPARE TIRE. YOU SEE A HOUSE IN THE DISTANCE, AND DECIDE TO GO GET HELP WHILE YOUR FRIEND STAYS IN THE CAR.

And what follows is, as you can tell by the screenshot below, probably the scariest adventure you will ever face in your life!




Download Haunted Halloween here: HH22.bas

On a side note, I believe I neglected to tell you the full title of this game: Haunted Halloween - How to Abuse the GOTO Statement. If you're looking for the perfect teaching primer on how to create spaghetti code, this would be it! Be sure to check it out, definitely worth a look.

Anyway, lots of good stuff this month...but you already knew that. Enjoy QB Express #25!

-Pete

A big thanks goes to Imortis Inglorian for doing just about everything this month except for write a few lame little bits like this one, heheh.


Submit To QB Express

You all know the drill. This magazine can't exist without people SUBMITTING articles, editorials, tutorials, reviews, news and feedback. This is not just a solo effort by me... it's a group effort by people throughout the QB community. If you have anything to submit, or have time to write something, DO IT!

If you want to write about something, but can't think of a topic, or need inspiration, check out the "Official QB Express Article Requests" thread! There have been quite a few articles requested -- and even if none of them strikes your fancy, we can help you come up with something that you would like to write about. If you're interested in getting your own monthly column or just want to write an article or two, by all means, do it! Anything that is submitted will be included!

I also want feedback and letters to the editor regarding this magazine. I want suggestions and critiques. What do you like? What don't you like? What could be done better? Let me know!

All submissions and feedback can be sent to qbexpress@gmail.com. You can also PM me on the Pete's QB Site or QBasic News message forums. If QB Express is going to continue to be so good, YOU need to contribute!

-Pete



Letters

Letter From Lachie Dazdarian

The usual congratulations on the last issue are in order. I can't compliment you much on the News Briefs as I compiled a great deal of it, so reading it wasn't interesting as usual. I was also tad annoyed you missed to include my latest version of News Briefs which included news on ciw1973's compo (really nicely compiled) and dozen of nitpicks on other news. And they were sent few days before you released issue #24. Oh, well.

Anyway, really nice content in the last issue.

Qlympics results were interesting to read. I was somewhat annoyed with few winners, but that again is the result of few poor nomination choices by my opinion. ZeroG is really far from the best action game for that period of time. I mean, I can't even try to compare it with TerraScape. But it seems a great deal of people simply WANTS to ignore TerraScape for some reason. But if not TerraScape, Squealer TNT was a much better choice too. Call me moaner, but I just donít get this.

Also, I think most were too trigger-happy with Lynn's Legacy giving it few undeserved awards. I'm mainly referring here to sound, where The Quest For Opa Opa convincingly features the best work.

YAGL is another poor choice. A library that was soon abandoned after first half-baked release, a library in which nobody coded anything notable or complete, and a library that is basically a front-end for OpenGL.

Other choices are fine by me and I don't have anything to complain there. Yes.

Now I know I'm repeating myself, but this job should really be left to a panel of judges who, if can't be objective, at least will play/test/read all the nominated entries.

To continue.

It was really nice to see another game design related tutorial in the magazine. Very nicely written article Joe King, and thank you for that.

RubyNLís tutorials were a rather refreshing addition to the magazine, especially in the style they were written. To bad they were for QB. :P

Also, interesting to see Pritchardís articles in QBE, wouldnít you say so?

I didnít get FBHacker Series tutorial. It seems like a joke to me, but the author appears to be more serious that he should. Itís just that sort of content I wish people would think twice before submitting.

Anyway, good luck with this and next issues.

See you around.

Lachie Dazdarian - The Maker Of Stuff

Thanks for the feedback, Lachie! As for your assessment of the Qlympics results, I agree with you on many points. The best nominee (in my opinion) didn't always win, and Lynn's Legacy probably got a few too many awards. But then again, we awarded everything democratically, by a popular vote, and so the results showed us how the community as a whole feels - not just a few "elite" judges.

There's certainly a lot of value to a popular, democratic process. Then again, I personally prefer having a panel of considerate judges, because it usually results in every nominee getting a fair shake, and the best of the best, even if it's less popular, getting recognized. I bet most of the voters hadn't played even half of the games on the ballot, and thus voted for the ones they knew, based on name alone (Lynn's Legacy, The Griffon Legend), and some really quality titles missed out as a result.

In film, it's the difference between the People's Choice Awards and the Oscars. Which do we take more seriously as a culture? The democratically-awarded trophies that go to whichever movie had the best marketing campaign, or the awards carefully considered by a panel of industry experts? The people don't always make the best choice.

-Pete

Letter from MystikShadows

What a read this one was. It always amazes me to see just how much creative power there is in all that contribute. Just look at all the stuff there was for us to read this issue? I mean wow. Two thumbs up to everyone involved. 24 issues now, and a 25 that pomises to be yet another classic. QB Express is breaking alot of records and getting the expectations of it's users to a higher level with all the great contents. I think the contributors are definitaly attempting to respond to that higher expectation in a great way. As I said before, we're all growing, we're all doing different thinng (me taking on a game project like Color Triple Yahtzee! is a good example of that hehe). with these new directions comes new needs, new research, new subject that need to be talked about, discussed, taught (in he form of new tutorials) in subjects that just weren't talked about before. As we can all see here, this is being covered bery nicely by the contributors, all of them.

First, the QLympics, You know, it seems alot happened since the start of this great idea. including the one year hiatus of QB Express. To me, I think thtat was the part of the problem. Alot happens in a years, and the suggestions and submissions are probably what left QLympics fans on the drop some. Next time this event happens I think there will be alot of new things there that weren't even considerd before. I'm glad I got the runner up I I wonder how that's gonna fit next year :-). But I should have more stuff in my arsenal by then hehe. I think though that there's alot of lessons that got learn during this current QLympics that will just make next year's event much better than this year. In the submissions that were made and voted for however, I think all the top winners and runner ups (including me hehe) got their well deserved recognition. I think it ended up beign a pretty good event even if certain things weren't there. :-).

How about that news section? I like to think I cover the grounds pretty good as far as keeping myself up to date on what's going on but there's alot of thinsg I didn't know about in that news section. Alot of great stuff, great projects, I think i'll need to work harder to "really" keep myself up to date, or just wait for the next issue since it's all being covered so nicely. The cartoons are great once again. I still get a smirk from looking at diffeecult's high level programming cartoon, I really like his humor hehe. The other cartoons were as expected, as funny as they can, great originality, awesome stuff. And you know, when you think about it, none of this is really surprising to see considering where we all stand today.

One of the project news that caught my eyes is definitaly VISG GUI builder mrhx definitaly seems to be on the right track for this. I think it's really gonna help FB make it's mark as a true windows languages when tools like this take alot out of Windows API coding just to get an application off the ground. I hope this one is pushed far, it's a great idea, much needed. Though maybe it's windows only, atleast, using the API like that, it will make very streamlined windows applications compared to using things likes wxWidgets on windows. So I think it's a great concept that should be taken as far as they can.

I gotta hand to lachie, it's no wonder he has the FB Game website and such, it's amazing how much detail he puts into all his reviews and the review on lodestar was no exception. He has a knack for taking the good and useful information about a game to create his review from and really knows how to present this information to the readers. I've seen other good ones, but Lachie really takes it home. Two thumbs up for his work. I know I appreciate it, and I'm sure i'm not the only one. Looking forward to reading more of his reviews. And while I'm on the lachie subject, I really liked his highscore tutorial. I think there hsould be more tutorials that talk about these side features that help make a game more complete, things like that highscore feature, maybe tips and tricks or simple reviews of what people like to see in a game. How important is the ending for example, when you loose and when you win. How to score during the game play, who bonuses are so motivating to try to get, and a whole bunch of other side subjects like that I think can be quite a successful set of articles especially for newcomers to the game development scene (like me lol).

RubyNL, I have to say it's a rather new name for me, atleast I never noticed it too much before. But I think the time based motion & collision detection tutorial is gonna help me, and many others remember the name well. what a great tutorial. Great way to explain everything covered in the totorial, not affraid to use graphics and images to make the points being covered. I think RubyNL is gonna become quite the name if tutorials like that are being submitted continuously. I enjoyed it from start to finish. Very well done. Of course, the swirl effect tutorial was an interesting read too. Yes, I wan to read more from RubyNL.

Mentat's Wireframe, I can't finish this letter before talking about that. I was first introduced to wireframe graphics back in the late 70s with a language called MIRA. Back then, MIRA was THE language for 3D Graphics, in fact it was the only language for 3D graphics aside using assembler and C. Anyway, Since then, I've read more than one book / aritcle on wireframe graphics, and I think I can say that mentat's way of explaining it finally kicked in for me. I learned alot from relsoft's series of course, but there's something about mentat's way of putting it so to speak that just made sense to the way I read I guess. :-). Great work there too.

Dean Menezes' Searching Algorithm binary tree was one of the more informative tutorials I've read. Another fella that seems to have the knack for presenting the theory in such a way that it becomes a natural process to absorb the knowledge he has to sure. Systematic, from start to finish, each item either describing or announcing what's up ahead, seems like just a great wayt o go about teaching a subject, any subject. I definitaly want to read more from Dean, so crack your fingers and get writing Dean ;-).

FBHacker Series: I think has got to be the most original means of teaching programming I've ever seen. You basically learn as you play the role of a hacker. I've been there when this idea saw birth back then. And still today, I can't think of a more original and enticing way to learn programming. When done, I don't have a single doubt that FB Hacker is gonna be one for the books. It's a great concept that has all the look, feel and potential to be a real breakthrough way to teach the subject at hand. Awesome work so far and well, can't wait to see where it will all go.

The FBGfx buffer tutorial by Pritchard was pretty good. For one thing, the Image datatype is a new thing to me, I didn't know about it till I read his tutorial. I think this is a great way to allow to use and manipulate imnages. So not only did I learn something new about FB, but I think Pritchard's described how to use it pretty well. As the whole world knows I'm not much into graphics (atleast not yet, lol). But yeah, tutorials like this one is what's getting me more into the subject. So it was an interesting read for me.

Finally Stylin's XCP series is very interesting. I like where the idea came from and so far, I really like what he is including in the series so far. It will be a great source of information for anyone (newcomer or not) I think everyone will have something to learn about from this series. I'm not sure what's up a head, what he wants to talk about next, but I for one can't wait to read about it. Great idea and great work so far.

In a general way, all I can say is that it's really great seing how every article that make up each issue is so varied but useful to all of us I think. It just goes with the way we're all learning and where we're going with this knowledge. I can't wait to see what the line up is gonna be in the next issue but I bet it's gonna be a perfect reflexion of where things are abnd where we're ALL going, as it uisually been, in every issue, so far. Great work to all and I talk to you all next month.

MystikShadows

Stephane Richard

Thanks for all the praise of last months issue. I was rather happy with it myself. One note about FBHacker though, and this addresses Lachie's letter as well.

The originator of FBHacker, Shawn Hartnell, had not meant for all of them to be published all at once. Just a case of me an Pete having a lack of communication. The Original Idea was to post one at a time, and then in the next issue, post a short tutorial like solution to the last one, plus a new one. Sort of like a crossword puzzle.

I am truely sorry to Shawn Hartnell for my slip up on this one, and I am also sorry to readers who may have totaly missed the point of FBHacker as a result.

Oh well. There is always the future, right?

-Imortis Inglorian (Luther Ramsey)

Letter from notthecheatr

Hey Pete, notthecheatr writing this since you said you hadn't gotten any letters yet. Glad that another issue of QB Express is hitting the net, though it's a shame people haven't been submitting as much yet. I hope QBE continues to thrive, as it is (at least for me) one major benefit of the community - I've never seen any net magazine which I enjoyed as thoroughly as this one.

I'm not sure what all is going into this issue, but I'll just talk about some of the interesting things I've seen on the forum lately that may or may not go into this issue. If I start to ramble, just press page down a few times and I'll return to reality ;-)

One of my favourite things this month was all the graphics demos and things by KristopherWindsor. He posted a bunch of them in a single thread, some simple but some plenty interesting, then posted a really great tutorial about how one of them works- I hope that tutorial makes it into the magazine.

There is of course my tutorial, written for fun because I've been playing around with particles all week and I figured if I'm going to write a particle engine, I might as well write some tutorials alongside it so everyone can see how it's done... or how NOT to do it, anyways :P

freeLOLcode by sir_mud has easily PWNed everyone with it's greatness, and I suspect FreeBasic will be entirely replaced by LOLcode in the future... well maybe not, heh.

Best of all has got to be the products of various people answering to the pointless challenges of a certain QBasic fanatic whose purpose in life is apparently to ridicule FreeBasic (and most of what he types is fud anyways, to quote v1ctor). As a result of his charges, PCopy has now been implemented in FreeBasic, and much more compatible to QBasic and useful than any other Basic language, and a printing library has been written to make printing in Windows easier. Once again FreeBasic stands up to the charges and more!

And of course we're starting to see playable demos for a number of games (Pacenstein, a couple of RTS's, a space shooter) which indicates that we're going to have some finished games coming up pretty soon... so I'd say the FB community is alive and well, in spite of any failure on the part of people to submit to this months QBE ;-) Don't worry, next month will probably be bigger, badder, and better than usual, to make up for all this.

So anyways, I guess I'll finish my half-crazed ramblings... have a good month! I await QBE 25 with bated breath! (I dunno, it's a literary cliche... I read a lot, yes I know I'm a geek.)

I find it very cool that most of the people that read QB Express consider it one of their favorite magazines. Sure, we have pretty low readership in the grand scheme of things, but one of the most active reader bases anywhere. Here, the readers *are* the writers, and I'd say the majority of people who read QB Express every month and wait for it with "bated breath" have also contributed to the magazine.

Thanks to the Internet, we've really been able to innovate how media is produced, consumed and viewed -- and I think that's a very cool thing. Now, every month, rather than spending a half hour or an hour reading a magazine produced by a professional editorial staff that's well disconnected from the readers, we spend that time reading QB Express -- which might be a little rough around the edges and is certainly not professional by any means -- but it feels so much more direct and personal. This is "our" magazine, whereas I consider magazines created in the traditional way to be "theirs." And it's happening in all forms of media. How many of you spend more time watching YouTube videos than network television or big blockbuster movies? Sure, these viral videos are of shoddy quality, have blatantly low production values, and lack polish -- but they belong to us, the people. And it's something big companies will never quite understand.

-Pete


Have a letter for the editor? Send all your rants, raves, ideas, comments and questions to qbexpress@gmail.com.


Express Poll

Every issue, QB Express holds a poll to see what QBers are thinking. The poll is located on the front page of Pete's QBasic Site, so that's where you go to vote. Make sure your voice is heard!

Which QB/FB character are you going to dress up as for Halloween?

GenreVotesPercentGraph
Lynn (Lynn's Legacy)00%
The Griffon Knight (Griffon Legend) 00%
Jocke The Beast70%
Cooey (Secret of Cooey)00%
ASCII Smiley Face Character785603100%
785603 Total Votes

This was quite the shocker! It appears that all seven hundred eighty-five thousand, six hundred and three of the people that voted in this poll are going to dress up as the humble ASCII Smiley Face Character who has been the hero of countless QB games. Who knew he was so popular?!



News Briefs

Site News

Imortis opened his personal webpage

Imortis, a community regular and QB Express co-editor, opened his personal website. The site is planned to feature his tutorials, articles and programs, and he already made a first update.

Visit the website on this link: http://www.zendurl.com/imortis/

News Brief by Lachie Dazdarian


Project News

MiniCalc FreeBASIC

Kristopher Windsor released a FreeBASIC version of his old MiniCalc program (including many upgrades). MiniCalc is a console mode, command line based calculator.

For a download visit this forum post.

News Brief by Lachie Dazdarian

FBWinPrint - A FreeBASIC Windows Printing Library

vdecampo released a FreeBASIC printing library. According to his words, it allows the creation of a PrinterObject which helps in dealing with printing to the printer. It also has some ancillary features like quick access to common dialogs like open/save/printer/pagesetup/fonts/colors.

The current version is 0.12. For more information visit this forum thread.

News Brief by Lachie Dazdarian

TOAOGS - Generic Game Server written in and for FreeBASIC

sir_mud released a generic game server in FB with a goal to greatly simplify the process of making online/local network multiplayer games. The project is currently in development, but there is a stable version available.

Visit the official project page for more information: http://hmcsoft.org/p/toaogs.php

News Brief by Lachie Dazdarian

MP3 ReNamer by Rattrapmax6

Rattrapmax6 posted an MP3 batch renamer September 5th; it iterates through all the MP3s in a directory and renames them to match their internal information tags. Though there are other programs that do this in Windows, it was written specifically to provide that functionality for linux. His project thread is here, and the program was first uploaded to file-pasta (link may be broken).

News Brief by notthecheatr

Prichard's FreeBASIC Torrents project

A know community member Pritchard started an interesting project whose goal is for the community to share FB packages via batch torrents. The packages are planned to contain FreeBASIC itself and FreeBASIC programs (utilities and games), tutorials, code snippets and similar.

So far he released his first FB torrent entitled FBPackage Issue #1, divided in several separate sections to download.

The project is still changing and developing, so visit this forum topic and support the project by seeding this package and posting suggestions.

News Brief by Lachie Dazdarian

PNGcustomfont ver.1.1

notthecheatr released another FreeBASIC font library, but this one is the most complete one in FreeBASIC yet, featuring many fonts packed for the library, plenty of examples, and a huge manual and a tutorial.

Library features, listed by notthecheatr:

  • Support for 8-bit or 32-bit mode
  • Loading of BMP or PNG fonts with many options
  • Drawing with alpha channel or trans
  • A QB-like cursor system with plenty of bells and whistles
  • Clipping
  • Buffered drawing
  • Efficient memory usage
  • Variable-width characters
  • Font information is stored in an external file, not within the image as in Draw String.

For more information look at his forum post, or download the library here.

News Brief by Lachie Dazdarian

FreeLOLCode

sir_mud has released a library that allows FreeBasic to compile LOLcode. LOLcode is something of a joke language, but it's powerful and fun. The forum post is here.

CSGP : Cute Short Game Project : Announce of version 2.0

Yes after Over 18 months of existance... it's time for CSGP to grow up!

Firstly by its new look , check it at http://csgp.suret.net you can compare (for fun) with the old look ;P at http://csgp.suret.net/indexv1.html

Secondly by its rules ... You know these strange utopic rules.

They still there but, with small changes

  • Accept OpenGL as a natural FB gfx lib. (no CSGP title yet, but ...)
  • Accept any FB compatible Sound Library
  • Still refuse any resource files (bitmap or sound or others) except if the program itself generate the files (ex : the embeded level editor of CSGP Lander)
  • Any GFX resolution... prefer low res, but since lander (vectorial gfx game) high res is better.

so to resume the new rule set version is:

  • All; in 1 freeBASIC source file : one .BAS file that contains code + resource
  • No extra library dependency : Only native FB libs except for sound library
  • Linux, Windows and as much as DOS compatible: no OS specific API.
  • no ASM / INP /OUT platform specific usage: no low level access
  • .BAS file as short as possible: Comments are not counted ;) but the idea is to have small executable
  • As small s possible footprint memory usage : never waste memory
  • and of course have fun with it !

So now start a second step in the CSGP life

Enjoy and have fun !

News Brief by redcrab


Competition News

ciw1973's competition results

On August 25th ciw1973 posted results on his more that succesfull FreeBASIC game competition. The most noted competition rule was that "pre-drawn graphics" were not allowed.

There were totally 8 entries, out of which 5 can be rendered as complete.

ciw1973 was kind enough to award the two runners up (Mighy Line and Zonaxtic!) with $50, together with $200 for the winner (Catloaf 2600) as announced earlier.

The competition results, listed by final scores:

Congratulations go to Joe King for winning in a surprisingly tough competition, and to ciw1973 for making the entire event such a success.

Check the competitions results thread here.

I think I can speak for most and say that we are impatiently waiting for the next compo by ciw1973.

News Brief by Lachie Dazdarian


If you have news to report, email it to us at QBExpress@gmail.com!




Gallery

Written by Imortis Inglorian

Every issue QB Express features a preview and exciting new screenshots from an upcoming QB/FB game. If you would like your game featured, send in some screenshots!

Asteroids Inspired Game

download link: Here

Notes from the author, speedlemon:

I'd like to mention that I tested it on 3 computers, and there was a noticeable difference in the rate at which the enemies grew. In the worst case, the game is unbeatable (still playable). Keep this in mind while playing. However, the game, when played correctly, usually took me around 900 kills before I was able to all. In other words, you might think that it is unbeatable, but the times that I beat it, it took me a good 5 minutes or so (and the 5 minutes will be boring....) on design:
well, on this game, there wasn't one. I just started making it. That will explain the poor gameplay, etc. I didn't make the graphics for this game-- You may or may not recognize them from Angband.

I took the time to play this little gem, and I must say that it's really quite fun for it's simplicity. Aside from a few speed tweaks that need to be done and some custom graphics, I'd say it's worthy of a play. If nothing else it will show you what allegro can do.


More information can be found here.


The multi-faceted nature of games: the Dream, the Design, and the Reality:

Or how to stop hating your game and give it the attention it deserves

By Deleter

Now, Iím probably not the best person to be writing this article, as I havenít finished much of anything. On top of that I am planning to draw analogies to real life things that I donít any experience with, but to present them as if I knew what I was talking about. If you are still here then good, youíve probably read my other stuff and are already used to the fact that this is always true whether or not I say it. So onto the actual articleÖ

ÖSo now, what is this three part description of a game, and why do you care? Well the first part is obvious: The Dream. Everyone has them, many of them, in fact sometimes it feels like there are too many of them, as everyone and his nine-fingered sister has their own set of ideas for games. Dreams are the things that make you go through the grueling process of coding and debugging. Dreams are in short your games soul. They are the thing that all games start out as. And as they are the most numerous, they are also the most fragile. Countless game ideas, great ones included have been discarded on the side of the road, left to die.

Of course, some might view this as mercy compared to the necessary process of the next step. It can be such a rigorous process, that some dreams are viciously ripped apart by it and their creators are mentally scarred for life. This is the creation of the Design. See, in their natural state dreams are like the random scraps that hotdogs are made out of, though a lot more desirable. In fact thatís a horrible analogy, but in any case the point is, dreams are not fit for consumption. They need to be translated from the dream world to our world. This is the process of designing. The important thing to consider is how you do it. Just plopping down your dream into text will bring it somewhat into reality, but often this is a bad way of doing it and leads to problems down the road. Now thatís not to say you shouldnít write down your ideas, itís a good idea to start by doing that. But if you really want to do it justice, you should do a lot more.

First of all, what is even more important than the idea is the idea of the idea, your dreamís soul that is. What the heck do I mean? Well basically, if your dream was murdered, what part of it would you remember? What is its essence? What is the central part that all other parts of it point to? If you sat down in an interrogation room with your dream and asked it who it really was, what would it tell you? If you can answer any of these questions, the answer is your games soul. Finding it can really be a pain, and sometimes your dream wonít make it out of that interrogation room. But if the two of you come out alive, you are on your way to making something great.

So now that you have your gameís soul firmly in your grip, what now? Now you brainstorm, find every possible element and part of your dream that complements its soul, and refine the elements you had already thought of. Basically, build up your game idea, refine your game idea, make your gameís soul shine. Now this process never ends throughout your gameís life cycle, but the more you do in the beginning, the less refactoring and reprogramming you are going to have to do, which is a plus. Once you are fairly confident in what you have, make your design document. This is taking your concepts and ideas, and bringing them into code/modeling/hard-core designing world. Write some specifications for things, some implementation ideas, etc. There are much better and more detailed guides for this process elsewhere so I wonít bog you down with a mediocre one.

So now comes the real work. Yeah, you thought everything up to this point was hard? Well give up now if you arenít ready for a hundred-fold of everything youíve done up to this point. Still here? Good, games need dedication and perhaps a bit of foolhardiness. If you are reading this you probably have some of the latter at least, since you saw my name and still kept going. Now this isnít a technical article, again there are plenty of those, and even if they all suck thatís not what this is and if thatís what you want then annoy me enough and I might consider writing something in that vein, well that or adding you to my spam filter list. Instead I am going to try and discourage you from writing your game. Not intentionally mind you, there are far too few indie games as it is, but simply as a side effect of giving it straight as it is.

Basically, making games is like having children. If you canít help but love it, while at the same time knowing every one of its flaws, while having it treat you like shit, and still give it your all and more, then your game stands a chance. You may think that sounds stupid or is wrong, or that I have no clue what I am talking about as I have never had any children, and while that second point may be true, the first one is not. You have to love your game and be dedicated to it if itís going to make it to completion. Laugh if you want, but I will only laugh back when your game becomes nothing but lost bits after you erase it on your hard drive.

So again you are forced to do what you really donít want to. You must know your game in and out. Donít lie to yourself about what sucks, know it. If you can, make it better, but first thing always know everything about your game. No one should be able to point out a flaw that you arenít already aware of. Now Iím not talking about programming bugs. Iím talking about design and implementation issues. The loading screen is annoying, the so and so sound pisses people off, the reload counter is too long for this, but canít be any shorter because of this. This feature has been done better already in other games, etc. The tricky part is admitting all these faults to yourself, and still loving what you have. Know the good parts as well. Know how innovative your control system is, know how good the explosions reward the player, know how well your enemy designs stick in your playerís mind. Every change you make know what it makes and what it breaks.

Now that you have it, use this knowledge to the best of your abilities. Balance elements against each other until your game is as optimized as possible. Sometimes you will realize that a certain feature has to be cut, or another one added for the good of several others. Itís ok to do this now, though the sooner the better as it will mean less re-doing of stuff. If you donít like your game, know what you donít like and make it better. And get outside opinions, always get outside opinions. I have always found in the act of creation that my own idea of my works is often skewed. Iíve liked what others havenít, and found that things I didnít care for as much were generally enjoyed. As far as releasing is concerned what can I say. You will never feel like your game is really ready, but at some point you just have to come to terms with reality and release it. Fix up the bugs later if needed, make tweaks whatever. But get it out there. Near the end of a project sometimes things get slow and this can really invigorate you. Iím not saying to release an unready source, but talk with people who have helped you and see what they think.

And with that I will leave you. Iíve already said more than I should and more than you care to read Iím sure. Thanks if youíve made it this far, youíve been a great audience. Shoot any comments to deleter8_at_gmail.com. Just donít forget to love your game despite its flaws, if you can do this and keep going, the world will thank you for the product you release. Good luck and happy dreaming, designing, and realizing.



Accessibility and File Names

By notthecheatr

So you've written your first really cool program - or your first tutorial - or your first web page. Got all those images linked in, or perhaps you have some external .BI files included in your program - you're going to release it on the web for all to see. Generally you'd put these things into a ZIP, RAR, or 7z archive (or tar.gz, or just tar, or even bz2... the list is endless). Now presumably you want to include everyone - not just people who use Windows. But there's a little secret most Windows users don't know about Linux, and that is that filenames in Linux are case-sensitive. What does that mean? That means that "ABC", "abc", and "aBc" are ALL DIFFERENT. In Windows, this is no problem. In DOS, no problem. In Linux, BIG problem - for when the archive is extracted, the contents will make no sense, whether as HTML with images or a program with external files.

Why do I bring it up? Lately there have been complaints that people will use the wrong case. The file will be named "example.png" but in the HTML or BAS file it's referenced as "Example.PNG" - or something similar. Once again, this works just fine in Windows. It does NOT work in Linux. For this reason, I stress the importance of using consistent case throughout any project you use. In fact, unless capitalizing some letter in the filename adds any extra meaning (usually it doesn't), I recommend you don't use capital letters at all. In DOS, capital letters were pretty much standard - probably to make case-insensitivity easier - but in linux, you can have any case - but then you are case-sensitive, and you need to make sure you get the right case. If you want to maintain DOS-similarity, you'll probably have all letters capital and no letters lower-case. The important thing is consistency, it really doesn't matter how you do it just so long as you do it the same everywhere.

Of course, there is something a Linux user might be tempted to do that wouldn't go over well in Windows - the Linux user might put several different files with the same name but in different case in the same directory. I can't imagine WHY anyone would do this, and I have never SEEN anyone do it, but it is after all a possibility, and obviously this would cause major problems to the innocent Windows user who attempts to extract the contents of that archive.

In summary: Name all files in a directory uniquely, and keep the case consistent. This way your program, article, or web page will be accessible to users on all platforms. Are we all agreed? Good.



User Oriented Programming

Written by Stéphane Richard (MystikShadows)

INTRODUCTION:

When you are cerating your applications or games it's very easy to drift off from the one important fact you should never drift away from. Let's rule out your own personal projects that you make for yourself and yourself only. What's left? The projects (applications, tools, utilities or games) that you make for other people to use or play with. Believe it or not, this can make the impact of your program that much different to the users of it's users. User considerations thorughout your program is a way of showing that you put the extra effort in your program to make it as easy to use or play for the user as possible.

This will be the main focus of this article. User Oriented Programming essentially means that whatever part of the program that needs something from the user needs to consider certain things. This can depend on more than one thing. But in general there's always a way to improve how your programs interact with the user. We'll look at what can need user interaction, what's good to give back to the user, what's overkill and such things so that when you make your programs you can make sure to consider what you need to make using your program as pleasant as possible to the user. So let's get right to it.

USER PRESENTATION:

This is usually the first view that users get of your program. Attention to detail from the very start can make or break the success of your programs. This is regardless of it's it's a GUI application or a Text based application you still need to take care to present your application the right way to the user. How can you tell how things should be presented? There's moer than one criteria to help you determine this. Let's take a look at them.

Once you worked out these starting issues, it doesn't stop there. I guess you can say that user oriented programming is kinda like error management, it's a big job to integrate after your program is done so it's a good idea to start your programming project with these items in mind right from the start so that the work can be integrated into the regular programming tasks and hence not seem as big as it is. The general rule of thumb to remember for these projects is: "The easier you make it for the user, the harder you make it on you, the programmer.". Knowing this, since you already know you have big job ahead of you, you might as well break it down into small manageable parts throughout the project rather than putting it all in at the end of the project. You'll thank yourself later. Now what are the next steps? The very next step is all about where thinsg should be and how the user can access the features of your program.

USER NAVIGATIONS:

Once the user starts your program, it's very important that he doesn't have to look too long to find out what he's supposed to do. One of the key things to remember is that everything should be acessible while at the same time not being hidden within to many levels if menus or sequences of actions fromt he user. The main reason for that is that not many users like to have to remember six sequences of keys before they can perform an action. Since this is an article on user based programming we'll be putting the user first here. Hence, if you want to know what the user might want to do, where he might expect things to be and the likes, there's more than one tool to get that information. Let's take a look at some of the more common ones.

Of course, you can also ask friends, co workers, and other people live, in person or by telephone if you want. When it comes to gettign the answers you need for the success of your project I think there's no limit to what you can do without being considered a spammer. The earlier you can do this in your programming effort the less work it becomes to take this new found knowledge and implement it in your own project. So then, assuming you did all this and you now have all the features you want to put in your program you need to define where they go and why. Here again you have more than one possible means of presenting the features. Let's take a look at the two most widespread ways of organizing an application.

So then, at this point you0 have your list of features, you hopefully made a choice as to which of the two methods above you will use for your project. All this should also give you a good base idea of the structure of your menus and overall user interface so far. Typically, everything that follows here is based on whether you used the Method Based Approach or the Metaphore Approach to your project. So what come snext will be explained under both methods whenever it's necessary. The next step is to start implementing the features themselves, and yes, the method used will help define alot of what the data entry screens will look like and how they will behave, same thing for how reports will get generated (or how the sub sequent game screens would/should appear. What else is tehre to consider? Funny how I used consider here because the next step is exactly that, User Considerations. But before we get into that let's take a little time to define what the users really are.

TYPES OF USERS:

Throughout your project development you need to remember that all in all, there are two distinct types of users you'll be coding your project for. In each of these groups there are sub groups that determine alot of things for you. But all in all everyone can be classified under the following two groups.

I may have exagerated my descriptions of these users, but the point of this article is to accomodate both extremes of the user base you might have to deal with. The bottom line, if your program works with the mouse or keyboard you can get that many more users just because of that one considerations. People like to work they way they work and trying to impose a give way to them won't make them to fond of your prorgam. Is there certain projects or specific task that just acn'e be accomplished witht he keyboard or the mouse? Depending on the project I have to answer yes to that question. More often than none however you can and should accomodate for the keyboard and the mouse by default. And in the unlikely event that something needs to be done with only one of these input devices, state it clearly on the screen "this operation must be performed with the keyboard" so that the user doesn't need to try to do it with the mouse if he can't. (see the user feedback section below for more of these user feedback tricks. And now let's take a look at what other considerations you can do for the users.

USER CONSIDERATIONS:

Are you thinking something like "Hey, did I do enough of that so far?" the answer is no. Let's not hide the facts, you're probably doing this project not just for others to use, you probably want to use it yourself too. Hence you might have your way of doing things and you'd like to see them in there right? At least some of them right? So where do you draw the line? The answer is you don't. But it doesn't mean you can't accomodate yourself too in this. Back in your surveys, you could suggest your method of doing it, and seeing if people agree with them or not. Leave them as suggestions however, not as set in stone methods of operations. IF you like it that way, chances or so will others. Who knows, you might be lucky enough that the way you want everything to work is ok with your potential users so it's not a bad idea to suggest your ways as at least some of your potential users might not have an idea of hwo to use the feature and might think your way seems logical and good.

I already mentioned some considerations throughout this article so far. Things like keeying the features as directly and quicly accessible as possible to minimize keyboard or mouse actions is one of them. Another one I mentioned is to keep things consistent (such as the F2 key always being used to open files and documents). These are very important things to consider. There is more hwoever. Let's take data entry into considerations. Data entry screens are of course the most user specific programs to write. Data Entry typically implies that something will get saved to somekind of file and hence the data provided by the user is very important. Users are human however, they do make mistakes and as such there are more than one trick to help make lower (or sometimes even eliminate) human errors. They have a great deal to do with the controls you use on the forms in most cases. So let's see what role these controls play in the data entry process. This of course can be used in Text Based user itnerfaces and in Graphical user Interfaces alike.

There are more controls but typically the play the role of one of these. Things like Date Drop Down which are a Masked Text Box with a buttom on the right side to make a calendar appear to allow to select a date. And others. Basically, since these controls can be used it becomes impossible to enter a wrong value into the system thus eliminating human error (or at the very least keeping human error to a strict minimum. After that is in place, you can of course add data validation to the whole screen. For example, if certain fields must be entered (known as mandatory fields) you can do a check before exiting the form or saving the data to make sure that these fields really do have values in them and warn the user if they are missing. The combination of these two elements will make sure that what gets saved is valid. And if there's valid data to begin with, chances are, valid data will come out of the system. So when data is a high priority element of your project, these techniques can really help you make sure that you get only good and valid data to begin with, to process, and most likely valid results from them (if you're making a report or something). There's one more form of user oriented programming I want to cover in this article and that is, of course, user feedback.

USER FEEDBACK:

Did you ever call the support department of a company, any company, got left there for a few minutes or a 1/2 hour without no one telling you what's going on? How does it feel? Did you know that your program, unless you make it do things otherwise, will treat your users exactly that way? In a sentence, this is what user feedback is all about, not leaving the user in the dark. Making sure he/she knows what's about to happen (especially of a complex process is about to occur). Letting them know how things are going, let them know if their choice was executed successfully or if a problem occured. user feedback has been implemented before, you can probably learn a whole lot about what type of feedback you need to include just by looking at other programs. User feedback is a very important aspect of any projects that will be used by other users (this includes games and applications).

There are many things that can happen in a program. Some require more or less feedback to the user, This is were common sense and a bit of background knowledge is needed to determine where and when to give the user feedback. Here are some of the major situations where I believe user feedback becomes almost crucial.

User feedback is a must, it's really the best thing to remember from this article. To determine what needs to be done where, put yourself in the shoes of your users. Try out everything before you publish your program. Once again, if there are places where you would have liked to know about something before executing a certain part of your project, chances are, yoru users will probably expect that too. So taking the time to see what's happening where and how important it is to you to know about them is a great first step to making sure user feedback is adequate throughout your project. <.p>

Another important thing is that if one of the feedback means that the user can't perform his selected action to let him know, if at all possible, why he can't perform it. Maybe there's no paper int he printer, maybe the record he's working with is locked by some else in the syustem and he has to wait for that user to be done with that specific record, maybe the system resources are inssufficient for the needs of the application. No matter what the reason, lettign the user know will also help him make sure he can perform the action which is, let's face it, the main reason why he's using your application in the first place. So when you can know the reason why, don't be affraid to share it witht he user, he'll only appreciate your application more because of this little consideration.

AND IN CONCLUSION:

As you might have noticed, most of User Oriented Programming items I mentioned here involved more common sense than business knowledge per se. As a final note, I'd like to talk about business knowledge itself. In one speciial form. If your program is an accounting system, it's important that the terminology you use reflect the terminology used by your users (in this case, accountants). Just that fact, keeping the terminology domain specific like that, can often give the impact you need to make your program stand out from the rest as far as user consideration goes. Hence you need to learn enough about the domain your project is created for in order to use the right terminology that your users, in that field, will expect to see. It's a small form of respect if you will, for the knowledge your users have and are accustomed to work with. It doesn't take that much time to learn the right terminology, and does make a big difference in the impact of your project. So it's a quick and valuable tool for your creations.

I could probably write a whole book on just this subject alone, there's so many considerations involved when creating an applciation for your users. I hope this article opened up your eyes and your mind for your future projects. However, it's very possible that I've left something out, or didn't detail a specific aspect enough. IF you want more details you can email me (email is below my signature below). And talk to me about it, if there's enough grey areas I will create a 2nd part to this article that will cover them. This subject is important in all your projects that you want others to use so let me know if there's somethign you'd want to know more about that has been mentioned here. Until next time, happy reading and coding.

MystikShadows
Stéphane Richard
mystikshadows@gmail.com



Comics

By Deleter, sir_mud, Pritchard, and Michael D.

We have lots of good cocmis this issue. We have six episodes of Deleter's "Whitespace" comics, a new godObject from sir_mud, a comic from Pritchard, and a couple form Michael D.

Click the images to see them full-size! (NOTE to comic submitters: If you keep the width of your images less than 640 pixels, we don't have to resize them and add clicky-links. It makes them *that much* more fun to read!)





Whitespace

Whitespace

Whitespace

Whitespace

Whitespace

Whitespace


FB Pirate.PNG









Fun with Recursion

Written by Dean Menezes

What is recursion? Well, according to Wolfram Mathworld (http://mathworld.wolfram.com/), recursion is

"A recursive process is one in which objects are defined in terms of other objects of the same type. ..."

What does this mean?

Well, let's look at this recursive factorial function:

 FUNCTION Factorial (i)
   IF i=0 THEN
     Factorial=1
   ELSE
     Factorial=n*Factorial(n-1)
   END IF
 END FUNCTION

Basically, recursion is when a function or subroutine calls itself. There is also mutual recursion:

FUNCTION ODD (X)
 IF X = 0 THEN ODD = 0 ELSE ODD = EVEN(X - 1)
END FUNCTION

FUNCTION EVEN (X)
  IF X = 0 THEN EVEN = -1 ELSE EVEN = ODD(X - 1)
END FUNCTION

Use of recursion in an algorithm has both advantages and disadvantages. The main advantage is usually simplicity. The main disadvantage is often that the algorithm may require large amounts of memory if the depth of the recursion is very large.

EXERCISES:

1. Write a subroutine using Recursion to print numbers from n to 0.

Answer:

SUB NtoZero(n)
 PRINT n
 IF n>0 THEN CALL NtoZero(n-1)
END SUB

2. Write a sub using Recursion to print numbers from 0 to n.

Answer:

SUB ZeroToN(n)
 CALL XtoY(0, n)
END SUB

SUB XtoY (x, y)
 PRINT x
 IF x < y THEN CALL XtoY(x+1, y)
END SUB

3. Write a function using Recursion to reverse a string.

Answer:

FUNCTION Reverse$(X$)
 IF x$="" THEN
   Reverse$=""
 ELSE
   Reverse$=LEFT$(X$, 1)+Reverse(RIGHT$(X$, LEN(X$)-1))
 END IF
END FUNCTION


RECURSION PART II

By Dean Menezes

In the previous section, I gave an introduction to recursion and had you do some short exercises. In this section, I will show some more applications of recursion.

Euclid's algorithm is a recursive algorithm for computing the GCF.

See if you can write a function that does this now.

OK, if you can't, that's fine. Now read this description:

"Given two natural numbers a and b, not both equal to zero: check if b is zero; if yes, a is the gcd. If not, repeat the process using, respectively, b, and the remainder after dividing a by b. The remainder after dividing a by b is usually written as a mod b."

Now write the function based on the description.

Answer:

FUNCTION GCD(A, B)
 IF B=0 THEN EUCLID=A: EXIT FUNCTION
 EUCLID=GCD(B, A MOD B)
END FUNCTION

Tower of Hanoi:

The objective of the game is to move the entire stack to another peg, obeying the following rules:

Try to find out an algorithm for solving this tower on your own.

Answer:

The algorithm for moving h disc from peg f to peg t, with peg r being the remaining peg is:

  1. If h>1 then first use this procedure to move the h-1 smaller disks from peg t to peg r.
  2. Now the largest disk, i.e. disk h-1 can be moved from peg f to peg t.
  3. If h>1 then again use this procedure to move the h-1 smaller disks from peg r to peg t.

Now, modify this existing code to put QBASIC code for this algorithm into SUB MOVEPILE. MOVEDISC is a sub to move a disc from peg START to peg FINISH.

DECLARE SUB INSTRUCT ()
DECLARE SUB AUTO ()
DECLARE SUB PLAYGAME ()
DECLARE SUB MOVEPILE (N%, START%, FINISH%)
DECLARE SUB MOVEDISC (START%, FINISH%)
DECLARE SUB SHOWDISCS ()
DEFINT A-Z
CONST NUMDISCS = 8
DIM SHARED TOWERS(0 TO 2, 1 TO NUMDISCS), TOP(0 TO 2), NUMMOVES AS LONG
CLS
TOP(0) = NUMDISCS: TOP(1) = 0: TOP(2) = 0
FOR I = 1 TO NUMDISCS
 TOWERS(0, I) = 9 - I
NEXT
LOCATE 1, 26
PRINT CHR$(218); STRING$(14, CHR$(196)); CHR$(191)
LOCATE 2, 26
PRINT CHR$(179); "TOWER OF HANOI"; CHR$(179)
LOCATE 3, 26
PRINT CHR$(192); STRING$(14, CHR$(196)); CHR$(217)
PRINT STRING$(80, CHR$(196))
PRINT
PRINT "1: AUTO"
PRINT "2: HUMAN"
PRINT STRING$(20, CHR$(196))
WHILE CHOICE$ <> "1" AND CHOICE$ <> "2"
 INPUT "CHOOSE ONE: ", CHOICE$
 CHOICE$ = LTRIM$(RTRIM$(CHOICE$))
WEND
IF CHOICE$ = "1" THEN CALL AUTO ELSE CALL PLAYGAME

SUB AUTO
 CALL SHOWDISCS
 CALL MOVEPILE(8, 0, 2)
END SUB

SUB INSTRUCT
 PRINT "The TOWER OF HANOI is a mathematical game or puzzle.  It consists"
 PRINT "of three pegs and a number of discs which can slide onto any peg."
 PRINT "The puzzle starts with the discs stacked in order of size on one peg."
 PRINT
 PRINT "The object of the game is to move the entire stack onto another peg,"
 PRINT "obeying the following rules:"
 PRINT TAB(2); CHR$(248); " Only one disc may be moved at a time."
 PRINT TAB(2); CHR$(248); " Each move consists of taking the upper disc from"
 PRINT TAB(4); "one peg and sliding it onto another peg, on top of any discs"
 PRINT TAB(4); "that may already be on that peg."
 PRINT TAB(2); CHR$(248); " No disc may be placed on top of another disc."
 PRINT "PRESS ANY KEY TO CONTINUE..."
 NULL$ = INPUT$(1)
END SUB

SUB MOVEDISC (START, FINISH)
 DIM T AS SINGLE
 TOWERS(FINISH, TOP(FINISH) + 1) = TOWERS(START, TOP(START))
 TOP(FINISH) = TOP(FINISH) + 1
 TOWERS(START, TOP(START)) = 0
 TOP(START) = TOP(START) - 1
 NUMMOVES = NUMMOVES + 1
 CALL SHOWDISCS
 T = TIMER
 WHILE TIMER - T < .2:
   IF INKEY$ = CHR$(27) THEN END
 WEND
END SUB

SUB MOVEPILE (N, START, FINISH)
 ' Insert your code here
END SUB

SUB PLAYGAME
 DO
   INPUT "WOULD YOU LIKE INSTRUCTIONS"; NULL$
   NULL$ = UCASE$(LEFT$(LTRIM$(NULL$), 1))
   IF NULL$ = "Y" THEN CALL INSTRUCT: EXIT DO
   IF NULL$ = "N" THEN EXIT DO
 LOOP
 CALL SHOWDISCS
 DO
   LOCATE 1, 1
   COLOR 7
   PRINT "TYPE NUMBER OF START PEG FOLLOWED BY NUMBER OF END PEG"
   PRINT "LEFT = 1", "MIDDLE = 2", "RIGHT=3"
   DO
     KEY$ = INKEY$
     SELECT CASE KEY$
       CASE CHR$(27)
         END
       CASE "1"
         START = 0
         EXIT DO
       CASE "2"
         START = 1
         EXIT DO
       CASE "3"
         START = 2
         EXIT DO
     END SELECT
   LOOP
   DO
     KEY$ = INKEY$
     SELECT CASE KEY$
       CASE CHR$(27)
         END
       CASE "1"
         FINISH = 0
         EXIT DO
       CASE "2"
         FINISH = 1
         EXIT DO
       CASE "3"
         FINISH = 2
         EXIT DO
     END SELECT
   LOOP
   IF TOP(START) = 0 THEN PRINT "There are no discs on that peg.": GOTO 1
   IF START = FINISH THEN PRINT "The start peg is the same as the end peg.": GOTO 1
   IF TOP(FINISH) > 0 THEN
     IF TOWERS(START, TOP(START)) > TOWERS(FINISH, TOP(FINISH)) THEN PRINT "You may not put a larger disc on top of a smaller disc.": GOTO 1
   END IF
   CALL MOVEDISC(START, FINISH)
   IF TOP(0) = 0 AND TOP(1) = 0 THEN EXIT DO
   IF TOP(0) = 0 AND TOP(2) = 0 THEN EXIT DO
1 LOOP
END SUB

SUB SHOWDISCS
CLS
LOCATE 1, 60: PRINT "MOVES: "; NUMMOVES
LOCATE 25, 1
PRINT STRING$(80, CHR$(196));
FOR J = 0 TO 2
FOR I = 1 TO TOP(J)
 LOCATE 25 - I, 27 * J + I
 X = TOWERS(J, I)
 IF X = 0 THEN EXIT FOR
 SELECT CASE X MOD 8
   CASE 1
     COLOR 12
   CASE 2
     COLOR 14
   CASE 3
     COLOR 10
   CASE 4
     COLOR 2
   CASE 5
     COLOR 9
   CASE 6
     COLOR 1
   CASE 7
     COLOR 13
   CASE 0
     COLOR 5
   END SELECT
 PRINT STRING$(X * 2, CHR$(219));
NEXT
NEXT
END SUB

Answer:

SUB MOVEPILE (N, START, FINISH)
 IF N > 1 THEN CALL MOVEPILE(N - 1, START, 3 - START - FINISH)
 CALL MOVEDISC(START, FINISH)
 IF N > 1 THEN CALL MOVEPILE(N - 1, 3 - START - FINISH, FINISH)
END SUB


Peek and poke tutorial

By RubyNL

If you have looked up some source and wondered why you didnít found one single PSET in it, or just wondered how to the fast, flicker-free graphics that you see sometimes, for example in demoís, this tutorial is the right thing to you. Iíll teach you about peek and poke, which can be used as an alternative for point and pset, and some other things.

What are peek and poke?

Peek and poke are two instructions that you use to look up bytes at certain positions. With poke you can set a byte at a certain position a certain value. With peek you look up a byte at a certain address. So, for the clearness:

POKE position, value
value = PEEK(position)

In case you donít know, a byte is not quite the same as an integer. While an integer contains a value between -32768 and 32767, a byte is a value between 0 and 255. So, these bytes are perfect to represent a colour at some point in the screen. First, Iíll teach you how to use peek and poke for normal variables. Before that, I have some theory about memory for you. That means, no code yet. Sorry.

Memory theory

Something you need to know about is memory segments. A segment of memory is a piece out of the memory. Its size is 65535 bytes(= 32767 integers). You can only peek and poke in the current segment. The segment can be anywhere in the memory, as long as it starts somewhere and ends 65535 bytes further. The default segment is starts at 0, so it stops at 65536. When you need much memory(more then 32767 integers) you need to change the segment.

Actually using peek and poke

Now, come on with the code! Before you read the example proggie, I will explain the some things:

VARPTR is used address the integer that follows. A variable that is used to address (point) to an integer, is called a pointer. Instead of address you can use VARPTR(variable), but when you need to have the address to the variable more frequently, it is faster to save it in a variable.

CLS
DIM address AS INTEGER
DIM variable AS INTEGER
address = VARPTR(variable)
'let address be the pointer to the integer variable
variable = 10
PRINT variable
PRINT PEEK(address)
POKE address, 15
PRINT variable
PRINT PEEK(address)

When you look at this code, you can see that poking to address is the same as setting variable to another value. But, this only is like that if variable is a value under 255. Why? Well, remember that one byte can only have a value from 0-255? And with peek, you only read one byte. So, cuz one integer is two bytes, we need to peek another byte. Which? Simply the next one. And, for integers, we use this formula:

Value = PEEK(address) + PEEK(address + 1) * 256

Value is the value of the variable where address points to. This formula is only true for positive values of the variable.

For negative values of variable, we subtract 256 from the second bit:

Value = PEEK(address) + (PEEK(address + 1) Ė 256) * 256

Poking to the screen

Note: The ideas and code I present here, are only for SCREEN 13! Be sure that youíre in screen 13 when you use this code!

Now you know how to look up the values if integers with peek and poke. But actually, this is quite useless and I just cover this for completion and for learning purposes. It is only to give you the idea of how the memory works. To help you with a really useful trick you can do with peek and poke, you have to know how the screen is saved in the memory:

The screen (in screen 13) has 320 * 200 = 64000 pixels. Each pixel can have a value from 0-255. Hey! This exactly the same value a byte can have. And, it wonít surprise you: each pixel is one byte in memory. But how you know which coordinates (x: 0-319, y: 0-199) are with which offset(0-63999)? When you have the coordinates and you want to get the offset, use this formula:

offset& = y * 320& + x

Then you poke or peek at that offset. Poke to set the pixel at that offset to a certain colour, and peeking to get the colour at that offset. Like this you can use peeking and poking instead of psetting and pointing. When you poke a value or colour more then 255, the counting will start over again. So 256 becomes 0, 257 becomes 1, just like -1 becomes 255, -2 becomes 254Ö

Be sure that you use LONG integers for the offset, and a LONG sign after the 320, to avoid an overflow error. But, of course you canít peek and poke to that yet, because the screen offset doesnít start at the starting segment of qbasic. It actually is at &HA000. That looks weird, because it is a hex number(hex numbers have &H before it always in qbasic). We use hex numbers just because &HA000 is easier to remember then -24576(the matching decimal integer for the hex number &HA000). Now you set the segment to &HA000 or -24576, you can poke to it where you want. Code:

DEF SEG = &HA000

When youíre ready with poking to the memory, always return to the normal segment by DEF SEG (without any arguments). When you donít do this there may be some weird errors (screens that freeze and a bunch of other things).

That is a useful feature of peeking and poking: it is much faster then pset and point.

But this can still be made a lot faster. We can either replace that multiply with a lookup table with a size of 199 that multiplies every value you pass to it with 320. Make sure that you multiply with 320& and use a long integer-type array. An other way to do it fast, and avoid that some memory is wasted by a lookup table, is just to access the screen in the way it is stored in memory: row by row. You just initialise the pointer to zero, and after you plotted a pixel, you add one to the pointer to go the next pixel. Be sure that the x loop is the inner loop and the y the outer loop! Else it will mess up. Hereís some code.

SCREEN 13
DEFINT A-Z
DEF SEG = &HA000
pointer& = 0
FOR y = 0 TO 199
  FOR x = 0 TO 319
    POKE pointer&, colour
    pointer& = pointer& + 1
  NEXT
NEXT
DEF SEG

Of course, the pointer is a long integer again.

There are still some faster and faster ways to get an image to the screen. That is with a buffer. BTW, I still want to teach you something extras about the VARSEG shit.

VARSEG and VARPTR

Do you remember the value of an integer, that was stored in memory with the second bit representing a value 0-255 that needs to be multiplied with 256? Well, varseg and varptr work actually the same way. Only, varseg is 16 times the pointer instead of 256(that is because the segment is shifted 4 bits to the left, if you understand, and care). Instead of adding 16 to the pointer, you could add one to the segment. The same logic: instead of adding 320 to the pointer, we could add 20 to the segment. So, instead of drawing the whole screen with one long integer, we can draw the screen with one integer, and update the segment every line. We do this like this:

DEF SEG = &HA000 + y * 20

Then we draw like this:

FOR x = 0 TO 319
  POKE x, colour
NEXT

This is not really faster then the other ways I showed you, but it is added for completion, and to show you how varseg and varptr relate to each other. BTW, some programs use this, and now you know exactly whatís happening ;). It is possible to get the multiply by 20 out, and then it is, as I believes, the fastest way of poking to the screen. You just add a variable that increases with 20 each time on the end of the y-loop. You donít need a long integer anymore that makes it slow. The code is:

SCREEN 13
DEFINT A-Z
yp = 20
FOR y = 0 TO 199
  DEF SEG = &HA000 + yp
  FOR x = 0 TO 319
    POKE x, colour
  NEXT
  yp = yp + 20
NEXT

Of course, you can replace the &HA000 segment by a buffer segment(you will learn later how to do this). The fastest way, however, would be to write some ASM routines. One to write to the buffer, some kind of PSET, and another to copy the buffer to &HA000. This is not that hard to do, and well worth the effort when youíre, for instance, making a game. It is also a fairly good practice for your ASM coding.

Poking to a buffer

When you want to have flicker free graphics, a buffer is very useful. It eliminates almost every flickering. The trick with a buffer is to set the segment not to &HA000, but to the segment of a buffer. The actual buffer is just a large array of short integers. The buffer needs to have enough memory for

  1. All pixel colours
  2. The height and width information

The size for the pixels is easily calculated by just multiplying the width of the pic by the height of it. Then you divide by two, because we donít use bytes but integers, which exist out of two bytes. Then you add two, because the information needed to put the pic to the screen is two integers. Formula:

Size = INT(width * height / 2 + .5) + 2

So, for a screen size buffer you get (320 * 200) / 2 + 2 = 32002 elements. So, you dim an integer array with 32002 elements:

DIM buffer(32001) AS INTEGER

That actually are 32002 integers, because the counting starts at 0. So 0-32001 elements are 32002 elements ;).

Then you set the segment to the segment of the buffer:

DEF SEG = VARSEG(buffer(0))

Or, when you want to use this segment more frequently, you can first save the segment in an integer:

buffersegment = VARSEG(buffer(0))
DEF SEG = buffersegment

But before you try this, you have to give up the width and height of the buffer too. This is called initialising the buffer. It is needed to blit the buffer to the screen. We do it in the following way: we set the first element (in this example: buffer(0)) to 8 times the width. I donít exactly know why the width needs to be multiplied by eight, but be sure to do it, else it wonít work. The second element is set to the height. In code:

buffer(0) = 8 * bufferwidth
buffer(1) = bufferheight

Ok, now youíve set the segment and initialise the buffer, youíre ready to poke to it. One thing I have to tell you about: there are easier ways to use a buffer. With get and put. Once you have dimmed an array with enough space you can get any portion of the screen with this command:

GET (x1, y1) Ė (x2, y2), buffer(0)

And put it on any position:

PUT (x, y), buffer(0), PSET

With PSET you make sure that the buffer overwrites the background. When you donít use it, it looks very weird. Not recommended :P.

An easier way to initialise the buffer is to simply GET the whole screen:

GET (0, 0) Ė (319, 199), buffer(0)

But, this copies the whole screen into your buffer, so when you want to have a empty buffer your screen needs to be empty too ;). I told you about the height/width manner too, because you need to know how GET/PUT work. GET initialises the array automatically with the width and height, and PUT blits the buffer super-fast to the screen.

Youíre almost ready to use the buffer, the only thing to know is that your pointer starts with a value(offset) of 4. This is because the first two integers are used to save the height and width. And each integer is two bytes. So, when we put everything together:

SCREEN 13
DEFINT A-Z
DIM buffer(32001)
buffer(0) = 320 * 8
buffer(1) = 200
pointer& = 4
FOR y = 0 TO 199
  FOR x = 0 TO 319
    POKE pointer&, RND * 255
    pointer& = pointer& + 1
  NEXT
NEXT
PUT (0, 0), buffer(0), PSET

Well, itís not that hard, huh? And, the nicest, it gets rid of all flicker! When your having memory problems, try using REM $DYNAMIC in your code. It gives you more memory to use. Very handy when you use a buffer, because a buffer eats up almost all memory. The only little downside of a buffer. But when you use dynamic memory, with REM $DYNAMIC, you have no problems with that. One last thing, when you have still some flicker, try this:

WAIT &H3DA, 8
WAIT &H3DA, 8, 8

Right before you PUT the buffer to the screen. That should eliminate all flicker. Bang :P.

Quick filtering

I will present a way to do very fast blurring or a fast fire filter. You can do this by poke at the current pixel and peek at the right places: for a blur filter, peek at the surrounding pixels, so:

Then you divide the value of all this pixels together, and poke this at the pointer. You may need a loop from x: 1 to 318 and y: 1 to 198. This is a bit weird coded, because the pointer needs to skip the first and last pixels of a line. Also notice that you need to have a gradient palette to blur. Code:

SCREEN 13
DEFINT A-Z
FOR c = 0 TO 255
  OUT &H3C8, c
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
NEXT
DEF SEG = &HA000
P& = 0
FOR y = 0 TO 199
  FOR x = 0 TO 319
    p& = p& + 1
    POKE p&, RND * 255
  NEXT
NEXT
DO UNTIL LEN(INKEY$)
  p& = 321
  FOR y = 1 TO 198
    FOR x = 1 TO 318
      total = PEEK(p& - 320) + PEEK(p& - 1) + PEEK(p& + 1) + PEEK(p& + 320)
      POKE p&, total \ 4
      p& = p& + 1
    NEXT
    p& = p& + 2
  NEXT
LOOP
DEF SEG

You can make a LUT for the divide by four. The size of the LUT needs to be from 0 to 4 * 255 = 1020. You can add the centre pixel too and divide by five. The size needs to be 5 * 255 = 1275 then. This gives a slightly less, but better blur.

If you want to copy one buffer to another, you just make a 0-199 loop and swap the segment every time in the inner loop, like I told you in ĎVARSEG and VARPTRí. Between the PEEKing from the one buffer and POKEing to the other, you need to save the values in a LUT, which can hold 319 integers.

If you want to know more:

Memory tutorial (© by Qbasic for All!) Ė Teaches you all about the memory in Qbasic(not about using it or implemention, but just how all the commands concerning memory):
http://www.petesqbsite.com/sections/tutorials/tuts/memory.htm

Get and put tutorial by Andrew L. Ayers Ė All about GET and PUT.
http://www.petesqbsite.com/sections/tutorials/tutorials/getput13.txt

Extra

I actually finished this tutorial, but then I though about something that Galleon learned me. Credits go to him.

It is another way of using a buffer, but now you write to it with Qbasicís graphical functions(PSET, POINT, LINE, CIRCLE, GET, PUT). I think that it is very cool when you understand it. But, itís gonna be a bit complicated, so donít worry when you donít understand. Thatís why I called this part ďExtraĒ.

What Iím going to try to explain to you, is a way to use Qbasicís graphical functions with a buffer. So, when you use LINE, it draws a line to the buffer. Unfortunately, this doesnít work for PRINT because text uses another segment.

First, we have this idea:

We know that Qbasic build-in graphical functions writes pixels to the screen. But we want qbasic to write to our buffer. I accidentally know that the place where Qbasic lets its graphical functions draw, is stored somewhere in memory. So somewhere in the memory must be standing: &HA000. OK. Now we must translate this idea to code:

Search in the whole default segment of Qbasic (0-65535) for &HA000. When you make &HA000 an integer, and you look up each byte, you get:

So it is easier to make an IF into the loop:

'Scan the whole memory
FOR pointer& = 0 TO 65534
  IF PEEK(pointer&) = 0 AND PEEK(pointer& + 1) = 160 THEN
    'Youíve found &HA000, now check if itís the right place in the memory

Now we need to check if itís actually the right place in the memory. We do this by messing up the place where &HA000 stood first. Then we PSET a colour at (0, 0). Then we peek a value at segment &HA000, offset 0. If that is the colour, then it is not the right place in memory, so we restore &HA000 at the current offset and we need to continue searching. If it is not that colour, we save the current value of pointer&, and stop searching:

  'We mess up the first byte(we make it 1 instead of 0)
  POKE pointer&, 1
  'Then we try if PSET still PSETs at &HA000
  PSET (0, 0), 127
  'If it does not, we have the right place
  DEF SEG = &HA000
  IF PEEK(0) = 127 THEN
    'We have a wrong place, restore pixel at (0,0), and get &HA000 back into memory
    DEF SEG
    PSET (0, 0), 0
    POKE pointer&, 0
ELSE
    'We found the right offset, so get the buffer segment into the place of &HA000, then exit from the FOR loop
    DEF SEG
    place& = pointer&

Now we need to get the segment of the buffer into the memory. But here is a little problem, because you can only set a segment in the memory and no pointer. But we want to start the drawing at buffer(2), because the first two elements are saved for the height and width of the pic. We canít increase the segment by two due to the fact that the segment is 16 times the offset. So an offset of 1(byte) in the segment would mean 16 bytes in the pointer. Hey! Now we can use it! We just dim our buffer a bit bigger then normal, and let the drawing start at buffer(8), which is the 16th byte in the array. So the width is set at buffer(6) and the height at buffer(7). Then we can PUT it to the screen with:

PUT (0, 0), buffer(6), PSET

Also, you can easily initialise it with GET again:

GET (0, 0) Ė (319, 199), buffer(6)

Now we need to write the segment(VARSEG(buffer(8))) into memory. The memory is split up in bytes, we need to split up the segment in bytes. We do this by letting the first byte be the value AND 255, and the second byte the value \ 256. Code:

Segment = VARSEG(buffer(0)) + 1
POKE pointer&, segment AND 255
POKE pointer& + 1, segment \ 256

This manner(ANDing by 255 for the first byte and integer dividing by 256 for the second) works also fine with writing positive values to integers.

Now youíve done all this, weíre almost finished. One important thing to know is that you canít PUT to the screen no more, because you PUT to the buffer. When you want to put to the screen, you will need to restore the &HA000 segment in memory. Therefore, you need to save the pointer into a variable. To restore the &HA000 segment:

POKE place&, 0
POKE place& + 1, 160

Then you put the pic to the screen and then you restore the segment of the buffer in memory.

Itís recommended to put this into two subs, one for initialising the buffer and finding the &HA000 in the memory, and the other to put the buffer to the screen. Well, I will give you the whole code with some code to test it:

Note: the SUB initialises the buffer, searches the memory for &HA000 and sets the buffer segment in the place of &HA000.

The SUB refresh sets the segment in memory back to &HA000, blits the buffer to &HA000, then sets the segment back.

DECLARE SUB refresh ()
DECLARE SUB initialise ()
CLS
SCREEN 13
DEFINT A-Z
DIM SHARED buffer(32007)
DIM SHARED segment, firstbyte, secondbyte
DIM SHARED place AS LONG
initialise
DO
'Use this instead of CLS:
LINE (0, 0)-(319, 199), 0, BF
'(CLS will work on &HA000 always, and we only want to clean the buffer)
FOR x& = 0 TO 10000
  PSET (RND * 319, RND * 199), RND * 255
NEXT
refresh
LOOP

SUB initialise
'Initialise the buffer, set the segment and the two bytes
buffer(6) = 320 * 8
buffer(7) = 200
segment = VARSEG(buffer(0)) + 1
firstbyte = segment AND 255
secondbyte = segment \ 256
FOR pointer& = 0 TO 65534
  IF PEEK(pointer&) = 0 AND PEEK(pointer& + 1) = 160 THEN
    'Youíve found &HA000, now check if itís the right place in the memory
    'We mess up the first byte(we make it 1 instead of 0)
    POKE pointer&, 1
    'Then we try if PSET still PSETs at &HA000
    PSET (0, 0), 127
    'If it does not, we have the right place
    DEF SEG = &HA000
    IF PEEK(0) = 127 THEN
      'We have a wrong place, restore pixel at (0,0), and get &HA000 back into memory
      'then continue with the loop
      DEF SEG
      PSET (0, 0), 0
      POKE pointer&, 0
    ELSE
      'We found the right offset, so get the buffer segment into the place of &HA000
      'and save pointer& into place&, then exit from FOR loop
      DEF SEG
      place& = pointer&
      POKE place&, firstbyte
      POKE place& + 1, secondbyte
      EXIT FOR
    END IF
  END IF
NEXT
END SUB

SUB refresh
'Set memory to &HA000
POKE place&, 0
POKE place& + 1, 160
'Put the buffer to &HA000
PUT (0, 0), buffer(6), PSET
'Restore buffer segment into memory
POKE place&, firstbyte
POKE place& + 1, secondbyte
END SUB

Conclusion

I hope you liked this tut, and everything was clear. You may send me some feedback on basicallybest@live.nl. BTW, thanks for reading the tut.

And also donít bother to mail me if you donít understand something. Also check out my site:
http://members.lycos.nl/rubynl

Itís a bit old, but Iím doing a re-organization soon.

Greets,

Codemss/RubyNL (Codemss is my new username).

Credits
Plasma for SetVideoSeg



Sphere mapping tutorial

By Codemss (RubyNL)

Introduction

Sphere mapping is a very nice trick that is used much on demos. I found however that there are no tutorials on how to do it easily. One approach is to use inv-sin and inv-cos operations, but I found that that way sucks. When I was messing with some things to make a circle, I found out that it is actually very simple. I will describe a pretty nice technique for this. I believe that Relsoft used this technique also in Mono and disco, for both the sphere mapping and the lenses. A little knowledge of vectors and normals would come in handy to understand what this manner actually does, but it is not a must. The dot product and normalizing vectors will be explained here too, but I wonít go in any details.

Introduction to geometric equation of spheres

What? Geometric equations for spheres? What is that? Well, you remember the equation that you may have learned on high school, for a circle:

SQR(x ^ 2 + y ^ 2) = r

R is the radius of the circle, of course. We can easily translate this to 3d:

SQR(x ^ 2 + y ^ 2 + z ^ 2) = r

When we want to so sphere mapping, we need to know the z coordinate a point(x, y) on the sphere. We need to check if itís on the sphere also. Now, we can use this equation to make an equation for z.

SQR(x ^ 2 + y ^ 2 + z ^ 2) = r
					Take the square of both sides
x ^ 2 + y ^ 2 + z ^ 2 = r ^ 2
					Subtract x ^ 2 and y ^ 2 on both sides
z ^ 2 = r ^ 2 Ė x ^ 2 Ė r ^ 2
					Take the square root of both sides
z = SQR(r ^ 2 Ė x ^ 2 Ė y ^ 2)

This square root is necessary evil, so I will explain one or more ways to make a lookup table for this later. First, we need to check if the point(x, y) is actually on the sphere. This is very easy. You know that you canít take a square root of a negative number, so when the equation inside the brackets of the square root is negative, the point cannot be on the sphere. Notice that x and y have to be in range Ėr to r, not in range 0 Ė 2r. Code:

CLS
SCREEN 13
DEFINT A-Z
CONST r = 50
FOR y = -r TO r
  FOR x = -r TO r
    equation& = r ^ 2 - x ^ 2 - y ^ 2
    IF equation& => 0 THEN
     Z = SQR(equation&)
     PSET (x + 50, y + 50), 15
    END IF
  NEXT
NEXT
END

Colouring based on z-coordinate

I used a long integer for the equation, because it may overflow at larger radiuses.

We didnít use the z coordinate in the colouring. We could use colouring based on the z coordinate. BTW, the z coordinate is always positive, because we are saying: z = square root (blabla) and square roots are always positive. When you would want the backside of the sphere, you had to take the negative of z (Ėz). Now when we want to look it 3d, we simple say colour(x, y) = zcoordinate(x, y)

BTW, we divide by the radius and multiply by 255 to get the colours into the good range. Code:

CLS
SCREEN 13
DEFINT A-Z
FOR c = 0 TO 255
  OUT &H3C8, c
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
NEXT
CONST r = 50
FOR y = -r TO r
  FOR x = -r TO r
    equation& = r ^ 2 - x ^ 2 - y ^ 2
    IF equation& => 0 THEN
      Z = SQR(equation&)
      PSET (x + 50, y + 50), z / r * 255
    END IF
  NEXT
NEXT
END

In case you donít know: the first FORÖNEXT loop is for the palette.

Ok, now this is more what we want! It looks already a bit 3d. Before going on I got to tell you some optimizing stuff:

So, this would the new code for the inner loop be:

CONST r2 = r * r
FOR y = -r TO r
  y2 = y * y
  FOR x = -r TO r
    equation& = r2 - x * x - y2
    IF equation& => 0 THEN
      Z = SQR(equation&)
      PSET (x + 50, y + 50), z / r * 255
    END IF
  NEXT
NEXT
END

This is quite a bit faster, but we still canít use this in real-time. I will tell you: that is kinda impossible, so we have to calculate a lut. Not that Iím explaining this now, just that you know it. I hate surprises :P.

Texture mapping

OK, now youíre ready to learn to texture the sphere! It is basically a matter of just multiplying the x and the value by the z component of the normal. If you donít know what that is, donít be scared. It is just the z coordinated divided by the radius. So:

u = x / z * r

v = y / z * r

U and V are just texture coordinates. When we implant this into our loop, we have to change from the geometrically right equation& => 0 to equation > 0, to avoid dividing by zero issues. We can, instead of using a real texture, use a XOR texture, made by easily XORíring the u by the v coordinate:

colour = u XOR v

I donít give you code yet, because Iíve got more to tell you. We can also multiply the z component of the normal by the texture to get a shaded and textured sphere. You only have to make sure that you AND the colour from the XOR texture by 255, so for the colour:

colour = ((u XOR v) AND 255) / r * z

If you donít do this, the shading wonít work.

Full code is(note that I used a gradient palette again):

CLS
SCREEN 13
DEFINT A-Z
FOR c = 0 TO 255
  OUT &H3C8, c
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
NEXT
CONST r = 50
CONST r2 = r * r
FOR y = -r TO r
  y2 = y * y
  FOR x = -r TO r
    equation& = r2 - x * x - y2
    IF equation& > 0 THEN
      z = SQR(equation&)
      u = x / z * r
      v = y / z * r
      PSET (x + 50, y + 50), ((u XOR v) AND 255) * z / r
    END IF
  NEXT
NEXT
END

Y ratio correction

You may have noticed that the sphere is a little too high. We have to fix this with manipulating the y coordinate of a point. We have to divide by 5 and multiply by 6. Then we use this y coordinate in the equation for the texture.

An easy way to do this:

...
FOR yy = -r TO r
  y = yy / 5 * 6
  y2 = y * y
  ...
  PSET (x + r, yy + r), colour
...

Like this you can fit in the code easily.

Bigger radiusí

When you use a bigger radius, you may come across some problems:

  1. Overflow errors
  2. Very big, obvious and ugly aliasing on the edges

For the first issue, you can just use long integers (like I already did with equation&). The second issue is a little more complicated. I think that the best way for this is to do the checking just the same, but then add a constant (I call it Ďerrorfixí) to the squareroot. The constant needs to be relative to the radius. Code:

CONST r = 100
CONST r2 = r * r
CONST somevalue = 25
CONST errorfix = (r + r / somevalue) ^ 2
...
equation& = r2 Ė x * x Ė y2
IF equation& > 0 THEN
  z = SQR(equation& + errorfix)
  ...
END IF

Notice that Iíve added a constant named somevalue. The bigger this value, the more aliasing problems etc you got. Still, you can make it fairly big without real problems. Donít make it small, because the sphere mapping effect will be not or hardly noticeable. A value between 10-200 is fine. When you use shading also, you can better use the normal z, so without the added errorfix, for that. This goes for the shading with the z component as for the shading with a light vector or point (you will learn this next).

Shading with a light vector

This is a pretty neat technique that you can use to light the sphere. First you got to define a light vector. Try a bit what looks right. I found that this one looks good:

lx! = -1
ly! = -1
lz! = 3

Notice that I use singles, and you should do that too, because we gonna work with values from 0-255. You could use fixed point math for this, but that would be a waste of work, because that would be not that much faster (and weíre going to make LUTís for the texturing and shading anyway)

After that, you normalize this vector. What that means is that you make the vector have a total length of 1. You do this by dividing all components of the vector by itís length. I quickly made a SUB for this:

SUB normalize (x!, y!, z!)
length ! = SQR(x! * x! + y! * y! + z! * z!)
x! = x! / length!
y! = y! / length!
z! = z! / length!
END SUB

In the inner loop, you take the dot product by the normal vector of the sphere. Make sure that the normal vector is also normalized by dividing it by the radius of the sphere (because that is the total length of the not-normalized normal vector). Code, for the dot product:

FUNCTION dot! (x1!, y1!, z1!, x2!, y2!, z2!)
dot! = x1! * x2! + y1! * y2! + z1! * z2!
END FUNCTION

Then we just multiply the dot product by 255. But if it less then 0, you should make it zero, because we donít want a negative colour. Hereís the final version for a lighted sphere. Play a bit with the light vector if you want. Code:

DECLARE SUB normalize (x!, y!, z!)
DECLARE FUNCTION dot! (x1!, y1!, z1!, x2!, y2!, z2!)
CLS
SCREEN 13
DEFINT A-Z
FOR c = 0 TO 255
  OUT &H3C8, c
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
NEXT
lx! = -1
ly! = -1
lz! = 3
normalize lx!, ly!, lz!

CONST r = 50
CONST r2 = r * r
FOR y = -r TO r
  y2 = y * y
  FOR x = -r TO r
    equation& = r2 - x * x - y2
    IF equation& > 0 THEN
      z = INT(SQR(equation&))
      dotp! = dot(lx!, ly!, lz!, x / r, y / r, z / r)
      IF dotp! < 0 THEN dotp! = 0
      PSET (x + 50, y + 50), dotp! * 255
    END IF
  NEXT
NEXT
END

FUNCTION dot! (x1!, y1!, z1!, x2!, y2!, z2!)
dot! = x1! * x2! + y1! * y2! + z1! * z2!
END FUNCTION

SUB normalize (x!, y!, z!)
length! = SQR(x! * x! + y! * y! + z! * z!)
x! = x! / length!
y! = y! / length!
z! = z! / length!
END SUB

I took the integer part of z, because it may be a little too big else. For texturing this doesnít matter, but for lightning it does, because it turns black at the brightest points else. Well, try removing the INT if you donít know what I mean.

Real-time rendering by using a LUT

A LUT, a lookup table, is a handy thing when you want to do real-time things that actually canít be done in real-time. The Ďsecretí of a lookup table is that you do precalculations before the actual inner loop. That precalculations may take like 3 seconds, and this is MUCH to long to do these precalculations in real-time. For sphere mapping, the first reflex for a sphere with radius 50, would be DIM sphere(100, 100). Donít do this. You use too much memory and you canít, for instance, make a sphere with radius 150, as that would get you an error.

One way to solve this is to use a LUT that is half the size of the sphere. So:

CONST r = 50
DIM sphereLUT(r, r)

Then, you need to mirror the data in the LUT if the point you want to draw is on the right or on the bottom side of the sphere. The good thing is that you can use this table for both the u and the v coordinates if you mirror it right.

It is also possible to save everything in one large array, but this costs some more memory. Less memory then saving the whole sphere is used however, but still more then the other manner. You need to check one time how many pixels are used, then DIM the LUT, and then fill the LUT. That costs a little more time, but I believe this method is a bit faster.

A problem occurs when you try to draw the sphere, because it tries to draw a solid rectangle. For this, you need an array of size radius, where you save every highest value on the x-axis. Then in the inner loop you do:

FOR y = -r TO r
  FOR x = -array(y) TO array(y)
    Draw sphere here
  NEXT
NEXT

An example program is here. If youíre having problems, look it up here. This is quite old code (I am too lazy too write this code again), so much things may be vague to you. I use a weird sphere mapping technique there, but ignore that. Before I forget :P, hereís the code:

SCREEN 13
DEFINT A-Z
xc = 160
yc = 100
r = 100

FOR x = 0 TO 255
  OUT &H3C8, x
  OUT &H3C9, x / 255 * 63
  OUT &H3C9, x / 255 * 63
  OUT &H3C9, x / 255 * 63
NEXT

DIM array(100, 100), xwidth(100), shade(100, 100)
FOR y = 0 TO 100
  FOR x = 0 TO 100
    delta = -(x ^ 2 + y ^ 2 - r ^ 2)
    IF delta > 0 THEN
      nz! = SQR(delta) / r
      nx! = ((x) / r)
      ny! = ((y) / r)
      u = (((((nx! * (2 - nz!)) / 2) + 1) / 2) * 255)
      array(x, y) = u
      IF x > xwidth(y) THEN xwidth(y) = x
      shade(x, y) = nz! * 128
    END IF
  NEXT
NEXT

LINE (0, 0)-(319, 199), 0, BF
DEF SEG = &HA000
DO UNTIL LEN(INKEY$)
  FOR y = -100 TO 100
    yd = ABS(y)
    xw = xwidth(yd)
    offset& = (yc + y) * 320& + xc - xw
    FOR x = -xw TO xw
      xd = ABS(x)
      IF xd <= 100 AND yd <= 100 THEN
        pixel = array(xd, yd)
        u = pixel
        IF x < 0 THEN u = 255 - u
        v = array(yd, xd)
        IF y < 0 THEN v = 255 - v
        c = ((u + a) XOR (v + b)) AND 255
        c = (c * shade(xd, yd) \ 128)
        POKE offset&, c
        offset& = offset& + 1
      END IF
    NEXT
  NEXT
  a = (a + 1) AND 255
  b = (b + 1) AND 255
LOOP
DEF SEG
SLEEP
END

A lighting LUT is implemented exact the same way. But notice that when youíre shading with a light vector, the shading cant be implemented with the first way, because you really have to save each pixel apart. This is because the shading is non-symmetrical, so you cannot use the array size optimization because that uses symmetry.

Other ideas

This tutorial has become a bit longer then I meant it to be, so I aint writing no more in detail. I explained everything what I know in detail now, but I still have some other thing I havenít really tried myself to tell you. The first thing is about lightning. I explained the shading with a light vector to you, but itís also possible with a light point. For this, you have to take the vector to the point. This is not that hard. Do this in the inner loop:

  1. Set the vector as (lx - sx, ly - sy, lz - sz). (Sx, sy, sz) is the sphere position and (lx, ly, lz) the light position.
  2. Normalize vector.
  3. Take the dot product of vector to the light and the normalized (divided by radius) normal.
  4. If the dot product is less then 0, set it to 0.
  5. Plot the point with colour dot! * 255.

Ready! Now about rotating. Normally, there are three rotation angles, one for each angle.

Rotation about x-axisPitchDone with adding numbers to texture coordinates.
Rotation about y-axisYawDone with adding numbers to texture coordinates.
Rotation about z-axisRollDone with a rotation formula.

Pitch and yaw can be easily implemented by adding numbers to the u and v coordinates (texture coordinates). Of course you need to AND the texture numbers to keep them in range.

When you want to implement roll, you will need this formula:

u = (x * cos(angle)) + (y * sin(angle))

v = (y * cos(angle)) - (x * sin(angle))

Roll is so hard to implement because the u and v coordinates actually rotate. With the x and y rotation, the x and y actually stay the same, but you just change their range by adding something to them. The pitch and yaw we use, depends on texture sizes, but when you want to give them up in radians, you need to convert them by diving through 2 * pi and multiplying with texture size.

What also is very nice to do is making two LUTís, one for the texturing and one for the lightning, and them mix them in real-time. Then you could add for example, phong lighting to the light LUT. It is also possible to rotate the lighting LUT, just the same way you rotate the texture LUT.

A scroller must be to easy to implant, and you could make it transparent by not painting the background pixels. Like that you could make more scrollers inside each other, sounds like a nice idea...

Conclusion

I hope you liked this tut, and everything was clear. You may send me some feedback on basicallybest@live.nl. BTW, thanks for reading the tut.

And also donít bother to mail me if you donít understand something. Also check out my site:

http://members.lycos.nl/rubynl

Itís a bit old, but Iím doing a re-organization soon.

Greets,


Codemss/RubyNL (Codemss is my new username).


Almost forgot:

Credits

Galleon for helping to optimize my program.

Relsoft and (especially) Biskbart for their sources.



Making a Particle Engine, Part I

Written by notthecheatr

Note: This is a beginning level tutorial written using FreeBasic - converting to QBasic, or other similar BASIC variants, should not be very difficult, only bear in mind that this is written mainly for FreeBasic.

Another note: All source code and example programs may be downloaded in a ZIP file - the link is at the bottom of the tutorial.

Hey there, notthecheatr here writing my first tutorial. I'm going to show you how to write a particle engine in FreeBasic. We'll start with the basics, no pun intended, and work from the ground up. There aren't a whole lot of tutorials on this topic (though if you Google you'll find a few), so I'm writing from a beginners stand-point, building up over several parts to a powerful and flexible particle engine built entirely from scratch. I think you'll enjoy it, maybe you'll even learn something from it.

Right, so the first thing we need to understand is what a particle engine is and why you'd want to make or use one. A particle engine is essentially the structures for organizing and the code for updating particles. In other words, you'd have a few data structures that tell how to create and initialize these particles, then you'd have the code to move the particles around in a predefined way. The result? Well, when you have a couple thousand particles on the screen, the result generally looks pretty cool. In fact, particle engines can be used to create decently realistic explosions and fireworks, flames, smoke, water fountains, sprinklers, jets, rain, snow, and much more. Probably if you're writing an RPG you'll want to do some of the spell animations this way. So in short, the reasons for writing a particle engine are many - but to me, the most important one is that it's a lot of fun. It's not hard to do, and the return (really cool effects) is worth it. So let's begin!

OK, so now we know what a particle engine is, how shall we go about implementing it? Well a particle system generally has two basic parts: the emitter, and the particles. You might have more parts, but these form the foundation. The emitter emits the particles, and keeps track of them. In my system, the emitter is responsible for updating the particles and eventually drawing them (actually, each particle draws itself, but that'll come later...) Thus, you can think of the emitter as a kind of gun that shoots out little tiny dots. Note that these dots can move in a variety of ways - they won't necessarily look like bullets, they might look like snowflakes. Or rain, smoke, fire - or anything else you can think of that is made of particles.

OK, so we want a structure for the emitter and a structure for each individual particle. Now if we're going to have thousands of particles in memory then it would probably be wise to keep the variables in the particle structure down. Thus, we'll try to put as many variables in the emitter as we can, since there usually won't be thousands of those.

So what is a particle? Essentially an object. In physics, your average object has two essential characteristics. These characteristics are the basis for our particle, and in fact without these characteristics our particle would be nothing. We will add other things to our particle type later, to make it do more interesting things, but for the moment we'll focus on the most important thing. Every particle has to know where it is now, and where it's going (how fast in each direction, to be exact). In other words, you need to keep track of it's position (x, y, z) and velocity (vx, vy, vz):

Note: Throughout this tutorial I develop a particle engine that keeps track of the z coordinate, though I personally stick to 2d particles. If you don't need a 3d particle, you may also leave out z and any code relating to it. Otherwise, you may do as I do and simply ignore the z component when you don't need it. Either way is fine, updating a third component doesn't take particularly long to do.

Now our emitter also needs to start out at some location, or else all our particles will come out of the top-left corner of the screen, which is not always what we want. The emitter also needs to know how many particles it may create maximum, how many particles it is keeping track of NOW, and what the rate of emission is. We should also have some flags that allow us to quickly turn the emitter on or off. Once again, we'll be adding tons of things to this, but for now our two structures look like this:

  #Define FBPE_FLAGS_EMITTING 1
  #Define FBPE_FLAGS_VISIBLE     2
  #Define FBPE_FLAGS_MOVING    4

  Type FBPE_Particle
    As Single x, y, z
    As Single vx, vy, vz

    'r = radius.  Used mainly to keep track of whether the particle has been initialized
    'yet, or in collision detection.
    As Integer r
  End Type

  Type FBPE_Particle_Emitter

    As Integer location_x, location_y, location_z

    As Integer maxparticles, numparticles, emissionrate

    As Byte emitterflags

    As FBPE_Particle Ptr particles

  End Type

Notice that particle ptr! It let's us use a variable number of particles in each emitter, so you can have 20 particles or 20,000 from two emitters with the same UDT. That means we'll need to allocate and deallocate space. This is very simple. To make it blazing fast, we'll allocate everything at the beginning, when we initialize the emitter, and deallocate everything when we close it. No need to reallocate when we create new particles, for example. Thus, we start out with this:

  Sub FBPE_Init_Emitter (emitter As FBPE_Particle_Emitter, mparticles As Integer, prate As Integer)
  Dim As Integer i

    emitter.maxparticles = mparticles
    emitter.emissionrate = prate
    emitter.particles = Allocate(mparticles*SizeOf(FBPE_Particle))
    'Set each particle to be uninitialized
    For i = 0 to mparticles-1
      emitter.particles[i].r = 0
    Next i
  End Sub

OK, so our emitter is initialized... let's add some code for making it start:

  Sub FBPE_Start_Emitter (emitter As FBPE_Particle_Emitter)
    emitter.emitterflags = (FBPE_FLAGS_EMITTING Or FBPE_FLAGS_VISIBLE Or FBPE_FLAGS_MOVING)
  End Sub

  Sub FBPE_Pause_Emitter (emitter As FBPE_Particle_Emitter)
    'Turn off all flags except visibility
    emitter.emitterflags = emitter.emitterflags And FBPE_FLAGS_VISIBLE
  End Sub

  Sub FBPE_ToggleVisibility_Emitter (emitter As FBPE_Particle_Emitter)
    'Toggle visible flag
    emitter.emitterflags = emitter.emitterflags Xor FBPE_FLAGS_VISIBLE
  End Sub

  Sub FBPE_Close_Emitter (emitter As FBPE_Particle_Emitter)
    emitter.emitterflags = 0
    emitter.numparticles = 0
    DeAllocate(emitter.particles)
  End Sub

Now our emitter does: precisely nothing. We need an update function. What the update function does is move particles around on the screen. It's usually used in a loop, so it'll need to be fairly efficient. It also needs to work. Note that we won't display our particles using this update function - we'll only move them around. Displaying the particles is done separately, so eventually the user will be able to choose how they're drawn and when.

  Sub FBPE_Update_Emitter (emitter As FBPE_Particle_Emitter)
  Dim As Integer i

    'If we're still emitting...
    If (emitter.emitterflags Or FBPE_FLAGS_EMITTING) <> 0 Then
      'Emit the number of particles we're supposed to emit...
      emitter.numparticles += emitter.emissionrate

      'If we've emitted as many as we can, turn the emitter off.
      If emitter.numparticles > emitter.maxparticles Then
        emitter.numparticles = emitter.maxparticles
        emitter.emitterflags = emitter.emitterflags And (Not FBPE_FLAGS_EMITTING)
      End If
    End If

    If (emitter.emitterflags Or FBPE_FLAGS_MOVING) <> 0 Then
      For i = 0 to emitter.numparticles-i
        'If the particle has not yet been initialized...
        If emitter.particles[i].r = 0 Then
          emitter.particles[i].x = emitter.location_x
          emitter.particles[i].y = emitter.location_y
          emitter.particles[i].z = emitter.location_z
          emitter.particles[i].vx = 1
          emitter.particles[i].vy = 1
          emitter.particles[i].vz = 1
          emitter.particles[i].r = 1
        Else
          'Move the particles, with a small amount of randomness built in
          emitter.particles[i].x += emitter.particles[i].vx + (Rnd*2) - 1
          emitter.particles[i].y += emitter.particles[i].vy + (Rnd*2) - 1
          emitter.particles[i].z += emitter.particles[i].vz + (Rnd*2) - 1
        End If
      Next i
    End If
  End Sub

OK, now we have everything but drawing. Our particles are just numbers in memory up until now. Let's draw them on the screen! Once again we'll be making this far more flexible, but for now, you can draw your particles as single dots - or circles, or squares if you like, but keep it simple.

  Sub FBPE_Draw_Emitter (emitter As FBPE_Particle_Emitter)
  Dim As Integer i

    If (emitter.emitterflags Or FBPE_FLAGS_VISIBLE) = 0 Then Exit Sub
    For i = 0 to emitter.numparticles-i
      PSet(emitter.particles[i].x, emitter.particles[i].y), &hffffff
    Next i
  End Sub

Now if you haven't typed this all in, do so or else download the first ZIP file below. Either way, you should compile the following test program to see how it looks:

''
''
''FB Particle Engine - Test Program 1-A
''
''
#Include "fbgfx.bi"
Using FB

'Change this if you named it something different
#Include "fb_particle_engine.bi"

Dim As FBPE_Particle_Emitter myemitter

Screen 20, 32, 2, 1
ScreenSet 1, 0

Randomize Timer

FBPE_Init_Emitter(myemitter, 10000, 10)

FBPE_Start_Emitter(myemitter)

Do
  FBPE_Update_Emitter(myemitter)
  Cls
  FBPE_Draw_Emitter(myemitter)
  ScreenCopy
Loop Until MultiKey(SC_ESCAPE)

Sleep

Well it doesn't do a whole lot, but it already looks nice. But there are some things we can do to improve it. Let's give the particles an optional life time. Particles may die - or may be reincarnated, too.

#Define FBPE_LIFE_IMMORTAL            0
#Define FBPE_LIFE_DEATH                   1
#Define FBPE_LIFE_REINCARNATION  2

Type FBPE_Particle
...
  As Integer life
...
End Type

Type FBPE_Particle_Emitter
...
  As Integer initial_life
  As Byte life_behaviour
  As Integer deadparticles
...
End Type

And we'll have to modify the update Sub a bit:

Sub FBPE_Update_Emitter (emitter As FBPE_Particle_Emitter)
Dim As Integer i

  'If all particles are dead, then skip everything else
  If emitter.deadparticles = emitter.maxparticles Then Exit Sub

  'If we're still emitting...
  If (emitter.emitterflags Or FBPE_FLAGS_EMITTING) <> 0 Then
    'Emit the number of particles we're supposed to emit...
    emitter.numparticles += emitter.emissionrate

    'If we've emitted as many as we can, turn the emitter off.
    If emitter.numparticles > emitter.maxparticles Then
      emitter.numparticles = emitter.maxparticles
      emitter.emitterflags = emitter.emitterflags And (Not FBPE_FLAGS_EMITTING)
    End If
  End If

  If emitter.life_behaviour <> 0 Then
    For i = 0 to emitter.numparticles-1
      emitter.particles[i].life -= 1
      If emitter.particles[i].life = 0 Then
        If emitter.life_behaviour = FBPE_LIFE_REINCARNATION Then
          'Set particle to be re-initialized
          emitter.particles[i].r = 0
          emitter.particles[i].life = emitter.initial_life
        Elseif emitter.life_behaviour = FBPE_LIFE_DEATH
          emitter.deadparticles += 1
          If emitter.deadparticles = emitter.maxparticles Then Exit Sub
        End If
      End If
    Next i
  End If

  If (emitter.emitterflags Or FBPE_FLAGS_MOVING) <> 0 Then
    For i = 0 To emitter.numparticles-1
      'If the particle has not yet been initialized...
      If emitter.particles[i].r = 0 Then
        emitter.particles[i].x = emitter.location_x
        emitter.particles[i].y = emitter.location_y
        emitter.particles[i].z = emitter.location_z
        emitter.particles[i].vx = 1
        emitter.particles[i].vy = 1
        emitter.particles[i].vz = 1
        emitter.particles[i].r = 1
        emitter.particles[i].life = emitter.initial_life
      Else
        'Move the particles, with a small amount of randomness built in
        emitter.particles[i].x += emitter.particles[i].vx + (Rnd*2) - 1
        emitter.particles[i].y += emitter.particles[i].vy + (Rnd*2) - 1
        emitter.particles[i].z += emitter.particles[i].vz + (Rnd*2) - 1
      End If
    Next i
  End If
End Sub

We also need to change the drawing Sub a bit so it doesn't draw dead particles:

Sub FBPE_Draw_Emitter (emitter As FBPE_Particle_Emitter)
Dim As Integer i

  If (emitter.emitterflags Or FBPE_FLAGS_VISIBLE) = 0 Then Exit Sub
  For i = 0 To emitter.numparticles-i
   If emitter.particles[i].life <> 0 Or emitter.life_behaviour = FBPE_LIFE_IMMORTAL Then PSet(emitter.particles[i].x, emitter.particles[i].y), &hffffff
  Next i
End Sub

The result can be seen by adding the following lines to the test program:

myemitter.life_behaviour = 2
myemitter.initial_life = 100

Add these before the main loop (after the call to FBPE_Init_Emitter) and watch the result! Particles now dissappear after a time - it looks a bit odd, but quite a nice effect I think. The number of particles that dissappear at a time are dependent on the emission rate, since they are all initialized with the same amount of health; how soon they dissappear is determined by the initial_life given to the particles. You'll probably choose these based on the kind of effect you want to produce. By the way, in FBPE_LIFE_REINCARNATION, your program will run forever. In FBPE_LIFE_IMMORTAL, it will run forever but eventually the particles will run off-screen. In FBPE_LIFE_DEATH, it will run forever but eventually all the particles will be dead and you'll be looking at a blank screen. If you use either of the latter, you may want to add code to check when they run out, so your user isn't left staring at empty space, wondering what to do next - of course, there'll be other ways of solving that problem, coming up in later parts.

Well I hope you've enjoyed it, this has been lots of fun for me, but there's much to do still. We want to be able to send the particles in random directions, for explosion-like effects, to control the way the particles are drawn, and much more. You'll probably want gravity, you may want the particles to bounce off the walls, you might like some wind effects, and many other things. In it's current state, our particle engine is very simple and very limited. In Part II of this series I'll show you how to take it a step further, add some very useful details to it. Later, we'll work on the interface, adding a high-level layer so you can generate common effects like smoke or explosions without typing a whole lot of code. Get ready for Part II!



Contact:
TheMysteriousStrangerFromMars@yahoo.com
(or)
notthecheatr@nerdshack.com

http://notthecheatr.deviantart.com/

(and on the Official FreeBasic Forum my username is notthecheatr.)

Downloads:
fbpe_tutorial1.zip (49k)



QBE Helper and You

By Luther Ramsey (Imortis Inglorian)

Welcome to the wonderful world of QBE Helper. I will be your ruggedly handsome guide. There will be many hardships along the way. Many times you, or even I, may wet yourself in terror. Prepare for a thrill ride the likes of which has never been seen before and will never be seen again.

OkayÖ Maybe NotÖ

QBE Helper IS however a rather easy to use tool for formatting QBE articles and tutorials. As a matter of fact, Iím using it right now. It make all the tedious work of typing in the html codes as easy as clicking a button. I am here to give you an in depth walkthrough for using this helpful tool.

Getting Started

This is what the program looks like when you open it. It may look a little overwhelming, but itís really quite simple.

Each of the top buttons represents an html tag that is commonly used in the process of formatting QBE articles of all types. Below that is a dropdown box that contains the editor programs that are currently open. At the very bottom of the window is a button to refresh the list of open editors, and a button that leads to a dialog that allows you to add to the list of editor programs.

The Editors Dialog

This is the editor dialog. When the program is first run, you will have Notepad++, Notepad 2, Notepad, and ConTEXT in this list. The editor is what ever you like to use to write your articles. It can be any program that can accept information from the Windows Clipboard. (Sorry DOS people. You may be able to use this from a console window, but I havenít tried.) The Editors list contains a block of text that you can see in the title bar of the editors you use. It only has to be part of the text, not all of it. If, for instance, you wanted to use OpenOffice.org Writer, you would add this to the list:

In order to add this to the list, simply type the above into the text box on the Editors dialog, then hit enter, or click the Add button. That is all it takes.

Want to delete one from the list? If, say, you had to tweak an editor listing to get it just right, or if you want to remove an editor from the list that you never use, just highlight the item on the list that you want to get rid of and click the Delete button.

A note about ConTEXT. As you can see, the listing for ConTEXT is ďConTEXT Ėď. The reason for this is that ConTEXT actually makes an invisible window with the title ďConTEXTĒ. QBE Helper will work with either entry, but this makes the combo box much cleaner. Some programs may require this kind of hack to make them work cleanly.

Once you have added the editor of your choice to the list, just click the close button in the top right corner of the Editors dialog.

Now On To The Good Stuff

Once you are back to the main window, click the Refresh Apps button at the bottom of the window. This will look for all open windows that contain text from the list, and load them into the combo box. Then you click the combo box and select the editor window you wish to use.

Now it is time for a brief description of each of the tag buttons:

How To Use It

All you do to put a tag into the editor is click the button that corresponds to it. This will copy the tag into the Windows Clipboard, then paste it into the editor you have chosen from the dropdown box. Simple, yes?

Example Outline

Your article will look similar to this:

Anchor
Div Open
Title
By Line
--Repeat block as often as needed
Heading Open  (Name of Article) Heading Close
Para Open (content)
(content)LCode Open (one line of code) LCode Close
Link
Center(Image)
Mailto:
Para Close

BCode Open
(preformatted code)
BCode Close

OrderedList(List Item, List Item, etcÖ)
UnOrderedList(list Item, List Item, etcÖ)
--

Div Close
Break
Break

This is of course just psudo-code. It represents the general outline of an article. Every thing between the two -- sets in the above block is variable. They can go in any order you like. You donít have to use all tags like I have here. You can use what ever is needed. But everything else should fall into that exact outline.

Anchor
Div Open
Title
By Line
--
(everything here is up to you)
--
Div Close
Break
Break

Note that this makes editing an article a lot less time consuming, but it will not do it for you. A healthy knowledge of HTML is a plus when editing your articles.

Conclusion

Way back when( read: last issue), someone asked if we had an automated editing tool for QBE. We didnít then, and we still donít now. Many articles need to be edited by a human to make them come out nice and pretty for QBE.
I wrote this tool because I was tired of having to type all the tags over and over again. I will probably update the program a few times to add new tags (already thinking about a BOLD tag), or to fix errors. If anyone has feature requests, you can send them to QBExpress@gmail.com. I will personally respond to and reject each and every suggestion. Because Iím a dick like thatÖ Not really. I want this program to be the be-all-end-all for QBE formatting needs.



Floormapping/Mode7 tutorial

By Codemss

Introduction

Floor mapping is a nice effect that can be used both in a demo or a game. I spend quite a long time to search for tutorials on this subject. I didnít find any good tutorials. Itís also called mode7 sometimes because it is used much on the old Super Nintendo. This was done using a hardware device that was called Mode 7. Anyway, because the computer hasnít such a device, you will have to do it in software mode. That may be a little bit slower, but we have also more control about the program.

In know that Eclipzer has a good tutorial on floor mapping, but that one doesnít cover things as rotating. And I will :P. This tutorial uses much trigonometry such as SIN and COS. Since I couldnít find any documentation , I tried to do it myself. My first attempt didnít work at all, due to a little fact that I forgot. But my second attempt worked great, and, after some optimizations, I runned on a high frame rate. Enough, letís start.

In this tutorial you will need some knowledge of SIN and COS and how angles with SIN and COS work. Some algebra knowledge is useful but not necessary.

Theory

Because it is necessary, I will write about some theory first. I use a 90 degree field of view (FOV). I call the angle where we are viewing to Ďtí. The FOV lies between t Ė 45 degrees and t + 45 degrees. If we define out viewing system well, then we can say: 45 degrees = 160 pixels. Therefore, we have to make sure that have a angle system of 1280 degrees. To do that we, have to multiply any angle in our new angle system by 1 / 1280 * 2 * pi. Because two pi is 360 degrees in radians (if you donít know what the heck Iím talking about, never mind ;), just use it). So we can make a const from this, I cal it Ďmulí:

CONST pi = 22 / 7
CONST mul = 1 / 1280 * 2 * pi

I used 22 / 7 as alternative to pi, because constants can use ATN(1) * 4, which is a better way to calculate pi. Now when we have this clear, I will display what we have now in a picture:

Because we canít use a formula for each pixel, you have to interpolate. There are two ways to interpolate, but I use only one. That is horizontal interpolation. I will explain it with a picture:

It may not be so obvious in the picture, but you need to calculate p1 and p2 again for every distance. We take one point p1 on the left axis(which is on angle t - 160) and a point p2 on the right axis (on angle t + 160). We can pick this points with COS and SIN:

p1u = COS((t - 160) * mul) * distance
p1v = SIN((t - 160) * mul) * distance
p2u = COS((t + 159) * mul) * distance
p2v = SIN((t + 159) * mul) * distance

Then we interpolate between that points. We have to check this on every distance. Well, that is actually not right. We donít have to loop through every distance, but only the ones where a ray intersects the floor. This is what Iíve done wrong in my first attempt to code a mode 7 demo. Now I know at which distances to check. When we assume a player of Ďhí pixels height, that is Ďdí pixels between the projection screen:

You have to check where every ray intersects the floor. This can be done by setting up an equation. This is the same equation that Eclipzer uses for the v solving in his floor mapping tutorial. We first calculate how much the ray goes down in d pixels, then with that we can calculate how far the ray goes before it intersects the floor.

  1. The ray goes down h Ė y pixels when it travels d pixels into the depth (z-axis).
  2. We say: Every d pixels, the ray goes down h Ė y pixels, so we can set up a ratio for the travelled distance / distance that the ray has travelled downwards. This ratio is: d / h - y. When we want to know how much our ray travels into depth, we need to multiply this ratio by h. Because it starts at h, we want to know when the ray travelled h pixels downwards from his starting point at y = h.
  3. We have now the formula: distance = d / (h - y) * h. Division by zero is impossible, so h - y can not be zero. This means that y can NEVER equal h.
  4. This would mean that h has to be more then 199 or less then zero (for screen 13). But there is another solution. You donít have to render the whole screen. We can simply start at h + 1 and then go on until 199. You can also try to add more then one to avoid the worst aliasing problems, for example h + 5 or so. Or we can simply check if y = h, and if itís so, donít render that scanline.

A good value for both d and h is 100, but experiment a bit with these, so that you know what effects appear when you choose bigger or smaller values for d or h.

Now we need on which distance to check, we only have to interpolate between the two points. I like to make a LUT for the distance of the rays. To make a LUT, I use this code:

DIM length(h + 1 TO 199)
FOR y = h + 1 TO 199
  length(y) = (h - y) / d * h
NEXT

When I write (the important part of) the whole procedure in code:

REM Set up mul constant
CONST pi = 22 / 7
CONST mul = 1 / 1280 * 2 * pi

CONST d = 100
CONST h = 100

FOR y = h + 1 TO 199
  REM Take points p1 and p2
  p1u = (COS((t - 160) * mul) * length(y))
  p1v = (SIN((t - 160) * mul) * length(y))
  p2u = (COS((t + 159) * mul) * length(y))
  p2v = (SIN((t + 159) * mul) * length(y))
  REM In the x-loop, interpolate from p1 to p2, and draw points
NEXT

I used the same LUT as I made above. Since the interpolating is quite simple, I wonít explain that in detail, but I have a very good trick for you. Use fixed point math with integers. Use as scale value 512. This crashes when you the program in the IDE, but runs so much faster when you compile it! I got it running like ten times faster then with floating point! If you donít know what fixed point math is, search some tutorial on it. This one is very good (scroll down to the bottom part).

Example program

This is a floormapping example that Iíve made. I think it isnít optimized that much, but it runs very fast like this. I was first using long integers, but I found out that normal integers were like 10 times faster, so I decided to use those. The program wonít run in the IDE due an overflow error. You donít have this in the compiled program. If you want to run it in the IDE, Iíve made a DEFLNG A-Z line, which makes all undefined variables log integers. It is commented now, but when you make the DEFINT A-Z a comment (place Ď or REM before it), and uncomment DEFLNG A-Z (remove the Ď), it runs fine (but very slow). I made a LUT for the distance of the rays, because the height stays the same.

DECLARE SUB ffix ()
CLS
SCREEN 13

REM Comment this line if you want to run in the IDE
DEFINT A-Z

REM And uncomment this line
'DEFLNG A-Z

REM Remove this if you have troubles with it. It speed s up floating point math.
ffix

FOR c = 0 TO 255
  OUT &H3C8, c
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
  OUT &H3C9, c / 255 * 63
NEXT
COLOR 255

DIM tex(127, 127)
FOR x = 0 TO 127
  FOR y = 0 TO 127
    tex(x, y) = 255 - SQR((x - 63.5) ^ 2 + (y - 63.5) ^ 2) / 63 * 255
    IF tex(x, y) < 0 THEN tex(x, y) = 0
  NEXT
NEXT


CONST d = 100
CONST h = 100

CONST scale = 512
DIM length(105 TO 199)
FOR y = 105 TO 199
  length(y) = d * h \ (h - y)
NEXT

CONST mul = 1 / 1280 * 44 / 7
t1! = TIMER
FOR t = 0 TO 1280
yp = 2200
FOR y = 105 TO 199
  DEF SEG = &HA000 + yp
  yp = yp + 20
  p1u = (COS((t - 160) * mul) * length(y))
  p1v = (SIN((t - 160) * mul) * length(y))
  p2u = (COS((t + 159) * mul) * length(y))
  p2v = (SIN((t + 159) * mul) * length(y))
  tu = p1u * scale
  uadd = (p2u - p1u) / 320 * scale
  tv = p1v * scale
  vadd = (p2v - p1v) / 320 * scale
  FOR x = 0 TO 319
    u = (tu \ scale + t) AND 127
    v = (tv \ scale + t) AND 127
    POKE x, tex(u, v)
    tu = tu + uadd
    tv = tv + vadd
  NEXT
NEXT
IF INKEY$ <> "" THEN END
NEXT
t2! = TIMER
DEF SEG
PRINT 1280 / (t2! - t1!)
SLEEP
END

Conclusion

I hoped you like this tutorial and everything was clear. I could explain some more on interpolation but most programmers know how to do that and there are very good tutorials for it available. If youíre really having troubles with it, you may want to use my source, for everything that you donít understand. That is good, as long as you donít directly copy anything around it.

You might want to try some other things, such as changing height of the player and looking up and down. I donít know how you can look up and down, but when (I can better say: Ďifí) I find out how to do that, I will write another tutorial.

I have to say thanks to Eclipzer and Relsoft who got me interested in floormapping and Eclipzer also for helping me with it.

Greets,

Codemss



Angles in 2D environment and artificial smarts based on them - Part II

Written by Lachie Dazdarian (Semptember, 2007)

Introduction

Ok then. In the previous edition of this tutorial we stopped after creating an artificial smart algorithm which controlled multiple objects that followed around the screen an object controlled by the user. This was happening in a top-down 360į rotating environment, Star Control style.

What I promised last time to do is to show you how to implement basic scrolling (among other things) to this simple educational engine. So let's get busy!

Part One

Now how does one implement scrolling? Well, relatively easy, especially in our case as we don't feature tiled background or something like that only moving objects. Scrolling is simply implemented by deducting cameraX from all x coordinates used to draw our objects with drawing primitives, and cameraY from all y coordinates. cameraX being a variable that flags the X position of the camera and cameraY being a variable that flags the Y position of the camera (both setting the upper-left corner of the camera). Just observe this code snippet:

LINE (mainx-cameraX, mainy-cameraY)-(mainx-cameraX+sin(anglerad)*20,mainy-cameraY-cos(anglerad)*20), RGB(200, 0, 0)
CIRCLE (mainx-cameraX, mainy-cameraY), 3, RGB(200, 0, 0)

Now what have we accomplished with that? Well nothing if cameraX and cameraY are 0 and/or don't change in the program. But if we "tell" the camera to lock onto the player's object, we'll get the desired result. The code that does this is as follows:

cameraX = mainx - screenmidx
cameraY = mainy - screenmidy

Where screenmidx is the half width of the screen, while screenmidy is the half height of the screen, both of which we need to set (declare) before. Why do we need to use these variables? Allow me illustrate the entire problem with a nice...illustration.


Picture 1

As you see the camera is the size of the screen, meanining, it captures from (cameraX, cameraY) to (cameraX + screenwidth, cameraY + screenheight). Everything within this area will be visible. How come? Let's imagine that player's position is (2200, 3000). When will player's object be visible on the screen? When cameraX < 2200 and camerax + screenwidth > 2200, and when cameraY < 3000 and cameraY + screenheight > 3000. For example, if the screen resolution is 640*480 and camera position is (2000, 2700), it will "capture" from (2000, 2700) to (2640, 3180). The player's object will be drawn on (mainx - cameraX, mainy - cameraY) or (2200-2000, 3000-2700), which is (200,300), fully inside the visible screen. And that's the "trick" of scrolling in its essence.

In more graphics intensive games it is recommended you skip drawing of objects inside the map that are off the camera view, meaning in situations when object.x Ė camerax or object.y Ė cameray are less than 0 or above screenwidth/screenheight. You can do that with few simple IF clauses.

When telling the camera to lock onto an object and keep it in the center of the screen we need to use screenmidx and screenmidy because the CENTER of the camera is in (cameraX + screenmidx, cameraY + screenmid). In some situations you might have different requirements and might prefer camera not always centering the player's object. A somewhat different story is camera following (tracking) the player's or any other object (moving with slower speed than the tracked object), or moving using some path set with a script. Later on that.

I recommend putting these constants on the beginning of your program:

const screenwidth = 640
const screenheight = 480
const screenmidx = screenwidth/2
const screenmidy = screenheight/2

Where screenwidth and screenheight will correspond with the chosen screen resolution. After that you can simply set the screen resolution with:


SCREENRES screenwidth, screenheight, 32, 2, GFX_FULLSCREEN

cameraX and cameraY need to be declared as SHARED variables. Also, you need to declare two more variables, MapXMax and MapYMax, representing the size of the playfield (vertically and horizontally). In the following version of the engine I've set the player's starting position to (1000, 1000), playfield size to 2000*2000 (MapXMax = 2000; MapYMax = 2000), and randomized the computer controlled objects somewhere in the middle of the playfield (around the starting position of the player).

To keep the camera within the playfield we need to limit its movement. After...

cameraX = mainx - screenmidx
cameraY = mainy - screenmidy

...we need to place the following code:

IF cameraX < 0 THEN cameraX = 0
IF cameraY < 0 THEN cameraY = 0
IF cameraX > MapXMax - screenwidth THEN cameraX = MapXMax - screenwidth
IF cameraY > MapYMax - screenheight THEN cameraY = MapYMax - screenheight

The first two lines should be clear as cameraX and cameraY flag the upper-left corner of the very camera. The second two lines keep the camera not going off of the right and bottom edge of the map. If you observe picture 1 once more, everything should be clear. Since the bottom-right corner of the map is on (cameraX + screenwidth, cameraY + screenheight) this very coordinate must never cross the map edge (determined with MapXMax and MapYMax). screenwidth and screenwidth are placed on the right side of the two equations as we are reseting cameraX and cameraY, while MapXMax and MapYMax are always constant.

The only other piece of code I added is the one that draws the map edges so you can more easily visualize where the playfield ends. They are nothing but lines drawn around the map borders. This is the code:

LINE (MapXMax-1-camerax, MapYMax-1-cameray)-(0-camerax,MapYMax-1-cameray), RGB(255,255,255)
LINE (MapXMax-1-camerax, 0-cameray)-(MapXMax-1-camerax,MapYMax-1-cameray), RGB(255,255,255)
LINE (0-camerax, 0-cameray)-(MapXMax-1-camerax,0-cameray), RGB(255,255,255)
LINE (0-camerax, MapYMax-1-cameray)-(0-camerax,0-cameray), RGB(255,255,255)

Which I placed above the DrawProjectiles call. Once more, everywhere where LINE and DRAW statements were used to draw objects that appear INSIDE the playfield x and y coordinates were deducted by cameraX and cameraY.

The altered source code: codever3.txt

What about different camera movement? I'll show you how to implement "dragging camera" effect, where the camera moves with slower speed that the object it is tracking. For this you need to declare the following variables with SINGLE precision, and change cameraX and cameraY to same.

DIM SHARED AS SINGLE cameraX, cameraY
DIM SHARED AS SINGLE camera_xinertia, camera_yinertia
DIM SHARED AS SINGLE camera_acceleration, camera_maxinertia
DIM SHARED AS SINGLE Friction

How will the camera move in this case? With inertia! When, for example, the object that the camera is following is right from the camera, camera's X inertia will increase by camera's acceleration. In each loop the absolute value of camera's inertia will decrease (so it would stop when there is no movement) as the result of emulated friction, so the value of Friction needs to be less that camera_acceleration. Both x and y inertia mustn't become higher in absolute value than the maximum camera's inertia as the camera wouldn't move too fast and appear bouncy. This means that inertia (in both dimensions) will span from - max inertia to + max inertia.

I picked the following camera values, but you can play with them to get different results (put this with other variable initiations):

camera_maxinertia = 3
camera_acceleration = 0.4
Friction = 0.2

To move the camera in this manner replace...

cameraX = mainx - screenmidx
cameraY = mainy - screenmidy

..with:

' Update camera. Increase camera's X or Y inertia according to the
' position of the player's object.
IF cameraX < mainx - screenmidx THEN camera_xinertia = camera_xinertia + camera_acceleration
IF cameraX > mainx - screenmidx THEN camera_xinertia = camera_xinertia - camera_acceleration
IF cameraY < mainy - screenmidy THEN camera_yinertia = camera_yinertia + camera_acceleration
IF cameraY > mainy - screenmidy THEN camera_yinertia = camera_yinertia - camera_acceleration

' Keep the x or y inertia under maximum inertia value.
IF camera_xinertia > camera_maxinertia THEN camera_xinertia = camera_xinertia - camera_acceleration
IF camera_xinertia < -camera_maxinertia THEN camera_xinertia = camera_xinertia + camera_acceleration
IF camera_yinertia > camera_maxinertia THEN camera_yinertia = camera_yinertia - camera_acceleration
IF camera_yinertia < -camera_maxinertia THEN camera_yinertia = camera_yinertia + camera_acceleration

' Reduce camera's inertia by Friction.
IF camera_xinertia <> 0 and camera_xinertia > 0 THEN camera_xinertia = camera_xinertia - Friction
IF camera_xinertia <> 0 and camera_xinertia < 0 THEN camera_xinertia = camera_xinertia + Friction
IF camera_yinertia <> 0 and camera_yinertia > 0 THEN camera_yinertia = camera_yinertia - Friction
IF camera_yinertia <> 0 and camera_yinertia < 0 THEN camera_yinertia = camera_yinertia + Friction

' Move the camera according to current inertia.
cameraX = cameraX + camera_xinertia
cameraY = cameraY + camera_yinertia

The code is pretty much self-explanatory. Note how the camera is actually moved (last two lines of code).

In the continuation of this text I'll use the original "lock onto target" camera movement.

So now after we implemented scrolling and set the playfield size we should add few more constraints to the player's and computer controlled objects, not allowing them to pass the map edges. I personally don't recommend such constraints as they are ugly and somewhat annoying to players. There is a better solution. Make the map circular. Meaning, when the player passes the bottom of the map he should appear on its top. This movement can be done continuously which is more difficult to code, or with a clear break (warping-like jumps). For this to work properly you need to instruct the artificial smarts to "see" the map edges and follow the player's ship ACROSS the map edges, which is again somewhat tricky to code. I won't go into this in this tutorial as this is something you should be able to code after reading this tutorial and would unnecessarily complicate this lesson. Still, if I receive more requests regarding circular playfield movement, I'll whip up another issue of this series.

To limit the movement of the player's object place the following code in the IF clause that moves the player if he pushes the up arrow key:

IF MULTIKEY(SC_UP) THEN 
mainx = mainx + sin(anglerad)*mainspeed
mainy = mainy - cos(anglerad)*mainspeed
IF mainx < 3 OR mainx > MapXMax - 3 THEN mainx = mainx - sin(anglerad)*mainspeed
IF mainy < 3 OR mainy > MapYMax - 3 THEN mainy = mainy + cos(anglerad)*mainspeed
END IF

Simple as that. This code prevents the player's object to pass the invisible borders 3 pixels away from the actual map borders. This depends on the size of your object/sprite and where its center is, so you will have to modify the previous code according to this. For example, if the size of the sprite representing the player is 20*20 and mainx and mainy flag the upper-right corner of it (and not its center like in our case), you would limit its movement to the right and bottom with mainx > MapXMax - 20 and mainy > MapYMax Ė 20, while its movement to the left and up with mainx < 0 and mainy < 0.

To limit the movement of the computer controlled objects we should do the same thing. The first two lines move the current computer controlled object and they are present from the first issue of this tutorial. The second two lines limit its movement.

CPUobject(countCPUobj).X = CPUobject(countCPUobj).X + sin(CPUobject(countCPUobj).AngleRad)*CPUobject(countCPUobj).Speed
CPUobject(countCPUobj).Y = CPUobject(countCPUobj).Y - cos(CPUobject(countCPUobj).AngleRad)*CPUobject(countCPUobj).Speed
IF CPUobject(countCPUobj).X < 3 OR CPUobject(countCPUobj).X > MapXMax - 3 THEN CPUobject(countCPUobj).X = CPUobject(countCPUobj).X - sin(CPUobject(countCPUobj).AngleRad)*CPUobject(countCPUobj).Speed
IF CPUobject(countCPUobj).Y < 3 OR CPUobject(countCPUobj).Y > MapYMax - 3 THEN CPUobject(countCPUobj).Y = CPUobject(countCPUobj).Y + cos(CPUobject(countCPUobj).AngleRad)*CPUobject(countCPUobj).Speed

The remaining things I'm going to show you are just extra candy not directly related to scrolling. These include stars and planets in the playfield, smarter artificial smarts, and homing missiles.

Part Two

Let's start with adding stars and planets into our playfield.

First, let's declare the number of stars in the playfield with a constant. I picked 1000 stars.

const numofstars = 1000

Let's declare stars with ObjTyp user defined type (the one used with computer controlled objects), because it contains all the variables we need (x, y and Typ). Place the following code with the rest of variable declarations:

DIM SHARED Star(numofstars) AS ObjType

On the place where we initiate the position of computer controlled objects we should randomize the positions of our stars and their type (so that not all stars would look the same). Use the following code:

FOR initstar AS INTEGER = 1 TO numofstars
    Star(initstar).X = INT(RND * 2000) + 1
    Star(initstar).Y = INT(RND * 2000) + 1
    Star(initstar).Typ = INT(RND * 6) + 1
NEXT initstar

We need now to construct a subroutine where the stars will be drawn. It's a rather simple one. Check the following code (put it at the end of the current code):

SUB DrawStars ()
 
' Loop through stars and draw them according to their type.
' Type 5 and type 6 are larger stars, and thus require more
' drawing primitives to be drawn.
FOR countstar AS INTEGER = 1 TO numofstars
    
    SELECT CASE Star(countstar).Typ
    CASE 1
        PSET (Star(countstar).X-cameraX, Star(countstar).Y-cameraY), RGB(255,255,255)
    CASE 2
        PSET (Star(countstar).X-cameraX, Star(countstar).Y-cameraY), RGB(205,205,205)
    CASE 3
        PSET (Star(countstar).X-cameraX, Star(countstar).Y-cameraY), RGB(100,100,100)
    CASE 4
        PSET (Star(countstar).X-cameraX, Star(countstar).Y-cameraY), RGB(150,150,150)
    CASE 5
        LINE (Star(countstar).X-1-cameraX, Star(countstar).Y-cameraY)-(Star(countstar).X+1-cameraX, Star(countstar).Y-cameraY), RGB(90,90,90)
        LINE (Star(countstar).X-cameraX, Star(countstar).Y-1-cameraY)-(Star(countstar).X-cameraX, Star(countstar).Y+1-cameraY), RGB(90,90,90)
        PSET (Star(countstar).X-cameraX, Star(countstar).Y-cameraY), RGB(250,250,250)
    CASE 6
        LINE (Star(countstar).X-1-cameraX, Star(countstar).Y-cameraY)-(Star(countstar).X+1-cameraX, Star(countstar).Y-cameraY), RGB(50,50,50)
        LINE (Star(countstar).X-cameraX, Star(countstar).Y-1-cameraY)-(Star(countstar).X-cameraX, Star(countstar).Y+1-cameraY), RGB(50,50,50)
        PSET (Star(countstar).X-cameraX, Star(countstar).Y-cameraY), RGB(170,170,170)
    END SELECT
    
NEXT countstar
    
END SUB

Inside the subroutine we have a FOR loop that loops through all the stars and draws them on their appropriate positions according to their type. Note the cameraX and cameraY variables. Everything draw within the playfield must be drawn using cameraX and cameraY. Declare this subroutine with the remaining subroutine declarations and call the subroutine after CLS in the main loop simply with:

DrawStars

Almost on the same way the planets should be drawn, we'll just have less of them in the playfield. I've chosen only to have 4 planets it the playfield, and 3 types of them.

Declare this constant:

const numofplanets = 4

Declare our planets variable:

DIM SHARED Planet(numofplanets) AS ObjType

Initiate our planets with:

FOR initplanet AS INTEGER = 1 TO numofplanets
    Planet(initplanet).X = INT(RND * 1800) + 100
    Planet(initplanet).Y = INT(RND * 1800) + 100
    Planet(initplanet).Typ = INT(RND * 3) + 1
NEXT initplanet

Construct a subroutine that draws our planets as shown:

SUB DrawPlanets ()
 
' Loop through planets and draw them according to their type.
FOR countplanet AS INTEGER = 1 TO numofplanets
    
    SELECT CASE Planet(countplanet).Typ
    CASE 1
        CIRCLE (Planet(countplanet).X-cameraX, Planet(countplanet).Y-cameraY), 20, RGB(168,24,24),,,,F
        CIRCLE (Planet(countplanet).X-cameraX, Planet(countplanet).Y-cameraY), 20, RGB(148,4,4)
    CASE 2
        CIRCLE (Planet(countplanet).X-cameraX, Planet(countplanet).Y-cameraY), 40, RGB(255,255,128),,,,F
        CIRCLE (Planet(countplanet).X-cameraX, Planet(countplanet).Y-cameraY), 40, RGB(205,205,88)
    CASE 3
        CIRCLE (Planet(countplanet).X-cameraX, Planet(countplanet).Y-cameraY), 25, RGB(0,192,0),,,,F
        CIRCLE (Planet(countplanet).X-cameraX, Planet(countplanet).Y-cameraY), 25, RGB(0,152,0)
    END SELECT
    
NEXT countplanet
    
END SUB

Declare the subroutine and call it after DrawStars call (since we want to draw planets OVER stars).

The entire code so far: codever4.txt

Compile it and you should get something like this:


Screenshot 1

Neat, eh?

So what's next? Let's implement somewhat more complex artificial smarts. Here we go.

I'll implement only two main types of artificial smarts as we want to keep this simple. One will travel between two points and only follow the player if the player is very near it. The second will attack the player and exchange between attack and run modes. I should just say that these artificial smarts are only for educational purposes. I'm teaching you the concepts of creating artificial smarts, not how to create GOOD artificial smarts. This is up to you and is what will eventually make your game better that a similar one.

For the purpose of the extended artificial smarts code we need to input two more variables in the ObjTyp user defined type. These are ASMode that will flag the main AS mode of a ship, and ASSubMode that will flag the sub mode of a specific AS (like if the ship is in run or attack mode). The new ObjTyp user defined type should look like this:

TYPE ObjType
X             AS SINGLE   ' Used to flag object's x position.
Y             AS SINGLE   ' Used to flag object's y position.
AngleDeg      AS INTEGER  ' Used to flag object's angle in degrees.
AngleRad      AS SINGLE   ' Used to flag object's angle in radians.
Speed         AS SINGLE   ' Used to flag object's speed.
RotationSpeed AS SINGLE   ' Used to flag object's rotation speed
Active        AS INTEGER  ' Used to flag object's status
ActiveTime    AS INTEGER  ' Use to expire object's activity (once we activate it).
Typ    AS INTEGER         ' Used to flag type of the object (if we want to
                          ' have more kinds of the same object -> different
                          ' ships, projectiles, etc.).
ASMode        AS INTEGER  ' Used to flag the artificial smart type of a ship.
ASSubMode     AS INTEGER  ' Used to flag the artificial smart sub mode of a ship (attacking, running, etc.)
END TYPE

Also, it is recommended we declare 3 extra variables (as SHARED and INTEGER) named targetX, targetY and ASdirection.

targetx and targety will flag the target that the computer controlled object should follow or run away from, and using these two variables we can change the behavior of computer controlled objects quite easily. In one word, this gives us flexibility. The third variable, ASdirection, will be used to change the rotation of the computer controlled object when tracking a target, meaning, will it rotate toward or away from that target.

Now let's code the first artificial smart (ASMode = 1), the one traveling from one point to another. It will have two sub modes. When ASSubMode = 1 then it will be going to toward point A, while when ASSubMode = 2 then it will be going toward point B. But the AS will travel to any of these points only if the player's ship is not near enough (80 pixels away in both directions).

We should first alter the beginning of our AS code by implementing the targetX and targetY variables:

' If ASMode is true apply the artificial smart code on the current CPU controlled object
    IF ASMode = TRUE THEN
        
        ' By default the target is player's object.
        targetX = mainx
        targetY = mainy
        ' By default computer controlled object rotates TOWARD
        ' the target.
        ASdirection = 1

After this code put the the following code:

        ' If the computer controlled object's ASMode = 1 and
        ' the player's object is not near him travel to point A (120, 120)
        ' or point B (1700,400) according to ASSubMode.
        IF CPUobject(countCPUobj).ASMode = 1 THEN
            IF ABS(CPUobject(countCPUobj).X-mainx)>80 OR ABS(CPUobject(countCPUobj).Y-mainy)>80 THEN
                IF CPUobject(countCPUobj).ASSubMode = 1 THEN
                    targetX = 120
                    targetY = 120
                END IF
                IF CPUobject(countCPUobj).ASSubMode = 2 THEN 
                    targetX = 1700
                    targetY = 400
                END IF
            ' Change AS sub mode if reached one or the other point.
            IF ABS(CPUobject(countCPUobj).X-targetX)<20 AND ABS(CPUobject(countCPUobj).Y-targetY)<20 THEN
                IF CPUobject(countCPUobj).ASSubMode = 1 THEN 
                    CPUobject(countCPUobj).ASSubMode = 2
                ELSE
                    CPUobject(countCPUobj).ASSubMode = 1
                END IF
            END IF
            END IF
        END IF

The previous code should be self-explanatory. Just have in mind you can play with this AS by adding more AS sub modes (travel points) and by changing the coordinates of these points. And note how this code is skipped if the player's object is less than 80 pixels away in both directions. In that case the computer controlled object simply skips to the usual AS that tracks the player (default target).

Do not forget to alter the resultanglerad equation since now it will be calculated according to targetX and targetY.


resultanglerad = ATAN2((-1)*(targetY-CPUobject(countCPUobj).Y),(targetX-CPUobject(countCPUobj).X))

Let's now do the second artificial smart. This one will employ an attack and run tactic. It will engage the player until very near it after which it will switch to run mode. In run mode it will run away from the player until far enough when it will switch back to attack mode. Switching between attack and run will be done using the ASdirection variable. We need to alter the equations that change the angle of the computer controlled object by multiplying RotationSpeed with ASdirection. For one of the four equations that need to be changed this would look like this:


IF (360-CPUobject(countCPUobj).AngleDeg+resultangledeg) >= (CPUobject(countCPUobj).AngleDeg-resultangledeg) THEN CPUobject(countCPUobj).AngleDeg = CPUobject(countCPUobj).AngleDeg - CPUobject(countCPUobj).RotationSpeed*ASdirection

So when ASdirection is 1 (the default behavior) the computer controlled object rotates toward the player. But when it's -1 the computer controlled object rotates away from the player, and if thrusting it moves away from the player. Put the following code after the first AS code:

        
        ' If the computer controlled object's ASMode = 2
        ' swap between attack (ASdirection = 1) and
        ' run modes (ASdirection = - 1).
        IF CPUobject(countCPUobj).ASMode = 2 THEN
            IF CPUobject(countCPUobj).ASSubMode = 1 THEN
                ' Attack mode. Swap to run mode if
                ' less than 50 pixels away in both directions
                ' from the target.
                ASDirection = 1
                IF ABS(CPUobject(countCPUobj).X-targetX)<50 AND ABS(CPUobject(countCPUobj).Y-targetY)<50 THEN CPUobject(countCPUobj).ASSubMode = 2
            END IF
            IF CPUobject(countCPUobj).ASSubMode = 2 THEN
                ' Run mode. Swap to attack mode if
                ' more than 200 pixels away in any direction
                ' from the target.
                ASDirection = -1
                IF ABS(CPUobject(countCPUobj).X-targetX)>200 OR ABS(CPUobject(countCPUobj).Y-targetY)<200 THEN CPUobject(countCPUobj).ASSubMode = 1
            END IF
        END IF

This code should also be self-explanatory. Again, you can play with this AS by changing the distances on which the computer controlled objects swaps from one mode to another. You can also add more juice to it by timing the run mode. For example, if the player decides to chase the running computer controlled object, it would be nice that this object stops running after a while and starts to attack again. But this is really a large thing to ponder on. Like I said, use this tutorial only to grasp to basic concept of AS creation.

I also recommend you to add a thrust or engage variable and connect it with the computer controlled objects' movement equations. For example, if you would want for the computer controlled object not to move, thrust would be 0. Otherwise it would be 1. Connected with the movement equations it would look like this:

CPUobject(countCPUobj).X = CPUobject(countCPUobj).X + sin(CPUobject(countCPUobj).AngleRad)*CPUobject(countCPUobj).Speed*thrust
        CPUobject(countCPUobj).Y = CPUobject(countCPUobj).Y - cos(CPUobject(countCPUobj).AngleRad)*CPUobject(countCPUobj).Speed*thrust

By using this variable with artificial smarts you can instruct computer controlled objects to stop in certain situations. Like if you would want them to approach the player, stop and start shooting.

For the AS code to work we need to initiate ASModes of our computer controlled objects and their default ASSubModes. I've decided that half of the computer controlled objects will use AS mode 1, while the other half will employ AS mode 2.

' We loop through our cpu controlled object and initiate
' their variables.
FOR initCPUobj AS INTEGER = 1 TO numofCPUobjects
    CPUobject(initCPUobj).X = INT(RND * 600) + 720 ' Randomize cpu object's position from 20 to 620
    CPUobject(initCPUobj).Y = INT(RND * 440) + 820 ' Randomize cpu object's position from 20 to 460
    CPUobject(initCPUobj).AngleDeg = INT(RND * 360) + 1 ' Randomize cpu object's angle from 1 to 360
    CPUobject(initCPUobj).AngleRad = (CPUobject(initCPUobj).AngleDeg*PI)/180
    CPUobject(initCPUobj).RotationSpeed = INT(RND * 2) + 2 ' Randomize cpu object's rotation speed from 2 to 3
    CPUobject(initCPUobj).Speed = INT(RND * 3) + 1 ' Randomize cpu object's rotation speed from 1 to 3
    CPUobject(initCPUobj).Active = TRUE ' All object active (alive) by default.
    CPUobject(initCPUobj).ASMode = 2
    IF initCPUobj < 11 THEN CPUobject(initCPUobj).ASMode = 1
    CPUobject(initCPUobj).ASSubMode = 1
NEXT initCPUobj

The entire code so far: codever5.txt

Phew. The AS is done. What remains is adding ability to shoot projectiles to computer controlled objects, implementing homing projectiles for the player, and replace those ugly circles with lines into rotating triangles.

Adding projectile shooting ability to computer controlled objects is relavively simple. First, we need to call InitiateProjectile sub inside the computer controlled objects' FOR loop on the appropriate location and if specific conditions are met. Just place the following code above the code that draws our computer controlled objects, inside the IF CPUobject(countCPUobj).Active = TRUE clause:

	IF CPUobject(countCPUobj).Reload = 0 AND ABS(CPUobject(countCPUobj).X-targetX)<120 AND ABS(CPUobject(countCPUobj).Y-targetY)<120 THEN
            CPUobject(countCPUobj).Reload = 35
            InitiateProjectile CPUobject(countCPUobj).X+sin(CPUobject(countCPUobj).AngleRad)*20,CPUobject(countCPUobj).Y-cos(CPUobject(countCPUobj).AngleRad)*20, CPUobject(countCPUobj).AngleRad, 5
        END IF
        IF CPUobject(countCPUobj).Reload > 0 THEN CPUobject(countCPUobj).Reload = CPUobject(countCPUobj).Reload - 1

It is very similar to the one that initiates a projectile when the player pushes space. In this code the computer controlled object will shoot a projectile if the player's object is less than 120 pixels away horizontally and vertically, and if the computer controlled object's weapon is reloaded. As you see I'm using the same reload method like with the player. Note I initiate projectile type 5 with computer controlled objects because I decided for projectile types above 4 to represent projectiles that "hurt" the player, while projectile types less than 5 "hurt" computer controlled objects. Don't forget to add the Reload variable in the ObjTyp custom defined type.

For this code to work we need to alter the InitiateProjectile and DrawProjectiles subroutines to "support" the new projectile type.

In the InitiateProjectile sub we should add this IF clause after the same one for projectile type 1:


	IF Projectile(initproj).Typ = 5 THEN Projectile(initproj).Speed = 3

We can do this differently if we, for example, want for projectile type 5 (while being slower that the player's object) to last shorter amount of time or perhaps longer. Feel free to use the Typ variable to set the desired characteristics of your projectiles, whatever they might be.

We should also alter the DrawProjectiles sub as it follows:

SUB DrawProjectiles ()
    
FOR countproj AS INTEGER = 1 TO numofprojectiles
    
    ' We loop through our projectiles and if an active one is
    ' found, we move and draw it.
    IF Projectile(countproj).ActiveTime > 0 THEN
    
        ' The next line is used to expire the projectile so it wouldn't
        ' be active infinitely. We can do this on different ways, like
        ' by deactivating an object once it passes the edge of screen.
        ' Still, this is a very handy way of setting the "life time" of an object.
        Projectile(countproj).ActiveTime = Projectile(countproj).ActiveTime - 1
        
        ' Projectiles are moved just like the main and computer controlled
        ' objects.
        Projectile(countproj).X = Projectile(countproj).X + sin(Projectile(countproj).AngleRad)*Projectile(countproj).Speed
        Projectile(countproj).Y = Projectile(countproj).Y - cos(Projectile(countproj).AngleRad)*Projectile(countproj).Speed
        
        ' According to projectile type, we draw it.
        SELECT CASE Projectile(countproj).Typ
        CASE 1
            LINE (Projectile(countproj).X-cameraX, Projectile(countproj).Y-cameraY)-(Projectile(countproj).X-cameraX+sin(Projectile(countproj).Anglerad)*3,Projectile(countproj).Y-cameraY-cos(Projectile(countproj).AngleRad)*3), RGB(192, 192, 0)
        CASE 5
            LINE (Projectile(countproj).X-cameraX, Projectile(countproj).Y-cameraY)-(Projectile(countproj).X-cameraX+sin(Projectile(countproj).Anglerad)*3,Projectile(countproj).Y-cameraY-cos(Projectile(countproj).AngleRad)*3), RGB(222, 10, 0)
        END SELECT
        
        ' The next FOR loop checks for collision with all the
        ' active computer controlled  objects, and if collision is
        ' preset (pixel distance check), we deactivate (kill) that 
        ' computer controlled object.
        FOR colcheckobj AS INTEGER = 1 TO numofCPUobjects
            
            ' If the current projectiles is less that 4 pixels horizontally
            ' and vertically to an computer controlled object, if the projectile
            ' type is less than 5 (a projectile shot by the player) diactivate
            ' that object and the projectile.
            IF (Projectile(countproj).Typ < 5 AND CPUObject(colcheckobj).Active = TRUE AND ABS(CPUObject(colcheckobj).X-Projectile(countproj).X) < 5 AND ABS(CPUObject(colcheckobj).Y-Projectile(countproj).Y) < 5) THEN
                ' Initiate some explosions (once you implement an explosion layer)
                ' Add score to player
                ' Etc.
                CPUObject(colcheckobj).Active = FALSE
                Projectile(countproj).ActiveTime = 0
            END IF
            ' If the current projectiles is less that 4 pixels horizontally
            ' and vertically from the player, if the projectile
            ' type is more than 4 (not player's projectile) reduce
            ' player's energy and diactivate the projectile.
            IF (Projectile(countproj).Typ > 4 AND ABS(mainx-Projectile(countproj).X) < 5 AND ABS(mainy-Projectile(countproj).Y) < 5) THEN
                ' Initiate some explosions (once you implement an explosion layer)
                ' Reduce player's energy
                ' "Kill" him if energy < 0
                ' Etc.
                Projectile(countproj).ActiveTime = 0
            END IF
        
        NEXT colcheckobj
        
    END IF
    
NEXT countproj
    
END SUB

I changed the drawing of projectiles (instead of IF clauses I used SELECT CASE and added drawing for projectile 5 (I set its color to red)) and separated projectile collision with the computer controlled objects from that with the player. Projectiles with Typ < 5 will harm the computer controlled objects, while projectiles with Typ > 4 will harm the player. For the purpose of this example engine the player can't be destroyed (its energy is not reduced).

The last thing related to weapons I'll do is implement a secondary weapon to the player - homing missiles. Homing missiles are rather simple to add into this code. All we need to do is add one more projectile type and implement the basic AS used with the computer controlled objects with it.

First, let's add code that will initiate projectile type 2 when the user pushes M. Place the following code after the IF clause that initiates projectile type 1:

IF MULTIKEY(SC_M) AND main_reload = 0 THEN
    main_reload = 20
    InitiateProjectile mainx+sin(anglerad)*20,mainy-cos(anglerad)*20, anglerad, 2
END IF

Nothing to say here but that I made for the homing missiles to reload slower. We could use here multiple reload variables (one for each weapon), but not to complicate let's leave it as it is.

We should now add another IF clause in the InitiateProjectile sub as follows:

	IF Projectile(initproj).Typ = 2 THEN 
            Projectile(initproj).Speed = 4
            Projectile(initproj).RotationSpeed = 4
            IF MULTIKEY(SC_UP) THEN Projectile(initproj).Speed =  Projectile(initproj).Speed + mainspeed
        END IF

Note that I need to initiate a rotation speed for this projectile as it will have to rotate (change its angle) in order to follow the object it is currently tracking.

Inside the DrawProjectiles sub we need to add the code that will draw this new projectile:

	SELECT CASE Projectile(countproj).Typ
        CASE 1
            LINE (Projectile(countproj).X-cameraX, Projectile(countproj).Y-cameraY)-(Projectile(countproj).X-cameraX+sin(Projectile(countproj).Anglerad)*3,Projectile(countproj).Y-cameraY-cos(Projectile(countproj).AngleRad)*3), RGB(192, 192, 0)
        CASE 2
            LINE (Projectile(countproj).X-cameraX, Projectile(countproj).Y-cameraY)-(Projectile(countproj).X-cameraX+sin(Projectile(countproj).Anglerad)*3,Projectile(countproj).Y-cameraY-cos(Projectile(countproj).AngleRad)*3), RGB(200, 200, 200)
            LINE (Projectile(countproj).X+sin(Projectile(countproj).Anglerad)*2-cameraX, Projectile(countproj).Y-cos(Projectile(countproj).AngleRad)*2-cameraY)-(Projectile(countproj).X-cameraX+sin(Projectile(countproj).Anglerad)*3,Projectile(countproj).Y-cameraY-cos(Projectile(countproj).AngleRad)*3), RGB(222, 0, 0)
        CASE 5
            LINE (Projectile(countproj).X-cameraX, Projectile(countproj).Y-cameraY)-(Projectile(countproj).X-cameraX+sin(Projectile(countproj).Anglerad)*3,Projectile(countproj).Y-cameraY-cos(Projectile(countproj).AngleRad)*3), RGB(222, 10, 0)
        END SELECT

Inside the FOR colcheckobj loop we need to place the following code which is basically the artificial smart for our homing missile:

	    ' If the current projectile is typ 2 (homing missile)
            ' instruct it to follow the closest computer controlled 
	    ' object. Same artificial smart code as with computer 
            ' controlled objects.
            IF (Projectile(countproj).Typ = 2 AND CPUObject(colcheckobj).Active = TRUE AND ABS(CPUObject(colcheckobj).X-Projectile(countproj).X) < 100 AND ABS(CPUObject(colcheckobj).Y-Projectile(countproj).Y) < 100) THEN
                
                ' Missile always moving TOWARD the target and
                ' target always the closest computer controlled
                ' object.
                ASdirection = 1
                targetY = CPUObject(colcheckobj).Y
                targetX = CPUObject(colcheckobj).X
        
                resultanglerad = ATAN2((-1)*(targetY-Projectile(countproj).Y),(targetX-Projectile(countproj).X))
        
                resultanglerad = PI/2 - resultanglerad
                IF resultanglerad < 0 THEN resultanglerad = resultanglerad + 2*PI 
        
                resultangledeg = (resultanglerad*180)/PI
                
                Projectile(countproj).AngleDeg = (Projectile(countproj).AngleRad*180)/PI
                
                IF Projectile(countproj).AngleDeg > resultangledeg THEN
                    IF (360-Projectile(countproj).AngleDeg+resultangledeg) >= (Projectile(countproj).AngleDeg-resultangledeg) THEN Projectile(countproj).AngleDeg = Projectile(countproj).AngleDeg - Projectile(countproj).RotationSpeed*ASdirection
                    IF (360-Projectile(countproj).AngleDeg+resultangledeg) < (Projectile(countproj).AngleDeg-resultangledeg) THEN Projectile(countproj).AngleDeg = Projectile(countproj).AngleDeg + Projectile(countproj).RotationSpeed*ASdirection
                END IF
                IF Projectile(countproj).AngleDeg < resultangledeg THEN
                    IF (360-resultangledeg+Projectile(countproj).AngleDeg) >= (resultangledeg-Projectile(countproj).AngleDeg) THEN Projectile(countproj).AngleDeg = Projectile(countproj).AngleDeg + Projectile(countproj).RotationSpeed*ASdirection
                    IF (360-resultangledeg+Projectile(countproj).AngleDeg) < (resultangledeg-Projectile(countproj).AngleDeg) THEN Projectile(countproj).AngleDeg = Projectile(countproj).AngleDeg - Projectile(countproj).RotationSpeed*ASdirection
                END IF
                IF Projectile(countproj).AngleDeg<0 THEN Projectile(countproj).AngleDeg=Projectile(countproj).AngleDeg+360
                IF Projectile(countproj).AngleDeg>359 THEN Projectile(countproj).AngleDeg=Projectile(countproj).AngleDeg-360
        
                Projectile(countproj).AngleRad = (Projectile(countproj).AngleDeg*PI)/180
		EXIT FOR

            END IF

Note the EXIT FOR which is used because I need to exit the FOR loop once a target is found. Guess what would happen if the homing projectile was flying in between two computer controlled objects and there was no EXIT FOR. The projectile would try to follow both of the objects and on the end wouldn't follow either one (rotation in both directions would nullify each other).

The entire code so far: codever6.txt

Play with it, and don't forget to push M to fire homing missiles.

I'm not sure how many of you are still with me, but we have one more final thing to do. We'll replace those ugly circles with lines into pretty rotating triangles. How? Simple, with some basic trigonometry introduced in the first lesson.

I will explain two methods of drawing rotated objects (consisted of lines connected with points) in 2D space.

Both include setting the center of your object which doesn't have to be the geometrical center, although with the first method (polar) it is highly recommended you choose the geometrical center. The object center is just a point from which you calculate all the other points of the object relative to the center point. In our case we have a triangle. On the following picture I've set the center of the object in the geometrical center. Points 1, 2 and 3 define the triangle.


Picture 2

In the polar method we'll use polar coordinate system logic to rotate these 3 points. We'll define our 3 points with a radius and an angle that these points close with the center and vertical line going from the center toward up (zero angle axis in our coordinate system - lesson 1). In our case point 1 is determined with the angle of 0 (or 2*PI) and radius1. Point 2 is determined with the angle of PI*3/4 (135į) and radius2, while point 3 is determined with the angle of PI*5/4 (225į) and radius3. Also, in this specific case radius1 = radius2 = radius3, but often this is not the case as you will probably want to rotate other things than equilateral triangles.

The following equations should be self-explanatory and result from the above method. All angles are in radias, and object_angle is basically the angle in radians by which the object is rotated from the 0 angle position.

Position of point 1 before rotation (blue triangle): 
centerx + sin(0) * radius1, centery - cos(0) * radius1
Position of point 2 before rotation: 
centerx + sin(0+PI*3/4) * radius2, centery - cos(0+PI*3/4) * radius2
Position of point 3 before rotation:
centerx + sin(0*5/4) * radius2, centery - cos(0+PI*5/4)* radius2

Position of point 1 after rotation (red triangle): 
centerx + sin(object_angle) * radius1, centery - cos(object_angle) * radius1
Position of point 2 after rotation: 
centerx + sin(object_angle+PI*3/4) * radius2, centery - cos(object_angle+PI*3/4) * radius2
Position of point 3 after rotation:
centerx + sin(object_angle*5/4) * radius2, centery - cos(object_angle+PI*5/4) * radius2

In our code for computer controlled objects it looks like this:

	LINE (CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad)*10,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad)*10)-(CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad+PI*3/4)*10,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad+PI*3/4)*10), RGB(2,117, 250)
        LINE (CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad+PI*3/4)*10,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad+PI*3/4)*10)-(CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad+PI*5/4)*10,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad+PI*5/4)*10), RGB(2,117, 250)
        LINE (CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad+PI*5/4)*10,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad+PI*5/4)*10)-(CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad)*10,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad)*10), RGB(2,117, 250)

The LINE statements connect the 3 triangle defining points. 10 is the chosen radius. Change it for smaller/bigger triangles.

The Cartesian method uses Cartesian coordinate system logic and I don't consider it suitable as the polar method for this problem, but I will show it for educational purposes. Observe the following picture which illustrates how to rotate a single point. We don't need to complicate this with more points as a line is nothing but two points connected.



Picture 3

If you observe the picture, after rotation we need to determine x2 and y2 by knowing the origin point (x1, y1) and the angle by which the object has rotated (object_angle). We need to break this problem in two parts.

We know how to rotate a point which is on y axis (for x = 0) when the rotation angle is 0. We did that in lesson 1 with the line showing the direction of ships. It's done by adding +sin(object_angle)*y_distance to x coordinate, and -cos(object_angle)*y_distance to y coordinate, where y_distance is the distance of the point from the x axis. When the point is not lying on the y axis (x <> 0) then during rotation we also need to add +cos(object_angle)*x_distance to x coordinate, and +sin(object_angle)*x_distance to y coordinate, where x_distance is the distance of the point from the y axis when the rotation angle is 0. So we would need to have this sort of formulae for coordinates:


(centerx + sin(object_angle)*y_distance + cos(object_angle)*y_distance, centery - cos(object_angle)*y_distance + sin(object_angle)*x_distance)

Yeah, scary. Have in mind x_distance is positive when the point is right from the center of the object, and negative when the point is left from the center of the object. y_distance is positive when the point is above the center, and negative when the point is below the center. In our case for the computer controlled objects this method would result in something like this (approximately):

	LINE (CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad)*10+cos(CPUobject(countCPUobj).AngleRad)*0, CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad)*10+sin(CPUobject(countCPUobj).AngleRad)*0)-(CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad)*-10+cos(CPUobject(countCPUobj).AngleRad)*-10,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad)*-10+sin(CPUobject(countCPUobj).AngleRad)*-10), RGB(2,117, 250)
        LINE (CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad)*-10+cos(CPUobject(countCPUobj).AngleRad)*-10, CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad)*-10+sin(CPUobject(countCPUobj).AngleRad)*-10)-(CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad)*-10+cos(CPUobject(countCPUobj).AngleRad)*10,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad)*-10+sin(CPUobject(countCPUobj).AngleRad)*10), RGB(2,117, 250)
        LINE (CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad)*-10+cos(CPUobject(countCPUobj).AngleRad)*10, CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad)*-10+sin(CPUobject(countCPUobj).AngleRad)*10)-(CPUobject(countCPUobj).X-cameraX+sin(CPUobject(countCPUobj).AngleRad)*10+cos(CPUobject(countCPUobj).AngleRad)*0,CPUobject(countCPUobj).Y-cameraY-cos(CPUobject(countCPUobj).AngleRad)*10+sin(CPUobject(countCPUobj).AngleRad)*0), RGB(2,117, 250)

Yikes! So I'm sure the choice is quite clear. Go with the polar method.

The final code: codever7.txt

If you compile the last code you should get something like this:



Screenshot 1

You'll notice the player's ship is somewhat different. Check the code to see how I draw it. Also, collision hit zones were changed (as the ships are now bigger) and the locations from which the projectiles are initiated.

Download the final example engine compiled: multi_object_rotation_engine.zip

If you prefer rotating sprites over rotating triangles and such, you might want to check D.J.Peters' MultiPut routine (multiput_nov06.bas), or counting_pine's unofficial release of the same routine (multiput_unofficial.bas).

And that would be it. I hope you learned something new. You are encouraged to expand on topics covered in this tutorial or come up with better solutions for some of the problems discussed here. I am sure many things in this example engine could have been done better, but all of this was to show you HOW to do certain things, and do not represent the best way to do them.

Until some other tutorial. I have few I want to write, but now I want to concentrate in rewriting and revising my older tutorials so they would compile in FB ver.0.18b.

Cheers!



Acceleration, Velocity, and Position

By Mentat

Position Ė Place in spaceÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖDistance

Velocity Ė Change of placeÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖDistance/Time

Acceleration Ė Rate of change of placeÖÖÖÖÖÖÖÖÖÖÖÖ..Distance/Time2

Three ideas. Now how to use them.

If youíre using objects in space (2d or 3d), they all have coordinates. So you can display that particular object.

Hereís Position Code:

I had to change around the code so it would be narrower. Such as letting B=A+1. And Iím sorry this Tutorial is so bland. I havenít had much to time to program and write tutorials.

Ď--------------------------------------------- START
ĎPosition.BAS

START:
SCREEN 12
CLS
Dim C(8,3)               Ď8 POINTS, 3 COORDINATES PER POINT
LET X=1
LET Y=2
LET Z=3

FOR POINTS = 1 TO 8
	FOR COORDINATES=1 TO 3
			READ C(POINTS, COORDINATES)
		NEXT COORDINATES
	NEXT POINTS

MAIN:
	FOR A = 1 TO 3
		LET B=A+1
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	LINE (C(4,X)/C(4,Z), C(4,Y)/C(4,Z))-(C(1,X)/C(1,Z),C(1,Y)/C(1,Z))
	FOR A=5 TO 7
		LET B=A+1
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	LINE (C(8,X)/C(8,Z), C(8,Y)/C(8,Z))-(C(5,X)/C(5,Z),C(5,Y)/C(5,Z))
	FOR A = 1 TO 4
		LET B=A+4
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	
	DO
		LET KEY$ = INKEY$
	LOOP WHILE KEY$=ĒĒ

STOP

Ď             X   Y    Z
DATA 040,075,001 
DATA 240,075,001
DATA 240,300,001
DATA 040,300,001
DATA 040,075,002
DATA 240,075,002
DATA 240,300,002
DATA 040,300,002

Ď-------------------------------------------------------------- END

And this should be FB and QB compatible. I checked and both worked.

What does the program do? In chronological order:

  1. Fixes the screen.
  2. Loads data to the cube array
  3. Prints cube. Looks messy but it isnít. All it does is go around.
  4. Sits around until the user presses a key.

Thatís about it. For simplicity and spaceís sake I left out vanishing point coordinates and such.

So that just prints a cube. So what? How can I make it move? Well, thatís called velocity. Velocity is a speed of an object in some direction.

So lets say x is you position, and it changes with respect to time.

(X m) dT = (X m/s )*(T s)+(Xo m)

If this shows up correctly (Iím using ASCII characters), then it still will look odd. All it says is that the change of position with respect to time is the old position (C meters) plus the distance rate (X meters per second) multiplied by time (T seconds). Itís just a proof and it really doesnít have much importance for velocity. But it will come into play with acceleration and jerk (change in acceleration). In terms of computers, youíll use pixels and not meters.

Donít run this at midnight unless you want youíre cube to disappear.

'--------------------------------------------- START
'VELOCITY.BAS

START:
SCREEN 12
CLS
Dim C(8,3)               '8 POINTS, 3 COORDINATES PER POINT
LET X=1
LET Y=2
LET Z=3

FOR PO = 1 TO 8
	FOR CO = 1 TO 3
			READ C(PO, CO)
		NEXT CO
	NEXT PO
    LET BTIMER = TIMER
    
MAIN:
DO	
	CLS
    LET KEY$ = INKEY$
	LET ATIMER = TIMER

	FOR PO = 1 TO 8
			LET C(PO, 1) = 10*(ATIMER - BTIMER) + C(PO,1)
	NEXT PO
    LET BTIMER=TIMER


	FOR A = 1 TO 3
		LET B=A+1
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	LINE (C(4,X)/C(4,Z), C(4,Y)/C(4,Z))-(C(1,X)/C(1,Z),C(1,Y)/C(1,Z))
	FOR A=5 TO 7
		LET B=A+1
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	LINE (C(8,X)/C(8,Z), C(8,Y)/C(8,Z))-(C(5,X)/C(5,Z),C(5,Y)/C(5,Z))
	FOR A = 1 TO 4
		LET B=A+4
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	
    SLEEP 1
LOOP WHILE KEY$=""

STOP

'     X   Y   Z
DATA 040,075,001 
DATA 240,075,001
DATA 240,300,001
DATA 040,300,001
DATA 040,075,002
DATA 240,075,002
DATA 240,300,002
DATA 040,300,002

'-------------------------------------------------------------- END

This too has been QB and FB tested.

So what did I do? Besides copying, pasting, and a little debugging, I had the cubeís X position change 10 pixels for each second. Note: this not the same as 10 pixels PER second, which could mean a pixel change per 1/10 of a second. What I did is every one second, the pixels change by 10, which is bumpier.

So how to change the velocity smoothly? Well, besides actually changing the velocity variable, thereís another way. Remember that handy little integrand? Well, itís coming back.

(X m/s)*(T s) + (Xo) dT = .5*(X m/s 2)*(T2 s)+(Xo m/s)*(T s)+ (Xo m)

Donít worry about the squiggly lines. To make things easier to read, Iíll simplify it to this:

P=.5*A*T2+V*T+X

Where:

This time I changed around the timing so it is elapsed from the programís start.

'--------------------------------------------- START
'Acceleration.BAS

START:
SCREEN 12
CLS
Dim C(8,3)               '8 POINTS, 3 COORDINATES PER POINT
LET X=1
LET Y=2
LET Z=3

FOR PO = 1 TO 8
		FOR CO = 1 TO 3
				READ C(PO, CO)
			NEXT CO
		NEXT PO
    	LET BTIMER = TIMER
    
    	LET A = .1    'RATE OF ACCELERATION
    	LET V = 0  'INITIAL VELOCITY
    
MAIN:
DO	
	CLS
    	LET KEY$ = INKEY$
	LET ATIMER = TIMER

	FOR PO = 1 TO 8
			LET T=ATIMER-BTIMER 
            LET C(PO, 1) = .5*(A*T*T)+(V*T)+C(PO,1)
	NEXT PO

	FOR A = 1 TO 3
		LET B=A+1
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	LINE (C(4,X)/C(4,Z), C(4,Y)/C(4,Z))-(C(1,X)/C(1,Z),C(1,Y)/C(1,Z))
	FOR A=5 TO 7
		LET B=A+1
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	LINE (C(8,X)/C(8,Z), C(8,Y)/C(8,Z))-(C(5,X)/C(5,Z),C(5,Y)/C(5,Z))
	FOR A = 1 TO 4
		LET B=A+4
		LINE (C(A,X)/C(A,Z), C(A,Y)/C(A,Z))-(C(B,X)/C(B,Z),C(B,Y)/C(B,Z))
	NEXT A
	
    SLEEP 1
LOOP WHILE KEY$=""

STOP

'     X   Y   Z
DATA 040,075,001 
DATA 240,075,001
DATA 240,300,001
DATA 040,300,001
DATA 040,075,002
DATA 240,075,002
DATA 240,300,002
DATA 040,300,002

'-------------------------------------------------------------- END

It speeds up. Notice how the velocity variable never changes, yet it still speeds up.

Well, thatís all for that. I could go on to Jerk and even higher, but they play a diminishingly smaller part. If youíre curious, the equation for jerk is:

P=(1/6)*J*T3+(1/2)*A*T2+V*T+X

Where:

As you can see, the fractions get smaller. Thatís why I stopped (the next one would be 1/24, then 1/120, then 1/720, and it goes on forever)



Final Word

That's all for this month's issue! Hope you enjoyed it on this wonderful All Hallow's Eve.

Submissions for Issue #26 are due by: November 15th

As always, email your submissions to qbexpress@gmail.com. We need tutorials, as always, as well as articles, editorials, rants / raves, GAME REVIEWS (we've been lacking in this department lately), NEWS BRIEFS, comics, and anything else you'd like to submit. As long as it's about QBasic, QuickBasic of FreeBasic, we'll publish it!

Until next time...END.

-Pete


Copyright © Pete Berg and contributors, 2007. All rights reserverd.
This design is based loosely on St.ndard Bea.er by Altherac.

QB Top 50 - The best QBasic and QuickBasic Sites    FreeBasic Top 50