Qbasic: the Magazine
08.14.99
Issue 12

Want to print? Use this version

Back issues are in the Archive

Go back to the main site

Download a zip version

Don't like frames? Kill 'em.

LOGIC g a t e
The beloved letter from the Editor
DATA p a t h
The Latest News and Game Info
THE a r r a y
Your Stuff has a new look
PRINT 'Anniversary!
It's been one year already! Let's look back.
VIEW b o x
The gallery of Qb's newest cred
PRINT '3d: Part VII
Alias continues to show you his voxel secrets
LEARN t h i s
Little bits of coder we know you'll use
LINE 'Darkdread Battles
DD explains semi-active RPG battles
OPEN 'Power Programming II
Seav's hints about Timing methods
DRAW 'Art Basics III
Gavan is an art god, and here's his newest tut
NEXT.i s s u e
A thrill-packed preview of next month!

 

Letter from the Editor

credits
Editor
zkman

Contributors
Gavan
Alias
MA*SNART
Darkdread
Seav

Website Host
quickbasic.com

Thanks to all who contributed news and info used in Data Path or Array. Special thanks to Sonicblue for temporarily hosting Qb:tm.

(c) 1999 Grand Scimitar Studios. No part of this document may be reproduced without expressed written consent from the editor. Donkeys don't fly. 36% of Senegal's ethnic breakup is Wolof (really). Props go out to all the 'migos who helped with this redesign. Oh, read this section every month, a veces I'll hide a contest for ya =)

Mon Dieu! The summer heat bakes us still, at least stateside, and, in an effort to stave off things like skin cancer, we at qb:tm have been workin' diligently on our new redesign of the mag; hopefully your favourite issue after our 2 month summer hiatus.

An 8 month old design on the internet is 8 months behind the rest of the world. Despite being the magazine for a legacy language, we are trying to ride the cutting edge in terms of look and readability. We're hoping this new look has accomplished that. But the dynamics of the internet, in our mind, do not simply mean more eye candy, or even faster-loading pages. Says Roger Black, "It's the content, stupid". In addition to LEARNthis, and the In-Box, we're going to add more new columns to the mag in coming months. Also, articles will more than likely be supplemented with sidebars and factoids.

In terms of "content" this month, we've got quite a nice assortment. Seav is back with a new Power Programming article for intermediate coders. Gavan, DA2's artist, has more art help for you tile and texture artists, this time on tiling backgrounds. Our 'migo Alias continues down the 3d road with some more 3d tutorial for you. Darkdread brings you a not-so-concise article on how to build an effective Final Fantasy-style battle engine. In addition to that, we've got quite a helping of news, game info, and quick cuts, and a cgi form for submitting a poll.

During the month, we'll bring ya a MOD II tut from Bill McDonald and Bloodknight's articles on making a real OS with 286 assembly.

But that's not all. This issue marks our 1 year Anniversary, and in the time we've printed more original articles than any legacy language magazine. Ever. To mark the occasion, I look back on the last year of qbasic, which last summer was marked by such quotes such as "I have big plans for a new library" posted on Angelo Mottola's old page.

It's been a great year for qbasic, our user base growing to one of it's all time peaks, and innovations growing from all segments (and offsets ^_^). I'm not positive how long the mag will remain and continue to publish, but we hoped we've helped you improve your code. If you like an article, or would like to request an article on something, feel free to contact the author or myself; we're always gracious to hear from our readers.

Au Revoir,

zkman

L
O
G
I
C
G
A
T
E
>>>Back to top<<<

 

qbasic Hot List

D
A
T
A
P
A
T
H
Neozones/Sonicblue Merge
Neozones, arguably one of the most well-known and respected qbasic sites, has merged with Sonicblue's sonicblue.com, to create a new basic mecca "Bluetek". Combining Sonicblue's cutting-edge design and cgi skills with the huge file library, helpful Ask Tek and Link sections, and popular Web Forum that were formerly at Neozones, the new site has, thus far, lived up to it's slogan "Basic just got better".

A recent webboard posting to explain the move claims that sonicblue.com and Neozones had long complemented each other in terms of the type of service they provide, and that the merger would allow less repitition and greater content from both sites.

 

Glib set to rock the QB World.
Glib, the name of the new library which combines aspects of the previously announced GSLib2 by TheBrain and Dash2 by Danny Gump, has begun work. The library is set to be a fully svga library with super-speedy assembly routines for scrolling, graphic manipulation, and text, among others. Although current obligations mean the library may not see the light of day for quite a few months, it is definitely the one to watch, and the first real library with a chance to become a "dqb-killer".

TheBrain is known for his work on Gslib, Peanut Patrol 2, and Puz, while Danny Gump of Virtuasoft is known for his Dash library and The Mystical Journey.

 

Westfront gaining publicity
Paul Allen Panks' (Dunric) seminal text-based qbasic adventure game, "Westfront PC" has gained acclaim not only from the qb community, but also from an international publication. Mikrobitti, the largest PC magazine in Finland, recently ran a short commentary about the game. This is arguably the most mainstream publicity for a qb game since Dark Ages was included on the PC Gamer CD.

 

But that's not the end for other libs...
Even whilst working on Glib, Danny Gump is continuing to progress on the 13h Dash library. A recent talk with Danny drug out info concerning the possibility of Glide support being added to Dash within the next couple of months. We're not sure how work is going on this prospect, but we'll bring you more info within the month.

DirectQB is also set to recieve it's next update, as we mentioned next issue, in terms of an IRQ Modem Protocol which will be added to the library in "version1.7". No word on when this will be finished, but we do know preliminary work on the function is complete.

 

Qb:tm joins Open Board
At the request of some longtime readers for the addition of a webboard to the site, we added the next best thing by joining the Open Board. This is a "community" webboard that is linked to from various sites including Entropy's, Pasco's, Seav's and a few others. Response is given to nearly any question on the board, and it's much less crowded that the more popular boards at EC and BlueTek, so give it a try today!

 

GamesBasic
A professional coding group led by James Hunter is working on a Windows-based programming language which is similar in many ways to Quickbasic. The IDE will be similar to Borland Delphi or Visual Basic, featuring RTF to highlight and color keywords, it will most likely support inline asm, and "nearly all" of the commands found in Quickbasic should be supported. More on this exciting project as it becomes available.

 

Alphx Coding Group
The Alphx Coding Group is looking to be one of the hottest programming groups in coming months. A quick scan of their Projects includes the Resurrected Sypharage, one of the nicest-looking qb games anywhere, a graphics library called LiquidGL which is "secretive" right now (but we promise next issue to bring ya info, no matter how hard we have to beg), and a Zelda-style adventure. But the most promising of all of these is a program called BasicX.

Although little information is available, we do know that BasicX is a protected-mode compiler that is to have "qbasic language at the speed of C++", and also svga and music built directly into the language.

 

Jpeg and TCP/IP
Dmitry Brant is continuing to prove that's he's one of qb's best coders with 2 recent projects. First is Build 2 of his JPEG displayer, which, although still not nearly as fast as the GIF displayers for obvious reasons, is now the fastest yet written in QB. On top of that, rumour says that he is working on a comprehensive set of TCP/IP routines in qb, another notoriously difficult piece of work.

 

Speakin' about good coders...
Eric Carr's, of Spinball fame, recent 3d engine has been turning a lot of heads recently, with a Polygon count much higher than we saw in Xeno or Bubble Fighters, as well as Dynamic Lighting and a reasonable frame-rate. A while ago is was stated the engine is for use in a project called WTSC; we'll try and get some more gameplay related info on this project for you next issue.

 

Alogic now run by Pete
Hal_8900 has let us know that his popular tutorial site Alternate Logic is now run by Pete, whose own well-known gigantic site has been gaining accolades. Hal says that this decision should allow more frequent updates to Alogic.

 

Breaking News: Rage
Just as we were going to press, we got word that Maz might release the library behind Agent Squeak 2. It's codenamed "Rage", and features XMS and double-buffering, as is reported to be as easy to use as dqb. More on this as we get it.

 

Oops!
Last month, we misspelt Jernej Simoncic's name in the News section. Apologies go out for that.

 

 

quicknews

The Mystical Journey from Danny Gump will feature Zelda-style battles instead of the FFstyle it previously used

Nutzboy and Scruffy Dog are working on a Grand Theft Auto sytle game

Bubble Fighters character models are in the works. Enigma tells us the foot of the model uses about as many polys as the sock in the demo screens

A Resident-Evil sytle game called I3DX is in the works. Check it out here

Alias' game Attack of the Blobeteers will have a demo released on or about 23 Aug, and looks very crisp thus far

PHAT Kids are working on a new RPG called Kids of Karendow. Info can be found here

Vampira recently released some new shots and it's looking great. This isometric game has a graphic direction unlike anything I've seen

Latest Game updates:

Dark Ages 2 has a few new features added recently such as animated tiles and a neat background which you can see in the ViewBox, but what really got us was the size of the final game. At current plans, the size of the overworld map will be over 3 billion square pixels. 3 billion. That's over 400 times the size of the original game. The first "gameplay" demo should be out in a couple months, and will feature a battle system as well as a map 20 times as big as Dark Ages 1.

Shadow of Power, a recent game from German group master creating was released to much acclaim, and no doubt: It rocks. This is one of the best qbasic games ever. Combining much humour, the best sound of any qb game, fair graphics and great Zelda-parody style gameplay, no qb'er should be without it.

Hal_8900 and Progman are hard at work on the sequel to the well-recieved RPG Of Hell and magic, a sequel which will use DirectQB. Although exact gameplay ideas are not solid as of yet, we know it will build on the Zelda-style play of the first.

More info on Freelancer has become available. Pasco's penultimate vertical shooter will feature many gameplay ideas not formerly seen in qbasic. There will be "boss ships" on some levels, which Pasco says will be huge. The missions are based on gaining "bounty", with which you can upgrade many different parts of your ship for more damage. Also, there are damage ideas which Pasco has wanted to remain secret, but suffice to say, they'll look *nice*.

Rumours of ProjectRT being cancelled seem unfounded as of yet. Angelo tells us that although he has been having more work with his University at the current time, Prt is by no means cancelled or even delayed indefinitely.

Agent Squeak 2, the heavily hyped game from Maz, is looking *very* nice. Featuring a wicked fast scrolling engine with faked-parallax, "frontscreen" objects, and great graphics all around, this game is firmly in Super Nintendo quality level. Expect a demo within a couple months that is playable.

Arrakis, a C+C syle Real Time Strategy Title from Future is coming along nicely, with a playable demo recently completed. Although the control is somewhat awkward and the gameplay a little hard to follow, the title definitely looks to be one of the most promising RTS titles in qb.

Tipline give us news and rumours here

D
A
T
A
P
A
T
H

>>>Back to Top<<<

 

The Array

contactsyour letters to us
A
R
R
A
Y

 

We need a new issue of qb:tm! GRRRR!
Newnewnewnewnew!!

-MajikO

Well, 'ere ya go, then!

You said you wanted some letters in support or against nekrophidius' letter from the current issue, so here's mine:

I'm just writing to say that I support nekrophidius 100% of the way. First of all, because I never read Issue #9, I just checked out Timothy's letter. I'm getting really sick of people who complain about certain content in games. I thought that had finally died. It felt like I was in a time warp to 1993 when Mortal Kombat came out. Just like with porn on the internet, if it offends you, don't look at it, just ignore it. It rarely makes any difference if these kind of people have to rally together and bitch about it. This Timothy guy has no right to play the role of some sort of QB game censor here. I don't know when people will understand this, if ever.

Sniper

Your viewpoint, (and the opposite) are shared by many others. I personally agree, though, that if you're not interested in it, don't look at it. Any other comments on this issue?

 

To Priject RT:

G'day fellow programmers...plz tell me u are not using 2d gifs for a NPC...use polygon players actual 3d charectors not DOOM stile 2d...and if it slows down programing too much scale it back make 3 versions of the NPC upclose charector 100 vertexes medium distance 66 and far away 33...just something to keep on mind...and plz dont tell me that ur gonna use just one floor and all inside...u should DEFINITLY make actual real looking buildings not same hight all the way that is just retarded...

Dave Neubert

Welp, I ain't Angelo Mottola, so t'aint my decision what kind of NPC graphics they use (although I'm 99% sure Project RT will use Doom style NPC's). On a side note, I'd like to award this letter the "English is probably my second language" Award for having the worst use of punctuation of any of the 856 emails in my box right now. Cheers to that!

 

Dear Grand Scimitar Studios,

First of all, I'd like to tell you what a great job you do with the qb:tm magazine. It has a lot of useful info for people like me who are just starting in on Qbasic, or for advanced coders looking to increase their knowledge. Keep up the good work. Secondly, I am responding to a tutorial by one of the best qb RPG writers, DarkDread. It was titled RPG Scripts, and I found it very informative. I would like you to know that you are very good, and I hope you will continue to have DarkDread writing for your magazine. Thanks for your time,

unibrow02

Thanks for the comments =). A lot of people email me concerning an article by a certain writer, and I always make sure to pass it along, as they enjoy hearing what ppl think of their tuts. We liked Darkdread so much, that he's back with a new tutorial this issue, so now you know.

 

The new design looks really professional. I've been reading QB:tm since the first issue, it's really great. I hope that QB:tm stays online for many years to come.

icarus3

Icarus here shows ya that kissin' our batoot is a good way to get printed in the mag =). Hope everyone likes the new issue design also, btw.

 

I got this information from Developers Corner. Here's what someone posted:

I have heard that C, BASIC, C++ will not work beyond 2038, but Java will...has anyone else heard this rumor?

Here's the reply:
One C or C++ library (not sure which) records time as seconds since Jan. 1, 1970 at midnight (qb does this also -ed). Storing it as a 32-bit integer gives ~ 4 billion seconds. Those 4 billion seconds run out in Mid-January 2038. These languages will soon be moving to a 64-bit version, ( as Merced processor from IBM later this year is 64-bit -ed). so older programs will need to be recompiled to operate.

I am posting this so that you can inform others in the next issue of qb:tm, after all Microsoft doesn't seem to want to support qb anymore and probably won't bother to recompile it. What's your opinion on this?

Fred28

This is indeed true, that qb (nor, in fact, anything compiled with it for the most part) will not work past 2038. Since I'll be nearing retirement then, and, basing on Moore's Law, the new processors will be ~ 900 terahertz (i.e., 900 million megahertz), hard drives will be ~ 10 million gigabytes, and the new 3d card should calculate around 6 billion lighted polygons/second, I don't think recompiling Wetspot 2 will be the most major of concerns =). And, by the way, squaring numbers that big in my head ain't easy.

 

Just wondering if you have any bmp, gif, pcx, or any type of format that allows good quality format loaders. I really need one to start on my program. I'm using a loader now but the result when it's loaded is not as good as when viewed in Windows. Please help me if you can. Is there an editor to create pictures?

Jade Eplin

Qbprogger has a very nice loaded called GIF2IMG that's as fast as BLOAD on gifs. Of course, if you're in 13h and the image is > 256 colors, it'll look worse in qb. There are many good sprite editors, like SpriteShop and PP256, available at BlueTek or any of the major file sites.

 

Hello,

I'm looking for the sourcecode or a library to manipulate the registry in a BASIC application. Do you know something for this?

Thanks,

Wilfred Van Dijk

The best I was able to come up with was "DOS and Windows programs usually don't like eachother". Does anyone know how to do such a thing as Van Dijk is looking for?

 

I greatly appreciate your good works about Senseless Violence, however, I would like to point something out.

SV is not supposed to be a Diablo-style game. In fact, it's quite impossible. I started making SV 5 years before Diablo came out. I was attempting to make my own version of Ultima 8, which is, imho, much better than Diablo as Diablo lacks plot and a story.

Just wanted to clear that up,

1000101

Thanks for the clarification. By Diablo-style, I meant in the graphical style that the game is attempting, which is quite a feat in and of itself.

qbasic: the magazine reserves the right to edit any letter recieved for purposes of clarity and space. But you already knew that.

A
R
R
A
Y
what's hotsurvey and in-box
 

You chose the best - here they are


 Favourite Game     |  Last Month  | Change 
 1. Wetspot 2              1           <> 
 2. Dark Ages (t)          2(t)        <>
 2. Mono. Shooter (t)      2(t)        <>
 4. Of Hell & Magic (t)    --          --
 4. Shadow of Power (t)    --          --
 
 
 Comments: Two new titles crack the list, while cgi voting
   means a large increase overall in terms of total votes.

 Favourite Utility  |  Last Month  | Change
 1. DirectQB               1           <>
 2. PP256                  2(t)        <>
 3. GSlib (t)              --          --
 3. zsvga (t)              --          --
 3. QMIDI (t)              --          --
 3. Dash (t)               --          --

 Comments: DirectQB dominates, Dash and GSlib rejoin
   the list, and PP256 does very well again.

 Best Upcoming      |  Last Month  | Change
 1. Dark Ages 2            1(t)        <>
 2. ProjectRT              1(t)        D1
 3. Freelancer (t)         1(t)        D2
 3. Agent Squeak 2 (t)     --          --
 3. Glib (t)               --          --
 
 Comments: Dark Ages 2 is once again in sole possession
   of 1st, while a variety of titles from Oham2 to 
   Senseless Violence to Groov 2.
Get some Voting Help
 
must downloadsthe editor's top ten

You need this stuff!

We've got a slightly different format in the Must Downloads this month. From now on, we'll be displaying the 10 programs you *need*, and that's all. And the ratings will go in terms of pure playability or usefulness, not nostalgia. So here's the new list (in no particular order)! Oh, and if you see somethin' missing, write in and tell me!

 
  Absolute Assembly
Petter Holmberg of Enhanced Creations's assembly converter. By typing in normal asm, this proggy will convert the asm into goods that qb will understand. Super-spiffy!

  Dark Ages
One of the most engaging QB games ever, as well as one of the only complete rpg's. This was featured in PC Gamer! Check it out!

 

Monospace Shooter
Gradius' 2 color side-scrolling space shooter. Featuring very detailed enemies, flickerless animation and a devious AI, this game is a classic.

 

Wetspot & Wetspot 2
Wetspot, the bubble-bobble like block-pushing action game was one of the best QB games when it came out, but W2 is just incredible. Super midi sound, great fast graphics, tons of variety, an insane number of levels...everything you could want. GET this game. Now.

 

SFB2
The BEST qbasic fighter. Ever. Even though it's wireframe, it has cool particles and smooth animation as well as rippin' gameplay.

 

PP256
Called the best tile editor in QB ever, PP256 has loads of tile-editing options at your disposal. If you use tiles in your game, you can't live without this.

 

DirectQB
The library that has it all: .fli players, sound, super-speedy graphics, special effects, the works.

 

Shadow of Power
A rockin' new RPG from German group MasterCreating, this game has humour and SNES style production value.

 

QMIDI4.1
This is the best version of Qmidi. Play .mid's in your game! The new qmidi4.1 rules! It has tons of features and a "light" version. Get it now!

 

Of Hell and Magic
An RPG by Progman, featuring smooth-scrolling, a fantasy story, and Zelda: Link to the Past style gameplay.

 

site of the monthgo here
in-boxgood and bad (pasco edition)
A
R
R
A
Y

Pete's QB Site

Sites usually don't do this. Y'know, go from nothing into gigantic in just a few months. But Pete's done it. Files. Links. Tutorials. Daily Updates. An easy to use design. It's *all* here.

The pure enormity of the site is what makes it so interesting. Literally hundreds of links. Tons of programs, most of which have a short review accompanying. Many of the visitors also submit reviews frequently for the site. Go there. Now.

good


No html on the major webboards has meant less annoyance so far from people writing huge lettered posts or bolding the next 10 pages.

bad


People still can be annoying in their posts. "Kum see my kikin gam!" 20 times over sucks.
A
R
R
A
Y

>>>Back to Top<<<

 


advertisement

 

classic issue

By zkman

One year.

12 issues.

Too many articles to count. From assembly to RPG Scripting, to your first looks at DA2 and ProjectRt, among others, to art, music, and more, we've covered a ton in that 1 year. And it's not just us that have brought ya a lot in the last year: the qbasic community has been more active than at any point ever before. Don't believe me? Let's take a walk down memory lane, and see what's gone on in the qb community each season since August 1998.

Fall 1998
Summer was winding down, qbasic.com was still being updated with regularity, and Angelo Mottola, recently revered with his creation Wetspot 2 that spring, announced on his page that he had "big plans for a DirectQB library". Blast! was then the most-used, and Dash was gaining subscribers, although no one could've guessed just how pertinent to the future of qbasic those "big plans" would end up being.

SFB2, still the best fighter in qbasic, with a primitive-based graphics system, great particle support, and a fab roto-zooming camera, became a splash in this season also. Danny Gump released a build of Super Mario World Clone, showing that Super Nintendo quality games were by all means possible in qbasic today.

But all was not well in the qbasic community. The newly-christened (and short-lived) QBVoice broke news of an attempted sabotage of Dash by implanting a virus into the file caused qb'ers to question for the first time the safety of the .exe's compiled in qb on the internet. Tsugumo's heralded graphical acheivement in TheGame and big plans for the game were sidetracked as he cancelled the project. Apester disapeared from the qb scene, in all respects. And the IDSA sent letters to top qb sites including the qbt50 that they would no longer be able to post links to or post quickbasic4.5 on threat of having the site license revoked. As we know now, that threat never carried thru, but it made it hard to find for those who had "lost their only copy" at the time.

Most saddening though, was the loss of arguably one of the greatest qbasic coders ever in Milo Sedlacek, who passed away as a result of a chronic illness on September 24, 1998. Milo, aka Gradius, is known for many of his programs, most notably Monospace Shooter and the never-completed RPGDemo4, both amazing technology demos for qb in the pre-library days. But happier days would soon follow, as we entered the frosty cold of...

Winter 1998/1999
As winter rolled along, all of America was focused on Slick Willy and the White House Three; all except the qb community was hearing enough good news to "blow off" the allegations, so to speak. With Winter beginning, DA2 production also started. But, almost as importantly as this, were the following words from the November issue of the mag:

"More interestingly, we've picked up some rumours about Enhanced Creations future plans (cough...next game...cough)"

Of course, this game ended up being "Project RT", still one of the most hyped and most secretive qb games in the works (despite the first screens which we procured for this spring's issue). Speculation about what the game was ranged from a Massive Multiplayer RPG to Wetspot 3D to a Real Time Strategy title. Amazingly, what's known about the gameplay of the game is almost as vaulted over at Enhanced now as it was then.

With Christmas break, us coders were enjoying the frosty snow...by looking out the window near the computer, of course. Who has time to trim the tree when announcements such as "a new Vertical Scroller by Pasco", "a super-secret, rocking demo called Xeno", Puz and DA2 shots were coming thru the pipes? Recollecting back, we'll also notice the December issue commented on a "secret graphics library from LordQB". Could this be the LiquidGL from News this month?

And even with the holidays passing, new paradigms in qbasic coding such as uNESs, the first Nintendo emu in qbasic, or LTPB, Interplay's new beginner level BASIC language proved that qbasic was far from dead. But the frivalty of the holidays proved no match for...

Spring 1999
Spring is conventionally "lull-time" as far as qb code is concerned, and although no earth-shaking programs came out, there was certainly nothing to complain about in terms of the qb community.

Most of the talk during this month focused on speculation about what would win at Sonicblue's Qlympics. The 1st Qlympics were tainted by allegations of cheating, and Sonic assured us he was doing all he could to prevent that from happening. In the end, there weren't many major upsets in any category, with big name titles like Dark Ages and Wetspot 2 easily conquering their category, as had been suspected.

PP2, the Labryinth and MOTG Project all got their fair share of publicity, either due to the demos that were released or the name recognition of their creators. And, as luck would have it, they've all panned out thus far to be equals in the regality they recieved, most notably thus far The Labyrinth by Seav. The only major First Person Shooter in qb so far, progress continues on it at a good clip, and it already plays very well.

Much chatter was also diverted to the future of the QBIDE. The memory and speed problems of qb have hindered the language from being anything but a hobbyist language since it's creation, but plans such as QBCC by Leroy threatened to change that distinction. QBCC is planned to work by taking qb code and "converting" it into a DJGPP file, which would give it muchisimo speed. Not only that, there were, and still are, plans to make an alternate IDE that is Windows based, to give you the ability to have multiple progs open at the same time.

All was not well, though. Viruses hit many qb coders, including Angelo Mottola and myself, which is why qb:tm reccomends you never accept a file from someone you don't know except in .bas form. Nekro's game Killerz stirred up controversy among some, much as WoS before it, because of it's content. Nekro, true to form, was not swayed by the commentary, standing up for his games whenever possible.

Finally, our April Fools joke about Project RT being an Action Strategy RPG starring Cuby and Coby was not, err, picked up by some, leading to boards being peppered questioning whether it was, in fact, an Action Strategy RPG.

Summer 1999
The summer of '99 is by no means over yet, but it has already become one of the greatest for qb enthusiasts. Shadow of Power and Of Hell and Magic, two acclaimed RPG's, were both released, and both offered quality as high or higher than Dark Ages, the benchmark for programs of that variety thus far.

Dmitry Brant and Petter Holmberg showed that they were the most 1337 of coders by displaying (relatively) fast jpeg viewing in qbasic. Jpegs, are, of course, one of the most difficult types of files to decode, due to the complex compression they use. The future of Qb also looked to have been shown by two libraries, and one future one, in Gslib, FutureLibrary and Glib.

QB's next year promises to be as great as the one before. The mainstream considers qbasic all but dead, but the last few pages have shown what a group of quality coders can accomplish. As has been said before, the difference between good and bad code doesn't hedge on the language. See ya next year!

 

A
N
N
I
V
E
R
S
A
R
Y
!
 
Did you think Pascal "rox qbee nyday!"? Lemme know.
 

>>>Back to Top<<<

 

Gallery

V
I
E
W
B
O
X

DA2 shows off layering from trees and a gorgeous hand-drawn border

da2

V
I
E
W
B
O
X

>>>Back to Top<<<

 

3d: Part VII

By Alias

A
L
T
E
R
N
A
T
E
 
P
R
O
J
E
C
T
I
O
N

Wow, since our start 3 months ago we have covered literally most every aspect of voxel animation! There is but one area left I want to cover: translucency. By now, if you have followed us through you may have tried your hand a creating your own voxel program to suit your fancy, and if you've tried transparent things you may have encountered a problem. Because the drawn portions of voxels overlap, the drawing gets uneven and in some places can even look plaid or worse. Problem? You bet.

Voxel Render problems

  • Solidity Make your voxel objects solid. This does not really fix the problem, but it can hurt framerates. Make your voxel model solid rather than hollow, if it isn't that way already. This can make the problem less apparent by making the voxel model look more or less transparent in different areas in a different way. Make it look more deliberate, and thus better.
  • Hollowness If that didn't work or made the problem worse, you can hollow out the voxel model and make it more evenly transparent. This can have drawbacks too, because if it tries to be evenly translucent but has overlapping and has plaid spots, it is a lot more apparent than with solid models.
  • Sprite caching This method is very difficult to do without a library like DirectQB, as you have to hide the drawing of the voxel model from the screen, and doing so in pure QB is kind of hard. What happens is you draw the model before hand, on a separate layer, and then you GET it into a separate array and blend it into the scene. This has the disadvantage of being slower, more memory-consuming, and much less versatile.
  • Cuisineart Maneuver Change your blender map. I have found that using an additive or subtractive blender map instead of an average or weighted average map helps the appearance of blended voxel maps. This helps even more if you use it with the solid method above.
  • Deal with it! Sometimes imperfection can be left in with no ill effects. Look at Quake. By all means, that game should not have left the shelf for all the graphical glitches. It is, mathematically, so incorrect that it's amazing it's even playable. Why does no one notice this? Because the graphics are done just barely well enough that the human eye can't see the errors unless you specifically look for them. If you make your action fast enough, it's possible that no one will ever notice. Michael Abrash (ASM programming God) wrote "Motion and fast action can surely cover for a multitude of graphics sins."

On to alternate projections
This article will deal exclusively with isometric projection because, of all the 3D projection methods out there, this is one of the best. It has the advantage of being quicker to calculate, and very easy to see and understand. The major downside is that it is very possible to make it look like something is in the wrong place - which is very annoying when you're trying to play a game! If you don't know how far you have to make the jump, you can't know if you're going to make the jump. I'll explain more later.

In the first installment of MA*SNART's 3D series, he gave us this algorithm for 3D projection:

x' = (((x - vx) / z) * 90) + vx
y' = (((y - vy) / z) * 90) + vy

(I will call this FPS projection from now on, as that is what it is primarily used for. That is, ALL FPS games use this formula.)

Where x' and y' are the projected coordinates of any point and vx and vy are the coordinates of the pixel in the middle of the screen. (Or they should be at least - if you ever have some free time you can play around with these and have some REAL fun.) The formula for isometric projection goes like this:

x' = (x - y) + vx
y' = ((x + y) / 2) + z

As you can see there is only one divide and everything else is add / subtract, making for a very fast algorithm. It's also amenable to all the same deformations FPS projection is - whatever you can do to a point there you can do to a point here and it generally looks just as cool. (The only thing I really miss is having the main character throw a missile right at the screen, and freeze-framing when the tip of the missile is so close to the nose of the player that my pentium's FPU can't tell they're apart at all so the missile is left there waiting to explode, and the player by this time has wet his pants because he _FINALLY_ made that big jump across the canyon and now, before he could hit quicksave, he's being destroyed by a pentium FP bug!!! But I digress.)

The problem with this I mentioned earlier - it often makes things appear in the wrong position. Take this example:

 

 

That looks, in the isometric world, like a long line of blocks but below is what it really is

 

Screenshot taken from Attack of the Blobeteers

 
 

Que? Believe it! The projection model has that one major flaw, that something in one position can appear to be in another. There is a very simple, very elegant way around it though - it's called rotation. Yes, it's true, if you rotate every point around the Z axis -8 degrees, it will make everything unmistakeable. Here's the formula to do that:

x' = cos(-8) * x - sin(-8)
y' = sin(-8) * y _ cos(-8)

Please note that the -8's in there need to be changed to -8 * (3.14159 / 180) because theta is in a different measure than degrees.

Now that you understand projection in Isometric form and everything there is to know about voxels, my work here is done. On the 23d of this month you will see a fine example of isometric perspective, that is, a demo of my game, Attack of the Blobeteers. Thanks for reading and I'll see you on the release date.

Alias doesn't know that rotated along the sine wave, z' = w* of the quaternion.

 

>>>Back to Top<<<<

 

tip of the month

By MA*SNART

I
N
D
E
X

There you are: your cool new vertical shooter game is about done. You've used every trick you know about to get it where it is today. You have an array for the players bullets, another for the enemy's shots and yet another for the enemies themselves. But you have a problem... the frame rate, while consistant, is slow [even with the help of tons of assembly]. Well it's time to look at your game object routines...

A game object routine is just a catch all term for game specific routines that involve the interaction of the various game pieces. An example of such a routine would be the way by which you check for collision between enemy fighters and players bullets. And more specifically how enemies and bullets are 'spawned'.

An example of a 'spawning' player bullet routine would involve calculating where the bullet will be on screen then placing these and other values into the 'bullet array'. The problem is figuring out which array subscript is 'open' or 'good' in order to place the stats for the bullet. A typical 'bullet array' may have a variable that indicates if the particular bullet is 'alive'. The usual way to do this is by scanning through the array with a FOR/NEXT loop looking the first 'dead' one you find.

This will seem very fast if, say, you only have 10 possible bullets to check. But look again. For every frame you would need to 'scan' the array for all the 'alive' bullets just to be able to draw them! Then again for each enemy that might be in contact with them! And again just to move them for the next frame!

If only one enemy is onscreen and you limit the bullets to ten max but only have one bullet that is 'alive'... that means for every frame you have to 'scan' the array 3 times!. If you have 20 onscreen bad guys and still only one bullet... your scanning 22 times [or to put it another way... your checking 220 times per frame if a bullet is 'alive']! Now you know why your game engine is so slow!

But what to do about it? Well you could use several index lists.

Index Lists
An index list is a simple 'list' of 'array subscripts' [or 'indexes']. You can have a separate index list for all the 'alive' bullets, the 'dead' ones, 'alive' enemies, and 'dead' ones too. In fact you could have a separate index list for just about anything you want in relation to a particular array.

The great thing about index lists is that they contain all the array subscripts you need for a particular routine. You no longer need to 'scan' a whole array to find the 'alive' from the 'dead'. In the example above, one bullet and 20 enemies might mean that you have 'scanned' the array 220 times per frame. With a index list of 'alive' bullets and 'alive' enemies you are no longer 'scanning' but instead you are working with only the 'alive' bullets and enemies.

Great, so how do I make an index list in QB?

Pure and simple... variable length STRINGs. That and the assorted QB functions like MID$, CVI, ASC, and MKI$ [NOTE: use of PEEK and POKE will give faster results, but for the sake of clarity I'll use MID$ and the others for this article].

Okay here are some routines for a 'bullet' array:

CONST maxbullets = 10

'bullet data type that contains
'x/y location and kind of bullet
TYPE bulletdata
x AS INTEGER
y AS INTEGER
kind AS INTEGER
END TYPE

' here is the bullet array
DIM SHARED bullets(maxbullets) AS bulletdata

' here are the index lists.. one for "alive bullets" [balive]
' the other for "dead" ones [bdead]
DIM SHARED balive AS STRING
DIM SHARED bdead AS STRING

The first thing you want to do is "initialize" the "dead" array, like below. This is done so that the index list is accurate in that all the bullets are effectively "dead" when the game starts.

FOR i = 1 TO maxbullets
bdead = bdead + MKI$(i)
NEXT i

'
'your game code here :P
'

These subs are used to "spawn", "kill", "hit detect"[hdbullet] the bullets

SUB spawnbullet( x%, y%, k%)
' spawns a bullet... just pass the
'location x/y and kind of bullet and thats all you have to do

This works by grabbing and removing the first "dead" bullet from the bdead STRING. It then adds it to the end of the balive string, thus it becomes active and it then enters the data into the array.

IF (LEN(balive)/2) < maxbullets THEN

index$ = MID$(bdead,1,2)

bdead = MID$(bdead,3,LEN(bdead))
balive = balive + index$

v% = CVI(index%)

bullets(v%).x = x%
bullets(v%).y = y%
bullets(v%).k = k%

END IF

END SUB

This removes a bullet from the balive STRING and adds it to bdead, thus the particular bullet becomes inactive. The variable passed to this SUB is the actual array subscript; however, this SUB is only intended to be CALLed by the collision detection routine named "hdbullet" or any routine [like movement] that will need to remove the bullet for some reason.

SUB killbullet(v%)

index$ = MKI$(v%)

IF index$ = MID$(balive,1,2) THEN

balive = MID$(balive,3,LEN(balive))

ELSE

IF index$ = MID$(balive,(LEN(balive)-2),2) THEN

balive = MID$(balive,1,LEN(balive)-2))

ELSE

FOR i = 3 TO LEN(balive) STEP 2
test$ = MID$(balive,i,2)
IF index$ - test$ THEN
balive = MID$(balive,1,(i-1)) + MID$(balive, i+2, LEN(balive))
EXIT FOR

END IF
END IF
END IF

bdead = bdead + index$

END SUB

This sub is where you will do hit detection with the enemy array subscript enum%, as most of this will really depend on the particular method you use for collision detection and etc.. My main reason for including this in the article is to show how to use a FOR/NEXT loop with the index list; by replacing the core "hit detection" routine inside the FOR/NEXT loop, you can replace it with sprite drawing routines,etc.. Just remember to use index% as the array subscript and not "i"

SUB hdbullet(enum%)

FOR i = 1 TO LEN(balive) STEP 2
index% = CVI( MID$(balive,i,2))

' IF bullets(index%) CONTACTS ENEMY(enum%) THEN
' CALL killbullet(index%)
' CALL killenemy(enum%) if need be....
' END IF

NEXT i

END SUB

Okay those more advanced programmers out there will see that you can have one large "generic" array for bullets, enemies, et al. And simply use a bunch of index list STRINGs [possibly even in an STRING array!]. Doing this will reduce your needed index list management subroutines to only a handful [spawn, remove, drawing, movement, and hit detection]! Mmm... less code equals less work, and less code to debug! :)

I've used a vertical shooter as a example game that would be helped out by index lists. But in truth any game could easily use this technique! If memory is a problem [but speed isn't] you could store the contents of your array in a RANDOM access file and use the index lists to GET/PUT one array record at a time. This would save a TON of memory as only the index list STRINGs would be needed! Another thing you can do with index list is to sort them based on some value held in the array. This is cool because only the order of characters in the STRING needs to be changed [and using a "radix" type sort programmed in Qbasic, it only takes less then a 10th of a second to sort 3,000 items on my P-150!].

So now you, hopefully, know some ways to optimize your code. If not, then I hope that you have learned of a new way to use STRINGs for things beside holding text...

Happy progging :)

Email MA*SNART telling him how you used a Reverse-culling bubble LZW Index in your game at this address.

L
I
S
T
S

>>>Back to Top<<<

 

mod 1

By Darkdread

Download the related program

Hail!

Welcome to the second installment of the new RPG tutorials. Hopefully by now, most of the rust has worn off... and I'll be able to provide you all with a much better tutorial.

If you missed the previous one... It showed you how to write a scripting engine for your RPG. If you don't see it around here somewhere, ask zkman, I'm sure that he still has it. :)

This time around, I'll show you how to write a semi-active battle engine!

Before we start, let me point out that all of the code used in this tutorial is available in a zip file, along with some graphics, which demonstrate the engine in action. You can download it now, or read the tutorial first. If you're new to this, I would suggest reading the tutorial, as the code is not commented.

NOTE: This tutorial is aimed at a novice QB programmer. If you're a beginner, it shouldn't be too tough to figure out, But you may wish to brush up on your coding skills first.

Setting it all up.
First, we need to set up our variables, and the sub routines which we will use. Remember, always start with the two lines below. The $DYNAMIC meta- command is used to store arrays dynamically; This means that your array space is much more flexible. DEFINT A-Z defines all arrays as integers. This basically speeds up execution of the program.

'$DYNAMIC
DEFINT A-Z

Next, we delcare our sub routines. I'll explain what each of these is for once we get to them, so don't worry about it for now. You may also wish to know that this step is optional, as QB will automatically create these lines when you write the actual subroutines.

DECLARE SUB GetHandLocation ()
DECLARE SUB TimerDelay (Seconds!)
DECLARE SUB Battle ()
DECLARE SUB StatsBox ()
DECLARE SUB ChoiceBox (BoxType%)
DECLARE SUB DrawBattleScreen (ScreenType%)
DECLARE SUB InitBattle ()
DECLARE SUB InitRandomStats ()
DECLARE SUB InitSprites ()

Next, we must dimension our arrays, set up our global variables, and our constants as well. The first constants, are true and false. We will be using these for our 'flag' variables, to see check for certain happenings and tell the program to comtinue doing something if they are false, or to do something else if they are true.

CONST True = -1, False = NOT True

Next, we dimension the arrays we will use in the engine. These are needed to store our graphics. For this engine, there a four different enemies, and all are 32x32 in size. The two player characters are also 32x32, there are also three frames of animation for them. Next, our hand pointer is one 16x16 sprite. Finally, we need to allocate two arrays to hold parts of the graphic background which our sprites might be put over. This way, we can get what's behind the sprites, put or sprite on the screen, then, restore the background when we're done. Finally, we will also allocate space for masks in our sprite arrays.

Basically, This is the calculation I used to determine how big each array would have to be:

((SpriteXSize * SpriteYSize / 2) * NumberOfSprites) - 1

If you are not familiar how this all works, I would suggest reading a tutorial about sprites and graphics in SCREEN 13. It would help you understand more of what we're doing here.

DIM SHARED Hand%(258)
DIM SHARED Players%(4626)
DIM SHARED Enemies%(2570)
DIM SHARED BackSprite%(1028)
DIM SHARED BackHand%(129)

Next, we need to declare all the variables and dimension all of the arrays, which we will be using for our data. Basically, we need space to hold the players stats and enemy stats. Below, are the stats I deemed necessary for this engine to work. It's a good idea to give everything a name which will tell you what each variable is for; Ie. PlayerHP% is a much better variable name than, say A1% to hold the player's Hit Points.

DIM SHARED PlayerName$(1 TO 2), PlayerAlive%(1 TO 2), PlayerType%(1 TO 2)
DIM SHARED PlayerHP%(1 TO 2), PlayerMaxHP%(1 TO 2), PlayerMP%(1 TO 2), PlayerMaxMP%(1 TO 2)
DIM SHARED PlayerST%(1 TO 2), PlayerDF%(1 TO 2), PlayerAG%(1 TO 2)
DIM SHARED PlayerMS%(1 TO 2), PlayerMD%(1 TO 2)
DIM SHARED PlayerEXP&(1 TO 2), PlayerGold&
DIM SHARED PlayerX%(1 TO 2), PlayerY%(1 TO 2), PlayerGo%(1 TO 2)


DIM SHARED EnemyName$(1 TO 4), EnemyAlive%(1 TO 4), EnemyType%(1 TO 4)
DIM SHARED EnemyHP%(1 TO 4), EnemyMaxHP%(1 TO 4), EnemyMP%(1 TO 4), EnemyMaxMP%(1 TO 4)
DIM SHARED EnemyST%(1 TO 4), EnemyDF%(1 TO 4), EnemyAG%(1 TO 4)
DIM SHARED EnemyMS%(1 TO 4), EnemyMD%(1 TO 4)
DIM SHARED EnemyEXP%(1 TO 4), EnemyGold%(1 TO 4)
DIM SHARED EnemyX%(1 TO 4), EnemyY%(1 TO 4), EnemyGo%(1 TO 4), EnemyThere%

 

R
P
G
 
B
A
T

T
L
E
S

  Next, we'll declare arrays to hold the X and Y location of our hand pointer. This will come in handy (No pun intended... really...) later.

DIM SHARED HandX%, HandY%

Now, we will initialize the random number seed. What is this? Well, we need the ability to generate random numbers, so that player and enemy damage isn't always the same. This line tells QB that we will be doing so later.

RANDOMIZE TIMER

Okay... Now we're more or less set up. The next parts of this tutorial will deal with individual parts of the engine. It is recommended that you read all of them carefully, as they are all intertwined closley together.

The rest of the main module.
This is how the rest of our main module would look. Note that some of this refers to subroutines I have not talked about yet... You may scroll down and read about these subroutines first... Then come back here.

Well, we have to tell QB to load our initialization routines next. To do this, we just call them by name like so:

InitSprites
InitRandomStats

Once we're initialized... Let's switch to screen 13 (320x200 resolution video mode, with 256 colours if you don't know) and call our main Battle sub to begin the fight:

SCREEN 13 Battle

Well... The battle's done. Now, the engine reverts to text mode and displays a short message and waits for a key press. After this, it exits. This last bit of code, you should leave out.

SCREEN 0: WIDTH 80
PRINT "Semi-Active Battle Engine. Created by DarkDread, 1999"
PRINT "You may use this in your programs... Just give me credit :)"
PRINT "Press any key to exit."
WHILE INKEY$ = "": WEND
END

The main battle sub.
This is the biggest sub routine in the engine, as it controls the flow of the battle. You may split this up into smaller routines, but for the sake of simplicity, I kept it this way for the tutorial.

Our subroutine starting code... QB will automatically do this for you, when you create a new sub, so you may skip this part if you wish.

REM $STATIC
SUB Battle

First, we must initialize our battle stats, then tell the program to draw the battle screen. It is done by calling the DrawBattleScreen. Note that we also pass a value of 1 to it. This way, the sub will know to draw a starting battle screen, and not an ending one.

InitBattle
DrawBattleScreen 1

Now, we begin our main battle loops.

DO

DO

These next lines, will calculate the agility of player and enemy characters that are still alive. This sub will take the agility of each character, and add it to a total every time it loops. Once a character's total is greater than 99... That character will be allowed a turn. Also, some of the player part of these lines, draws the little red and yellow status bar, which shows how much longer a player has to wait until their next turn.

FOR I = 1 TO 4
IF EnemyAlive%(I) THEN EnemyGo%(I) = EnemyGo%(I) + EnemyAG%(I)
IF EnemyGo%(I) > 99 THEN
EnemyGo%(I) = -1: GoThere% = True
END IF
IF EnemyGo%(I) = -1 THEN EXIT FOR
NEXT I
FOR I = 1 TO 2
IF PlayerAlive%(I) THEN PlayerGo%(I) = PlayerGo%(I) + PlayerAG%(I)
IF I = 1 THEN
Yellow = PlayerGo%(I) + 128
IF Yellow% > 228 THEN Yellow% = 228
IF Yellow% > 128 THEN LINE (128, 16)-(Yellow%, 18), 44, BF
IF Yellow% < 228 THEN LINE (Yellow% + 1, 16)-(228, 18), 40, BF
ELSEIF I = 2 THEN
Yellow = PlayerGo%(I) + 128
IF Yellow% > 228 THEN Yellow% = 228
IF Yellow% > 128 THEN LINE (128, 32)-(Yellow%, 34), 44, BF
IF Yellow% < 228 THEN LINE (Yellow% + 1, 32)-(228, 34), 40, BF
END IF
IF PlayerGo%(I) > 99 THEN
GoGo% = True
END IF
IF PlayerGo%(I) > 99 THEN EXIT FOR
NEXT I

Here, we have a little timer delay. This is to slow the battles down to a playable speed. If the delay wasn't here, you would notice that everyone attacks almost at once! The battles wouldn't be much fun then. You can raise this number to slow a battle down, and lower the number to speed it up.

TimerDelay .1

LOOP UNTIL GoThere% OR GoGo%

A character can now take a turn... If GoThere% is true, this means that it's an enemy's turn.

IF GoThere% THEN

Next, we check which enemy is allowed a turn, and if they are still alive or not... Just in case. Once we have found out, we draw the enemy mask on it's location. This will draw the enemy in all black, so the player knows which enemy is about to attack them.

FOR I = 1 TO 4
IF EnemyGo%(I) = -1 AND EnemyAlive%(I) THEN
PUT (EnemyX%(I), EnemyY%(I)), Enemies%(514 * 4), AND

This next line randomly selects which player the enemy will attack. This is where our random seed generator is put to work.

HitPlayer% = INT(RND * 2) + 1

After a player has been selected, these next lines check if that player is alive. Then a calculation is made based on the enemy's strength, the player's defense, and the help of a few random numbers, to determine how much damage the enemy has done. The random numbers are used just to add some variety to the damage. Instead of an enemy doing, say, 5 damage on a player all the time, they may do 3 sometimes, and 6 some other time.

Finally, we must take the damage done off of the player's HP. If the player's HP is less than one after this, it means they are dead. We then change the player's HP to 0 (So you don't see negative numbers in their stats) and make the PlayerAlive% variable of that player false. This way, the engine will know not to let that player have a turn, or to show their picture on the screen.

IF PlayerAlive%(HitPlayer%) THEN
Damage% = (EnemyST%(I) * 2 + (INT(RND * 4))) - PlayerDF%(HitPlayer%)
IF Damage% < 1 THEN Damage% = INT(RND * 2) + 1
PlayerHP%(HitPlayer%) = PlayerHP%(HitPlayer%) - Damage%
IF PlayerHP%(HitPlayer%) < 1 THEN
PlayerHP%(HitPlayer%) = 0: PlayerAlive%(HitPlayer%) = False
END IF
ELSE
IF HitPlayer% = 1 THEN HitPlayer% = 2 ELSE HitPlayer% = 1
Damage% = (EnemyST%(I) * 2 + (INT(RND * 4))) - PlayerDF%(HitPlayer%)
IF Damage% < 1 THEN Damage% = INT(RND * 2) + 1
PlayerHP%(HitPlayer%) = PlayerHP%(HitPlayer%) - Damage%
IF PlayerHP%(HitPlayer%) < 1 THEN
PlayerHP%(HitPlayer%) = 0: PlayerAlive%(HitPlayer%) = False
END IF
END IF

Next, we show the damage done on the appropriate player, and delay for 3 seconds. The delay is necessary to allow us to see how much damamge the enemy did, and on who.

IF HitPlayer% = 1 THEN
LOCATE 12, 36: PRINT Damage%
TimerDelay 3
ELSEIF HitPlayer% = 2 THEN
LOCATE 18, 36: PRINT Damage%
TimerDelay 3
END IF

Now that the enemy's turn is done, their agility meter must be set back to 0. We must also make GoThere% false, so the engine knows to return to the pervious loop. Finally, we must make our DrawNeed% variable equal to 1. We will use this variable later when calling the DrawBattleScreen so we know what parts of the battle we will need to redraw.

EnemyGo%(I) = 0: GoThere% = False: DrawNeed% = 1
END IF
NEXT I
END IF

Next, our routine checks if it was a player's turn right after the enemy's. If so, we must redraw the screen, using our DrawNeed% variable from before.

IF DrawNeed% = 1 AND GoGo% THEN DrawBattleScreen DrawNeed%

Now, we check if it's the player's turn to go. If so, we check which player will be going. Then, we draw a choice box for them which will give the player their options (RUN, ATTACK) and set up our hand pointer X and Y values. We then draw the player's mask over the player, this way, we will know which character's turn it is. We also draw our hand pointer next to the first choice in our choice box.

IF GoGo% THEN
FOR I = 1 TO 2
IF PlayerGo%(I) > 99 AND PlayerAlive%(I) THEN
ChoiceBox 1
HandY% = 5: HandX% = 10
PUT (PlayerX%(I), PlayerY%(I)), Players%(514 * 6), AND
PUT (HandX%, HandY%), Hand%(0), PSET
ChoiceMade% = False

Now, we have to create a loop. Then, we trap the keyboard buffer using INKEY$ to check which keys the player has pressed. This is all used so that the routine knows to move the hand pointer up or down, and which choice the player has selected when they hit enter.

DO
SELECT CASE INKEY$
CASE CHR$(0) + CHR$(72)
IF HandY% = 5 THEN
HandY% = 29
ELSEIF HandY% = 29 THEN
HandY% = 5
END IF
LINE (5, 7)-(24, 45), 0, BF
PUT (HandX%, HandY%), Hand%, PSET
CASE CHR$(0) + CHR$(80)
IF HandY% = 5 THEN
HandY% = 29
ELSEIF HandY% = 29 THEN
HandY% = 5
END IF
LINE (5, 7)-(24, 45), 0, BF
PUT (HandX%, HandY%), Hand%, PSET
CASE CHR$(13)

The player has hit enter. Now, we check where the hand pointer was when they pressed the enter key. If it was at Attack (HandY% = 5), then we allow the player to choose which enemy they wish to attack. This is done with the hand pointer, and another loop while checking the keyboard. You will also notice that before the handpointer is moved, the routine checks if which enemy is still alive and which one isn't. This is so that the hand pointer is never pointing to an enemy that is no longer there.

You will also notice that this is where we use our array to hold the back- ground graphics were the hand pointer is put. This is necessary because once the player moves the hand pointer, we need to restore the graphics that were there before the pointer was drawn.

After the player hits enter here... We determine which enemy they have decided to attack. Then, we make a calculation based on the player's strength and the enemy's defense... As usual, we throw in a few random numbers to add variety.

Finally, we show an animation of the player attacking (We use our second array to hold background graphics here) and display the damage done on the enemy they've attacked. Again, we pause after this for 3 seconds, so that we may see who was attacked by whom, and how much damage was done.

IF HandY% = 5 THEN
ChoiceBox 0
GetHandLocation
GET (HandX%, HandY%)-(HandX% + 15, HandY% + 15), BackHand%
PUT (HandX%, HandY%), Hand%(130), AND
PUT (HandX%, HandY%), Hand%(0), XOR
ChoiceMade2% = False
DO

SELECT CASE INKEY$
CASE CHR$(0) + CHR$(72), CHR$(0) + CHR$(80)
PUT (HandX%, HandY%), BackHand%, PSET
IF HandX% = 8 AND HandY% = 85 THEN
IF EnemyAlive%(3) THEN
HandX% = 8: HandY% = 135
ELSEIF EnemyAlive%(4) THEN
HandX% = 114: HandY% = 135
ELSEIF EnemyAlive%(2) THEN
HandX% = 114: HandY% = 85
END IF
ELSEIF HandX% = 114 AND HandY% = 85 THEN
IF EnemyAlive%(4) THEN
HandX% = 114: HandY% = 135
ELSEIF EnemyAlive%(3) THEN
HandX% = 8: HandY% = 135
ELSEIF EnemyAlive%(1) THEN
HandX% = 8: HandY% = 85
END IF
ELSEIF HandX% = 8 AND HandY% = 135 THEN
IF EnemyAlive%(1) THEN
HandX% = 8: HandY% = 85
ELSEIF EnemyAlive%(2) THEN
HandX% = 114: HandY% = 85
ELSEIF EnemyAlive%(4) THEN
HandX% = 114: HandY% = 135
END IF
ELSEIF HandX% = 114 AND HandY% = 135 THEN
IF EnemyAlive%(2) THEN
HandX% = 114: HandY% = 85
ELSEIF EnemyAlive%(1) THEN
HandX% = 8: HandY% = 85
ELSEIF EnemyAlive%(3) THEN
HandX% = 8: HandY% = 135
END IF
END IF
GET (HandX%, HandY%)-(HandX% + 15, HandY% + 15), BackHand%
PUT (HandX%, HandY%), Hand%(130), AND
PUT (HandX%, HandY%), Hand%(0), XOR
CASE CHR$(0) + CHR$(75), CHR$(0) + CHR$(77)
PUT (HandX%, HandY%), BackHand%, PSET
IF HandX% = 8 AND HandY% = 85 THEN
IF EnemyAlive%(2) THEN
HandX% = 114: HandY% = 85
ELSEIF EnemyAlive%(4) THEN
HandX% = 114: HandY% = 135
ELSEIF EnemyAlive%(3) THEN
HandX% = 8: HandY% = 135
END IF
ELSEIF HandX% = 114 AND HandY% = 85 THEN
IF EnemyAlive%(1) THEN
HandX% = 8: HandY% = 85
ELSEIF EnemyAlive%(3) THEN
HandX% = 8: HandY% = 135
ELSEIF EnemyAlive%(4) THEN
HandX% = 114: HandY% = 135
END IF
ELSEIF HandX% = 8 AND HandY% = 135 THEN
IF EnemyAlive%(4) THEN
HandX% = 114: HandY% = 135
ELSEIF EnemyAlive%(2) THEN
HandX% = 114: HandY% = 85
ELSEIF EnemyAlive%(1) THEN
HandX% = 8: HandY% = 85
END IF
ELSEIF HandX% = 114 AND HandY% = 135 THEN
IF EnemyAlive%(3) THEN
HandX% = 8: HandY% = 135
ELSEIF EnemyAlive%(1) THEN
HandX% = 8: HandY% = 85
ELSEIF EnemyAlive%(2) THEN
HandX% = 114: HandY% = 85
END IF
END IF
GET (HandX%, HandY%)-(HandX% + 15, HandY% + 15), BackHand%
PUT (HandX%, HandY%), Hand%(130), AND
PUT (HandX%, HandY%), Hand%(0), XOR
CASE CHR$(13)
IF HandX% = 8 AND HandY% = 85 THEN
EnemySelect% = 1
ELSEIF HandX% = 114 AND HandY% = 85 THEN
EnemySelect% = 2
ELSEIF HandX% = 8 AND HandY% = 135 THEN
EnemySelect% = 3
ELSEIF HandX% = 114 AND HandY% = 135 THEN
EnemySelect% = 4
END IF
ChoiceMade2% = True
END SELECT
LOOP UNTIL ChoiceMade2%

Damage% = (PlayerST%(I) * 2 + INT(RND * 4) + 1) - EnemyDF%(EnemySelect%)
IF Damage% = 0 THEN Damage% = INT(RND * 2) + 1
EnemyHP%(EnemySelect%) = EnemyHP%(EnemySelect%) - Damage%
IF EnemyHP%(EnemySelect%) < 1 THEN EnemyAlive%(EnemySelect%) = False
FOR J = 7 TO 8
PUT (PlayerX%(I), PlayerY%(I)), BackSprite%(514 * (I - 1)), PSET
PUT (PlayerX%(I), PlayerY%(I)), Players%(514 * J), AND
IF J = 7 THEN
PUT (PlayerX%(I), PlayerY%(I)), Players%(514 * (PlayerType%(I) + 1)), XOR
ELSEIF J = 8 THEN
PUT (PlayerX%(I), PlayerY%(I)), Players%(514 * (PlayerType%(I) + 2)), XOR
END IF
TimerDelay .5
NEXT J
IF EnemySelect% = 1 THEN
LOCATE 12, 4: PRINT USING "###"; Damage%
ELSEIF EnemySelect% = 2 THEN
LOCATE 12, 17: PRINT USING "###"; Damage%
ELSEIF EnemySelect% = 3 THEN
LOCATE 18, 4: PRINT USING "###"; Damage%
ELSEIF EnemySelect% = 4 THEN
LOCATE 18, 17: PRINT USING "###"; Damage%
END IF
TimerDelay 3
ChoiceMade% = True: DrawNeed% = 1

 

A pic of the engine

 
  This is the rest of our first choice box. Here, if the player has chosen to run away (HandY% = 29) then we do a calculation based on the player's agility and the agility of a random enemy to detrmine if the player can succesfully run, or not. Why a random enemy? Simple, we want to allow the player to have a fair chance from running from any group of enemies that isn't based on the agility of the quickest or slowest enemy in the group. If the player can run, then RanAway is made true.

ELSEIF HandY% = 29 THEN
RandomEnemy% = INT(RND * 4) + 1
IF INT(RND * PlayerAG%(I)) + PlayerAG%(I) / 2 > INT(RND * EnemyAG%(RandomEnemy%)) + EnemyAG%(RandomEnemy%) / 2 THEN RanAway = True
ChoiceMade% = True
END IF
END SELECT
LOOP UNTIL ChoiceMade%

Well... A player's turn has occured, so now we must reset their agility meter to 0 and make GoGo% false so that our program knows to return to the previous loop.

PlayerGo%(I) = 0: GoGo% = False
END IF
NEXT I
END IF

After a player or an enemy has had their turn, this routine checks to see if either the enemies or the party have been wiped out. If the players are dead, then Lost is made true. If the enemies are dead, then Won is made true.

IF EnemyAlive%(1) = False AND EnemyAlive%(2) = False AND EnemyAlive%(3) = False AND EnemyAlive%(4) = False THEN
Won = True
ELSEIF PlayerAlive%(1) = False AND PlayerAlive%(2) = False THEN
Lost = True
END IF

Next, we check the situation (Players won, lost, ran away, or still fighting) and redraw the battle screen based on what has happened. After this is done, or DrawNeed% variable is reset to 0 again.

IF Lost = False AND Won = False AND RanAway = False THEN
DrawBattleScreen DrawNeed%
ELSE
DrawBattleScreen 2
END IF
DrawNeed% = 0

Finally... This whole battle loop continues until something occurs to end the battle. This will be the players winning, losing, or running away. If any of these happen. The engine exits the loop.

LOOP UNTIL Lost OR Won OR RanAway

Once the loop is exited, the engine determines what has happened. If the players lost, a losing message is displayed. If they ran away, a message is displayed saying so. If they won, a message is displayed saying so, then the total gold and experience they won is calculated, displayed, and added to the living characters totals.

IF Lost THEN
LOCATE 2, 2: PRINT "Annhiliated..."
ELSEIF RanAway THEN
LOCATE 2, 2: PRINT "You got away!"
ELSEIF Won THEN
LOCATE 2, 2: PRINT "Victory!"
WHILE INKEY$ = "": WEND
TotalEXP% = EnemyEXP%(1) + EnemyEXP%(2) + EnemyEXP%(3) + EnemyEXP%(4)
FOR I = 1 TO 2
IF PlayerAlive%(I) THEN PlayerEXP&(I) = PlayerEXP&(I) + TotalEXP%
NEXT I
DrawBattleScreen 2
LOCATE 2, 2: PRINT "Gained"; TotalEXP%; "experience."
WHILE INKEY$ = "": WEND
TotalGold% = EnemyGold%(1) + EnemyGold%(2) + EnemyGold%(3) + EnemyGold%(4)
DrawBattleScreen 2
LOCATE 2, 2: PRINT "Gained"; TotalGold%; "gold."
END IF
WHILE INKEY$ = "": WEND
END SUB

The ChoiceBox sub.
This sub is used to draw the box in the upper left corner of the battle screen. It is used to show the names of the enemies that are alive and also used to create the menu for choosing a character's course of action.

Notice that a BoxType% variable is passed to it. This tells the sub which type of box to draw. If a box with the enemy names is needed, it is called like this: ChoiceBox 0 If a box with the player's choices is needed, it is called like this: ChoiceBox 1

SUB ChoiceBox (BoxType%)

LINE (0, 0)-(120, 50), 23, B
LINE (1, 1)-(119, 49), 25, B
LINE (2, 2)-(118, 48), 24, B
LINE (3, 3)-(117, 47), 0, BF

IF BoxType% = 0 THEN
IF EnemyAlive%(1) THEN
LOCATE 2, 2: PRINT EnemyName$(1)
END IF
IF EnemyAlive%(2) THEN
LOCATE 3, 2: PRINT EnemyName$(2)
END IF
IF EnemyAlive%(3) THEN
LOCATE 4, 2: PRINT EnemyName$(3)
END IF
IF EnemyAlive%(4) THEN
LOCATE 5, 2: PRINT EnemyName$(4)
END IF
ELSEIF BoxType% = 1 THEN
LOCATE 2, 5: PRINT "ATTACK"
LOCATE 5, 5: PRINT "RUN"
END IF

END SUB

The DrawBattleScreen sub.
This sub is used to draw the battle screen, and all of the characters which are currently on it.

Notice that the ScreenType% variable is passed to it. This is used to determine what parts of the screen to draw.

SUB DrawBattleScreen (ScreenType%)

DEF SEG = &HA000
IF ScreenType% = 0 THEN
ChoiceBox 0
StatsBox
ELSEIF ScreenType% = 1 THEN
BLOAD "back1.bsv"
GET (PlayerX%(1), PlayerY%(1))-(PlayerX%(1) + 31, PlayerY%(1) + 31), BackSprite%(0)
GET (PlayerX%(2), PlayerY%(2))-(PlayerX%(2) + 31, PlayerY%(2) + 31), BackSprite%(514)
ChoiceBox 0
StatsBox
ELSEIF ScreenType% = 2 THEN
BLOAD "back2.bsv"
END IF
DEF SEG

IF ScreenType% < 2 THEN
Yellow = PlayerGo%(1) + 128
IF Yellow% > 228 THEN Yellow% = 228
IF Yellow% > 128 THEN LINE (128, 16)-(Yellow%, 18), 44, BF
IF Yellow% < 228 THEN LINE (Yellow% + 1, 16)-(228, 18), 40, BF
Yellow = PlayerGo%(2) + 128
IF Yellow% > 228 THEN Yellow% = 228
IF Yellow% > 128 THEN LINE (128, 32)-(Yellow%, 34), 44, BF
IF Yellow% < 228 THEN LINE (Yellow% + 1, 32)-(228, 34), 40, BF
END IF

FOR I = 1 TO 4
IF EnemyAlive%(I) THEN
PUT (EnemyX%(I), EnemyY%(I)), Enemies%(514 * 4), AND
PUT (EnemyX%(I), EnemyY%(I)), Enemies%(514 * EnemyType%(I)), XOR
END IF
NEXT I

FOR I = 1 TO 2
IF PlayerAlive%(I) THEN
PUT (PlayerX%(I), PlayerY%(I)), Players%(514 * 6), AND
PUT (PlayerX%(I), PlayerY%(I)), Players%(514 * PlayerType%(I)), XOR
END IF
NEXT I

END SUB

The GetHandLocation sub.
This sub is used to determine where to draw the hand pointer when the player chooses to attack an enemy. Notice that, it will check to see which enemy is alive before giving values to HandX% and HandY%. This is done so that the hand is never pointing to an enemy which is no longer there.

SUB GetHandLocation

IF EnemyAlive%(1) THEN
HandX% = 8: HandY% = 85
ELSEIF EnemyAlive%(2) THEN
HandX% = 114: HandY% = 85
ELSEIF EnemyAlive%(3) THEN
HandX% = 8: HandY% = 135
ELSEIF EnemyAlive%(4) THEN
HandX% = 114: HandY% = 135
END IF

END SUB

The InitBattle sub.
This sub is used to determine four enemies for the battle and to load their stats into the respective variables. Here, you can change the enemy names, as well as their statistics. You can make tough enemies, or weak enemies using this sub.

REM $DYNAMIC
SUB InitBattle

FOR I = 1 TO 4
EnemyType%(I) = INT(RND * 4)
IF EnemyType%(I) = 0 THEN
EnemyName$(I) = "Green Goblin"
EnemyMaxHP%(I) = 20: EnemyMaxMP%(I) = 5
EnemyST%(I) = 3: EnemyDF%(I) = 2: EnemyAG%(I) = 3
EnemyMS%(I) = 1: EnemyMD%(I) = 3
EnemyEXP%(I) = 10: EnemyGold%(I) = 4
EnemyAlive%(I) = True
ELSEIF EnemyType%(I) = 1 THEN
EnemyName$(I) = "Blue Goblin"
EnemyMaxHP%(I) = 20: EnemyMaxMP%(I) = 25
EnemyST%(I) = 2: EnemyDF%(I) = 1: EnemyAG%(I) = 4
EnemyMS%(I) = 5: EnemyMD%(I) = 4
EnemyEXP%(I) = 14: EnemyGold%(I) = 6
EnemyAlive%(I) = True
ELSEIF EnemyType%(I) = 2 THE

Quick! Spot the stoner rock referance in the next line! :)

EnemyName$(I) = "Orange Goblin"
EnemyMaxHP%(I) = 40: EnemyMaxMP%(I) = 0
EnemyST%(I) = 5: EnemyDF%(I) = 3: EnemyAG%(I) = 2
EnemyMS%(I) = 1: EnemyMD%(I) = 1
EnemyEXP%(I) = 18: EnemyGold%(I) = 10
EnemyAlive%(I) = True
ELSEIF EnemyType%(I) = 3 THEN
EnemyName$(I) = "Purple Goblin"
EnemyMaxHP%(I) = 25: EnemyMaxMP%(I) = 15
EnemyST%(I) = 3: EnemyDF%(I) = 3: EnemyAG%(I) = 3
EnemyMS%(I) = 3: EnemyMD%(I) = 3
EnemyEXP%(I) = 16: EnemyGold%(I) = 8
EnemyAlive%(I) = True
END IF
EnemyHP%(I) = EnemyMaxHP%(I): EnemyMP%(I) = EnemyMaxMP%(I)
NEXT I

END SUB

The InitRandomStats sub.
This sub is used to create random stats for the players just for the puprose of this engine. Most of these stats you will not need to use for your battle engine. They are there simply to give some usable character statistics in battle. You will want to replace these with your own character stats.

SUB InitRandomStats

PlayerName$(1) = "DarkDread": PlayerName$(2) = "Tyranny"
PlayerAlive%(1) = True: PlayerAlive%(2) = True

PlayerMaxHP%(1) = 52: PlayerMaxHP%(2) = 37
PlayerMaxMP%(1) = 11: PlayerMaxMP%(2) = 31

PlayerHP%(1) = PlayerMaxHP%(1): PlayerHP%(2) = PlayerMaxHP%(2)
PlayerMP%(1) = PlayerMaxMP%(1): PlayerMP%(2) = PlayerMaxMP%(2)

PlayerST%(1) = 7: PlayerST%(2) = 5
PlayerDF%(1) = 4: PlayerDF%(2) = 0
PlayerAG%(1) = 4: PlayerAG%(2) = 3
PlayerMS%(1) = 4: PlayerMS%(2) = 8
PlayerMD%(1) = 5: PlayerMD%(2) = 9

PlayerType%(1) = 0: PlayerType%(2) = 3

These next lines, you may wish to keep. They tell the engine where to put each enemy and character when drawing them.

EnemyX%(1) = 25: EnemyY%(1) = 75
EnemyX%(2) = 125: EnemyY%(2) = 75
EnemyX%(3) = 25: EnemyY%(3) = 125
EnemyX%(4) = 125: EnemyY%(4) = 125

PlayerX%(1) = 275: PlayerY%(1) = 75
PlayerX%(2) = 275: PlayerY%(2) = 125

END SUB

The InitSprites sub.
This sub is used to load the enemy, player, and hand pointer graphics into the arrays. These arrays are then used to display the necessary graphics by the battle engine.

SUB InitSprites

DEF SEG = VARSEG(Players%(0)): BLOAD "players.spr", VARPTR(Players%(0)): DEF SEG
DEF SEG = VARSEG(Enemies%(0)): BLOAD "enemies.spr", VARPTR(Enemies%(0)): DEF SEG
DEF SEG = VARSEG(Hand%(0)): BLOAD "handy.spr", VARPTR(Hand%(0)): DEF SEG

END SUB

The StatsBox sub.
This sub draws the player statistic box in the upper right corner of the screen. It is called from the DrawBattleScreen sub when the player's HP/MP have changed and need to be displayed. You'll also notice a calculation which checks to see if a player has 25% or less HP and changes the colour of the HP/MaxHP to a bright red if this is so. It is done this way to give the player a warning when the HP of one of the characters is falling to dangerous levels. You will also notice that the MP is displayed in a bright green colour. This is to easily differentiate between the HP and MP. Handy no?

REM $STATIC
SUB StatsBox

LINE (121, 0)-(319, 50), 23, B
LINE (122, 1)-(318, 49), 25, B
LINE (123, 2)-(317, 48), 24, B
LINE (124, 3)-(316, 47), 0, BF

LOCATE 2, 17: PRINT PlayerName$(1)
IF PlayerHP%(1) / PlayerMaxHP%(1) < .25 THEN COLOR 40
LOCATE 2, 30: PRINT USING "HP ###/"; PlayerHP%(1)
LOCATE 2, 37: PRINT USING "###"; PlayerMaxHP%(1)
COLOR 47
LOCATE 3, 30: PRINT USING "MP ###/"; PlayerMP%(1)
LOCATE 3, 37: PRINT USING "###"; PlayerMaxMP%(1)

COLOR 15
LOCATE 4, 17: PRINT PlayerName$(2)
IF PlayerHP%(2) / PlayerMaxHP%(2) < .25 THEN COLOR 40
LOCATE 4, 30: PRINT USING "HP ###/"; PlayerHP%(2)
LOCATE 4, 37: PRINT USING "###"; PlayerMaxHP%(2)
COLOR 47
LOCATE 5, 30: PRINT USING "MP ###/"; PlayerMP%(2)
LOCATE 5, 37: PRINT USING "###"; PlayerMaxMP%(2)
COLOR 15

END SUB

The TimerDelay sub.
This sub is used as a delay sub. It is much more precise than the SLEEP command, as is not system dependant like a FOR...NEXT delay. Of course, there are much more precise delay routines which you can do, but this one is good enough for the battle. One thing you may wish to add to this, is a small routine which checks for midnight rollover. I've left it up to you if you wish to include the midnight rollover check.

SUB TimerDelay (Seconds!)

CurrentTime! = TIMER
WHILE CurrentTime! + Seconds! > TIMER: WEND

END SUB

Putting it all together.
Well... Now that you have an idea of how this engine works, it's time to put it all together. If you look at the battle.bas file from the zip included with this routine, you'll see how it all looks in QB.

Basically, what you'll want to do, is take the stuff that's in the main part of the battle.bas file, and copy it over to your RPG. Of course, don't forget to change anything you may need to change. Then, copy all of the subs to your RPG as well. Now you're one step away from having semi-active battles in your RPG!

This is the easy part... Below, I've given you an example of how to make the random battles work in your RPG. You will likely have to change some lines, but this is the basic idea. Just put this code in your main loop:

RANDOMIZE TIMER
Fight% = INT(RND * 10) + 1
IF Fight% = 5 THEN
Battle
END IF

There you go. That's all you need to get started. Should you have any problems with the engine, feel free to e-mail me at darkdread@hotmail.com and I'll be happy to help you out. Good luck with your RPG!

Next time (and there WILL be a next time, mwuahahah!).

Well... These tutorials are written for YOU. So tell me what you want to read about next. Do you want to know how to create an Eye of the Beholder/ Legend of Lith 2 style 3D engine... or something else?

Incidentally, if you wish to check out the Serenity homepage for some free RPGs written in QB, you can go here

Cheers!

Do you suggest that DD cover the FREE Semi-Active Modulating Action battle engine? Tell him here.

 

>>>Back to Top<<<

mod 1

By Seav

P
O
W
E
R

P
R
O
G
R
A
M
M
I
N
G

2

Hello there! Here comes the second installment of my power programming article. This time around, I'll be teaching you of the many ancient and arcane ways of implement timing into your programs!

What the hell is timing? Let me answer by giving you some applications. For instance, you can insert precise delays while running cut scenes. You can also display your program's frame rate to see if it's competing on a level with snail races. You can also do a synchronization technique popularly used in low-level music programming.

QBasic commands that do time
There are a lot of commands and statements in QBasic that can be used for timing applications. But in my opinion, all you ever need is the TIMER function. I'll tell you why by degrading the other statements. =}

The ON TIMER statement, and it's sister statements, TIMER ON, TIMER OFF, and TIMER STOP, certainly makes timing very easy for newbies. But if you're advanced enough to actually bother with timing, then you should leave these statements alone. They slow down your program and when you compile it, they bloat the size up.

Another command that QBASIC has is the SLEEP statement. SLEEP is essentially a delaying statement, but it delays in only 1-second increments, and when a person presses a key, the timing is skipped. Also, the keystroke that ends a SLEEP isn't flushed from the keyboard buffer. So if you use SLEEP as a "Press any key to continue" command, you'll eventually get keyboard beeps, especially if you don't use INPUT$ or INKEY$ to relieve the keystrokes.

Don't bother anymore with TIME$. It's only useful purpose is to display and change the date. In order to do calculations with time using TIME$, you still have to mess around with string functions like MID$ and VAL (which can be very slow). Besides, you can easily compute the hour, minute, and second using TIMER.

My verdict? TIMER is possibly the best command to use for all your timing processes. One major problem with TIMER is that it only has a frequency of 18.2 Hz (ticks per second). This means that you can only time up to around five-hundredths of a second. This may seriously affect your timing, especially if you don't know about it.

Basic timing principle
Imagine that you have a broken ruler. The end with the zero mark was broken off by your younger brother, so that means that the ruler is useless, right? Well, you can use the ruler to spank your errant brother. Err...what I mean is, even if the end is broken, as long as the ruler is longer than the things you want to measure, you can still use it to measure the length of things. You just line up the object of choice along the ruler and record the points where the object starts and ends. The difference is the length.

The same principle is used for timing inside QBasic. You just record the time when the thing you want to time started, and record the time when it ended. The time elapsed is then the difference between the two recorded times.

Start! = TIMER
.
. Some part of the program you want to time
.
Finish! = TIMER

PRINT "Time Elapsed: "; Finish! - Start!

It's that simple. This basic principle is used in almost all applications that involve the TIMER command. You can use this principle to compare similar routines and see which is faster. This is called benchmarking.

Degree of error
Now I'll show you why the frequency of the TIMER is an important issue when you're dealing with timing applications. If the frequency or resolution of your timing device is very low, the larger the degree of error you may get when timing events.

I'll illustrate this point with an extreme example. Let's suppose that your digital watch only tells you the time in minutes (no seconds). And you wish to see how long a TV commercial lasted. When the commercial starts, you record the time on your watch: It's 8:25. When the commercial ends, you find out that the time is 8:27. So the commercial lasted 2 minutes, right? Wrong. The commercial lasted for 2 minutes, plus or minus 1 minute. Why plus or minus a whole minute? Well, the commercial could have actually started at 8:25:59 and ended at 8:27:00, in which case, the commercial actually lasted for 1 minute and 1 second. You could go the other way around. If the commercial started at 8:25:00 and ended at 8:27:59, then the commercial lasted for 2 minutes and 59 seconds—almost three minutes! So with your weird watch, you could say that the commercial lasted from around 1 minute to 3 minutes. See my point?

Your watch has a frequency of 1 tick per minute or a resolution one minute while the TIMER function has a frequency of around 18.2 Hz (ticks per second), or a resolution of around 5 hundredths of a second. You might say that since the frequency of the TIMER function is high, then our timing will be precise. Not quite. If the event you're timing lasts on the order of milliseconds, then TIMER isn't precise enough for that. Just remember that the time results you will obtain will have an possible error of around 0.1 seconds.

Midnight Rollover Issues
Another thing you should beware of is the midnight rollover. At midnight, the TIMER function would wrap around from 86399.91 seconds to 0 seconds. So time calculations that span midnight would be wrong. Make sure that all your timing routines work at midnight especially if it's in a game. Otherwise, just avoid running the program near midnight.

Using TIMER to delay
A popular application of TIMER is to implement it as a delaying subroutine. Most programmers do their own variation of it, and here's my version:

SUB Delay (Secs!)

' Get time when the delay ends
EndTime! = TIMER + Secs!
' Adjust for midnight
IF EndTime! > 86400 THEN
     EndTime! = EndTime! - 86400
END IF

' Delaying loop
DO
LOOP UNTIL TIMER >= EndTime! AND (TIMER - EndTime!) < 1

END SUB

You should avoid using an empty FOR-NEXT loop to delay the program, since the length of the delay created by the loop would vary according to the speed of a computer. What would be a one-second delay on a 286 might not be a delay at all on a high-end Pentium. I really wouldn't recommend it even if you perform a speed test at the start of the program to find out the computer's speed.

Frame rates
Ah, frame rates. One of the most often asked question in discussion boards. How does one compute frame rates or the FPS (frames per second)? To quote Entropy, "however you want." There are many solutions to this problem, and which one you choose depends on your program's particular needs.

A frame is essentially a single loop of code (usually one that includes drawing to the screen). By computing the frame rate, you are, in effect, computing how many loops the computer can perform in a given amount of time.

The first method of getting the FPS is very simple. You just time your whole program (while counting the number of frames executed) then divide the number of frames by the time, that's your FPS.

Start! = TIMER
DO
     .
     . Program loop
     .
     Frames& = Frames& + 1
LOOP UNTIL Finished
Finish! = TIMER

ProgramTime! = Finish! - Start!
PRINT "FPS:"; Frames& / ProgramTime!

This method can be very accurate especially if you run the program for a while. The problem with this method is that it only prints the FPS when the program is through. You have no way to know where your program bogs down.

Obviously the answer to the first method's inadequacies is to display the frame rate while the program is running. One method would be to time each frame and derive the frame rate from that. This isn't very nice since you'd only get a maximum of 18.2 FPS for slow programs while faster programs would give the "Divide by Zero" error. Why is that? Recall that the TIMER has a resolution of 5-hundredths of a second, which would give us 18.2 FPS. If your frames are faster than .05 seconds, then the frame would have an elapsed time of 0 seconds, giving a Divide by Zero error.

To get around this, instead of timing every frame, you can time a specified number of frames, and compute the frame rate from that. It would be vastly better, however, to display the FPS on a per-second basis. That is, instead of timing frames, you just count the frames that has been performed every second.

TimeNow! = TIMER
DO
     .
     . Program loop
     .
     Frames = Frames + 1

     IF TIMER - TimeNow! >= 1 THEN
          PRINT "FPS:"; Frames
          TimeNow! = TIMER
          Frames = 0
     END IF
LOOP UNTIL Finished

I hope that you can follow the logic of the program code above and see how it manages to display the frame rate every second.

An alternative to TIMER
Sadly, TIMER does not fulfill the requirements of quite a number of programmers. The most disappointing fact of it is that it only has a frequency of 18.2 Hz. Many have tried to get around this by changing the frequency which is done by programming the PIT chip (the chip that handles the system time). However, changing the frequency without disrupting the system clock is a messy process (which involves programming the PIT chip, and redirecting interrupts). Also, this method does not seem to work inside Windows!

Fortunately, I have found a workaround. I have devised a routine called CLOCK which can have a frequency greater than 18.2 Hz. In my game, The Labyrinth, I use a variant of the routine to give me a frequency of 291.2 Hz. This has a resolution of 3 milliseconds (very adequate for my purposes). This routine does not mess up the system time and is very fast, since it uses integer math. The routine, however, does not return the number of seconds that have elapsed since midnight, but instead returns the number of "ticks" that have elapsed since midnight. The frequency of the ticks corresponds to the frequency of the routine. (In my game, the value increases by 291 every second.) Here's the routine (which has a frequency of around 4660 Hz):

FUNCTION CLOCK&

' Get the number of timer ticks at
' 0000:046C
DEF SEG = 0
Ticks& = PEEK(&H46C)
Ticks& = Ticks& + PEEK(&H46D) * 256
Ticks& = Ticks& + PEEK(&H46E) * 65536
DEF SEG

' Latch the counter and obtain the PIT
' countdown status.
OUT &H43, &H4
LSB = INP(&H40)
HSB = 255 - INP(&H40)

' Compute the CLOCK& value
CLOCK& = Ticks& * 256 + HSB

END FUNCTION

I can't explain in detail how the routine works. Suffice it to say that it "peeks" at the PIT chip and system timer status.

To verify that the program works, try the program below with the function above. Here the program divides the CLOCK value by its frequency, 4660.859, to see that it indeed matches the value provided by TIMER. The actual difference varies from 1 to 16 milliseconds. That's because the CLOCK updates faster than TIMER. Notice that the difference doesn't exceed the resolution of TIMER which is 54 milliseconds.

CLS
DO
     TimerVal! = TIMER
     ClockVal! = CLOCK / 4660.859#

     LOCATE 1
     PRINT USING "CLOCK: #####.###"; ClockVal!
     PRINT USING "TIMER: #####.###"; TimerVal!
LOOP UNTIL LEN(INKEY$)

It would be a good idea to convert the function to assembly and to compile it in include it in a library, or as a string to be executed by CALL ABSOLUTE.

It's the end of the article as we know it =}
Due to space constraints, I can't possibly include every possible application of timing and I can't address all the questions that you might have regarding the subject. If you have any questions, comments, or stuff, just send me some mail at the address below. For now, that's it, and always remember...Time is Gold...=}

What Seav doesn't know is that we like programming the PIT Chip to mess with ppl's computers. Give him a whompin' for thinking otherwise.

 

>>>Back to Top<<<<

 

Art by Gavan

By Gavan

Welcome to the third art tutorial, written by…Me. This tutorial can be read independently of the previous two, although it is suggested that you read the others anyway, because they were written by Me (and you can pick up the conventions used in My tutorials). Is there a slight air of egotism in this article? =) This tutorial steps out of shading concepts and into the wonderful world of tiling. Tiles are (usually square) blocks of art that can be repititiously blitted to the game screen to make a world. The advantage to using tiles, as opposed to using very large bitmaps depicting entire areas, is that tiles take up much less space, and are easier to work with from a programming perspective (testing collision, for example).

The downside to tiles is that they are not entirely flexible, and the final product they represent tends to look "blocky" or repititious. There are several methods of tiling, but first it is necessary to learn seamless tiling, the root of tiling techniques. One thing to note in this tutorial is that some of the larger coordinate blocks are depicted with cyan borders for the purpose of simplicity.

 

A
R
T
 
3

Mmm...examples =)

 
 

seamless tiling
For the purpose of making this tutorial less confusing (as if it were not confusing enough to begin with |-) ), I have divided the example bitmap into two parts: the first part shows the tiles used, the second part shows the tiles being tiled in a two by two square. I will distinguish between the two examples by using a prefix of "A" or "B" (the former refers to the first bitmap, the latter to the second) in front of the bitmap coordinates. That said, look at the asphalt tile in A(1,1). You can already tell this tile sucks =)…and sure enough, looking how it tiles in B(1,1), you can see that it is definitely not seamless.

What is that you ask, grasshopper? What is a seamless tile? A seamless tile is a bitmap that can be copied and placed repeatedly in an adjacent fashion such that the viewer can not tell where the origin of the original bitmap is. In simpler terms, a seamless tile, when tiled, has no visible edges. One bitmap should line right up with the next with no awkward lines between if it is seamless. Look at A(2,1) for an example of a seamless tile, and its tiled output in B(2,1).

Making a seamless tile can be very complex and frusterating, if you only use trial and error. However, a fairly simple trick can save you lots of work. I call it CAPEA, or "cut and paste edge alignment." It works like this: first, a tile is made, without regard to its seamlessness. Next it is cut in half horizontally…the upper half is place adjacently below the lower half. Then the edge between the two is "fixed," such that no seam appears in the currently visible tile. Then the lower half is placed in its original position, and the tile is cut vertically, the right half is placed adjacent to the other side of the left.

Finally, the seam between the left and right halves is fixed, and the right half is placed back in its original position. This process may seem awkward, but it is the single most efficient way I know of making a seamless tile. To clear things up a bit, I made two examples. In the first example A(1,2)-A(7,2), the goal was to create a checker pattern that would blend completely along the edges, but not the inside (which is rather redundant for a checker tile, but it was intended for an easily visualized example). The resulting tiling can be seen in B(3,1). In the second example A(1,3)-A(7,3), a (more real-world) random ellipse pattern was made; the goal of course, was to have it tile seamlessly.

This required, in some instances, slightly reshaping or resizing the ellipses. In the result B(4,1), there are several larger ellipses in the center of each tile…this results in a conspicuous pattern (the viewer can easily discern where each tile is, relative to the akward oval). In general, you will want to avoid making any sections of your tiles too different from other sections…no internal components of your tiles should be akwardly large, small, colored, or greatly varied in any other way from the rest of the tile components. I noticed in a certain game the bookshelf tile had a skull on it, next to several books. The skull did add a bit of atmosphere, but when I saw that every bookshelf in the game had the same skull on it…it made me wonder =). To make lots of jargon short, avoid this mistake.

blending tile regions
Knowing how to make a seamless tile is only the first step…what if you want to make one tile type blend with another type? This is particularly useful for terrain…say if you wanted your grass tiles to gradually fade to your sand tiles, or your rock tiles to your dirt tiles. We want to have tiles that fade in every direction, so we can create any possible shape (imagine trying to create a large, rectangular patch of grass on some sand: we would need pieces that fade to each of the four sides, and the four corners). So let us enumerate ALL possible transitions, so we can build any shape of transitions. This can be done with some simple block tricks…if we divide a tile into four quadrants, where each quadrant can be "on" (represented by black) or "off" (represented by any shade of red) (think binary =) ).

There are sixteen possible combinations…but we do not want the one that is completely blank (all quadrants off); after all, how useful is a blank tile? Subtracting one, this leaves us with fifteen possible combinations. If you align these possible combinations A(1,4), you can easily distinguish that they form four sides, four corners, four inverse corners, two touching corners, and a whole tile. This is the base set we want to use for creating tiles that shift from one tile type to another. From this set, we can create a blender mask, a mask that will dither your base tile set onto any type of terrain. To create a blender mask, simply fade the black dot region onto the red, using less and less frequent dot dithering, and do the opposite with the red region A(2,4). Then, once you have a base tile, you can just make fifteen copies of the tile and place this blender mask over it A(3,4), dropping out the black color as a transparency. Then only the red region has to be dropped out from the transitioning tiles when they are placed on top of other tiles, as shown in B(1,5). As difficult as I may have made that simple process seem, I guarantee that it works great, and is very easy once you create the blender mask…or you can even copy mine if you are lazy =).

 

More tiles for ya

 
 

(not so?) trivial corrections from previous issues
I would like to correct some explanations of techniques that I have now refined…the first is metallic shading for flat objects. A better way to do it is use streaked regions that fade from dark to light, and then back to dark again (like the gold ones in A(2,5). These regions are used in varied angles on each face of the object. The second technique that I want to correct is pseudo- refraction for flat faced objects…for each face, a random angle of the metallic shading described above should be used. Good examples of this are the jewels I ripped off of the DA2 border, shown in A(3,5).

More tips for making cool tiles

  • Like I've said before, avoid making any areas of your tile too different from the rest of the areas in the tile…unless…
  • …you make multiple versions of one type of tile. Let's take a brick-filled tile for example…you could have a large, awkward brick in one of the tiles, another tile be slightly normal, and another tile have some cracks in it. This way you could distribute the tiles randomly for a varied, not-so- bland-and-uniform environment.
  • Try as you might, you cannot shade an entire tile, because it will not tile correctly (unless you shade darkly on all sides with a highlight in the center, but damn that looks stupid). You can, however, shade components of a tile…take a cobblestone pavement tile for example. You can shade each individual cobblestone and still have the texture tile perfectly.
  • The food pyramid you always see on cereal is boxes BS…nobody can eat eleven servings of carbohydrates in a day…Its just a cheap ploy to get more cereal consumers on the market.
  • Don't run with scissors.

summary
This must be my most confusing, sloppy, and rushed article yet…so if you have any questions, feel free to email me at gavbug@napanet.net, or better yet, ICQ me at 17653643 (no authorization required). Just be so kind as to not ask about the Spice Girls, Backstreet Boys, or any crap like that =) Look out for the next article I will do (not necessarily next issue)…it goes into quantum mechanics and the theory of the fourth dimension; well, it does three dimensions, at least >8) .

Ask Gavan how you can seamlessly tile your blitted sprites at this address.

 

>>>Back to Top<<<

 

next issue

By Zkman

N
E
X
T

Hope you've enjoyed the all-new qb:tm. One year is past, and hopefully the next year will exceed even this. Look forward to a few new in-between articles, including a GREAT Rpg tut from Darkdread, as well as more ripping content on 18 September. Also, don't forget to register your QB company with the Visionaries Exchange. Till next time... END

It's the end of the issue as we know it

>>>Back to Top<<<