
2,525 535 52MB
Pages 1273 Page size 336 x 417.12 pts Year 1999
TE AM FL Y
Game Scripting Mastery Alex Varanese
© 2003 by Premier Press, a division of Course Technology. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system without written permission from Premier Press, except for the inclusion of brief quotations in a review. The Premier Press logo and related trade dress are trademarks of Premier Press, Inc. and may not be used without written permission. Publisher: Stacy L. Hiquet Marketing Manager: Heather Hurley Acquisitions Editor: Mitzi Koontz Series Editor: André LaMothe Project Editor: Estelle Manticas Copy Editor: Kezia Endsley Interior Layout: Bill Hartman Cover Designer: Mike Tanamachi Indexer: Kelly Talbot Proofreader: Sara Gullion ActivePython, ActiveTcl, and ActiveState are registered trademarks of the ActiveState Corporation. All other trademarks are the property of their respective owners. Important: Premier Press cannot provide software support. Please contact the appropriate software manufacturer’s technical support line or Web site for assistance. Premier Press and the author have attempted throughout this book to distinguish proprietary trademarks from descriptive terms by following the capitalization style used by the manufacturer. Information contained in this book has been obtained by Premier Press from sources believed to be reliable. However, because of the possibility of human or mechanical error by our sources, Premier Press, or others, the Publisher does not guarantee the accuracy, adequacy, or completeness of any information and is not responsible for any errors or omissions or the results obtained from use of such information. Readers should be particularly aware of the fact that the Internet is an ever-changing entity. Some facts may have changed since this book went to press. ISBN: 1-931841-57-8 Library of Congress Catalog Card Number: 2001099849 Printed in the United States of America 03 04 05 06 07 BH 10 9 8 7 6 5 4 3 2 1 Premier Press, a division of Course Technology 2645 Erie Avenue, Suite 41 Cincinnati, Ohio 45208
This book is dedicated to my parents, Ray and Sue, and to my sister Katherine, if for no other reason than the simple fact that they'd put me in a body bag if I forgot to do so.
iv
Foreword Programming games is so fun! The simple reason is that you get to code so many different types of subsystems in a game, regardless of whether it's a simple Pac Man clone or a complex triple-A tactical shooter. Coding experience is very enriching, whether you’re writing a renderer, sound system, AI system, or the game code itself; all of these types of programming contain challenges that you get to solve. The best way to code in any of these areas is with the most knowledge you can absorb beforehand. This is why you should have a ton of programming books close at hand. One area of game coding that hasn't gotten much exposure is scripting. Some games don't need scripting—whether or not a game does is often dependant on your development environment and team—but in a lot of cases, using scripting is an ideal way of isolating game code from the main engine, or even handling in-game cinematics. Most programmers, when faced with solving a particular coding problem (let's say handling NPC interaction, for instance), will usually decide to write their own elaborate custom language that integrates with their game code. With the scripting tools available today this isn't strictly necessary, but boy is it fun! Many coders aren’t aware of the range of scripting solutions available today; that’s where this fine book comes in. Game Scripting Mastery is the best way to dive into the mysterious world of game scripting languages. You’ll learn what a scripting language is and how one is written; you’ll get to learn about Lua, Python, and Tcl and how to make them work with your game (I’m a hardcore proponent for Lua, by the way); and, of course, you’ll learn about compiler theory. You’ll even get to examine how a full scripting language is developed! There's lots of knowledge contain herein, and if you love coding games, I'm confident that you'll enjoy finding out more about this aspect of game programming. Have "The Fun!”
John Romero
v
Acknowledgments It all started as I was standing around with some friends of mine on the second day of the 2001 Xtreme Game Developer's Conference in Santa Clara, California, discussing the Premier Press game development series. At the time, I'd been doing a lot of research on the subject of compiler theory—specifically, how it could be applied to game scripting—and at the exact moment I mentioned that a scripting book would be a good idea, André Lamothe just happened to walk by. "Let's see what he thinks," I said, and pulled him aside. "Hey André, have you ever thought about a book on game scripting for your series?" I expected something along the lines of "that's not a bad idea", or "sure-- it's already in production." What I got was surprising, to say the least. "Why don't you write it?" That was literally what he said. Unless you're on some sort of weird version of Jeopardy! where the rules of the game require you to phrase your answer in the form of a book deal, this is a pretty startling response. I blinked, thought about it for about a nanosecond, and immediately said okay. This is how I handle most important decisions, but the sheer magnitude of the events that would be set into motion by this particular one could hardly have been predicted at the time. Never question the existence of fate. With the obligatory anecdote out of the way, there are a number of very important people I'd like to thank for providing invaluable support during the production of this book. It'd be nothing short of criminal if this list didn't start with Mitzi Foster, my acquisitions editor who demonstrated what can only be described as superhuman patience during the turbulent submission and evolution of the book's manuscript. Having to handle the eleventh-hour rewrites of entire chapters (and large ones at that) after they've been submitted and processed is an editor's nightmare— and only one of the many she put up with—but she managed to handle it in stride, with a consistently friendly and supportive attitude. Next up is my copy editor, Kezia Endsley; if you notice the thorough grammatical correctness of even the comments in this book's code listings, you'll have her to thank. Granted, it'll only be a matter of time before the latest version of Microsoft's compilers have a comment grammar checking paperclip, dancing monkey, robot dog, or ethnically ambiguous baby, but her eye for detail is safely appreciated for now. Lastly, rounding out the Game Scripting Mastery pit crew is Estelle Manticas, my project editor who really stepped up to the plate during the later parts of the project, somehow maintaining a sense of humor while planet Earth crumbled around us. Few people have what it takes to manage the workload of an entire book when the pressure's on, and she managed to make it look easy.
vi
Of course, due to my relatively young age and penchant for burning through cash like NASA, I've relied on others to provide a roof over my head. The honor here, not surprisingly, goes to my parents. I'd like to thank my mom for spreading news of my book deal to every friend, relative, teacher, and mailman our family has ever known, and my dad for deciding that the best time to work so loudly on rebuilding the deck directly outside my room is somewhere around zero o'clock in the morning. I also can't forget my sister, Katherine—her constant need for me to drive her to work is the only thing that keeps me waking up at a decent hour. Thanks a lot, guys! And last, and most certainly least, I suppose I should thank that Lamothe guy. Seriously though—I may have toiled endlessly on the code and manuscript, but André is the real reason this book happened (and was also its technical editor). I've gotta say thanks for letting my raid your fridge on a regular basis, teaching me everything I know about electrical engineering, dumping so many free books on me, answering my incessant and apparently endless questions, restraining yourself from ending our more heated arguments with a golf club, and of course, extending such an obscenely generous offer to begin with. It should be known that there's literally no one else in the industry that goes out of their way to help people out this much, and I'm only one of many who've benefited from it. I'd also like to give a big thanks to John Romero, who took time out of his understandably packed schedule to save the day and write the book's Foreword. If not for him, I probably would've had to get my mom to do it. Oh and by the way, just because I think they'll get a kick out of it, I'd like to close with some horrendously geeky shout-outs: thanks to Ironblayde, xms and Protoman—three talented coders, and the few people I actually talk to regularly online—for listening to my constant ranting, and encouraging me to finish what I start (if for no other reason than the fact that I'll stop blabbering about it). You guys suck. Seriously. Now if you'll excuse me, I'm gonna wrap this up. I feel like I'm signing a yearbook.
vii
About the Author Alex Varanese has been obsessed with game development since the mid-1980's when, at age five, he first laid eyes—with both fascination and a strange and unexplainable sense of familiarity—on the 8-bit Nintendo Entertainment System. He's been an avid artist since birth as well, but didn't really get going as a serious coder until later in life, at around age 15, with QBASIC. He got his start as a professional programmer at age 18 as a Java programmer in the Silicon Valley area, working on a number of upstart B2B projects on the J2EE platform before working for about a year as both a semi-freelance and in-house graphic designer. Feeling that life in the office was too restrictive, however, he's since shifted his focus back to game development and the pursuit of future technology. He currently holds the position of head designer and systems architect for eGameZone (http://www.egamezone.net), the successor venture to André LaMothe's Xtreme Games LLC. He spends his free time programming, rendering, writing about himself in the third person, yelling at popup ads, starring in an off-Broadway production of Dude, Where's My Car? The Musical, and demonstrating a blatant disregard for the posted speed limit. Alex Varanese can be reached at [email protected], and is always ready and willing to answer any questions you may have about the book. Please, don't hesitate to ask!
viii
Letter from the Series Editor A long, long, time ago on an 8-bit computer far, far, away, you could get away with hard coding all your game logic, artificial intelligence, and so forth. These days, as they say on the Sopranos "forget about it.…" Games are simply too complex to even think about coding anymore—in fact, 99 percent of all commercial games work like this: a 3D game engine is developed, then an interface to the engine is created via a scripting language system (usually a very high-level language) based on a virtual machine. The scripting language is used by the game programmers, and even more so the game designers, to create the actual game logic and behaviors for the entire game. Additionally, many of the rules of standard programming, such as strict typing and single threaded execution, are broken with scripting languages. In essence, the load of game development falls to the game designers for logic and game play, and to game programmers for the 3D engine, physics, and core technologies of the engine. So where does one start when learning to use scripting in games? Well, there's a lot of stuff on the Internet of course, and you can try to interface languages like Python, Lau, and others to your game, but I say you should know how to do it yourself from the ground up. And that’s what Game Scripting Mastery is all about. This book is a monster—Alex covers every detail you can possibly imagine about game scripting. This is hard stuff, relatively speaking—we are talking about compiler theory, virtual machines, and multithreading here. However, Alex starts off assuming you know nothing about scripting or compilers, so even if you’re a beginner you will be able to easily follow along, provided you take your time and work through the material. By the end of the book you’ll be able to write a compiler and a virtual machine, as well as interface your language to
ix
your existing C/C++ game engine—in essence, you will have mastered game scripting! Also, you will never want to write another parser as long as you live. In conclusion, if game scripting is something you’ve been interested in, and you want to learn it in some serious detail, then this book is the book for you. Moreover, this is the only book on the market (as we go to publication) about this subject. As this is the flagship treatise on game scripting, we’ve tried to give you everything we needed when figuring it out on our own— and I think we have done much, much more. You be the judge! Sincerely,
André LaMothe Series Editor
CONTENTS
AT A
GLANCE
Contents at a Glance
AM FL Y
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xliv
Part One Scripting Fundamentals ..........................1 Chapter 1 An Introduction to Scripting. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Chapter 2 Applications of Scripting Systems. . . . . . . . . . . . . . . . . . . . . . . . 29
TE
x
Part Two Command-Based Scripting ...................61 Chapter 3 Introduction to Command-Based Scripting . . . . . . . . . . . . . . . . 63 Chapter 4 Advanced Command-Based Scripting . . . . . . . . . . . . . . . . . . . 113
Part Three Introduction to Procedural Scripting Languages ...........................153 Chapter 5 Introduction to Procedural Scripting Systems . . . . . . . . . . . . . 155
Team-Fly®
CONTENTS
AT A
GLANCE
Chapter 6 Integration: Using Existing Scripting Systems . . . . . . . . . . . . . 173 Chapter 7 Designing a Procedural Scripting Language . . . . . . . . . . . . . . . 335
Part Four Designing and Implementing a Low-Level Language ..........................367 Chapter 8 Assembly Language Primer. . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 Chapter 9 Building the XASM Assembler . . . . . . . . . . . . . . . . . . . . . . . . . 411
Part Five Designing and Implementing a Virtual Machine ..................................565 Chapter 10 Basic VM Design and Implementation . . . . . . . . . . . . . . . . . . . 567 Chapter 11 Advanced VM Concepts and Issues . . . . . . . . . . . . . . . . . . . . . . 651
Part Six Compiling High-Level Code................749 Chapter 12 Compiler Theory Overview. . . . . . . . . . . . . . . . . . . . . . . . . . . . 751 Chapter 13 Lexical Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 783 Chapter 14 Building the XtremeScript Compiler Framework . . . . . . . . . . 857
xi
xii
CONTENTS
AT A
GLANCE
Chapter 15 Parsing and Semantic Analysis . . . . . . . . . . . . . . . . . . . . . . . . . 983
Part Seven Completing Your Training ..................1137 Chapter 16 Applying the System to a Full Game . . . . . . . . . . . . . . . . . . . 1139 Chapter 17 Where to Go From Here . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1179 Appendix A What’s on the CD? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1203
INDEX........................................1207
CONTENTS
xiii
Contents Introduction ..................................xliv
Part One Scripting Fundamentals...............1 Chapter 1 An Introduction to Scripting................3 What Is Scripting? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Structured Game Content—A Simple Approach. . . . . . . . . . . . . 6 Improving the Method with Logical and Physical Separation . . 10 The Perils of Hardcoding. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Storing Functionality in External Files . . . . . . . . . . . . . . . . . . . . 14 How Scripting Actually Works. . . . . . . . . . . . . . . . . . . . . . . . . . . 15 An Overview of Computer Programming . . . . . . . . . . . . . . . . . . . . . . . . . . 16 An Overview of Scripting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
The Fundamental Types of Scripting Systems . . . . . . . . . . . . . . 20 Procedural/Object-Oriented Language Systems . . . . . . . . . . . . . . . . . . . . . . 21 Command-Based Language Systems. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Dynamically Linked Module Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Compiled versus Interpreted Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Existing Scripting Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Ruby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Lua. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
xiv
CONTENTS
Chapter 2 Applications of Scripting Systems.......29 The General Purpose of Scripting . . . . . . . . . . . . . . . . . . . . . . . 30 Role Playing Games (RPGs) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Complex, In-Depth Stories . . . . The Solution . . . . . . . . . . . . Non-Player Characters (NPCs) . The Solution . . . . . . . . . . . . Items and Weapons . . . . . . . . . . The Solution . . . . . . . . . . . . Enemies . . . . . . . . . . . . . . . . . . . The Solution . . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
32 33 34 35 41 43 45 46
First-Person Shooters (FPSs) . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 Objects, Puzzles, and Switches (Obligatory Oh My!) The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . Enemy AI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . The Solution . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
51 52 57 59
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Part Two Command-Based Scripting ..........61 Chapter 3 Introduction to Command-Based Scripting.......................................63 The Basics of Command-Based Scripting. . . . . . . . . . . . . . . . . . 64 High-Level Engine Control. . . . . . . Commands . . . . . . . . . . . . . . . . . . Master of Your Domain . . . . . . . . . Actually Getting Something Done .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
65 68 68 69
Command-Based Scripting Overview. . . . . . . . . . . . . . . . . . . . . 69 Engine Functionality Assessment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Loading and Executing Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Looping Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
CONTENTS
xv
Implementing a Command-Based Language . . . . . . . . . . . . . . . 74 Designing the Language . . . . . . . . . . . . . . Writing the Script . . . . . . . . . . . . . . . . . . Implementation . . . . . . . . . . . . . . . . . . . . Basic Interface . . . . . . . . . . . . . . . . . . Execution. . . . . . . . . . . . . . . . . . . . . . Command and Parameter Extraction. The Command Handlers . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
74 75 75 75 78 81 87
Scripting a Game Intro Sequence. . . . . . . . . . . . . . . . . . . . . . . . 90 The Language. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 The Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 The Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Scripting an RPG Character’s Behavior . . . . . . . . . . . . . . . . . . . 95 The Language. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Improving the Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Managing a Game Character . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 The Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 The Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 The Demo’s Main Loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Concurrent Script Summary . . . . . . . On the CD . . . . . . Challenges . . . . . .
Execution ......... ......... .........
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
109 110 111 111
Chapter 4 Advanced Command-Based Scripting.....113 New Data Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Boolean Constants . . . . . . . . . . . . . . Floating-Point Support . . . . . . . . . . . . General-Purpose Symbolic Constants An Internal Constant List. . . . . . . A Two-Pass Approach. . . . . . . . . . Loading Before Executing. . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
115 115 116 117 120 124
xvi
CONTENTS
Simple Iterative and Conditional Logic . . . . . . . . . . . . . . . . . . 125 Conditional Logic and Game Flags . Grouping Code with Blocks . . . . . The Block List . . . . . . . . . . . . . . . . Iterative Logic . . . . . . . . . . . . . . . . Nesting . . . . . . . . . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
125 128 129 131 133
Event-Based Scripting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Compiling Scripts to a Binary Format . . . . . . . . . . . . . . . . . . . 137 Increased Execution Speed . . . . . Detecting Compile-Time Errors . Malicious Script Hacking . . . . . . . How a CBL Compiler Works. . . . Executing Compiled Scripts . . Compile-Time Preprocessing . Parameters. . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
137 139 139 140 142 143 144
Basic Script Preprocessing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 File-Inclusion Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
Part Three Introduction to Procedural Scripting Languages ...............153 Chapter 5 Introduction to Procedural Scripting Systems ..........................155 Overall Scripting Architecture . . . . . . . . . . . . . . . . . . . . . . . . . 156 High-Level Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Low-Level Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 The Virtual Machine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
A Deeper Look at XtremeScript . . . . . . . . . . . . . . . . . . . . . . . 161 High-Level Code/Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Lexical Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
CONTENTS
Parsing/Syntactic Analysis . . . . . . . . . Semantic Analysis . . . . . . . . . . . . . . . Intermediate Code Generation . . . . Optimization . . . . . . . . . . . . . . . . . . Assembly Language Generation . . . . The Symbol Table . . . . . . . . . . . . . . . The Front End versus the Back End . Low-Level Code/Assembly. . . . . . . . . . . The Assembler . . . . . . . . . . . . . . . . . The Disassembler . . . . . . . . . . . . . . The Debugger . . . . . . . . . . . . . . . . . The Virtual Machine . . . . . . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
xvii
. . . . . . . . . . . .
164 165 165 165 166 166 166 167 167 167 167 168
The XtremeScript System . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 High-Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 Low-Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 Runtime. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
Chapter 6 Integration: Using Existing Scripting Systems ..........................173 Integration . . . . . . . . . . . . . . . . . . . . . . Implementation of Scripting Systems. The Bouncing Head Demo . . . . . . . . . Lua (and Basic Scripting Concepts) . . The Lua System at a Glance . . . . . The Lua Library . . . . . . . . . . . . The luac Compiler . . . . . . . . . . The lua Interactive Interpreter. The Lua Language . . . . . . . . . . . . . Comments . . . . . . . . . . . . . . . . Variables . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
174 179 181 185 . . . . . . .
185 185 185 186 187 188 188
xviii
CONTENTS
Data Types . . . . . . . . . . . . . . . . . . . . . . . . Tables. . . . . . . . . . . . . . . . . . . . . . . . . . . . Advanced String Features. . . . . . . . . . . . . Expressions . . . . . . . . . . . . . . . . . . . . . . . Conditional Logic. . . . . . . . . . . . . . . . . . . Iteration. . . . . . . . . . . . . . . . . . . . . . . . . . Functions . . . . . . . . . . . . . . . . . . . . . . . . . Integrating Lua with C . . . . . . . . . . . . . . . . . . Compiling a Lua Project. . . . . . . . . . . . . . Initializing Lua . . . . . . . . . . . . . . . . . . . . . Loading Scripts. . . . . . . . . . . . . . . . . . . . . The Lua Stack . . . . . . . . . . . . . . . . . . . . . Exporting C Functions to Lua . . . . . . . . . Executing Lua Scripts . . . . . . . . . . . . . . . . Importing Lua Functions . . . . . . . . . . . . . Manipulating Global Lua Variables from C Re-coding the Alien Demo in Lua . . . . . . Advanced Lua Topics . . . . . . . . . . . . . . . . . . . Web Links . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
191 193 197 198 200 201 203 205 206 207 208 209 215 219 221 226 228 241 242
Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 The Python System at a Glance . . . . . . Directory Structure . . . . . . . . . . . . The Python Interactive Interpreter The Python Language. . . . . . . . . . . . . . Comments . . . . . . . . . . . . . . . . . . . Variables . . . . . . . . . . . . . . . . . . . . Data Types . . . . . . . . . . . . . . . . . . . Basic Strings. . . . . . . . . . . . . . . . . . String Manipulation . . . . . . . . . . . . Lists . . . . . . . . . . . . . . . . . . . . . . . . Expressions . . . . . . . . . . . . . . . . . . Conditional Logic. . . . . . . . . . . . . . Iteration. . . . . . . . . . . . . . . . . . . . . Functions . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
242 243 243 244 244 244 246 247 248 251 254 256 258 261
CONTENTS
Integrating Python with C . . . . . . . . . Compiling a Python Project . . . . . Initializing Python . . . . . . . . . . . . . Python Objects . . . . . . . . . . . . . . Re-coding the Alien Head Demo . Advanced Topics . . . . . . . . . . . . . . . . Web Links . . . . . . . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
xix
. . . . . . .
263 263 265 265 277 286 286
Tcl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 ActiveStateTcl . . . . . . . . . . . . . . . . . . . . . . . . The Distribution at a Glance . . . . . . . . . . The tclsh Interactive Interpreter . . . . . . . What, No Compiler? . . . . . . . . . . . . . . . . Tcl Extensions . . . . . . . . . . . . . . . . . . . . . The Tcl Language . . . . . . . . . . . . . . . . . . . . . . Commands—The Basis of Tcl . . . . . . . . . Substitution . . . . . . . . . . . . . . . . . . . . . . . Comments . . . . . . . . . . . . . . . . . . . . . . . . Variables . . . . . . . . . . . . . . . . . . . . . . . . . Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . Expressions . . . . . . . . . . . . . . . . . . . . . . . Conditional Logic. . . . . . . . . . . . . . . . . . . Iteration. . . . . . . . . . . . . . . . . . . . . . . . . . Functions (User-Defined Commands) . . . Integrating Tcl with C . . . . . . . . . . . . . . . . . . Compiling a Tcl Project . . . . . . . . . . . . . . Initializing Tcl . . . . . . . . . . . . . . . . . . . . . . Loading and Running Scripts . . . . . . . . . . Calling Tcl Commands from C . . . . . . . . . Exporting C Functions as Tcl Commands. Returning Values from Tcl Commands . . . Manipulating Global Tcl Variables from C . Recoding the Alien Head Demo. . . . . . . . Advanced Topics . . . . . . . . . . . . . . . . . . . . . . Web Links . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . .
288 288 289 290 290 291 291 292 297 298 301 303 306 308 310 312 312 313 314 315 316 319 320 322 330 330
xx
CONTENTS
Which Scripting System Should You Use? Scripting an Actual Game . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . . On the CD . . . . . . . . . . . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
331 333 333 334
AM FL Y
Chapter 7 Designing a Procedural Scripting Language .....................................335 General Types of Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 Assembly-Style Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 Upping the Ante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
TE
Object-Oriented Programming . . . XtremeScript Language Overview . Design Goals . . . . . . . . . . . . . . Syntax and Features. . . . . . . . . . . . Data Structures . . . . . . . . . . . . Operators and Expressions . . . Code Blocks . . . . . . . . . . . . . . Control Structures. . . . . . . . . . Functions . . . . . . . . . . . . . . . . . Escape Sequences. . . . . . . . . . . Comments . . . . . . . . . . . . . . . . The Preprocessor . . . . . . . . . . Reserved Word List . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
346 349 349 351 351 354 358 358 361 363 363 363 364
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
Team-Fly®
CONTENTS
xxi
Part Four Designing and Implementing a Low-Level Language .............367 Chapter 8 Assembly Language Primer ..............369 What Is Assembly Language? . . . . . . . . . . . . . . . . . . . . . . . . . . 370 Why Assembly Now? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371 How Assembly Works . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372 Instructions . . . . . . . . . . . . . . . . . . . . Operands. . . . . . . . . . . . . . . . . . . Expressions . . . . . . . . . . . . . . . . . Jump Instructions . . . . . . . . . . . . . Conditional Logic. . . . . . . . . . . . . Iteration. . . . . . . . . . . . . . . . . . . . Mnemonics versus Opcodes . . . . . . . RISC versus CISC . . . . . . . . . . . . . . . Orthogonal Instruction Sets . . . . . . . Registers . . . . . . . . . . . . . . . . . . . . . . The Stack . . . . . . . . . . . . . . . . . . . . . Stack Frames/Activation Records . Local Variables and Scope. . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
372 372 373 375 377 380 383 386 388 389 389 392 395
Introducing XVM Assembly. . . . . . . . . . . . . . . . . . . . . . . . . . . . 397 Initial Evaluations . . . . . . . . The XVM Instruction Set. . Memory . . . . . . . . . . . . Arithmetic . . . . . . . . . . Bitwise . . . . . . . . . . . . . String Processing . . . . . Conditional Branching . The Stack Interface . . . The Function Interface. Miscellaneous. . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
398 399 399 400 401 402 402 403 403 404
xxii
CONTENTS
XASM Directives . . . . Stack and Data. . . Functions . . . . . . . Escape Sequences. Comments . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
404 405 406 407 407
Summary of XVM Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . 408 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
Chapter 9 Building the XASM Assembler ...........411 How a Simple Assembler Works . . . . . . . . . . . . . . . . . . . . . . . . 413 Assembling Instructions . . . . . . . . . . . Assembling Variables . . . . . . . . . . . . . Assembling Operands . . . . . . . . . . . . Assembling String Literals . . . . . . . . . Assembling Jumps and Function Calls
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
414 416 420 422 423
XASM Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428 Memory Management . . . . . . . . . . . . . . . . . Input: Structure of an XVM Assembly Script Directives . . . . . . . . . . . . . . . . . . . . . . . Instructions . . . . . . . . . . . . . . . . . . . . . . Line Labels . . . . . . . . . . . . . . . . . . . . . . . Host API Function Calls . . . . . . . . . . . . . The _Main () Function . . . . . . . . . . . . . . The _RetVal Register . . . . . . . . . . . . . . . Comments . . . . . . . . . . . . . . . . . . . . . . . A Complete Example Script. . . . . . . . . . Output: Structure of an XVM Executable . . Overview . . . . . . . . . . . . . . . . . . . . . . . . The Main Header . . . . . . . . . . . . . . . . . . The Instruction Stream . . . . . . . . . . . . . The String Table . . . . . . . . . . . . . . . . . . . The Function Table. . . . . . . . . . . . . . . . . The Host API Call Table . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . .
429 430 431 439 440 440 441 441 442 442 444 444 445 447 451 453 454
CONTENTS
xxiii
Implementing the Assembler . . . . . . . . . . . . . . . . . . . . . . . . . . 455 Basic Lexing/Parsing Theory. . . . . . . . . . . . . . . Lexing . . . . . . . . . . . . . . . . . . . . . . . . . . . . Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . Basic String Processing. . . . . . . . . . . . . . . . . . . Vocabulary . . . . . . . . . . . . . . . . . . . . . . . . . A String-Processing Library . . . . . . . . . . . . The Assembler’s Framework . . . . . . . . . . . . . . The General Interface . . . . . . . . . . . . . . . . A Structural Overview. . . . . . . . . . . . . . . . Lexical Analysis/Tokenization . . . . . . . . . . . . . . The Lexer’s Interface and Implementation . Error Handling. . . . . . . . . . . . . . . . . . . . . . . . . Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Initializing the Parser . . . . . . . . . . . . . . . . . Directives . . . . . . . . . . . . . . . . . . . . . . . . . Line Labels . . . . . . . . . . . . . . . . . . . . . . . . . Instructions . . . . . . . . . . . . . . . . . . . . . . . . Building the .XSE Executable . . . . . . . . . . . . . . The Header . . . . . . . . . . . . . . . . . . . . . . . . The Instruction Stream . . . . . . . . . . . . . . . The String Table . . . . . . . . . . . . . . . . . . . . . The Function Table. . . . . . . . . . . . . . . . . . . The Host API Call Table . . . . . . . . . . . . . . . The Assembly Process . . . . . . . . . . . . . . . . . . . Loading the Source File . . . . . . . . . . . . . . . The First Pass . . . . . . . . . . . . . . . . . . . . . . The Second Pass . . . . . . . . . . . . . . . . . . . . Producing the .XSE . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . .
456 457 459 462 462 464 469 470 470 495 496 525 527 528 529 542 543 552 552 553 555 556 557 558 558 559 560 562
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563 On the CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 564 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 564
xxiv
CONTENTS
Part Five Designing and Implementing a Virtual Machine..................565 Chapter 10 Basic VM Design and Implementation ..567 Ghost in the Virtual Machine. . . . . . . . . . . . . . . . . . . . . . . . . . . 568 Mimicking Hardware . . . . . . . . . . . . . The VM’s Major Components . . . . . . The Instruction Stream . . . . . . . . The Runtime Stack. . . . . . . . . . . . Global Data Tables . . . . . . . . . . . . Multithreading . . . . . . . . . . . . . . . . . . Integration with the Host Application
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
569 570 571 571 571 573 573
A Brief Overview of a VM’s Lifecycle . . . . . . . . . . . . . . . . . . . . 574 Loading the Script . . . . . . . . . . . . . . . . Beginning Execution at the Entry Point The Execution Cycle . . . . . . . . . . . . . . Function Calls . . . . . . . . . . . . . . . . . . . Calling a Function . . . . . . . . . . . . . Returning From a Function . . . . . . Termination and Shut Down . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
574 576 576 578 578 580 581
Structural Overview of the XVM Prototype. . . . . . . . . . . . . . . 582 The Script Header . . . . . . Runtime Values. . . . . . . . . The Instruction Stream . . The Runtime Stack . . . . . The Frame Index . . . . The Function Table. . . . . . The Host API Call Table . . The Final Script Structure
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
583 583 584 585 586 587 587 588
Building the XVM Prototype. . . . . . . . . . . . . . . . . . . . . . . . . . . 589 Loading an .XSE Executable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 590
CONTENTS
An .XSE Format Overview. . . . . . . . . . . . The Header . . . . . . . . . . . . . . . . . . . . . . . The Instruction Stream . . . . . . . . . . . . . . The String Table . . . . . . . . . . . . . . . . . . . . The Function Table. . . . . . . . . . . . . . . . . . The Host API Call Table . . . . . . . . . . . . . . Structure Interfaces . . . . . . . . . . . . . . . . . . . . The Instruction Stream . . . . . . . . . . . . . . The Runtime Stack. . . . . . . . . . . . . . . . . . The Function Table. . . . . . . . . . . . . . . . . . The Host API Call Table . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . . Initializing the VM. . . . . . . . . . . . . . . . . . . . . . The Execution Cycle . . . . . . . . . . . . . . . . . . . Instruction Set Implementation . . . . . . . . Handling Script Pauses . . . . . . . . . . . . . . . Incrementing the Instruction Pointer . . . . Operand Resolution. . . . . . . . . . . . . . . . . Instruction Execution and Result Storage. Termination and Shut Down . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
xxv
. . . . . . . . . . . . . . . . . . . .
590 594 595 599 601 602 603 604 616 621 621 622 624 627 628 633 634 636 637 646
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 648 On the CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 649 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 649
Chapter 11 Advanced VM Concepts and Issues......651 A Next Generation Virtual Machine . . . . . . . . . . . . . . . . . . . . . 652 Two Versions of the Machine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 652
Multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653 Multithreading Fundamentals . . . . . . . . . . . . Cooperative vs. Preemptive Multitasking From Tasks to Threads . . . . . . . . . . . . . . Concurrent Execution Issues . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
654 654 658 659
xxvi
CONTENTS
Loading and Storing Multiple Scripts . . The g_Script Structure. . . . . . . . . . Loading Scripts. . . . . . . . . . . . . . . . Initialization and Shutdown . . . . . . Handling a Script Array . . . . . . . . . Executing Multiple Threads . . . . . . . . . Tracking Active Threads . . . . . . . . . The Scheduler . . . . . . . . . . . . . . . . The First Completed XVM Demo .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
667 667 671 674 674 677 678 679 682
Host Application Integration. . . . . . . . . . . . . . . . . . . . . . . . . . . 682 Running Scripts in Parallel with the Host . . . . . . . . . Manual Time Slicing vs. Native Threads. . . . . . . . Introducing the Integration Interface . . . . . . . . . . . . Calling Host API Functions from a Script . . . . . . Calling Script Functions from the Host . . . . . . . Tracking Global Variables . . . . . . . . . . . . . . . . . . The XVM’s Public Interface . . . . . . . . . . . . . . . . . . . Which Functions Should Be Public? . . . . . . . . . . Name Clashes . . . . . . . . . . . . . . . . . . . . . . . . . . Public Constants . . . . . . . . . . . . . . . . . . . . . . . . Implementing the Integration Interface . . . . . . . . . . Basic Script Control Functions. . . . . . . . . . . . . . Host API Calls . . . . . . . . . . . . . . . . . . . . . . . . . . Script Function Calls . . . . . . . . . . . . . . . . . . . . . Invoking a Script Function: Synchronous Calls . . Calling a Scripting Function: Asynchronous Calls Adding Thread Priorities . . . . . . . . . . . . . . . . . . . . . Priority Ranks vs.Time Slice Durations . . . . . . . Updating the .XSE Format . . . . . . . . . . . . . . . . . Updating XASM . . . . . . . . . . . . . . . . . . . . . . . . . Parsing the SetPriority Directive . . . . . . . . . . . . Updating the XVM . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . .
683 684 686 686 687 689 694 694 695 696 696 697 700 711 713 719 728 730 731 733 734 735
Demonstrating the Final XVM . . . . . . . . . . . . . . . . . . . . . . . . . 739 The Host Application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 739 The Demo Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 739
CONTENTS
Embedding the XVM . Defining the Host API The Main Program . . . The Output . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
xxvii
. . . .
741 742 742 745
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 746 On the CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 746 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 747
Part Six Compiling High-Level Code .......749 Chapter 12 Compiler Theory Overview ................751 An Overview of Compiler Theory. . . . . . . . . . . . . . . . . . . . . . . 752 Phases of Compilation . . . . . . . . . . . . . . . . . Lexical Analysis/Tokenization . . . . . . . . . Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . Semantic Analysis . . . . . . . . . . . . . . . . . . I-Code . . . . . . . . . . . . . . . . . . . . . . . . . . Single-Pass versus Multi-Pass Compilers. Target Code Emission . . . . . . . . . . . . . . The Front and Back Ends . . . . . . . . . . . . Compiler Compilers . . . . . . . . . . . . . . . How XtremeScript Works with XASM . . . . Advanced Compiler Theory Topics . . . . . . . Optimization . . . . . . . . . . . . . . . . . . . . . Preprocessing. . . . . . . . . . . . . . . . . . . . . Retargeting . . . . . . . . . . . . . . . . . . . . . . Linking, Loading, and Relocatable Code . Targeting Hardware Architectures . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . .
753 755 760 764 765 766 768 768 769 769 771 771 773 778 779 780
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 782
xxviii
CONTENTS
Chapter 13 Lexical Analysis ............................783 The Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785 From Characters to Lexemes . Tokenization . . . . . . . . . . . . . . Lexing Methods . . . . . . . . . . . . Lexer Generation Utilities . Hand-Written Lexers. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
785 787 787 788 788
The Lexer’s Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793 Reading and Storing the Text File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793 Displaying the Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 795 Error Handling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 797
A Numeric Lexer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 797 A Lexing Strategy . . . . . . . . . . . . State Diagrams. . . . . . . . . . . . States and Token Types . . . . . . Initializing the Lexer . . . . . . . . Beginning the Lexing Process . The Lexing Loop . . . . . . . . . . Completing the Demo. . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
798 799 800 800 801 802 809
Lexing Identifiers and Reserved Words. . . . . . . . . . . . . . . . . . . 811 New States and Tokens The Test File . . . . . . . . Upgrading the Lexer . . Completing the Demo.
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
812 813 814 819
The Final Lexer: Delimiters, Operators, and Strings . . . . . . . . 822 Lexing Delimiters . . . . . . . New States and Tokens Upgrading the Lexer . . Lexing Strings . . . . . . . . . . New States and Tokens Upgrading the Lexer . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
822 822 823 827 827 828
CONTENTS
Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . Breaking Operators Down. . . . . . . . . . . . Building Operator State Transition Tables . New States and Tokens . . . . . . . . . . . . . . Upgrading the Lexer . . . . . . . . . . . . . . . . Completing the Demo. . . . . . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
xxix
. . . . . .
831 832 836 840 841 849
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 855 On the CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 855 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 856
Chapter 14 Building the XtremeScript Compiler Framework...................................857 A Strategic Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 858 The Front End . . . . . . . . . . . . . . . The Loader Module . . . . . . . . The Preprocessor Module . . . The Lexical Analyzer Module . The Parser Module . . . . . . . . The I-Code Module . . . . . . . . . . . The Back End . . . . . . . . . . . . . . . The Code Emitter Module. . . The XASM Assembler . . . . . . Major Structures . . . . . . . . . . . . . The Source Code. . . . . . . . . . The Script Header . . . . . . . . . The Symbol Table . . . . . . . . . . The Function Table. . . . . . . . . The String Table . . . . . . . . . . . The I-Code Stream . . . . . . . . Interfaces and Encapsulation . . . . The Compiler’s Lifespan . . . . . . . Reading the Command Line . . Loading the Source Code . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
859 860 861 861 862 862 863 863 863 863 863 864 864 865 866 866 866 867 867 867
CONTENTS
Preprocessing. . . . . . . . . . . . . . . . Parsing . . . . . . . . . . . . . . . . . . . . . Code Emission . . . . . . . . . . . . . . . Invoking XASM . . . . . . . . . . . . . . The Compiler’s main () Function .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
867 867 868 868 868
The Command-Line Interface. . . . . . . . . . . . . . . . . . . . . . . . . . 870 . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
AM FL Y
The Logo and Usage Info. Reading Filenames . . . . . . Implementation . . . . . Reading Options . . . . . . . Implementation . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
870 871 872 874 875
Elementary Data Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . 880 Linked Lists . . . . . The Interface . Stacks . . . . . . . . . The Interface .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
TE
xxx
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
880 881 888 888
Initialization and Shutdown. . . . . . . . . . . . . . . . . . . . . . . . . . . . 890 Global Variables and Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 890 Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 891 Shutting Down. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 892
The Compiler’s Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 893 The Loader Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 895 The Preprocessor Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 897 Single-Line Comments . . . . Block Comments . . . . . . . . Preprocessor Directives . . Implementing #include . Implementing #define. .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
898 899 902 902 903
The Compiler’s Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 904 The Symbol Table. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 905 The SymbolNode Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 905 The Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 907
Team-Fly®
CONTENTS
The Function Table. . . . . . . . . The FuncNode Structure . The Interface . . . . . . . . . . The String Table . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
xxxi
. . . .
910 911 911 915
Integrating the Lexical Analyzer Module . . . . . . . . . . . . . . . . . 916 Rewinding the Token Stream . . . . . . . Lexer States . . . . . . . . . . . . . . . . . A New Source Code Format. . . . . . . New Miscellaneous Functions . . . . . . Adding a Look-Ahead Character . Handling Invalid Tokens . . . . . . . . Returning the Current Token . . . . Copying the Current Lexeme . . . Error-Printing Helper Functions . . Resetting the Lexer . . . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
916 917 919 922 922 923 925 926 927 928
The Parser Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 928 Error Handling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 928 General Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 928 Code Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 928 Cascading Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 930
The I-Code Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 932 Approaches to I-Code . . . . . . . . . . . . . . . . . . A Simplified Instruction Set . . . . . . . . . . . The XtremeScript I-Code Instruction Set The XtremeScript I-Code Implementation . . Instructions . . . . . . . . . . . . . . . . . . . . . . . Jump Targets. . . . . . . . . . . . . . . . . . . . . . . Source Code Annotation . . . . . . . . . . . . . The Interface . . . . . . . . . . . . . . . . . . . . . . . . . Adding Instructions . . . . . . . . . . . . . . . . . Adding Operands. . . . . . . . . . . . . . . . . . . Retrieving Operands . . . . . . . . . . . . . . . . Adding Jump Targets. . . . . . . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
932 933 935 935 936 938 940 942 943 944 945 946
xxxii
CONTENTS
Adding Source Code Annotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 947 Retrieving I-Code Nodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 948
The Code-Emitter Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . 949 Code-Emission Basics . . . . . . . . . . . . . . . . . . The General Format . . . . . . . . . . . . . . . . . . . Global Definitions . . . . . . . . . . . . . . . . . . Emitting the Header. . . . . . . . . . . . . . . . . Emitting Directives. . . . . . . . . . . . . . . . . . Emitting Symbol Declarations . . . . . . . . . Emitting Functions . . . . . . . . . . . . . . . . . . Emitting a Complete XVM Assembly File .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
949 950 951 952 953 955 958 966
Generating the Final Executable. . . . . . . . . . . . . . . . . . . . . . . . 969 Wrapping It All Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 972 Initiating the Compilation Process . Printing Compilation Statistics . . . . Hard-coding a Test Script. . . . . . . . The Function . . . . . . . . . . . . . . The Symbols . . . . . . . . . . . . . . The Code . . . . . . . . . . . . . . . . The Results . . . . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
972 972 975 976 976 977 980
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 981 On the CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 981 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 982
Chapter 15 Parsing and Semantic Analysis .........983 What Is Parsing? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 985 Syntactic versus Semantic Analysis . . . . . . . . . . . . Expressing Syntax . . . . . . . . . . . . . . . . . . . . . . . . Syntax Diagrams . . . . . . . . . . . . . . . . . . . . . . Backus-Naur Form. . . . . . . . . . . . . . . . . . . . . Choosing a Method of Grammar Expression . Parse Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
985 987 987 988 989 989
CONTENTS
xxxiii
How Parsing Works . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 993 Recursive Descent Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 994
The XtremeScript Parser Module . . . . . . . . . . . . . . . . . . . . . . 996 The Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 996 Tracking Scope. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 996 Reading Specific Tokens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 997 The Parsing Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1000
Parsing Statements and Code Blocks . . . . . . . . . . . . . . . . . . . 1001 Syntax Diagrams . . . . . . The Implementation. . . . ParseSourceCode () . Statements . . . . . . . . Blocks . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
1002 1004 1004 1005 1007
Parsing Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1008 Function Declarations . . . . . . . . . . . . . . . . . . Parsing and Verifying the Function Name . Parsing the Parameter List . . . . . . . . . . . . Parsing the Function’s Body . . . . . . . . . . . Variable and Array Declarations . . . . . . . . . . . Host API Function Declarations. . . . . . . . . . . The host Keyword . . . . . . . . . . . . . . . . . . Upgrading the Lexer . . . . . . . . . . . . . . . . Parsing and Processing the host Keyword Testing Code Emitter Module . . . . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
1008 1010 1011 1015 1017 1021 1022 1022 1023 1026
Parsing Simple Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . 1028 An Expression Parsing Strategy . . . . . . . . . . . . . . . . . . Parsing Addition and Subtraction . . . . . . . . . . . . . . Multiplication, Division, and Operator Precedence . Stack-Based Expression Parsing . . . . . . . . . . . . . . . Understanding the Expression Parser . . . . . . . . . . . . . Coding the Expression Parser . . . . . . . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
1028 1028 1030 1031 1033 1037
Parsing Full Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1048 New Factor Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1048 Parsing Function Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1051
xxxiv
CONTENTS
New Unary Operators . . . . . . . . . . . New Binary Operators . . . . . . . . . . . Logical and Relational Operators. . . . The Logical And Operator . . . . . . Relational Greater Than or Equal . The Rest . . . . . . . . . . . . . . . . . . . L-Values and R-Values . . . . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
1053 1054 1054 1055 1056 1058 1058
A Standalone Runtime Environment . . . . . . . . . . . . . . . . . . . 1058 The Host Application. . . . . . . . . . . Reading the Command Line . . . Loading the Script . . . . . . . . . . Running the Script . . . . . . . . . . The Host API. . . . . . . . . . . . . . . . . PrintString () . . . . . . . . . . . . . . PrintNewline () and PrintTab () Registering the API. . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
1059 1060 1061 1062 1062 1063 1063 1064
Parsing Advanced Statements and Constructs . . . . . . . . . . . . 1064 Assignment Statements . . . . . . . . . . . . . . Function Calls . . . . . . . . . . . . . . . . . . . . . return . . . . . . . . . . . . . . . . . . . . . . . . . . . while Loops . . . . . . . . . . . . . . . . . . . . . . . while Loop Assembly Representation. Parsing while Loops . . . . . . . . . . . . . . break . . . . . . . . . . . . . . . . . . . . . . . . . Parsing break . . . . . . . . . . . . . . . . . . . continue. . . . . . . . . . . . . . . . . . . . . . . for Loops. . . . . . . . . . . . . . . . . . . . . . . . . Branching with if . . . . . . . . . . . . . . . . . . . if Block Assembly Representation. . . . Parsing if Blocks . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
. . . . . . . . . . . . .
1065 1073 1075 1079 1079 1081 1086 1088 1090 1092 1092 1092 1094
Syntax Diagram Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . 1099 The Test Drive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1099 Hello,World! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1099 Drawing Rectangles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1102
CONTENTS
The Bouncing Head Demo . . . . . . . . . . . Anatomy of the Program . . . . . . . . . . The Host Application . . . . . . . . . . . . . The Low-Level XVM Assembly Script. The High-Level XtremeScript Script. . The Results . . . . . . . . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
xxxv
1106 1107 1109 1116 1127 1132
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1134 On the CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1134 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1135
Part Seven Completing Your Training ........1137 Chapter 16 Applying the System to a Full Game..........................................1139 Introducing Lockdown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1140 The Premise . . . . . . . . . . . . . . . . . . . . . . . . . . . . Initial Planning and Setup . . . . . . . . . . . . . . . . . . . Phase One—Game Logic and Storyboarding . Phase Two—Asset Requirement Assessment . Phase Three—Planning the Code . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
1140 1142 1142 1150 1155
Scripting Strategy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1157 Integrating XtremeScript . . . . . . . . . . The Host API. . . . . . . . . . . . . . . . . . . Miscellaneous Functions . . . . . . . . Enemy Droid Functions . . . . . . . . Player Droid Functions. . . . . . . . . Registering the Functions . . . . . . . Writing the Scripts . . . . . . . . . . . . . . The Ambience Script . . . . . . . . . . The Blue Droid’s Behavior Script . The Grey Droid’s Behavior Script
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
1158 1158 1159 1159 1159 1160 1161 1161 1162 1163
xxxvi
CONTENTS
The Red Droid’s Behavior Script Compilation . . . . . . . . . . . . . . . . Loading and Running the Scripts . . . Speed Issues . . . . . . . . . . . . . . . . . . Minimizing Expressions. . . . . . . . The XVM’s Internal Timer . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
1167 1171 1171 1173 1174 1174
How to Play Lockdown . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1175 Controls . . . . . . . . . . . . . Interacting with Objects . The Zone Map. . . . . . . . . Battle . . . . . . . . . . . . . . . . Completing the Objective
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
1175 1176 1176 1176 1176
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1177 On the CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1177 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1178
Chapter 17 Where to Go From Here .................1179 So What Now? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1180 Expanding Your Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . 1181 Compiler Theory . . . . . . . . . . . . . . . . More Advanced Parsing Methods . Object-Orientation . . . . . . . . . . . Optimization . . . . . . . . . . . . . . . . Runtime Environments. . . . . . . . . . . . The Java Virtual Machine . . . . . . . Alternative Operating Systems. . . Operating System Theory . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
1181 1182 1182 1183 1184 1184 1185 1186
Advanced Topics and Ideas . . . . . . . . . . . . . . . . . . . . . . . . . . . 1186 The Assembler and Runtime Environment . . . . . . . . . . . . . . . . . . . . . . . . 1186 A Lower-Level Assembler. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1186 A Lower-Level Virtual Machine. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1187
CONTENTS
xxxvii
Dynamic Memory Allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1189 The Compiler and High-Level Language . . . . . . . . . . . . . . . . . . . . . . . 1190
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1199
Appendix A What’s on the CD? ........................1201 The CD-ROM Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1202 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1203 DirectX SDK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1203
INDEX .......................................1205
xxxviii
INTRODUCTION
Introduction f you've been programming games for any reasonable amount of time, you've probably learned that at the end of the day, the really hard part of the job has nothing to do with illumination models, doppler shift, file formats, or frame rates, as the majority of game development books on the shelves would have you believe. These days, it's more or less evident that everyone knows everything. Gone are the days where game development gurus separated themselves from the common folk with their in-depth understanding of VGA registers or their ability to write an 8bit mixer in 4K of code. Nowadays, impossibly fast hardware accelerators and monolithic APIs that do everything short of opening your mail pretty much have the technical details covered. No, what really make the creation of a phenomenal game difficult are the characters, the plot, and the suspension of disbelief.
I
Until Microsoft releases "DirectStoryline"—which probably won't be long, considering the amount of artificial intelligence driving DirectMusic—the true challenge will be immersing players in settings and worlds that exert a genuine sense of atmosphere and organic life. The floor should creak and groan when players walk across aging hardwood. The bowels of a ship should be alive with scurrying rats and the echoey drip-drop sounds of leaky pipes. Characters should converse and interact with both the player and one another in ways that suggest a substantial set of gears is turning inside their heads. In a nutshell, a world without compellingly animated detail and believable responsiveness won't be suitable for the games of today and tomorrow. The problem, as the first chapter of this book will explain, is that the only solution to this problem directly offered by languages like C and C++ is to clump the code for implementing a peripheral character's quirky attitude together with code you use to multiply matrices and sort vertex lists. In other words, you're forced to write all of your game—from the low-level details to the high-level logic—in the same place. This is an illogical grouping and one that leads to all sorts of hazards and inconveniences. And let's not forget the modding community. Every day it seems that players expect more flexibility and expansion capabilities from their games. Few PC titles last long on the shelves if a
INTRODUCTION
xxxix
community of rabid, photosensitive code junkies can't tear it open and rewire its guts. The problem is, you can't just pop up an Open File dialog box and let the player chose a DLL or other dynamically linked solution, because doing so opens you up to all sorts of security holes. What if a malicious mod author decides that the penalty for taking a rocket blast to the gut is a freshly reformatted hard drive? Because of this, despite their power and speed, DLLs aren't necessarily the ideal solution. This is where the book you're currently reading comes into play. As you'll soon find out, a solution that allows you to both easily script and control your in-game entities and environments, as well as give players the ability to write mods and extensions, can only really come in the form of a custom-designed language whose programs can run within an embeddable execution environment inside the game engine. This is scripting. If that last paragraph seemed like a mouthful, don't worry. This book is like an elevator that truly starts from the bottom floor, containing everything you need to step out onto the roof and enjoy the view when you're finished. But as a mentally unstable associate of mine is often heard to say, "The devil is in the details." It's not enough to simply know what scripting is all about; in order to really make something happen, you need to know everything. From the upper echelons of the compiler, all the way down to the darkest corners of the virtual machine, you need to know what goes where, and most importantly, why. That's what this book aims to do. If you start at the beginning and follow along with me until the end, you should pick up everything you need to genuinely understand what's going on.
How This Book is Organized With the dramatic proclamations out of the way, let's take a quick look at how this book is set up; then we'll be ready to get started. This book is organized into a number of sections: • Part One: Scripting Fundamentals. The majority of this material won't do you much good if you don't know what scripting is or why it's important. Like I said, you can follow this book whether or not you've even heard of scripting. The introduction provides enough background information to get you up to speed quick. • Part Two: Command-Based Scripting. Developing a complete, high-level scripting system for a procedural language is a complex task. A very complex task. So, we start off by setting our sights a bit lower and implementing what I like to call a "command-based language." As you'll see, command-based languages are dead simple to implement and capable of performing rather interesting tasks. • Part Three: Introduction to Procedural Scripting Languages. Part 3 is where things start to heat up, as we get our feet wet with real world, high-level scripting. Also covered in
INTRODUCTION
•
•
•
AM FL Y
•
this section are complete tutorials on using the Lua, Python and Tcl languages, as well as integrating their associated runtime environments with a host application. Part Four: Designing and Implementing a Low-Level Langauge. At the bottom of our scripting system will lie an assembly language and corresponding machine code (or bytecode). The design and implementation of this low-level environment will provide a vital foundation for the later chapters. Part Five: Designing and Implementing a Virtual Machine. Scripts—even compiled ones—don't matter much if you don't have a way to run them. This section of the book covers the design and implementation of a feature-packed virtual machine that's ready to be dropped into a game engine. Part Six: Compiling High-Level Code. The belly of the beast itself. Executing compiled bytecode is one thing, but being able to compile and ultimately run a high-level, procedural language of your own design is what real scripting is all about. Part Seven: Completing Your Training. Once you've earned your stripes, it's time to direct that knowledge somewhere. This final section aims to clear up any questions you may have in regards to furthering your study. You'll also see how the scripting system designed throughout the course of the book was applied to a complete game.
TE
xl
So that's it! You've got a roadmap firmly planted in your brain, and an interest in scripting that's hopefully piqued by now. It's time to roll our sleeves up and turn this mutha out.
Team-Fly®
Part One Scripting Fundamentals
This page intentionally left blank
CHAPTER 1
An Introduction to Scripting “We’ll bring you the thrill of victory, the agony of defeat, and because we’ve got soccer highlights, the sheer pointlessness of a zero-zero tie.” ——Dan Rydel, Sports Night
4
1. AN INTRODUCTION
TO
SCRIPTING
t goes without saying that modern game development is a multi-faceted task. As so many books on the subject love to ask, what other field involves such a perfect synthesis of art, music and sound, choreography and direction, and hardcore programming? Where else can you find each of these subjects sharing such equal levels of necessity, while at the same time working in complete unison to create a single, cohesive experience? For all intents and purposes, the answer is nowhere. A game development studio is just about the only place you’re going to find so many different forms of talent working together in the pursuit of a common goal. It’s the only place that requires as much art as it does science; that thrives on a truly equal blend of creativity and bleeding-edge technology. It’s that technical side that we’re going to be discussing for the next few hundred pages or so. Specifically, as the cover implies, you’re going to learn about scripting.
I
You might be wondering what scripting is. In fact, it’s quite possible that you’ve never even heard the term before. And that’s okay! It’s not every day that you can pick up a book with absolutely no knowledge of the subject it teaches and expect to learn from it, but Game Scripting Mastery is most certainly an exception. Starting now, you’re going to set out on a slow-paced and almost painfully in-depth stroll through the complex and esoteric world of feature-rich, professional grade game scripting. We’re going to start from the very beginning, and we aren’t even going to slow down until we’ve run circles around everything. This book is going to explain everything you’ll need to know, but don’t relax too much. If you genuinely want to be the master that this book can turn you into, you’re going to have to keep your eyes open and your mind sharp. I won’t lie to you, reader. Every single man or woman who has stood their ground; everyone who has fought an agent has died. The other thing I’m not going to lie to you about is that the type of scripting we’re going to learn—the seat-of-your-pants, pedalto-the-asphalt techniques that pro development studios use for commercial products—is hard stuff. So before going any further, take a nice deep breath and understand that, if anything, you’re going to finish this book having learned more than you expected. Yes, this stuff can be difficult, but I’m going to explain it with that in mind. Everything becomes simple if it’s taught properly, completely, and from the very beginning.
WHAT IS SCRIPTING?
5
But enough with the drama! It’s time to roll up your sleeves, take one last look at the real world, and dive headlong into the almost entirely uncharted territory that programmers call “game scripting.” In this chapter you will find ■ An overview of what scripting is and how it works. ■ Discussion on the fundamental types of scripting systems. ■ Brief coverage of existing scripting systems.
WHAT IS SCRIPTING? Not surprisingly, your first step towards attaining scripting mastery is to understand precisely what it is. Actually, my usual first step is breaking open a crate of 20 oz. Coke bottles and binge-drinking myself into a caffeine-induced frenzy that blurs the line between a motivated work ethic and cardiac arrest…but maybe that’s just me. To be honest, this is the tricky part. I spent a lot of time going over the various ways I could explain this, and in the end, I felt that I’d explain scripting to you in the same order that I originally stumbled upon it. It worked for me, which means it’ll probably work for you. So, put on your thinking cap, because it’s time to use your imagination. Here’s a hypothetical situation. You and some friends have decided to create a role-playing game, or RPG. So, being the smart little programmers you are, you sit down and draft up a design document—a fully-detailed game plan that lets you get all of your ideas down on paper before attempting to code, draw, or compose anything. At this point I could go off on a three-hour lecture about the necessity of design documents, and why programs written without them are doomed to fail and how the programmers involved will all end up in horrible snowmobile accidents, but that’s not why I’m here. Instead, I am going to quickly introduce this hypothetical RPG and cover the basic tasks involved in its production. Rather than explain what scripting is directly, I’ll actually run into the problems that scripting solves so well, and thus learn the hard way. The hypothetical hard way, that is. So anyway, let’s say the design document is complete and you’re ready to plow through this project from start to finish. The first thing you need is the game engine; something that allows players to walk around and explore the game world, interact with characters, and do battle with enemies. Sounds like a job for the programmer, right? Next up you’re going to need graphics. Lots of ‘em. So tell the artist to give the Playstation a rest and get to work. Now on to music and sound. Any good RPG needs to be dripping with atmosphere, and music and sound are a big part of that. Your musician should have this covered. But something’s missing. Sure, these three people can pump out a great demo of the engine, with all the graphics and sound you want, but what makes it a game? What makes it memorable
6
1. AN INTRODUCTION
TO
SCRIPTING
and fun to play? The answer is the content—the quest and the storyline, the dialogue, the descriptions of each weapon, spell, enemy, and all those other details that separate a demo from the next platinum seller.
STRUCTURED GAME CONTENT— A SIMPLE APPROACH So how exactly do you create a complete game? The programmer uses a compiler to code the design document specifications into a functional program, the artist uses image processing and creation software like Photoshop and 3D Studio MAX to turn concept art and sketches into graphics, and musicians use a MIDI composer or other tracking software to transform the schizophrenic voices in their heads into game music. The problem is, there really isn’t any tool or utility for “inputting” stories and character descriptions. You can’t just open up Microsoft VisualStoryline, type in the plot to your game, press F5 and suddenly have a game full of characters and dialogue. There doesn’t seem to be a clear solution here, but the game needs these things—it really can’t be a “game” without them. And somehow, every other RPG on the market has done it. The first and perhaps most obvious approach is to have the programmer manually code all this data into the engine itself. Sounds like a reasonable way to handle the situation, doesn’t it? Take the items, for instance. Each item in your game needs a unique description that tells the engine how it should look and function whenever the player uses it. In order to store this information, you might create a struct that will describe an item, and then create an array of these structures to hold all of them. Here’s an idea of what that structure might look like: typedef struct _Item { char * pstrName; // What is the item called? int iType; // What general type of item is it? int iPrice; // How much should it cost in shops? int iPower; // How powerful is it? } Item;
Let’s go over this a bit. pstrName is of course what the item is called, which might be “Healing Potion” or “Armor Elixir.” iType is the general type of the item, which the engine needs in order to know how it should function when used. It’s an integer, so a list of constants that describe its functionality should be defined: const HEAL = 0; const MAGIC_RESTORE = 1;
STRUCTURED GAME CONTENT—A SIMPLE APPROACH
const ARMOR_REPAIR const TELEPORT
= 2; = 3;
This provides a modest but useful selection of item types. If an item is of type HEAL, it restores the player’s health points (or HP as they’re often called). Items of type MAGIC_RESTORE are similar; they restore a player’s magic points (MP). ARMOR_REPAIR repairs armor (not surprisingly), and TELEPORT lets the player immediately jump to another part of the game world under certain conditions (or something to that effect, I just threw that in there to mix things up a bit). Up next is iPrice, which lets the merchants in your game’s item shops know how much they should charge the player in order to buy it. Sounds simple enough, right? Last is iPower, which essentially means that whatever this item is trying to do, it should do it with this amount, or to this extent. In other words, if your item is meant to restore HP (meaning its of type HEAL), and iPower is 32, the player will get 32 HP back upon using the item. If the item is of type MAGIC_RESTORE, and iPower is 64, the player will get 64 MP back, and so on and so forth. That pretty much wraps up the item description structure, but the real job still lies ahead. Now that the game’s internal structure for representing items has been established, it needs to be filled. That’s right, all those tens or even hundreds of items your game might need now must be written out, one by one: const MAX_ITEM_COUNT = 128;
// 128 items should be enough
Item ItemArray [ MAX_ITEM_COUNT ]; // First, ItemArray ItemArray ItemArray ItemArray
let's add something to heal injuries: [ 0 ].pstrName = "Health Potion Lv 1"; [ 0 ].iType = HEAL; [ 0 ].iPrice = 20; [ 0 ].iPower = 10;
// Next, wizards and mages and all those guys are gonna need this: ItemArray [ 1 ].pstrName = "Magic Potion Lv 6"; ItemArray [ 1 ].iType = MAGIC_RESTORE; ItemArray [ 1 ].iPrice = 250; ItemArray [ 1 ].iPower = 60; // Big burly warriors may want some of this: ItemArray [ 2 ].pstrName = "Armor Elixir Lv 2"; ItemArray [ 2 ].iType = ARMOR_REPAIR; ItemArray [ 2 ].iPrice = 30; ItemArray [ 2 ].iPower = 20;
7
8
1. AN INTRODUCTION
TO
SCRIPTING
// To be honest, I have no idea what on earth this thing is: ItemArray [ 3 ].pstrName = "Orb of Sayjack"; ItemArray [ 3 ].iType = TELEPORT; ItemArray [ 3 ].iPrice = 3000; ItemArray [ 3 ].iPower = NULL;
Upon recompiling the game, four unique items will be available for use. With them in place, let’s imagine you take them out for a field test, to make sure they’re balanced and well suited for gameplay. To make this hypothetical situation a bit easier to follow, you can pretend that the rest of the engine and game content is finished; that you already have a working combat engine with a variety of enemies and weapons, you can navigate a 3D world, and so on. This way, you can focus solely on the items. The first field test doesn’t go so well. It’s discovered in battle that “Health Potion Lv 1” isn’t strong enough to provide a useful HP boost, and that it ultimately does little to help the player tip the scales back in their favor after taking significant damage. The obvious solution is to increase the power of the potion. So, you go back to the compiler and make your change: ItemArray [ 0 ].iPower = 50;
// More healing power.
The engine will have to be recompiled in order for adjustment to take effect, of course. A second field test will follow. The second test is equally disheartening; more items are clearly unbalanced. As it turns out, “Armor Elixir Lv 2” restores a lot less of the armor’s vitality than is taken away during battle with various enemies, so it’ll need to be turned up a notch. On the other hand, the modification to “Health Potion Lv 1” was too drastic; it now restores too much health and makes the game too easy. Once again, these items’ properties must be tweaked. // First let's fix the Health Potion issue ItemArray [ 0 ].iPower = 40; // Sounds more fair. // Now the Armor Elixir ItemArray [ 2 ].iPower = 50;
// Should be more helpful now.
…and once again, you sit on your hands while everything is recompiled. Due to the complexity of the game engine, the compilation of its source code takes a quite while. As a result, the constant retuning demanded by the game itself is putting a huge burden on the programmer and wasting a considerable amount of time. It’s necessary, however, so you head out into your third field test, hoping that things work out better this time. And they don’t. The new problem? “Magic Potion Lv 6” is a bit too expensive. It’s easy for the player to reach a point where he desperately needs to restore his magic points, but hasn’t been
STRUCTURED GAME CONTENT—A SIMPLE APPROACH
9
given enough opportunities to collect gold, and thus gets stuck. This is very important and must be fixed immediately. ItemArray [ 1 ].iPrice = 80;
// This tweaking is getting old.
Once again, (say it with me now) you recompile the engine to reflect the changes. The balancing of items in an RPG is not a trivial task, and requires a great deal of field testing and constant adjusting of properties. Unfortunately, the length of this process is extended considerably by the amount of time spent recompiling the engine. To make matters worse, 99.9% of the code being recompiled hasn’t even changed—two out of three of these examples only changed a single line! Can you imagine how many times you’re going to have to recompile for a full set of 100+ items before they’ve all been perfected? And that’s just one aspect of an RPG. You’re still going to need a wide variety of weapons, armor, spells, characters, enemies, all of the dialogue, interactions, plot twists, and so on. That’s a massive amount of information. For a full game’s worth of content, you’re going recompile everything thousands upon thousands of times. And that’s an optimistic estimation. Hope you’ve got a fast machine. Now let’s really think about this. Every time you make even the slightest change to your items, you have to recompile the entire game along with it. That seems a bit wasteful, if flat out illogical, doesn’t it? If all you want to do is make a healing potion more effective, why should you have to recompile the 3D engine and sound routines too? They’re totally unrelated. The answer is that you shouldn’t. The content of your game is media, just like art, sound, and music. If an artist wants to modify some graphics, the programmer doesn’t have to recompile, right? The artist just makes the changes and the next time you run the game these changes are reflected. Same goes for music and sound. The sound technician can rewrite “Battle Anthem in C Minor” as often as desired, and the programmer never has to know about it. Once again, you just restart the game and the new music plays fine. So what gives? Why is the game content singled out like this? Why is it the only type of media that can’t be easily changed? The first problem with this method is that when you write your item descriptions directly in your game code, you have to recompile everything with it. Which sucks. But that’s by no means the only problem. Figure 1.1 demonstrates this. The problem with all of this constant recompilation is mostly a physical issue; it wastes a lot of time, repeats a lot of processing unnecessarily, and so on. Another major problem with this method is one of organization. An RPG’s engine is complicated enough as it is; managing graphics, sound, and player input is a huge task and requires a great deal of code. But consider how much more hectic and convoluted that code is going to become when another 5,000 lines or so of item descriptions, enemy profiles, and character dialogue are added. It’s a terrible way to organize things. Imagine if your programmer (which will most likely be you) had to deal with all the other game media while coding at the same time—imagine if the IDE was further cluttered by endless piles of graphics, music, and sound. A nervous breakdown would be the only likely outcome.
10
1. AN INTRODUCTION
TO
SCRIPTING
AM FL Y
Figure 1.1 The engine code and item descriptions are part of the same source files, meaning you can’t compile one without the other. Art, music, and sound, however, exist outside of the source code and are thus far more flexible.
TE
Think about it this way—coding game content directly into your engine is a little like wearing a tuxedo every day of your life. Not only does it take a lot longer to put on a tux in the morning than it does to throw on a v-neck and some khakis, but it’s inappropriate except for a few rare occasions. You’re only going to go to a handful of weddings in your lifetime, so spending the time and effort involved in preparing for one on a daily basis will be a waste 98% of the time. All bizarre analogies aside, however, it should now be clear why this is such a terrible way to organize things.
IMPROVING THE METHOD WITH LOGICAL AND PHYSICAL SEPARATION The situation in a nutshell is that you need an intelligent, highly structured way of separating your code from your game content. When you are working on the engine code, you shouldn’t have to wade through endless item descriptions. Likewise, when you’re working on item descriptions, the engine code should be miles away (metaphorically speaking, of course). You should also be able to change items drastically and as frequently as necessary, even after the game has been compiled, just like you can do with art, music, and sound. Imagine being able to get that slow, timewasting compilation out of the way up front, mess with the items all you want, and have the changes show up immediately in the same executable! Sounds like quite an improvement, huh? What’s even better is how easy this is to accomplish. To determine how this is done, you need not look any further than that other game media—like the art and sound—that’s been the subject of so much envy throughout this example. As you’ve learned rather painfully, they don’t require a separate compile like the game content does; it’s simply a matter of making changes and maybe restarting the game at worst. Why is this the case? Because they’re stored in separate files. The
Team-Fly®
IMPROVING
THE
METHOD
WITH
LOGICAL
AND
PHYSICAL SEPARATION
11
game’s only connection with this data is the code that reads it from the disk. They’re loaded at runtime. At compile-time, they don’t even have to be on the same hard drive, because they’re unrelated to the source code. The game engine doesn’t care what the data actually is, it just reads it and tosses it out there. So somehow, you need to offload your game content to external files as well. Then you can just write a single, compact block of code for loading in all of these items from the hard drive in one fell swoop. How slick is that? Check out Figure 1.2. Figure 1.2 If you can get your item descriptions into external files, they’ll be just as flexible as graphics and sound because they’ll only be needed at runtime.
The first step in doing this is determining how you are going to store something like the following in a file: ItemArray ItemArray ItemArray ItemArray
[ [ [ [
1 1 1 1
].pstrName = "Magic Potion Lv 6"; ].iType = MAGIC_RESTORE; ].iPrice = 250; ].iPower = 60;
In this example, the transition is going to be pretty simple. All you really need to do is take everything on the right side of the = sign and plop it into an ASCII file. After all, those are all of the actual values, whereas the assignment will be handled by the code responsible for loading it (called the loader). So here’s what the Magic Potion looks like in its new, flexible, file-based form: Magic Potion Lv 6 MAGIC_RESTORE 250 60
It’s almost exactly the same! The only difference is that all the C/C++ code that it was wrapped up in has been separated and will be dealt with later. As you can see, the format of this item file is
12
1. AN INTRODUCTION
TO
SCRIPTING
pretty simple; each attribute of the item gets its own line. Let’s take a look at the steps you might take to load this into the game: 1. Open the file and determine which index of the item array to store its contents in. You’ll probably be loading these in a loop, so it should just be a matter of referring to the loop counter. 2. Read the first string and store it in pstrName. 3. Read the next line. If the line is “HEAL”, assign HEAL to iType. If it’s “MAGIC_RESTORE” then assign MAGIC_RESTORE, and so on. 4. Read in the next line, convert it from a string to an integer, and store it in iPrice. 5. Read in the next line, convert it from a string to an integer, and store it in iPower. 6. Repeat steps 1-5 until all items have been loaded.
You’ll notice that you can’t just directly assign the item type to iType after reading it from the file. This is of course because the type is stored in the file as a string, but is represented in C/C++ as an integer constant. Also, note that steps 4 and 5 require you to convert the string to an integer before assigning it. This all stems from the fact that ASCII deals only with string data. Well my friend, you’ve done it. You’ve saved yourself from the miserable fate that would’ve awaited you if you’d actually tried to code each item directly into the game. And as a result, you can now tweak and fine-tune your items without wasting any more time than you have to. You’ve also taken your first major step towards truly understanding the concepts of game scripting. Although this example was very specific and only a prelude to the real focus of the book (discussed shortly), it did teach the fundamental concept behind all forms of scripting: How to avoid hardcoding.
THE PERILS
OF
HARDCODING
What is hardcoding? To put it simply, it’s what you were doing when you tried coding your items directly into the engine. It’s the practice of writing code or data in a rigid, fixed or hard-to-edit sort of way. Whether you decide to become a scripting guru or not, hardcoding is almost always something to avoid. It makes your code difficult to write, read, and edit. Take the following code block, for example: const MAX_ARRAY_SIZE = 32; int iArray [ MAX_ARRAY_SIZE ]; int iChecksum; for ( int iIndex = 1; iIndex < MAX_ARRAY_SIZE; ++ iIndex ) { int iElement = iArray [ iIndex ];
THE PERILS
OF
HARDCODING
13
iArray [ iIndex - 1 ] = iElement; iChecksum += iElement; } iArray [ MAX_ARRAY_SIZE - 1 ] = iChecksum;
Regardless of what it’s actually supposed to be doing the important thing to notice is that the size of the array, which is referred to a number of times, is stored in a handy constant beforehand. Why is this important? Well imagine if you suddenly wanted the array to contain 64 elements rather than 32. All you’d have to do is change the value of MAX_ARRAY_SIZE, and the rest of the program would immediately reflect the change. You wouldn’t be so lucky if you happened to write the code like this: int iArray [ 32 ]; int iChecksum; for ( int iIndex = 1; iIndex < 32; ++ iIndex ) { int iElement = iArray [ iIndex ]; iArray [ iIndex - 1 ] = iElement; iChecksum += iElement; } iArray [ 31 ] = iChecksum;
This is essentially the “hardcoded” version of the first code block, and it’s obvious why it’s so much less flexible. If you want to change the size of the array, you’re going to have to do it in three separate places. Just like the items in the RPG, the const used in this small example is analogous to the external file—it allows you to make all of your changes in one, separate place, and watch the rest of the program automatically reflect them. You aren’t exactly scripting yet, but you’re close! The item description files used in the RPG example are almost like very tiny scripts, so you’re in good shape if you’ve understood everything so far. I just want to take you through one more chapter in the history of this hypothetical RPG project, which will bring you to the real heart of this introduction. After that, you should pretty much have the concept nailed. So let’s get back to these item description files. They’re great; they take all the work of creating and fine-tuning game items off the programmer’s shoulders while he or she is working on other things like the engine. But now it’s time to consider some expansion issues. The item structure works pretty well for describing items, and it was certainly able to handle the basics like your typical health and magic potions, an armor elixir, and the mysterious Orb of Sayjack. But they’re not going to cut it for long. Let’s find out why.
14
1. AN INTRODUCTION
TO
SCRIPTING
STORING FUNCTIONALITY EXTERNAL FILES
IN
Sooner or later, you’re going to want more unique and complex items. The common thread between all of the items described so far is that they basically just increase or decrease various stats. It’s something that’s very easy to do, because each item only needs to tell the engine which stats it wants to change, and by how much. The problem is, it gets boring after a while because you can only do so much with a system like that. So what happens when you want to create an item that does something very specific? Something that doesn’t fit a mold as simple as “Tell me what stat to change and how much to change it by”? Something like an item that say, causes all ogres below a certain level to run away from battles? Or maybe an item that restores the MP of every wizard in the party that has a red cloak? What about one that gives the player the capability to see invisible treasure chests? These are all very specific tasks. So what can you do? Just add some item types to your list? const const const const const const const
HEAL = 0; MAGIC_RESTORE = 1; ARMOR_REPAIR = 2; TELEPORT = 3; MAKE_ALL_OGRES_BELOW_LEVEL_6_RUN_AWAY = 4; MAGIC_RESTORE_FOR_EVERY_WIZARD_WITH_RED_CLOAK = 5; MAKE_INVISIBLE_TREASURE_CHESTS_VISIBLE = 6;
No way that’s gonna cut it. With a reasonably complex RPG, you might have as many item types as you do actual items! Observant readers might have also noticed that once again, this is dangerously close to a hardcoded solution. You are back in the game engine source code, adding code for specific items—additions that will once again require recompiles every time something needs to be changed. Isn’t that the problem you were trying to solve in the first place? The trouble though, is that the specific items like the ones mentioned previously simply can’t be solved by any number of fields in an Item structure. They’re too complex, too specific, and they even involve conditional logic (determining the level of the ogres, the color of the wizards’ cloaks, and the visibility of the chests). The only way to actually implement these items is to program them—just like you’d program any other part of your game. I mean you pretty much have to; how are you going to test conditions without an if statement? But in order to write actual code, you have to go back to programming each item directly into the engine, right? Is there some magical way to actually store code in the item description files rather than just a list of values? And even if there is, how on earth would you execute it?
HOW SCRIPTING ACTUALLY WORKS
15
The answer is scripting. Scripting actually lets you write code outside of your engine, load that code into the engine, and execute it. Generally, scripts are written in their own language, which is often very similar to C/C++ (but usually simpler). These two types of code are separate—scripts use their own compiler and have no effect on your engine (unless you want them to). In essence, you can replace your item files, which currently just fill structure fields with values, with a block of code capable of doing anything your imagination can come up with. Want to create an item that only works if it’s used at 8 PM on Thursdays if you’re standing next to a certain castle holding a certain weapon? No problem! Scripts are like little mini-programs that run inside your game. They work on all the same principals as a normal program; you write them in a text editor, pass them through a compiler, and are given a compiled file as a result. The difference, however, is that these executables don’t run on your CPU like normal ones do. Because they run inside your game engine, they can do anything that normal game code can. But at the same time, they’re separate. You load scripts just like you load images or sounds, or even like the item description files from earlier. But instead of displaying them on the screen or playing them through your speakers, you execute them. They can also talk to your game, and your game can talk back. How cool is this? Can you feel yourself getting lost in the possibilities? You should be, because they’re endless. Imagine the freedom and flexibility you’ll suddenly be afforded with the ability to write separate mini-programs that all run inside your game! Suddenly your items can be written with as much control and detail as any other part of your game, but they still remain external and self-contained. Anyway, this concludes the hypothetical RPG scenario. Now that you basically know what scripting is, you’re ready to get a better feel for how it actually works. Sound good?
HOW SCRIPTING ACTUALLY WORKS If you’re anything like I was back when I was first trying to piece together this whole scripting concept, you’re probably wondering how you could possibly load code from a file and run it. I remember it sounding too complicated to be feasible for anyone other than Dennis Ritchie or Ken Thompson, (those are the guys who invented C, in case I lost you there) but trust me— although it is indeed a complex task, it’s certainly not impossible. And with the proper reference material (which this book will graciously provide), it’ll be fun, too! :) Before going any further, however, let’s refine the overall objective. What you basically want to be able do is write code in a high-level language similar to C/C++ that can be compiled independently of your game engine but loaded and executed by that engine whenever you want. The reason you want to do this is so you can separate game content, the artistic, creative, and design-oriented aspects of game development, from the game engine, the technological, generic side of things.
16
1. AN INTRODUCTION
TO
SCRIPTING
One of the most popular solutions to this problem literally involves designing and implementing a new language from the ground up. This language is called a scripting language, and as I’ve mentioned a number of times, is compiled with its own special compiler (so don’t expect Microsoft VisualStudio to do this for you). Once this language is designed and implemented, you can write scripts and compile them to a special kind of executable that can be run inside your program. It’s a lot more complicated than that, though, so you can start by getting acquainted with some of the details. The first thing I want you to understand is that scripting is analogous to the traditional programming you’re already familiar with. Actually, writing a script is pretty much identical to writing a program, the only real difference between the two is in how they’re loaded and executed at runtime. Due to this fact, there exist a number of very strong parallels between scripting and programming. This means that the first step in explaining how scripting works is to make sure you understand how programming works, from start to finish.
An Overview of Computer Programming Writing code that will execute on a computer is a complicated process, but it can be broken down into some rather simple steps. The overall goal behind computer programming is to be able to write code in a high-level, English-like language that humans can easily understand and follow, but ultimately translate that code into a low-level, machine-readable format. The reason for this is that code that looks like this: int Y = 0; int Z = 0; for ( int X = 0; X < 32; ++ X ) { Y = X * 2; Z += Y; }
which is quite simple and elementary to you and me, is pretty much impossible for your Intel or AMD processor to understand. Even if someone did build a processor capable of interpreting C/C++ like the previous code block, it’d be orders of magnitude slower than anything on the market now. Computers are designed to deal with things in their smallest, most fundamental form, and thus perform at optimal levels when the data in question is presented in such a fashion. As a result, you need a way to turn that fluffy, humanesque language you call C/C++ into a bare-bones, byte-for-byte stream of pure code.
HOW SCRIPTING ACTUALLY WORKS
That’s where compilers come in. A compiler’s job is to turn the C/C++, Java, or Pascal code that your brain can easily interpret and understand into machine code; a set of numeric codes (called opcodes, short for operation code) that tell the processor to perform extremely fine-grained tasks like moving individual bytes of memory from one place to another or jumping to another instruction for iteration and branching. Designed to be blasted through your CPU at lightning speeds, machine code operates at the absolute lowest level of your computer. Because pure machine code is rather difficult to read by humans (because it’s nothing more than a string of numbers), it is often written in a more understandable form called assembly language, which gives each numeric opcode a special tag called an instruction mnemonic. Here’s the previous block of code from, after a compiler has translated it to assembly language: mov mov mov jmp mov add mov cmp jge mov shl mov mov add mov jmp
dword ptr [ebp-4],0 dword ptr [ebp-8],0 dword ptr [ebp-0Ch],0 00401048h eax,dword ptr [ebp-0Ch] eax,1 dword ptr [ebp-0Ch],eax dword ptr [ebp-0Ch],20h 00401061h ecx,dword ptr [ebp-0Ch] ecx,1 dword ptr [ebp-4],ecx edx,dword ptr [ebp-8] edx,dword ptr [ebp-4] dword ptr [ebp-8],edx 0040103fh
NOTE For the remainder of this section, and in many places in this book, I’m going to use the terms machine code and assembly language interchangeably. Remember, the only difference between the two is what they look like. Although machine code is the numeric version and assembly is the humanreadable form, they both represent the exact same data.
If you don’t understand assembly language, that probably just looks like a big mess of ASCII characters. Either way, this is what the processor wants to see. All of those variable assignments, expressions, and even the for loop have been collapsed to just a handful of very quick instructions that the CPU can blast through without thinking twice. And the really useless stuff, like the actual names of those variables, is gone entirely. In addition to illustrating how simple and to-the-point machine code is, this example might also give you an idea of how complex a compiler’s job is.
17
18
1. AN INTRODUCTION
TO
SCRIPTING
Anyway, once the code is compiled, it’s ready to fly. The compiler hands all the compiled code to a program called a linker, which takes that massive volume of instructions, packages them all into a nice, tidy executable file along with a considerable amount of header information and slaps an .EXE on the end (or whatever extension your OS uses). When you run that executable, the operating system invokes the program loader (more commonly referred to simply as the loader), which is in charge of extracting the code from the .EXE file and loading it into memory. The loader then tells the CPU the address in memory of the first instruction to be processed, called the program entry point, (the main () function in a typical C/C++ program), and the program begins executing. It might be displaying 3D graphics, playing a Chemical Brothers MP3, or accepting user input, but no matter what it’s doing, the CPU is always processing instructions. This general process is illustrated in Figure 1.3. Figure 1.3 The OS program loader extracts machine code from the executable file and loads it into memory for execution.
This is basically the philosophy behind computer science in a nutshell: Turning problems and algorithms into high-level code, turning that high-level code into low-level code, executing that low-level code by feeding it through a processor, and (hopefully) solving the problem. Now that you’ve got that out of the way, you’re ready to learn how this all applies to scripting.
An Overview of Scripting You might be wondering why I spent the last section going over the processes behind general computer programming. For one thing, a lot of you probably already know this stuff like the back of your hand, and for another, this book is supposed to be about scripting, right? Well don’t sweat it, because this is where you apply that knowledge. I just wanted to make sure that the programming process was fresh in your mind, because this next section will be quite similar and it’s always good to make connections. As I mentioned earlier, there exist a great number of parallels between programming and scripting; the two subjects are based on almost identical concepts.
HOW SCRIPTING ACTUALLY WORKS
19
When you write a script, you write it just like you write a normal program. You open up a text editor of some sort (or maybe even an actual VisualStudio-style IDE if you go so far as to make one), and input your code in a high-level language, just like you do now with C/C++. When you’re done, you hand that source file to a compiler, which reduces it to machine code. Until this point, nothing seems much different from the programming process discussed in the last section. The changes, however, occur when the compiler is translating the high-level script code. Remember, the whole concept behind a script is that it’s like a program that runs inside another program. As such, a script compiler can’t translate it into 80X86 machine code like it would if it were compiling for an Intel CPU. In fact, it can’t translate it to any CPU’s machine code, because this code won’t be running on a CPU. So how’s this code going to be executed, if not by a CPU? The answer is what’s called a virtual machine, or VM. Aside from just being a cool-sounding term, a virtual machine is very similar to the CPU in your computer, except that it’s implemented in software rather than silicon. A real CPU’s job is basically to retrieve the next instruction to be executed, determine what that instruction is telling it to do, and do it. Seems pretty simple, huh? Well it’s the same thing a virtual machine does. The only difference is that the VM understands its own special dialect of assembly language (often called bytecode, but you’ll get to that later). Another important attribute of a virtual machine is that, at least in the context of game scripting, it’s not usually a standalone program. Rather, it’s a special “module” that is built into (or “integrated with”) other programs. This is also similar to your CPU, which is integrated with a motherboard, RAM, a hard drive, and a number of input and output devices. A CPU on its own is pretty much useless. Whatever program you integrate the VM with is called the host application, and it is this program that you are ultimately “scripting”. So for example, if you integrated a VM into the hypothetical RPG discussed earlier, scripts would be running inside the VM, but they would be scripting the RPG. The VM is just a vehicle for getting the script’s functionality to the host. So a scripting system not only defines a high-level, C/C++-style language of its own, but also creates a new low-level assembly language, or virtual machine code. Script compilers translate scripts into this code, and the result is then run inside the host application’s virtual machine. The virtual machine and the host application can talk to one another as well, and through this interface, the script can be given specific control the host. Figure 1.4 should help you visualize these interactions. Notice that there are now two more layers above the program—the VM and the script(s) inside it. So let’s take a break from all this theory for a second and think about how this could be applied to your hypothetical RPG. Rather than define items by a simple set of values that the program blindly plugs into the item array, you could write a block of code that the program tells the VM to execute every time the item is used. Through the VM, this block of code could talk to the game, and the game could talk back. The script might ask the game how many hit points the player has, and what sort of armor is currently being worn. The game would pass this information to the
20
1. AN INTRODUCTION
TO
SCRIPTING
AM FL Y
Figure 1.4 The VM’s script loader loads virtual machine code from the script file, allowing the VM to execute it. In addition to a runtime environment, the VM also provides a communication layer, or interface, between the running script and the host program.
TE
script and allow it process it, and ultimately the script would perform whatever functionality was associated with the item. Host applications provide running scripts with a group of functions, called an API (which stands for Application Programming Interface), which they can call to affect the game. This API for an RPG might allow the script to move the player around in the game world, get items, change the background music, or whatever. With a system like this, anything is possible. That was quite a bit of information to swallow, huh? Well, I’ve got some good and bad news. The bad news is that this still isn’t everything; there are actually a number of ways to implement a game scripting system, and this was only one of them. The good news, though, is that this method is by far the most complex, and everything else will be a breeze if you’ve understood what’s been covered so far. So, without further ado…
THE FUNDAMENTAL TYPES SCRIPTING SYSTEMS
OF
Like most complex subjects, scripting comes in a variety of forms. Some implementations involve highly structured, feature-rich compilers that understand full, procedural languages like C or even object oriented languages like C++, whereas others are based around simple command sets that look more like a LOGO program. The choices aren’t always about design, however. There exists a huge selection of scripting systems these days, most of which have supportive and dedicat-
Team-Fly®
THE FUNDAMENTAL TYPES
OF
SCRIPTING SYSTEMS
21
ed user communities, and almost all of which are free to download and use. Even after attaining scripting mastery, you still might feel that an existing package is right for you. Regardless of the details, however, the motivation behind any choice in a scripting system should always be to match the project appropriately. With the huge number of features that can be either supported or left out, it’s important to realize that the best script system is the one that offers just enough functionality to get the job done without overkill. Especially in the design phase, it can be easy to overdo it with the feature list. You don’t need a Lamborghini to pick up milk from the grocery store, so this chapter will help you understand your options by discussing the fundamental types of scripting systems currently in use. Remember: Large, complicated feature lists do look cool, but they only serve to bulk up and slow down your programs when they aren’t needed. This section will cover: ■ ■ ■ ■ ■
Procedural/object-oriented language systems Command-based language systems Dynamically linked module systems Compiled versus interpreted code Existing scripting solutions
Procedural/Object-Oriented Language Systems Probably the most commonly used of the mainstream scripting systems are those built around procedural or object-oriented scripting languages, and employ the method of scripting discussed throughout this chapter. In a nutshell, these systems work by writing scripts in a high-level, procedural or object oriented language which is then compiled to virtual machine code capable of running inside a virtual machine, or left uncompiled in order to be executed by an interpreter (more on the differences between compiled and interpreted code later). The VM or interpreter employed by these systems is integrated with a host application, giving that application the capability to invoke and communicate with scripts. The languages designed for these systems are usually similar in syntax and design to C/C++, and thus are flexible, free-form languages suitable for virtually any major computing task. Although many scripting systems in this category are designed with a single type of program in mind, most can be (and are) effectively applied to any number of uses, ranging from games to Web servers to 3D modelers.
22
1. AN INTRODUCTION
TO
SCRIPTING
Unreal is a high-profile example of a game that’s really put this method of scripting to good use. Its proprietary scripting language, UnrealScript, was designed specifically for use in Unreal, and provides a highly object oriented language similar to C/C++. Check out Figure 1.5. Figure 1.5 Unreal, a first-person shooter based around a proprietary scripting system called UnrealScript.
Command-Based Language Systems Command-based languages are generally built around extremely specialized LOGO-like languages that consist entirely of program-specific commands that accept zero or more parameters. For example, a command-based scripting system for the hypothetical RPG would allow scripts to call a number of game-specific functions for performing common tasks, such as moving the player around in the game world, getting items, talking to characters, and so on. For an example of what a script might look like, consider the following: MovePlayer PlayerTalk PlayAnim PlayerTalk GetItem
10, 20 "Something is hidden in these bushes..." SEARCH_BUSHES "It's the red sword!" RED_SWORD
As you can see, the commands that make up this hypothetical language are extremely specific to an RPG like the one in this chapter. As a result, it wouldn’t be particularly practical to use this
THE FUNDAMENTAL TYPES
OF
SCRIPTING SYSTEMS
23
language to script another type of program, like a word processor. In that case, you’d want to revise the command set to be more appropriate. For example: MoveCursor SetFont PrintText LineBreak SetFontSize PrintDate LineBreak
2, 2 "Times New Roman", 24, BLACK "Newsletter" 12
Once again, the key characteristic behind these languages is how specialized they are. As you can see, both languages are written directly for their host application, with little to no flexibility. Although their lack of common language constructs such as variables and expressions, branching, iteration, and so on limit their use considerably, they’re still handy for automating linear tasks into what are often called “macros”. Programs like Photoshop and Microsoft Word allow the users to record their movements into macros, which can then be replayed later. Internally, these programs store macros in a similar fashion; recording each step of the actions in a program-specific, command-based language. In a lot of ways, you can think of HTML as command-based scripting, albeit in a more sophisticated fashion.
Dynamically Linked Module Systems Something not yet discussed regarding the procedural scripting languages discussed so far are their inherent performance issues. You see, when a compiled script is run in a virtual machine, it executes at a significantly slower rate than native machine code running directly on your CPU. I’ll discuss the specific reasons for this later, but for now, simply understand that they’re definitely not to be used for speed-critical applications, because they’re just too slow. In order to avoid this, many games utilize dynamically linked script modules. In English, that basically means blocks of C/C++ code that are compiled to native machine code just like the game itself, and are linked and loaded at runtime. Because these are written in normal C/C++ and compiled by a native compiler like Microsoft Visual C++, they’re extremely fast and very powerful. If you’re a Windows user, you actually deal with these every day; but you probably know them by their more Windows-oriented name, DLLs. In fact, most (if not all) Windows games that implement this sort of scripting system actually use Win32 DLLs specifically. Examples of games that have used this method include id Software’s Quake II and Valve’s Half-Life. Dynamically linked modules communicate with the game through an API that the game exposes to them. By using this API, the modules can retrieve and modify game state information, and thus control the game externally. Often times, this API is made public and distributed in what is
24
1. AN INTRODUCTION
TO
SCRIPTING
called an SDK (Software Development Kit), so that other programmers can add to the game by writing their own modules. These add-ons are often called mods (an abbreviation for “modification”) and are very popular with the previously mentioned games (Quake and Half-Life). At first, dynamically linked modules seem like the ultimate scripting solution; they’re separate and modularized from the host program they’re associated with, but they’ve got all the speed and power of natively compiled C/C++. That unrestricted power, however, doubles as their most significant weakness. Because most commercial (and even many non-commercial) games are played by thousands and sometimes tens of thousands of gamers, often over the Internet, scripts and add-ons must be safe. Malicious and defective code is a serious issue in large-scale products— when that many people are playing your game, you’d better be sure that the external modules those games are running won’t attempt to crash the server during multiplayer games, scan players’ hard drives for personal information, or delete sensitive files. Furthermore, even non-malicious code can cause problems by freezing, causing memory leaks, or getting lost in endless loops. If these modules are running inside a VM controlled directly by the host program, they can be dealt with safely and securely and the game can sometimes even continue uninterrupted simply by resetting an out-of-control script. Furthermore, VM security features can ensure that scripts won’t have access to places they shouldn’t be sticking their noses. Dynamically linked script modules, however, don’t run inside their host applications, but rather along side them. In these cases, hosts can assert very little control over these scripts’ actions, often leaving both themselves and the system as a whole susceptible to whatever havoc they may intentionally or unintentionally wreak. This pretty much wraps up the major types of scripting systems out there, so let’s switch the focus a bit to a more subtle detail of this subject. A screenshot of Half-Life appears in Figure 1.6.
Compiled versus Interpreted Code Earlier I mentioned compiled and interpreted code during the description of procedural language scripting systems. The difference between these two forms of code is simple: compiled code is reduced from its human-readable form to a series of machine-readable instructions called machine code, whereas interpreted code isn’t. So how does interpreted code run? It’s a valid question, especially because I said earlier that no one’s made a CPU capable of executing uncompiled C/C++ code. The answer is that the CPU doesn’t run this code directly. Instead, it’s run by a separate program, quite similar in nature to a virtual machine, called an interpreter. Interpreters are similar to VMs in the sense that they execute code in software and provide a suitable runtime environment. In many ways, however, interpreters are far more complex because they don’t execute simplistic, fine-grained machine code.
THE FUNDAMENTAL TYPES
OF
SCRIPTING SYSTEMS
25
Figure 1.6 Half-Life handles scripting and add-ons by allowing programmers to write game content in a typical C/C++ compiler using the proprietary HalfLife SDK.
Rather, they literally have to process and understand the exact same human-written, high-level C/C++ code you and I deal with every day. If you think that sounds like a tough job, you’re right. Interpreters are no picnic to implement. On the one hand, they’re based on almost all of the complex, language parsing functionality of compilers, but on the other hand, they have to do it all fast enough to provide real-time performance. However, contrary to what many believe, an interpreter isn’t quite as black and white as it sounds. While it’s true that an interpreter loads and executes raw source code directly without the aid of a separate compiler, virtually all modern interpreters actually perform an internal, pre-compile step, wherein the source code loaded from the disk is actually passed through a number of routines that encapsulate the functionality of a stand-alone compiler and produce a temporary, in-memory compiled version of the script or program that runs just as quickly as it would if it were an executable read from disk. Most interpreters allow you the best of both worlds—fast execution time and the convenience of automatic, transparent compilation done entirely at runtime. There are still some trade-offs, however; for example, if you don’t have the option to compile your scripts beforehand, you’re forced to distribute human-readable script code with your game that leaves you wide open to modifications and hacks. Furthermore, the process of loading an ASCII-formatted script and compiling it at runtime means your scripts will take a longer time to load overall. Compiled scripts can be loaded faster and don’t need any further processing once in memory.
26
1. AN INTRODUCTION
TO
SCRIPTING
As a result, this book will only casually mention interpreted code here and there, and instead focus entirely on compiled code. Again, while interpreters do function extremely well as debuggers and other development tools, the work involved in creating them outweighs their long-term usefulness (at least in the context of this book).
Existing Scripting Solutions Creating your own scripting system might be the focus of this book, but an important step in designing anything is first learning all you can about the existing implementations. To this end, you can briefly check out some currently used scripting systems. All of the systems covered in this section are free to download and use, and are supported by loyal user communities. Even after attaining scripting mastery, using an existing scripting system is always a valid choice, and often a practical one. This section is merely an introduction, however; an in-depth description of both the design and use of existing scripting systems can be found in Chapter 6.
Ruby http://www.ruby-lang.org/en/index.html
Ruby is a strongly object-oriented scripting language with an emphasis on system-management tasks. It boasts a number of advanced features, such as garbage collection, dynamic library loading, and multithreading (even on operating systems that don’t support threads, such as DOS). If you download Ruby, however, you’ll notice that it doesn’t come with a compiler. This is because it is a fully interpreted language; you can immediately run scripts after writing them without compiling them to virtual machine code. Taken directly from the official web site, here’s a small sample of Ruby code (which defines a class called Person): class Person attr_accessor :name, :age def initialize(name, age) @name = name @age = age.to_i end def inspect "#@name (#@age)" end end p1 = Person.new('elmo', 4) p2 = Person.new('zoe', 7)
SUMMARY
27
Lua http://www.lua.org/
As described by the official Lua web site, “Lua is a powerful, lightweight programming language designed for extending applications.” Lua is a procedural scripting system that works well in any number of applications, including games. One of its most distinguishing features, however, lies in its ability to be expanded by programs written with it. As a result, the core language is rather small; it is often up to the user to implement additional features (such as classes). Lua is a compact, highly expandable and compiled language that interfaces well with C/C++, and is subsequently a common choice for game scripting.
Java http://java.sun.com/
Strangely enough, Java has proven to be a viable and feature-rich scripting alternative. Although Java’s true claim to fame is designing platform independent, standalone applications (often with a focus on the internet), Java’s virtual machine, known as the JVM, can be easily integrated with C/C++ programs using the Java Native Interface, or JNI. Due to its common use in professionalgrade e-commerce applications, the JVM is an optimized, multithreaded runtime environment for compiled scripts, and the language itself is flexible and highly object oriented.
SUMMARY Phew! Not a bad way to start things off, eh? In only one chapter, you’ve taken a whirlwind tour of the world of game scripting, covering the basic concepts, a general overview of implementation, common variations on the traditional scripting method, and a whole lot of details. If you’re new to this stuff, give yourself a big pat on the back for getting this far. If you aren’t, then don’t even think about patting your back yet. You aren’t impressing anyone! (Just kidding) In the coming chapters, you’re going to do some really incredible things. So read on, because the only way you’re going to understand the tough stuff is if you master the basics first! With that in mind, you might want to consider re-reading this chapter a few times. It covers a lot of ground in a very short time, and it’s more than likely you missed a detail here or there, or still feel a bit fuzzy on a key concept or two. I personally find that even re-reading chapters I think I understood just fine turns out to be helpful in the end.
This page intentionally left blank
CHAPTER 2
Applications of Scripting Systems
“What’s wrong with science being practical? Even profitable?” ——Dr. David Drumlin, Contact
30
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
s I mentioned in the last chapter, scripting systems should be designed to do as much as is necessary and no more. Because of this, understanding what the various forms of scripting systems can do, as well as their common applications, is essential in the process of attaining scripting mastery.
AM FL Y
A
TE
So that’s what this chapter is all about: giving you some insight into how scripting is applied to real-world game projects. Seeing how something is actually used is often the best way to solidify something you’ve recently learned, so hopefully the material presented here will compliment that of the last chapter well. This has actually been covered to some extent already; the last chapter’s hypothetical RPG project showed you by example how scripting can ease the production of games that require a lot of content. This chapter approaches the topic in a more detailed and directly informative way, and focuses on more than just role-playing games. In an effort to keep these examples of script applications as diverse as possible, the chapter also takes a look at a starkly contrasting game genre, but one that gets an equal amount of attention from the scripting community——the First-Person Shooter. I should also briefly mention that if you’re coming into the book with the sole purpose of applying what you learn to an existing project, you probably already know exactly why you need to build a scripting system and feel that you can sweat the background knowledge. Regardless of your skill level and intentions, however, I suggest you at least skim this stuff; not only is it a light and fairly non-technical read, but it sets the stage for the later chapters. The concepts introduced in this chapter will be carried on throughout the rest of the book and are definitely important to understand. But enough with the setup, huh? Let’s get going. This chapter will cover how scripting systems can be applied to the following problems: ■ ■ ■ ■
An RPG’s story-related elements—non-player characters and plot details. RPG items, weapons and enemies. The objects, puzzles and switches of a first-person shooter. First-person shooter enemy behavior.
THE GENERAL PURPOSE
OF
SCRIPTING
As was explained in the last chapter, the most basic reason to implement a scripting system is to avoid the perils of hardcoding. When the content of your game is separated from the engine, it allows the tweaking, testing, and general fine-tuning of a game’s mechanics and features to be
Team-Fly®
THE GENERAL PURPOSE
OF
SCRIPTING
31
carried out without constant recompilation of the entire project. It also allows the game to be easily expanded even after it’s been compiled, packaged, and shipped (see Figure 2.1). Modifications and extensions can be downloaded by players and immediately recognized by the game. With a system like this, gameplay can be extended indefinitely (so long as people produce new scripts and content, of course). Figure 2.1 Game logic can be treated as modular content, allowing it to be just as flexible and interchangeable as graphics and sound.
Because the ideal separation of the game engine and its content allows the engine’s executable to be compiled without a single line of game-specific code, the actual game the player experiences can be composed entirely of scripts and other media, like graphics and sound. What this means is that when players buy the game, they’re actually getting two separate parts; a compiled game engine and a series of scripts that fleshes it out into the game itself. Because of this modular architecture, entirely new games such as sequels and spinoffs can be distributed in script-form only, running without modification on the engine that players already have. One common application of this idea is distributing games in “episode” form; that means that stores only sell the first 25 percent or so of the game at the time of purchase, along with the executable engine capable of running it. After players finish the first episode, they’re allowed to download or buy additional episodes as “patches” or “add-ons” for a smaller fee. This allows gamers to try games before committing to a full purchase, and it also lets the developers easily release new episodes as long as the game franchise is in demand. Rather than spend millions of dollars developing a full-blown sequel to the game, with a newly designed and coded engine, additional episodes can be produced for a fraction of the cost by basing them entirely on scripts and taking advantage of the existing engine, while still keeping players happy.
32
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
With this in mind, scripting seems applicable to all sorts of games; don’t let the example from the first chapter imply that only RPGs need this sort of technology. Just about any type of game can benefit from scripting; even a PacMan clone could give the different colored ghosts their own unique AI by assigning them individual scripts to control their movement. So the first thing I want to impress upon you is how flexible and widely applicable these concepts are. All across the board, games of every genre and style can be reorganized and retooled for the better by introducing a scripting system in some capacity. So to start things off on a solid footing, let’s begin this tour of scripting applications with another look RPGs. This time I’ll of course go into more detail, but at least this gets you going with some familiar terrain.
ROLE PLAYING GAMES (RPGS) Although I’ve been going out of my way to assure you that RPGs are hardly the only types of games to which one can apply a scripting system, you do hear quite a bit of scripting-related conversation when hanging around RPG developers; often more so than other genres in fact. The reason for this is that RPGs lend themselves well to the concept of scripts because they require truly massive amounts of game content. Hundreds of maps, countless weapons, enemies and items, thousands of roaming characters, hundreds of megs worth of sound and music, and so on. So, naturally, RPG developers need a good way to develop this content in a structured and organized manner. Not surprisingly, scripting systems are the answer to this problem more often than not. In order to understand why scripting can be so beneficial in the creation of RPGs, let’s examine the typical content of these games. This section covers: ■ ■ ■ ■
Complex, in-depth stories Non-player characters (NPCs) Items and weapons Enemies
Complex, In-Depth Stories Role playing games are in a class by themselves when it comes to their storylines. Although many games are satisfied with two paragraphs in the instruction manual that essentially boil down to “You’ve got 500 pounds of firepower strapped to your back. Blow up everything that moves and you’ll save democracy!”, RPGs play more like interactive novels. This means multi-dimensional characters with endless lines of dialogue and a heavily structured plot with numerous “plot points” that facilitate the progression of a player through the story.
ROLE PLAYING GAMES (RPGS)
33
At any given point in the player’s adventure, the game is going to need to know every major thing the player has done up until that point in order to determine the current state of the game world, and thus, what will happen next. For example, if players can’t stop the villain from burning the bridge to the hideout early in the game, they might be forced to find an alternate way in later.
The Solution Many RPGs employ an array of “flags” that represent the current status of the plot or game world. Each flag represents an event in the game and can be either true or false (although similar systems allow flags to be more complex than simple Boolean values). At the beginning of the game, every flag will be FALSE because the player has yet to do anything. As players progress through the game, they’re given the opportunity to either succeed or fail in various challenges, and the flags are updated accordingly. Therefore, at any given time, the flag array will provide a reasonably detailed history of the player’s actions that the game can use to determine what to do next. For example, to find out if the villain’s bridge has been burned down, it’s necessary to check its corresponding flag. Check out figure 2.2.
Figure 2.2 Every event in the game is represented by an element (commonly Boolean) in the game flag array. At any time, the array can be used to determine the general course the player has taken. This can be used to determine future events and conditions.
Implementation of this system can be approached in a number of ways. One method is to build the array of flags directly in the engine source code, and provide an interface to scripts that allows them to read and write to the array (basically just “get” and “set” functions). This way, most of the logic and functionality behind the flag system lies in external scripts; only the array itself needs to be built into the game engine. Depending on the capabilities of your scripting system, however, you might even be able to store the array itself in a script as well, and thus leave the
34
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
engine entirely untouched. This is technically the ideal way to do it, because all game logic is offloaded from the main engine, but either way is certainly acceptable.
Non-Player Characters (NPCs) One of the most commonly identifiable aspects of any RPG is the constant conversation with the characters that inhabit the game world. Whether it be the friendly population of the hero’s home village or a surly guard keeping watch in front of a castle, virtually all RPGs require the player to talk to these non-player characters, or NPCs, in order to gather the information and clues necessary to solve puzzles and overcome challenges. Generally speaking, the majority of the NPCs in an RPG will only spark trivial conversations, and their dialogue will consist of nothing more than a linear series of statements that never branch and always play out the same, no matter how many times you approach them. Kinda like that loopy uncle you see on holidays that no one likes to talk about. Things aren’t always so straightforward however. Some characters will do more than just ramble; they might ask a question that results in the player being prompted to choose from a list of responses, or ask the player to give them money in exchange for information or items, or any number of other things. In these cases, things like conditional logic, iteration, and the ability to read game flags become vital. An example of real character dialogue from Square’s Final Fantasy 9 can be found in Figure 2.3. Figure 2.3 Exchanging dialogue with an NPC in Squaresoft’s Final Fantasy 9.
ROLE PLAYING GAMES (RPGS)
35
The Solution First, let’s discuss some of the simpler NPC conversations that you’ll find in RPGs. In the case of conversations that don’t require branching, a command-based language system is more than enough. For example, imagine you’d like the following exchange in your game: NPC: “You look like you could use some garlic.” Player: “Excuse me?” NPC: “You’re the guy who’s saving the world from the vampires, right?” Player: “Yeah, that’s me.” NPC: “So you’re gonna need some garlic, won’t you?” Player: “I suppose I will, now that you mention it.” NPC: “Here ya go then!” ( Gives player garlic ) Player: “Uh…thanks, I guess.” ( Player scratches head ) If you were paying attention, you might have noticed that only about four unique commands are necessary to implement this scene. And if you weren’t paying attention, you probably still aren’t, so I’ll take advantage of this opportunity and plant some subliminal messages into your unknowing subconscious: buy ten more copies of this book for no reason other than to inflate my royalty checks. Anyway, here’s a rundown of the functionality the scene requires: ■ Both the player and the NPC need the ability to talk. ■ The NPC needs to be able to give the player an item (vampire-thwarting garlic, in this case). ■ There should also be a general animation-playing command to handle the head scratching.
Here’s that same conversation, in command-based script form: NPCTalk "You look like you could use some garlic." PlayerTalk "Excuse me? NPCTalk "You're the guy who's saving the world from the vampires, right?" PlayerTalk "Yeah, that's me." NPCTalk "So you're gonna need some garlic, won't you?" PlayerTalk "I suppose I will, now that you mention it." NPCTalk "Here ya go then!" GetItem GARLIC PlayerTalk "Uh... thanks, I guess." PlayAnim PLAYER_SCRATCH_HEAD
36
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
Pretty straightforward, huh? Once written, this script would then be associated with the NPC, telling the game to run it whenever the player talks to him (or her, or it, or whatever your NPCs are classified as). It’s a simple but elegant solution; all you need to establish is a one-to-one mapping of scripts to NPCs and you’ve got an easy and reasonably flexible way to externally control the inhabitants of your game world. To see this concept displayed in a more visual manner, check out Figure 2.4. Figure 2.4 Every NPC in an RPG world is controlled and described by a unique script.The graphics simply personify them on-screen.
The honeymoon doesn’t last forever, though, and sooner or later some of the more audacious characters roaming through your village will want to do more than just rattle off an unchanging batch of lines every time the player talks to them. They might want to ask the player a question that’s accompanied by an on-screen list of answers to chose from, and have the conversation take different paths depending on the player’s response. Maybe they’ll need to be able to read the game flags and say different things depending on the player’s history, or even write to the flags to change the course of future events. Or perhaps one of your characters is short-tempered and should become noticeably agitated if you attempt to talk to him repeatedly. The point is, a good RPG engine will allow its NPCs to be as flexible and lifelike as necessary, so you’re going to need a far more descriptive and powerful language to program their behavior. With this in mind, let’s take a look at some of the more complex exchanges that can take place between the player and an NPC.
ROLE PLAYING GAMES (RPGS)
37
(Player talks to NPC for the first time) NPC: “Hey, you look familiar.” (Squints at player’s face) Player: “Do I? I don’t believe we’ve met.” NPC: “Wait a sec— you’re the guy who’s gonna save the world from the vampires, right?” NPC: (If player says Yes) “I knew it! Here, take this garlic!” ( Gives player garlic ) Player: “Thanks!” (Player talks to NPC again) NPC: “Sorry, I don’t have any more garlic. I gave you all I had last time we spoke.” Player: “Well that sucks. (Stamps feet)” (Player talks to NPC a third time) NPC: “Dude I told you, I gave you all my garlic. Leave me alone!” Player: But I ran out, and there’s still like 10 more vampires that need to be valiantly defeated!” NPC: “Hmm…well, my brother lives in the next town over, and he owns a garlic processing plant. I’ll tell him you’re in the area, and to have a fresh batch ready for you. Next time you’re there, just talk to him, and he’ll give you all the garlic you need.” Player: “Thanks, mysterious garlic-dispensing stranger!” NPC: “My name’s Gary.” Player: “Whatever.” (Player talks to NPC more than three times) NPC: “So, have you seen my brother yet?” That’s quite a step up from the previous style of conversation, isn’t it? Don’t bother trying to figure out how many commands you’d need to script it, because command-based languages just don’t deliver in situations like this. So instead, let’s look at the general features a language would need to describe this scene. ■ Basic conversational capabilities are a given; both the NPC and the player need to be
able to speak (which, more or less, just means printing their dialogue in a text box). ■ There are a number of points during the conversation at which small animations would
be nice, such as the NPC squinting his eyes and the player stamping his feet, so you’ll need to be able to tell the engine which animations to play and when. ■ Just like the previous example, the NPC gives the player garlic. Therefore, he’ll need access to the player’s inventory.
38
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
■ As you can see in the first exchange, the NPC needs the ability to ask the player a ques-
tion. At the very least, he needs to prompt the player for a yes or no response and branch out through the script’s code depending on the result. It’d be nice to provide a custom list of possible answers as well, however, because not everything is going to be a yes or no question (unless the player is a walking magic 8 ball, but to be quite honest I can’t see that game selling particularly well outside of Japan). ■ Obviously, because the NPC clearly says different things depending on how many times the player has talked to him (up to four iterations, in this case), you need to keep track of the player’s history with this character. Furthermore, because the player could theoretically quit and resume the game in between these separate conversations, you need not only the ability to preserve this information in memory during play, but also to save it to the disk in between game sessions. Generally speaking, you need the ability to store variable information associated with the NPC indefinitely. ■ Lastly, you need to alter the game flags. How else would Gary’s brother in the next town over be aware of the player’s need for garlic cloves? To put it in more general terms, NPCs need to be able to tell the engine what they’re up to so future events line up with the things they say. Likewise, because Gary’s brother’s script will need to read from the flags, this ability also lets NPCs base their dialogue on previous events. If you never talk to Gary a third time, his brother will have no idea who you are. Figure 2.5 illustrates the communication lines that exist between scripts, the game flags, and each other with this concept.
Judging by this list, the most prominent features you should notice are the ability to read and write variables and conditional logic that allows the script to behave differently depending on the situation. Now that you’ve really dissected it, I think this is starting to sound a lot less like a Figure 2.5 Scripts have the ability to both read and write to the game flag array. Reading allows the script to accurately respond to the player’s previous actions, whereas writing allows them to affect the future.
ROLE PLAYING GAMES (RPGS)
39
macro-esque, command-based script and a lot more like the beginnings a C/C++ program! In essence, it will be. Let’s take a look at some C/C++-like script code that you might write to implement this conversation. static int iConverseCount = 0; static bool bIsPlayerHero = FALSE; main () { string strAnswer; if ( iConverseCount == 0 ) { NPCTalk ( "Hey, you look familiar." ); PlayAnim ( NPC, SQUINT ); PlayerTalk ( "Do I? I don't believe we've met." ); strAnswer = NPCAsk ( "Wait a sec-- you're the guy who's gonna save the world from the vampires, right?", "Yes", "No" ); if ( iAnswer == "Yes" ) { NPCTalk ( "I knew it! Here, take this garlic!" ); GiveItem ( GARLIC, 4 ); PlayerTalk ( "Thanks!" ); bIsPlayerHero = TRUE; } else { NPCTalk ( "Ah. My mistake." ); bIsPlayerHero = FALSE; } } else { if ( bIsPlayerHero ) { if ( iConverseCount == 1 ) { NPCTalk ( "Sorry, I don't have any more garlic. I gave you all I had last time we spoke." ); PlayerTalk ( "Well that sucks." );
2. APPLICATIONS
40
OF
SCRIPTING SYSTEMS
AM FL Y
PlayAnim ( PLAYER, STAMP_FEET ); } elseif ( iConverseCount == 2 ) { NPCTalk ( "Dude I told you, I gave you all my garlic. Leave me alone!" ); PlayerTalk ( "But I ran out, and there's still like 10 more vampires that need to be valiantly defeated!" ); NPCTalk ( "Hmm... well, my brother lives in the next town over, and he owns a garlic processing plant. I'll tell him you're in the area, and to have a fresh batch ready for you. Next time you're there, just talk to him, and he'll give you all the garlic you need." ); PlayerTalk ( "Thanks, mysterious garlic-dispensing stranger!" ); NPCTalk ( "My name's Gary." ); PlayerTalk ( "Whatever." );
TE
SetGameFlag ( GET_GARLIC_FROM_GARYS_BROTHER ); } else { NPCTalk ( "Seen my brother yet?" ); } } else { NPCTalk ( "Hello again." ); } } iConverseCount ++; }
Pretty advanced for a script, huh? In just a short time, things have come quite a long way from simple command-based languages. As you can see, just adding a few new features can change the design and direction of your scripting system entirely. You might also be wondering why, just because a few features were added, the language suddenly looks so much like C/C++. Although it would of course be possible to add variables, iteration constructs and conditional logic to the original language from the first example without going so far as to implement something as sophisticated as the C/C++-variant used in the previous example, the fact is that if you already need such advanced language features, you’ll most likely need
Team-Fly®
ROLE PLAYING GAMES (RPGS)
41
even more later. Throughout the course of an RPG project, you’ll most likely find use for even more advanced features like arrays, pointers, dynamic resource allocation, and so on. It’s a lot easier to decide to go with a C/C++-style syntax from the beginning and just add new things as you need them than it is to design both the syntax and overall structure of the language simultaneously. Using C/C++ syntax also keeps everything uniform and familiar; you don’t have to “switch gears” every time to move from working on the engine to working on scripts. Anyway, there’s really no need to discuss the code; for one thing it’s rather self explanatory to begin with, and for another, the point here isn’t so much to teach you how to implement that specific conversation as it is to impress upon you the depth of real scripting languages. More or less, that is C/C++ code up there. There are certainly some small differences, but for the most part that’s the same language you’re coding the engine with. Obviously, if scripts need a language that’s almost as sophisticated as the one used to write the game itself, it’s a sign that this stuff can get very advanced, very quickly. NPCs probably seemed like a trivial issue 10 minutes ago, but after looking at how much is required just to ask a few questions and set a few flags, it’s clear that even the simpler parts of an RPG benefit from, if not flat-out require, a fully procedural scripting language.
Items and Weapons Items and weapons follow a similar pattern to most other game objects. Each weapon and item is associated with a script that’s executed whenever it’s used. Like NPCs, a number of items can be scripted using command-based languages because their behavior is very “macro-like”. Others will require interaction with game flags and conditional logic. Iteration also becomes very important with items and weapons because they’ll often require animated elements. The last chapter took a look at the basic scripting of items. Actually, it really just looked at the offloading of simple item descriptions to external files, but also touched upon the theory of externally stored functionality. This chapter, however, goes into far more detail and looks at the creation of a complete, functional RPG weapon from start to finish. Because RPGs are usually designed to present a convincingly detailed and realistic game world, there obviously has to be a large and diverse selection of items and weapons. It wouldn’t make sense if, spread over the countless towns, cities, and even continents often found in role-playing games, there was only one type of sword or potion. Once again, this means you’re looking for a structured and intelligent way to manage a huge amount of information. In a basic action game with only one or two types of weapons, hardcoding their functionality is no problem; in an RPG, however, anything less than a fully scripted solution is going to result in a tangled, unmanageable mess.
42
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
Furthermore, items and weapons in modern RPGs need to be attention-grabbers. Gone are the days of casting a spell or attacking with a sword that simply causes some lost hit points; today, gamers expect grandiose animations with detailed effects like glowing, morphing, and lens flares. Because graphics programming is a demanding and complicated field, a feature-rich scripting language is an absolute necessity. Item and weapon scripts generally need to do a number of tasks. First to attend to is the actual behind-the-scenes functionality. What this is specifically of course depends on the item or weapon—it could be anything from damaging an enemy (decreasing its hit points) or healing a member of your party (increasing their hit points) to unlocking a door, clearing a passage, or whatever—the point though, is that it’s always just a simple matter of updating game variables such as player/enemy statistics or game flags. It’s a naturally basic task, and can usually be accomplished with only a few lines of code. In most cases, it can be handled with a command-based language just fine. Check out Figure 2.6. The other side of things, however, is the version of the item or weapon’s functionality that the player perceives. Granted, the player is well aware that the item is healing their party members, or that the weapon is damaging the ogre they’re battling with simply because they’re the ones who Figure 2.6 Like NPCs, weapons are mapped directly to corresponding script files.The script file defines their behavior by providing blocks of code for the game to run when the weapon is used.
ROLE PLAYING GAMES (RPGS)
43
selected and used it. But that’s not enough; like I mentioned earlier, these things need to be experienced—they need to be seen and heard. What’s the fun in using a weapon if you don’t get to see some fireworks? So, the other thing you need to worry about when scripting items and weapons are the visuals. This is where command-based languages fall short. Granted, it’d be possible to code a bunch of effects directly in the engine and assign them commands that can be called from scripts, but that’ll only result in your RPG having a processed, “cookie cutter” feel. You’ll have a large number of items and weapons that all share a small collection of effects, resulting in a lot of redundancy. You’d also have a ton of game-specific effect code mixed up with your engine, which is rarely a good thing. As for coding the effects directly with the language, commands just aren’t enough to describe unique and original visual effects
The Solution Generally speaking, it’s best to use a C/C++-style, procedural language that will allow items and weapons to define their own graphical effects, down to the tiniest details, from within the script itself. This way, the script not only updates statistics and alters game flags, it also provides its own eye candy. This whole process is actually pretty easy; it’s just a matter of providing at least a basic set of graphical routines for scripts to call. All that’s really necessary is the typical stuff—pixel plotting, drawing sprites, or maybe even playing movie files to allow for pre-rendered clips of animation—basically a refined subset of the stuff that your graphics API of choice like DirectX, OpenGL, or SDL provides. With these in place, you can code up graphical effects just as you would directly with C/C++. Let’s try creating an example weapon. What we’re going to design is a weapon called the Fire Sword (yeah I know, that sounds pretty generic, but it’s just an example, so gimme a break, okay?). The Fire Sword is used to launch fireballs at enemies, and is especially powerful against aquatic or snow-based creatures such as hydras and ice monsters. Conversely, however, it’s weaker against enemies that are used to hot, fiery environments, such as dragons, demons, and Mariah Carey. Also, just to make things interesting and force the player to think a bit more carefully about his strategy, the weapon, due to its heat, should cause a slight amount of damage to the player every time it’s used. And, because it just wouldn’t be fun without it, let’s actually throw in a fireball animation to complete the illusion. That’s a pretty good description, but it’s also important to consider the technical aspect of this weapon’s functionality: ■ You’ll need the capability to alter the statistics of game characters; namely their hit
points. You also need to factor in the fact that the sword causes serious damage to wateror snow-based enemies, but is less effective against fire-based creatures.
44
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
■ The player needs to see an actual fireball being launched from the player’s on-screen
location to that of the enemy, as well as hear an explosion-like sound effect that’s played upon impact. Because you’re now dealing with animation and sound, you’re definitely going to need conditional logic and iteration. Command-based languages are no longer an option. In addition, a basic multimedia API will have to be provided by the host application that allows scripts to, at the very least, draw sprites on the screen and play sound effects. ■ Finally, the player must be dealt a small amount of damage due to the extreme heat levels expelled by the sword. Like the first task, this is just a matter of crunching some numbers and just means you need access to the player’s stats.
And there you have it. Two of the three tasks up there are simple and easily handled by a command-based language. Unfortunately, the need for animation, as well as the need to deal different levels of damage based on the enemy’s type, rules them out and pretty much forces you to adopt a language that gives you the capability to perform branches and loops. These concepts are the very basis of animation and pretty much all other graphical effects, so your hands are tied. So, let’s see some C/C++-style code for this weapon: Player.HP -= 4; int Y = Player.OnScreenY; for ( int X = Player.OnScreenY; X < Enemy.OnScreen.X; X ++ ) BlitSprite ( FIREBALL, X, Y ); PlaySound ( KA_BOOM ); if ( Enemy.Type == ICE || Enemy.Type == WATER ) Enemy.HP -= 16; elseif ( Enemy.Type == FIRE ) Enemy.HP -= 4; else Enemy.HP -= 8;
Pretty straightforward, no? As you can see, once a reasonably powerful procedural language like the C/C++-variant is in place, actually coding the effects and functionality behind weapons like the Fire Sword becomes a relatively trivial task. In this case, it basically just boiled down to a for loop that moved a sprite across the screen and a call to a sound sample playing function. Obviously it’s a simplistic example, but it should illustrate the fact that your imagination is the only real limitation with such a flexible scripting system, because it allows you to code pretty much anything you can imagine. This sort of power just isn’t possible with command-based languages. Check out Figure 2.7 to see the fire sword in all its fiery glory.
ROLE PLAYING GAMES (RPGS)
45
Figure 2.7 The fearsome Fire Sword being wielded in battle.
Enemies I’ve covered the friendlier characters, like NPCs, and you understand the basis for the items and weapons you use to combat the forces of darkness, but what about the forces of darkness themselves? Enemies are the evil, hostile characters in RPGs. They roam the game world and repeatedly attack the players in an attempt to stop them from fulfilling whatever it is their quest revolves around. During battle, a group of enemies is very similar to the players and their travel companions; both parties are fighting to defeat the other by attacking them with weapons and aiding themselves by using items such as healing elixirs and strength- or speed-enhancing potions. In more general terms, they’re the very reason you play RPGs in the first place; despite all of the conversing, exploring and puzzle solving, at least half of the gameplay time (and sometimes quite a bit more, depending on the game) is spent on the battlefield. Not surprisingly, the way enemies are implemented in an RPG project will have a huge effect on both the success of the project itself, as well as the quality of the final game. So don’t screw it up! Figure 2.8 is a screenshot from Breath of Fire, a commercial RPG with battles in the style we’re discussing. The great thing about enemies though, is that they draw primarily on the two concepts you’ve already learned; they have the character- and personality-oriented aspects of NPCs, but they also
46
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
Figure 2.8 A battle sequence in Capcom’s Breath of Fire series.
have the functional and destructive characteristics of items and weapons. As a result, determining how to define an enemy for use in your RPG engine is basically just a matter of combining the concepts behind these two other entities.
The Solution You could approach this situation in any number of ways, but they all boil down to pretty familiar territory. As was the case with NPCs, the most important characteristic to establish when describing an enemy is its personality and behavior. Is it a strong, fast and powerful beast that attacks its opponents relentlessly and easily evades their counter-attacks? Or is it a meek, paranoid creature with a slow attack rate and relatively weak abilities? It could be either of these, but it’ll most likely lie somewhere in between——a gray area that demands a sensitive and easily-tuned method of description. You might be tempted to solve this problem by defining your enemies with a common set of parameters. For example, the behavior of enemies in your game might be described by: ■ Strength. How powerful each attack is. ■ Speed. How likely each attack is to connect with its target, as well as how likely the
enemy is to successfully dodge a counter-attack.
ROLE PLAYING GAMES (RPGS)
47
■ Endurance. How well the enemy will hold up after taking a significant amount of dam-
age. Higher endurance allows enemies to maintain their intensity when the going gets rough. ■ Armor/Defense. How much damage incoming attacks will cause. The lower the armor/defense level, the faster its hit points will decrease over the course of the battle due to its vulnerability. ■ Fear. How likely the enemy is to run away from battles when approaching defeat. ■ Intelligence. Determines the overall “strategy” of the enemy’s moves during battle. Highly intelligent enemies might intentionally attack the weakest members of the player’s party, or perhaps conserve their most powerful and physically draining attacks for the strongest. Less intelligent creatures are less likely to think this way and might waste their time attacking the wrong people with the wrong moves, plugging away with a brute force approach until the opponent is defeated.
You could keep adding parameters like these all day, but this seems like a pretty good list. It’s clear that you can describe a wide variety of enemies this way; obviously a giant ogre-like beast would have super strength, endless endurance, rock-solid defense, and be nearly fearless. It wouldn’t be particularly smart or fast, however. Likewise, a ninja or assassin would have speed and endurance to spare, as well as high intelligence and a reasonable level of strength. A lowly slime would probably have low levels of all of these things, whereas the final, ultimate villain might be maxed-out in every category. Overall, this is a simple system but it allows you to rapidly define large groups of diverse enemies with an adequate level of flexibility. It should seem awfully suspicious, however, because as you learned in the last chapter with the item description files, defining such a broad group of entities in your game with nothing more than a set of common parameters can quickly paint you into a corner and deprive you of true creative control. As you’ve most certainly guessed by now, script code comes to the rescue once again. But how do you actually organize the script’s code? Despite the parallels I’ve drawn between enemy scripts and that of items and NPCs, astute readers might have noticed that there exists one major difference between them. Items, weapons, and NPCs are all invoked on a singular basis; they perform their functionality upon activation by some sort of trigger or event, and terminate upon completing their task. The Fire Sword is inactive until the moment you use it, at which point it hurls a fireball across the screen, decreases the enemy’s hit points, and immediately returns control the game engine. Gary the NPC works the same way; the only real difference is that he talks about garlic rather than attacking anyone. In either case though, the idea is that NPCs and weapons work on a per-use basis. Enemies, on the other hand, much like the player, are constantly affecting the game throughout the course of their battles. From the moment the battle starts to the point at which either the enemy or the player is defeated, the enemy must interpret to the player’s input and make
48
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
decisions based on it. It’s in a constant state of activity, and as such, its script must be written in a different manner. Basically, the difference is that you need to think of the code as being part of a larger, constant loop rather than a single, self-contained event. Check out Figure 2.9 for a visual idea of this. Figure 2.9 The basic outline of an RPG battle loop. At each iteration of the loop, the player and enemies are both polled for input. In the case of the player, this means handling incoming data from input devices; in the case of enemies, this means executing their battle scripts.
Like virtually all types of gameplay, an RPG battle is just a constantly repeating loop that, at each iteration, accepts input from the player and the enemy, manages their interactions, and calculates the overall results of their moves. It does this non-stop until either party is defeated, at which point it terminates and a victor is declared. So, rather than writing a chunk of code that’s executed once and then forgotten, you need to write a much more specific and fine-grained routine that the game engine can automatically call every time the battle loop iterates. Instead of doing one thing and immediately being done with it, an enemy’s AI script must repeatedly process whatever input was received since its last execution, and react to that input immediately. Here’s a basic example: void Act () { int iWeakestPlayer, iLastAttacker; if ( iHitPoints < 20 ) if ( rand () % 10 == 1 ) Flee ();
ROLE PLAYING GAMES (RPGS)
49
else { iWeakestPlayer = GetWeakestPlayer (); if ( Player [ iWeakestPlayer ].iHitPoints < 20 ) Attack ( iWeakestPlayer, METEOR_SHOWER ); else { iLastAttacker = GetLastAttacker (); switch ( Player [ iLastAttacker ].iType ) { case NINJA: { Attack ( iLastAttacker, THROW_FIREBALL ); break; } case MAGE: { Attack ( iLastAttacker, BROADSWORD ); break; } case WARRIOR: { Attack ( iLastAttacker, SUMMON_DEMON ); break; } } } } }
As you can see, it’s a reasonably simple block of code. More importantly, note that it doesn’t really have a beginning or an end; it’s written to be “inserted” into an already running loop that will provide the initial input it uses to make its decisions. In a nutshell, the AI works like this: First the enemy script determines how close to defeat it is. If it’s lower than a certain threshold (fewer than 20 hit points in this case), it simulates an “attempt” to escape the battle by fleeing only if a random number generated between 1 and 10 is 1. If it
50
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
feels strong enough to keep fighting, however, it calls a function provided by the battle engine to determine the identity of the weakest player. If the enemy deems the player suitably close to defeat (in this case, if his HP is less than 20), it wipes him out with the devastating “Meteor Shower” attack (whatever that is). If the weakest player isn’t quite weak enough to finish off yet, the enemy instead goes after whoever attacked it last and chooses a specific counter-attack based on that player’s type. Not too shabby, huh? Parameter-based enemy descriptions hopefully aren’t looking too appealing now, after seeing what’s possible with procedural code.
AM FL Y
Well that just about wraps up this discussion of RPG scripting, so you can now turn your attention to a more action-oriented game genre—first-person shooters.
FIRST-PERSON SHOOTERS (FPSS)
TE
The first-person shooter is another hot spot for the research and development of scripting systems. Because such games are always on the cutting edge of realism in terms of both the game environment as well as the player’s interaction with that environment’s inhabitants, scripting plays an important role in breathing life into the creatures and objects of an FPS game world. Although the overall volume of media required for an FPS is usually less than that of an RPG, the flip side is that the expected detail and depth of both enemy AI as well as environmental interaction is much higher. While RPGs are usually more about the adventure and storyline as a whole, first-person shooters rely heavily on the immediate experience and reality of the game from one moment to the next. Figure 2.10 is a screenshot from Halo, a next-generation FPS. As a result, players expect crates to explode into flying shards when they blow up; windows to shatter when they’re shot; enemies to be intelligent and strategic, attacking in groups and coordinating their efforts to provide a realistic opposition; and powerful guns to fight their way from one side of the level to the other. There’s no room in an FPS for cookie-cutter bad guys who all follow the same pattern, or weapons that are all the same basic projectile drawn with a different sprite. Even the levels themselves need a constantly changing atmosphere and sense of character. This all screams for a scripted solution that allows these elements to be externally coded and controlled with the same flexibility of the game’s native language. Furthermore, communication between running scripts and the host application is emphasized to an almost unparalleled degree in an FPS in order to keep the illusion of a real, cohesive environment alive during the game. Although a full-fledged FPS is of course based on a huge number of game elements, this section discusses the scripting possibilities behind two of the big ones: level objects, such as crates, retractable bridges and switches, as well as enemy AI.
Team-Fly®
FIRST-PERSON SHOOTERS (FPSS)
51
Figure 2.10 Halo, a popular first person shooter from Bungee. It might be harder to tell from a still, black-and-white image, but the game is rife with living, moving detail of all types. First person shooters thrive on this sort of relentless realism, and thus require sophisticated game engines, high-end hardware and intelligent use of scripting systems.
Objects, Puzzles, and Switches (Obligatory Oh My!) The world of a highly developed FPS needs to feel “alive.” Ideally, everything around you should properly react to your interaction with it, whether you’re using it, activating it, shooting it, throwing grenades at it, or whatever else you like doing with crates and computer terminals. If you see a light switch on the wall, you should be able to flip the lights on or off with it. If the door you want to open is locked and you see a computer terminal across the room, chances are that you can use the terminal to open the door. Crates, barrels, and pretty much any sort of generic storage container (the more toxic, the better) should explode or at least fall apart when a grenade goes off nearby. Bridges should retract and extend when their corresponding levers are thrown, windows should shatter when struck, lights should crack and dim when shot, and, well, you get the idea. The point is, objects in the game world need to react to you, and they should react differently depending on how you choose to interact with them. But it’s not entirely about property damage. As fun as it may be to blow up barrels, knock out windows and demolish light fixtures, interaction with game objects is also a common way for the player to advance through the level. Locating a hidden switch might be necessary in order to extend a bridge over a chasm, gaining access to a computer terminal might be the only way to
52
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
lower the shields surrounding the reactor you want to destroy, or whatever. In these cases, objects are no longer self-contained, privately-operating entities. They now work together to create complex, interconnected systems, and can even be combined to form elaborate puzzles. Check out Figure 2.11. Figure 2.11 A mock-up hallway scene from an FPS. In scenes such as this, scripts are interconnected as functional objects that form a basic communication network. Pulling the lever will send a message to the bridge, telling it to either extend or retract.The bridge might then want to send a message to the lever on the other side, telling it to switch positions.This kind of objectto-object communication is common in such games.
First-person shooters often use switches and puzzles to increase the depth of gameplay; when pumping ammunition into aliens and zombies gets old, the player can focus instead on more intellectual challenges.
The Solution Almost everything in an FPS environment has an associated script. These scripts give each object in the game world its own custom-tailored functionality, and are executed whenever said object comes into contact with some sort of outside force, such as the shockwave of an explosion, a few hundred rounds of bullets, or the player’s prying hands. Within the script, functionality is further refined and organized by associating blocks of code with events. Events tell the script who or what specifically invoked it, and allow the script to take appropriate action based on that information. Events are necessary because even the simplest objects need to behave differently depending on the circumstances; it wouldn’t make much sense for a
FIRST-PERSON SHOOTERS (FPSS)
53
crate to violently explode when gently pushed, and it’d be equally confusing if the crate only slid over a few inches after being struck by a nuclear missile. Events in a typical FPS relate to the abilities of the players and enemies who inhabit the game world. For example, players might be able to perform the following actions: ■ Fire. Fires the weapon the player is currently armed with. ■ Use. Attempts to use whatever is in front of the player. “Using” a crate would have little
to no effect, but using a computer terminal could cause any number of things to happen. This action can also flip switches, throw levers, and open doors. ■ Push/Move. Exerts a gentle force on whatever is in front of the player in an attempt to move it around. For example, if the player needs to reach the opening to an air vent that’s a few feet too high, he or she might push a nearby crate under it to use as a intermediate step. ■ Collide. Simply the result of walking into something. This is less of an “action” and more of a resulting event that might not have been intentional.
These form an almost one-to-one relationship with the events that ultimately affect the objects in question. For example, shooting a crate would cause the game engine to alert the crate’s respective script that it’s under fire by sending it a SHOT or DESTROYED event. It might even tell the crate what sort of weapon was used, and who was firing it. Using a computer terminal would send a USE event to the terminal’s script, and so on. Once these events are received by scripts, they’re routed to the proper block of code and the appropriate action is subsequently taken. Let’s look at some example code. I’m going to show you three object scripts; one for a crate, one for a switch that opens a door, and one for an electric fence. For the sake of the examples, let’s pretend that this is a structure that contains the properties of each object, such as its visibility and location. Also, Event is a structure containing relevant event information, such as the type of event, the entity that caused it, and the direction and magnitude of force. Obviously, InvokingEvent is an instance of Event that is passed to each event script’s main () function automatically by the host application (the game engine). Here’s the crate: /* * * * */
Crate Can be shot and destroyed, as well as pushed around.
main ( Event InvokingEvent ) { switch ( InvokingEvent.Type )
2. APPLICATIONS
54
OF
SCRIPTING SYSTEMS
{ case SHOT: { /* The crate has been shot and thus destroyed, so first let's make it disappear. */ this.bIsVisibile = FALSE; /* Now let's tell the game engine to spawn an explosion in its place. */ CreateExplosion ( this.iX, this.iY, this.iZ ); /* To complete the effect, we'll tell the game engine to spawn a particle system of wooden shards, emanating from the explosion. */ CreateParticleSystem ( this.iX, this.iY, this.iZ, WOOD ); break; } case PUSH: { /* Something or someone is pushing the crate, so it's pretty much just a simple matter of moving it in their direction. We'll assume that the game engine will take care of collision detection. :) The force vector contains the force of the event along each axis, so all we really need to do is add it to the location of the crate. */ this.iX += InvokingEvent.ForceVector.iX; this.iY += InvokingEvent.ForceVector.iY; this.iZ += InvokingEvent.ForceVector.iZ;
FIRST-PERSON SHOOTERS (FPSS)
} } }
And the door switch: /* * * * * */
Door Switch Can be shot and destroyed, and is also used to open and close a door.
main ( Event InvokingEvent ) { switch ( InvokingEvent.Type ) { case SHOT: { /* Just to be evil, let's make the switch very fragile. Shooting it will destroy it and render it useless! Ha ha! */ this.bIsBroken = TRUE; /* And just to make things a bit more realistic, let's emanate a small particle system of plastic shards. */ CreateParticleSystem ( this.iX, this.iY, this.iZ, PLASTIC ); break; } case USE: {
55
2. APPLICATIONS
56
OF
SCRIPTING SYSTEMS
/* This is the primary function of the switch. Let's assume that the level's doors exist in an array, and the one we want to open or close is at index zero. */ if ( Door [ 0 ].IsOpen ) CloseDoor ( 0 ); else OpenDoor ( 0 ); break; } } }
And finally, the electric fence. /* * * * * */
Electric Fence Simply exists to shock whoever or whatever comes in contact with it.
main ( Event InvokingEvent ) { switch ( InvokingEvent.Type ) { case COLLIDE: { /* The fence only needs to react to COLLIDE events because its only purpose is to shock whatever touches it. Basically, this means decreasing the health of whatever it comes in contact with. The event structure will tell us which entity (which includes players and enemies) has come in contact with the fence. */
FIRST-PERSON SHOOTERS (FPSS)
57
Entity [ InvokingEvent.iEntityIndex ].Health -= 10; /* But what fun is electrocution without the visuals? */ CreateParticleSystem ( this.iX, this.iY, this.iZ, SPARKS ); /* And to really drive the point home... */ PlaySound ( ZAP_AND_SIZZLE ); } } }
And there you go. Three fully-functional FPS game world objects, ready to be dropped into an alien corridor, a military compound, or a battle arena. As you can see, the real heart of this system is the ability of the game engine to pass event information to the script; once this is in place, objects can communicate with each other during gameplay via the game engine and form dynamic, lifelike systems. Switches can open doors; players and enemies can blow up kerosene barrels; or whatever else you can come up with. Event-based script communication is an extremely important concept, and one that will be touched upon many times in the later chapters. In fact, let’s discuss a topic that exploits it to an even greater extent right now.
Enemy AI If nothing else, an FPS is all about mowing down bad guys. Whether they’re lurking through corridors, hiding behind crates and under overhangs, or piling out of dropships, your job description is usually pretty straightforward—to reduce them to paint. Of course, things aren’t so simple. Enemies don’t just stand there and accept your high-speed lead injections with open arms; they’re designed to evade your attacks, return the favor with their own, and generally do anything they can to stop you in your tracks. Naturally, the actual strategies and techniques involved in combat such as this are complex, requiring constant awareness of the surrounding environment and a capable level of intelligence. This is all wrapped up into a nice tidy package called “enemy AI”.
58
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
AI, or artificial intelligence, is what makes a good FPS such a convincing experience. Games just aren’t fun if enemies don’t seem lifelike and unique; if you’re simply bombarded with lemminglike creatures that dive headlong into your gunfire, you’re going to become very bored, very quickly. So, not surprisingly, the AI of FPS bad guys is a rapidly evolving field. With each new generation of shooter, players demand more and more intelligence and strategy on behalf of their computer-controlled opponents in hopes of a more realistic challenge. As a result, the days of simply hardcoding a player-tracking algorithm and slapping it into the heads of every creature in your game are long gone. Different classes of enemies need to starkly contrast others, so as to provide an adequate level of variety and realism, and of course, to keep the player from getting bored. Furthermore, even enemies within the same class should ideally exhibit their own idiosyncrasies and nuances—anything to keep a particularly noticeable pattern from emerging. In addition to simply dodging attacks, however, enemies need to exhibit clearly realistic strategies; taking advantage of crates as hiding places, blowing up explosive objects near the player rather than directly shooting at him, and so on. So far, so good; by now I think it’s safe to say that you’re sold on the flexibility of scripts; obviously, a C/C++-style scripting language with maybe a few built-in math routines for handling vectors and such should be more than enough to program lifelike AI and associate it with individual enemies. But smart enemies aren’t enough if they simply operate alone. More and more, the concept of team play is taking over, and the real fun lies in taking on a hoard of enemies that have complete awareness of and communication with one another. Rather than simply acting as a chaotic mob that charges towards the player and relies solely on its size, enemies need to intelligently organize themselves to provide a unique and constantly evolving challenge. In games like Rainbow Six, when you’re up against a team of terrorists, the illusion would be lost if they simply rushed you with guns blazing. Especially in the case of hostage situations, structured enemy communication and intelligence is an absolute must. Returning to the general action genre of first person shooters, however, consider a number of group-based techniques enemies can employ when attacking the player: ■ Breaking into simple groups for the purpose of attacking the player from a number of
angles, depriving the player of a single target to focus on. ■ Breaking into logical “task groups” that hinder the player in different ways; as one group
directly attacks the player with a point-blank assault, other groups will set up more longterm defenses, such as blocking off power-ups or access to the rest of the level or arena. ■ Literally surrounding the player on all sides (assuming the group is large enough), leaving no safe exit for the player.
As you can see, they’re rather simple ideas, but they all share a common thread—the concept of enemy communication. In order to form any sort of group, pattern or formation, enemies need to be able to share ideas and information that help transition their current positions and objec-
FIRST-PERSON SHOOTERS (FPSS)
59
tives into the desired ones. So if one enemy, designated as the “leader” of sorts, decides that surrounding the player would be the most effective strategy, that leader needs the ability to spread that message around.
The Solution If enemies need to communicate, and enemies are based on scripts, what I’m really talking about here is inter-script communication. So, for example, the script that controls the “leader” needs to be able to send messages directly to the scripts that control the other enemies. The enemy scripts are written specifically with this message system in mind, allowing them to interpret incoming messages and act appropriately. I touched on this earlier in the section on FPS objects, where object scripts were passed event descriptions that allowed them to act differently depending on the entity’s specific method of interaction with them. In that case, however, you relied on the game engine to send the messages; although players and enemies were of course responsible for invoking the events in the first place due to their actions, it was ultimately the game engine that noticed and identified the events and properly informed the object. Although engine-to-script communication is a useful and valuable capability in its own right, direct script-to-script communication is the basis for truly dynamic systems of game objects and entities that can, entirely on their own, work together to solve problems and achieve goals. Figure 2.12 depicts this process graphically. Figure 2.12 FPS enemies using scripting to communicate. In this case, they’ve used their communication abilities to form a surrounding formation around the player (the guy in the center).
60
2. APPLICATIONS
OF
SCRIPTING SYSTEMS
An actual discussion of artificial intelligence, however, would be lengthy at best and is well beyond the scope of this book. The main lesson here is that script-to-script communication is a must for any FPS, because it’s required for group-based enemy AI.
SUMMARY
AM FL Y
With any luck, your interest in scripting has taken on a more focused and educated form over the course of this chapter. This chapter took a brisk tour of a number of ways in which scripts can be applied to two vastly different styles of games, and certainly you’ve seen plenty of reasons why scripts are a godsend in more than a few situations. Fortunately, you’re pretty much finished with the introductory and background-information chapters, which means actually getting your hands dirty with some real script system development is just around the corner.
TE
Brace yourself, because the gloves are coming off and things are going to get messy!
Team-Fly®
Part Two CommandBased Scripting
This page intentionally left blank
CHAPTER 3
Introduction to CommandBased Scripting “It’s not Irish, it’s not English, it’s just... well... it’s just Pikey.” ——Turkish, Snatch
64
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
ith the introductory stuff behind you, it’s time to roll up your sleeves and take a stab at some basic scripting. To get started, you’re going to explore a simple but useful method of scripting known as command-based scripting. Command-based scripts starkly contrast the types of scripts you’ll ultimately write—they don’t support common programming language features such as variables, loops, and conditional logic. Rather, as their name suggests, command-based languages are entirely based on specific commands that can be called with optional parameters. These commands directly cause the game engine to do something, such as move a player on the screen, change the background music, or display a bitmapped image. By calling a number of commands in a sequential fashion, you can externally control the engine’s behavior (albeit in a rather simplistic way).
W
Command-based languages have a number of advantages and disadvantages, covered shortly. The most important lesson to learn about them, however, is that they’re simple and relatively weak in terms of capabilities, but they’re very easy to implement and can be used to achieve a lot of very cool results. In this chapter, you’re going to ■ Learn about the theory behind command-based languages, and how they’re
implemented. ■ Implement a command-based language that manipulates the text console. ■ Use a command-based language to script the intro sequence to a generic game. ■ Apply command-based scripting to the behavior of the non-player characters in a basic
RPG engine.
This chapter introduces a number of very important concepts that will ultimately prove vital later. Because of this, despite the relative simplicity of this chapter’s contents, it’s important that you make sure to read and understand all of it before moving on to the following chapters.
THE BASICS SCRIPTING
OF
COMMAND-BASED
Command-based languages are based on a very simple concept—high-level control of a game engine. I say high-level because command-based scripts are usually designed to do major things. Rather than rasterize individual polygons or rotate bitmaps, for example, they’re more concerned with moving characters around in the game world, unlocking doors in fortresses, scripting the dialogue and events in cut scenes, and giving the player items and weapons. When you think
THE BASICS
OF
COMMAND-BASED SCRIPTING
65
in these terms, game engines really only perform a limited number of tasks. Even a game like Quake, for example, is based primarily on only a few major actions, such as: ■ ■ ■ ■
Player and robot movement within the game world. The firing of player and robot (bot) weapons. Managing the damage taken by collisions between players, bots, and projectiles. Assigning weapons and items to players and bots who find them, and decreasing ammo levels of those weapons as they’re used. ■ Loading new maps, changing background music, and other scene/background-oriented tasks.
Now don’t get me wrong—Quake the engine is an extremely complex piece of software. Quake the game, however, despite being highly complex, can be easily boiled down to these far simpler concepts. This is true for virtually all games, and is the idea that command-based languages capitalize on, as shown in Figure 3.1. Figure 3.1 Command-based scripts control the game’s basic functionality.
High-Level Engine Control Because game engines are really only concerned with these high-level tasks, a lot can be accomplished by simply giving the engine a list of actions you want it to perform in a sequential order. As an example, think about how a Quake-like, first-person shooter game engine would switch arenas, on both a high- and low-level. Here’s how it might work on a low-level: ■ The screen freezes or is covered with a new bitmap to hide the inner workings of the
process from the player. ■ The memory allocated to hold the current level is freed.
66
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
■ The file containing the new arena’s geometry, textures, shadow maps, and other such ■ ■ ■ ■ ■ ■ ■ ■
resources is opened. The file format is parsed, headers are verified, and data is carefully extracted. New structures are allocated to store the arena, which are incrementally filled with the data from the file. The existing background music fades out. The existing background music is freed. Some sort of sound is made to give the player an auditory cue that the level change has taken place. The new background music is loaded. The new background music fades in. The screen freeze/bitmap is replaced by the next frame of the game engine running again, this time with the new level loaded.
As you can see, there are quite a lot of details to consider (and even now I’m skimming over countless intricacies). On a high-enough level, however, you can describe this sequence in much simpler terms: ■ ■ ■ ■ ■ ■ ■
A background image is displayed (or the screen is frozen). A new level is loaded. The existing background music fades out. A level-change sound is played. A new background track is loaded. The new background music fades in. The game resumes execution.
Issues like the de-allocation of memory and the individual placement of blocks of data read from files can be glossed over entirely when explaining such a process in high-level terms, because all you care about is what’s conceptually going on. In a lot of ways, it’s almost like the difference between explaining this sequence to a technical person and a non-technical person. The techie will understand the importance of memory allocation and file handles, whereas such details will probably be lost on a less technical person, like your mail carrier. The mail carrier will, however, understand concepts like fading music in and out, switching levels, and so on (or just hand you some bills and catalogs and mysteriously stop delivering to your neighborhood the next day). Figure 3.2 illustrates how these high- and low-level entities interact.
THE BASICS
OF
COMMAND-BASED SCRIPTING
67
Figure 3.2 The functionality of a game and its engine is a multi-layered system of components.
The point to all this is that writing a command-based script is like articulating the high-level explanation of the process in a reasonably structured way. Let’s just jump right in and see how the previous process would look as a command-based script: ShowBitmap "Gfx/LevelLoading.bmp" LoadLevel "Levels/Level4.lev" FadeBGMusicOut PlaySound "Sounds/LevelLoaded.wav" LoadBGMusic "Music/Level4.mp3" FadeBGMusicIn
As you can see, a command-based language is exactly that— a language based entirely on commands. Each command maps to a specific action the game engine can perform, like displaying bitmap images, loading MP3s, fading music in and out, and so on. As you can also see, these commands can accept (and indeed, often require) various parameters to help specify their tasks more precisely. In this regard, commands are highly analogous to functions, and can be thought of in more or less the same ways.
68
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
Commands Specifically, a command is a symbolic name given to a specific game engine function or action. Commands can accept zero or more parameters, which can vary in data types but must always be literal values (command-based languages don’t support variables or other methods of indirection). Here’s the general syntax: Command Param0 Param1 Param2
Imagine writing a C program that defines a main () function and a number of other random functions, each of which accept zero to N parameters. Now imagine the main () function cannot declare any local variables, or use any globals, and can only call the other functions with literal values. That’s basically what it’s like to code in a command-based language. Of course, the syntax presented here is different. For simplicity’s sake, extraneous whitespace is not allowed—the command and each of its parameters must be separated by a single space. There are no commas, tabs, or anything along those lines. Commands are always expressed on a single line and must begin at the line’s first character.
Master of Your Domain Another defining characteristic of command-based languages is that they’re highly domain-specific. Because general-purpose structures like loops and branches don’t exist, every line of code is just a call to a specific game engine feature. Because of this, each language is custom-designed around a single specific game, or type of game. This is known as the language’s domain. As you’ll soon see, many of the underlying details of a command-based scripting system’s implementation can be ported from one project, but the command list itself, and each command’s implementation, is more or less hard-coded and generally only applicable to that specific project. For example, the following commands would suit an RPG or RPG-like game nicely: MovePlayer GetItem CastSpell PlayMovie Teleport InvokeBattle
These would hardly apply to a flight simulator or racing game, however.
COMMAND-BASED SCRIPTING OVERVIEW
69
Actually Getting Something Done With all of these restrictions, you may be wondering if command-based languages (or CBLs, as the street kids are saying nowadays) are actually useful for anything. Admittedly, the inability to define or use variables, expressions, loops, branches, and other common features of programming languages is a serious setback. What this means, however, is not that command-based scripting is useless, but rather that it has different applications. For example, a 16 MHz CPU that can address 64KB of RAM might seem completely useless when compared to a 64-bit Pentium whose speeds are measured in GHz. However, such a chip might prove invaluable when developing a remote-controlled car or clock radio. Rather than thinking in terms of whether something is useful or useless, think in terms of its applications. Remember, a command-based language is a quick and easy way to define a sequential and static series of events for the game engine to perform. Although this is obviously useless when attempting to script a particle system or complex AI logic for your game’s final boss, it can be applied to simpler things like the details of your game’s intro sequence, or the behavior of simple NPCs (non-player characters) in an RPG engine. In fact, you’ll see examples of both of these applications in the following pages.
COMMAND-BASED SCRIPTING OVERVIEW Now that you understand the basics of command-based scripting, you’re ready to take a brief look at how it’s actually done.
Engine Functionality Assessment Before doing anything else, the first step in designing and implementing a command-based language is determining two points: ■ What the engine can do. ■ What the engine’s scripts will need to do.
It’s important to differentiate between something the engine can do, and something scripts will actually need it to do. Also, just because an engine is capable of something doesn’t mean a script can access or invoke it. All of the functionality you’d like to make available to scripts must first be wrapped in a command handler, which is a small piece of code that actually performs the action associated with each command. For example, let’s consider a simple, top-down, 2D RPG engine like the ones seen on the Nintendo, Super Nintendo, and Sega Saturn. These games were based around 2D maps composed of small, square graphics called tiles. These maps defined the background and general
70
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
environment of each location in the game and could scroll in all four directions. On top of these maps, sprite-based characters would move around and interact with one another, as well the underlying background map. As you learned in the last chapter, one major issue of such games is the non-player characters (NPCs). NPCs need to appear lifelike, at least to some extent, and therefore can’t simply stand still and wait for the player to approach them. They must move around on their own, which generally translates into code that must be written to define their actions.
AM FL Y
In the case of this example, the commands listed in Table 3.1 might prove useful for scripts:
Table 3.1 RPG Engine Script Commands Description
SetNPCDir
Sets the direction in which the NPC is facing.
MoveNPC
Moves the NPC along the X and Y axes by the specified distances.
Pause
Causes the NPC to stand still for the specified duration.
ShowTextBox
Displays the specified string of text in a text box; used for dialogue.
TE
Command
Each of these commands requires some form of parameters to help direct its action. Such parameters can be expressed as one of two data types—integers and strings. Parameters are not separated by commas, but by a single space instead. The parameter list is also separated from the command itself by a single space, which means the overall syntax of a command in this language is as follows: Command Param0 Param1 Param2
And exactly this. The language is in no way free-form, so arbitrary use of whitespace is not permitted. With only four commands, this particular language is hardly feature-rich. You’d be surprised by how much these four simple commands can accomplish, however. Consider the following script. SetNPCDir "Up" MoveNPC 0 -20 Pause 200 SetNPCDir "Left" MoveNPC -20 0
Team-Fly®
COMMAND-BASED SCRIPTING OVERVIEW
71
Pause 400 SetNPCDir "Down" ShowTextBox "Hmmmmm... I know I left it here somewhere..." Pause 400
Can you tell what this does just by looking at it? In only a few lines of simplistic script code, I’ve defined the behavior for an NPC who’s clearly looking for something. He starts off in a given position, facing a given direction, and turns “up” (which actually just means north). He walks in that direction 20 pixels, pauses, and then turns left (west) and walks 20 more pixels. He pauses again, this time for a longer duration, and finally turns back towards the camera (“down”, or south) and makes a comment about something he lost. The script then pauses briefly to allow the player a chance to read it, and, presumably, the script loops back to the beginning and starts over.
NOTE You may be wondering why the cardinal directions in the NPC script like "Up" and "Down" are expressed as a string.This is because the language doesn’t support symbolic constants like C’s #define or C++’s const. It would be just as easy to create a SetNPCDir command that accepted integer codes that specified directions (0-3, for example), but it’s a lot harder to remember an arbitrary number than it is to simply write the string. Regardless, this is still a messy solution, so keep reading—the next chapter will revisit this matter.
For such a simple scripting system, and even simpler script, this is quite a lively little character. Imagine how much personality you could squeeze out of your NPCs if you added just a few more commands! Hopefully, you’re beginning to understand that you don’t need too much complexity to get decent results when scripting.
Loading and Executing Scripts The lifespan of a script spans multiple phases, each of which are illustrated in Figure 3.3. First, the script is loaded. In this simple language, where vertical whitespace and comments are not permitted, this simply means loading every line of the source file into a separate element of an array of strings. Once this process is complete, the array contains an in-memory copy of the script, ready to run. Check out Figure 3.4 for a visual idea of a script’s in-memory form. Once in memory, the script is executed by passing each line of code to a script handler (or executor, or whatever you want to call it) that processes each command, reads in parameters, and so forth. After a command and its parameters are processed and understood, the command handler performs whatever task the command is associated with. The command handler for MoveNPC, for example, uses the two integer parameters (the X and Y movement) to make direct changes to
72
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
Figure 3.3 The lifespan of a script.The script is loaded into an array of strings, executed through the script handler, and finally exerts its control of the game engine. Figure 3.4 A script in memory.
the NPC data within the game engine. At this point, the script has succeeded in controlling the game engine. The execution of command-based scripts is always purely sequential. This means that execution starts with the first command (line 0) and runs until the last command (line 5, in the case of Figure 3.4). At each step of the way, a global variable representing the current line of code within the script is updated to reflect the next command to process. This global might be called something like g_iCurrLine, for “current line”. When this process is repeated in a loop, the script
COMMAND-BASED SCRIPTING OVERVIEW
executes quickly and continually, simulating the execution of actual code. Once the last command in the script is reached, the script can either stop or loop back to the beginning and run again. Figure 3.5 illustrates the execution of a script. Figure 3.5 The execution of a script.
Looping Scripts So should your scripts loop or stop when the last command ends? There’s no straight answer to this question, because this is a decision that must be made on a per-script basis. For example, continuing on with the RPG engine theme, an example of a script that should execute once and immediately stop would be TIP the script that defines the behavior of an The issue of looping scripts and their tenitem or weapon. When the player uses dency to appear contrived or predictable the item, the script needs to execute can be resolved in a number of ways. First once, allowing the item to perform its of all, scripts that are sufficiently long can task or action, and then immediately terproduce enough unique behavior before minate. The item shouldn’t operate more looping that players won’t have the time (or interest) to notice a pattern develop.Also, than once unless the player has specificalit’s possible to write a number of small ly requested it to do so, or if the item has scripts that all perform the same action in a some sort of persistent nature to it (such slightly different way, which are then loaded as a torch that must remain lit). Scripts that should loop are those that primarily control background-related or
at random by the game engine to produce behavior that is truly random (or nearly so).
73
74
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
otherwise ambient entities. For example, NPCs represent the living inhabitants of the game world, which means they should be constantly moving to keep the player’s suspension of disbelieve intact. NPC scripts, therefore, should immediately revert to the first command after executing the last so that their actions never cease. Granted, this means that looped scripts will demonstrate a discernable pattern sooner or later, which might not be a good thing. I didn’t say command-based scripts weren’t without their disadvantages, though.
IMPLEMENTING LANGUAGE
A
COMMAND-BASED
With the theory out of the way, you can now actually implement a small, command-based language. To get things started, you’re going to keep it simple and design a set of commands for scripting a scrolling text console like the ones seen in old text mode programs, or any Win32 console app.
Designing the Language The first step is establishing a list of commands the language will need in order to effectively control the console. Table 3.2 lists them. Again, just four commands. Because text consoles are pretty simple by nature, you don’t need a lot of options and can get by with just a handful of commands. Remember, just because you can make something complex doesn’t mean you should. Now that you have a language specification to work with, you’re ready to write an initial script to test it.
Table 3.2 Text Console Commands Command
Parameters
Description
PrintString
String
Prints the specified string.
PrintStringLoop
String, Count
Prints the specified string the specified number of times.
Newline
None
Prints an empty line.
WaitForKeyPress
None
Suspends execution until a key is pressed.
IMPLEMENTING
A
COMMAND-BASED LANGUAGE
75
Writing the Script It won’t take much to test this language, because you can deem it functional after implementing just four commands. Here’s a reasonable test script, though, that will help determine whether everything is working right in the following pages: PrintString "This is a command-based language." PrintString "Therefore, this is a command-based script." Newline PrintString "...and it's really quite boring." Newline PrintStringLoop "This string has been repeated four times." 4 Newline PrintString "Okay, press a key already and put us both out of our misery." PrintString "The next demo is cooler, I swear." WaitForKeyPress
Yeah, this particular script is a bit of a downer, but it will get the job done. With your first script in hand, it’s time to write a program that will execute it.
Implementation Implementing a command-based language is a mostly straightforward task. Here’s the general process: ■ The script is loaded from the file into an in-memory string array. ■ The line counter is reset to zero. ■ The command is read from the first line of code. A line’s command is considered to be
everything from the first character of the string, all the way up to the first space. ■ Based on the command, any of a number of command handlers is invoked to handle it.
These command handlers need to access the command’s parameters, so two functions are created for that (one for reading integer parameters, the other for reading strings). With the parameters processed, the command handler goes ahead and performs its task. At this point, the current line of the script is completely executed. ■ The instruction counter is incremented and the process continues. ■ After the script finishes executing, its array is freed.
Basic Interface On a basic level, all the scripting system needs to do is load scripts, run them, and unload them. Let’s look at the load and unload functions now.
76
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
LoadScript () is used to load scripts into memory. It works like this: ■ The file is opened in binary mode, and every instance of the '\n' (newline) character is
counted to determine how many lines it contains. ■ A string array is then allocated to hold the script based on this number. ■ The script is then loaded, line-by-line, and the file is closed.
Here’s the code behind LoadScript (): void LoadScript ( char * pstrFilename ) { // Create a file pointer for the script FILE * pScriptFile; // ---- Find out how many lines of code the script is // Open the source file in binary mode if ( ! ( pScriptFile = fopen ( pstrFilename, "rb" ) ) ) { printf ( "File I/O error.\n" ); exit ( 0 ); } // Count the number of source lines while ( ! feof ( pScriptFile ) ) if ( fgetc ( pScriptFile ) == '\n' ) ++ g_iScriptSize; ++ g_iScriptSize; // Close the file fclose ( pScriptFile ); // ---- Load the script // Open the script and print an error if it's not found if ( ! ( pScriptFile = fopen ( pstrFilename, "r" ) ) ) { printf ( "File I/O error.\n" ); exit ( 0 ); }
IMPLEMENTING
A
COMMAND-BASED LANGUAGE
77
// Allocate a script of the proper size g_ppstrScript = ( char ** ) malloc ( g_iScriptSize * sizeof ( char * ) ); // Load each line of code for ( int iCurrLineIndex = 0; iCurrLineIndex < g_iScriptSize; ++ iCurrLineIndex ) { // Allocate space for the line and a null terminator g_ppstrScript [ iCurrLineIndex ] = ( char * ) malloc ( MAX_SOURCE_LINE_SIZE + 1 ); // Load the line fgets ( g_ppstrScript [ iCurrLineIndex ], MAX_SOURCE_LINE_SIZE, pScriptFile ); } // Close the script fclose ( pScriptFile ); }
Notice that this function makes a reference to a constant called MAX_SOURCE_LINE_SIZE, which is used to read a specific amount of text from the script file. I usually set this value to 4096, just to eliminate all possibilities of leaving something out, but this is overkill—especially in the case of a command-based language, I can virtually guarantee you’ll never need more than 192 or so. The only possible exceptions will be huge string parameters, which may come up now and then when scripting complicated dialogue sequences. So no matter what, with a large enough value this constant will have you covered (besides, you’re always free to change it). Once the source is loaded into the array, it can be executed. Before getting to that, however, check out UnloadScript (), which is called just before the program ends to free the script’s resources: void UnloadScript () { // Return immediately if the script is already free if ( ! g_ppstrScript ) return;
78
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
// Free each line of code individually for ( int iCurrLineIndex = 0; iCurrLineIndex < g_iScriptSize; ++ iCurrLineIndex ) free ( g_ppstrScript [ iCurrLineIndex ] ); // Free the script structure itself free ( g_ppstrScript ); }
The function first makes sure the g_ppstrScript [] array is valid, and then manually frees each line of code. After this step, the string array pointer is freed, which completely unloads the script from memory.
Execution With the script in memory, it’s ready to run. This is accomplished with a call to RunScript (), which will run until the entire script has been executed. The execution cycle for a commandbased language is really quite simple. Here’s the basic process: ■ The command is read from the current line. ■ The command is used to determine which command handler should be invoked, by
comparing the command string found in the script to each command string the language supports. In this case, the strings are PrintString, PrintStringLoop, Newline, and WaitForKeyPress. ■ Each of these commands is given a small block of code to handle its functionality. These blocks of code are wrapped in a chain of if/else if statements that are used to determine which command was specified. ■ Once inside the command handler, an optional number of parameters are read from the current line and converted from strings to their actual values. These values are then used to help perform the commands action. ■ The command block terminates, the line counter is incremented, and a check is made to determine whether the end of the script has been reached. If so, RunScript () returns; otherwise the process repeats.
All in all, it’s a pretty straightforward process. Just loop through each line of code and do what each command specifies. Now that you understand the basic logic behind RunScript (), you can take a look at the code. By the way, there will be a number of functions referenced here that you haven’t seen yet, but they should be pretty self-explanatory:
IMPLEMENTING
A
COMMAND-BASED LANGUAGE
void RunScript () { // Allocate strings for holding source substrings char pstrCommand [ MAX_COMMAND_SIZE ]; char pstrStringParam [ MAX_PARAM_SIZE ]; // Loop through each line of code and execute it for ( g_iCurrScriptLine = 0; g_iCurrScriptLine < g_iScriptSize; ++ g_iCurrScriptLine ) { // ---- Process the current line // Reset the current character g_iCurrScriptLineChar = 0; // Read the command GetCommand ( pstrCommand ); // ---- Execute the command // PrintString if ( stricmp ( pstrCommand, COMMAND_PRINTSTRING ) == 0 ) { // Get the string GetStringParam ( pstrStringParam ); // Print the string printf ( "\t%s\n", pstrStringParam ); } // PrintStringLoop else if ( stricmp ( pstrCommand, COMMAND_PRINTSTRINGLOOP ) == 0 ) { // Get the string GetStringParam ( pstrStringParam ); // Get the loop count int iLoopCount = GetIntParam (); // Print the string the specified number of times for ( int iCurrString = 0;
79
3. INTRODUCTION
80
TO
COMMAND-BASED SCRIPTING
iCurrString < iLoopCount; ++ iCurrString ) printf ( "\t%d: %s\n", iCurrString, pstrStringParam ); }
AM FL Y
// Newline else if ( stricmp ( pstrCommand, COMMAND_NEWLINE ) == 0 ) { // Print a newline printf ( "\n" ); }
TE
// WaitForKeyPress else if ( stricmp ( pstrCommand, COMMAND_WAITFORKEYPRESS ) == 0 ) { // Suspend execution until a key is pressed while ( kbhit () ) getch (); while ( ! kbhit () ); } // Anything else is invalid else { printf ( "\tError: Invalid command.\n" ); break; } } }
The function begins by creating two strings—pstrCommand and pstrStringParam. As the script is executed, these two strings will be needed to hold both the current command and the current string parameter. Because it’s possible that a command can have multiple string parameters, the command handler itself may have to declare more strings if they all need to be held at once, but because no command in this language does so, this will be fine. Note also that these two strings use constants as well to define their length. I have MAX_COMMAND_SIZE set to 64 and MAX_PARAM_SIZE set to 1024, just to make way for the potential huge dialogue strings mentioned earlier. A for loop is then entered that takes you from the first command to the last. At each iteration, an index variable called g_iCurrScriptLineChar is set to zero, and a call is made to a function called
Team-Fly®
IMPLEMENTING
A
COMMAND-BASED LANGUAGE
81
GetCommand () that fills pstrCommand with a string containing the specified command (you’ll learn more about g_iCurrScriptLineChar momentarily.) A series of if/else if’s is then entered to determine which command was found. stricmp () is used to make the language case-insensitive, which
I find convenient. As you can see, each comparison is made to a constant relating to the name of a specific command. The definitions for these constants are as follows: #define #define #define #define
COMMAND_PRINTSTRING COMMAND_PRINTSTRINGLOOP COMMAND_NEWLINE COMMAND_WAITFORKEYPRESS
"PrintString" "PrintStringLoop" "Newline" "WaitForKeyPress"
The contents of each of these if/else if NOTE blocks are the comWhy are the command names case-insensitive? Don’t C/C++ mand handlers themand indeed most other languages do just the opposite with selves, which is where their reserved words? Although it’s true that most modern you’ll find the comlanguages are largely case-sensitive, I personally find this mand’s implementaapproach arbitrary and annoying.All it seems case-sensitivity tion. You’ll find calls is good for is actually allowing you to create multiple identito parameter-returnfiers with the same name, as long as their case differs, which is a practice I find messy and highly prone to logic errors. Unless ing functions throughyou really want to differentiate between MyCommand and out these blocks of myCommand (which will only end in tears and turmoil), I suggest code—two of them, you stick with case-insensitivity. specifically—called GetStringParam () and GetIntParam (). Both of these functions scan through the current line of code and extract and convert the current parameter to its actual value for use within the command handler. I say “current” parameter, because repetitive calls to these functions will automatically return the command’s next parameter, in sequence. You’ll learn more about how parameters are dealt with in a second. After the command handler ends, the for loop automatically handles the incrementing of the instruction counter (g_iCurrScriptLine) and makes sure the script hasn’t ended. If it has, however, the RunScript () simply returns and the job is done.
Command and Parameter Extraction The last piece of the puzzle is determining how these parameters are read from the source file. To understand how this works, take a look first at how GetCommand () works; the other functions do virtually the same thing it does.
82
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
GetCommand () The key to everything is g_iCurrScriptLineChar. Although g_iCurrScriptLine keeps track of the current line within the script, g_iCurrScriptLineChar keeps track of the current character within that line. Whenever a new line is executed by the execution loop, g_iCurrScriptLineChar is immediately set to zero. This puts the index within the source line string at the very beginning, which, coincidentally, is where the command begins. Remember, because of this language’s strict whitespace policy, you know for sure that leading whitespace will never come before the command’s first character. For example, in the following line of code: PrintStringLoop "Loop" 4
The first character of the command, P, is found at character index zero. The name of the command extends all the way up to the first space, which, as you can see, comes just after p. Everything in between these two indexes, inclusive, composes a substring specifying the commands name. GetCommand () does nothing more than scans through these characters and places them in the specified destination string. Check it out: void GetCommand ( char * pstrDestString ) { // Keep track of the command's length int iCommandSize = 0; // Create a space for the current character char cCurrChar; // Read all characters until the first space to isolate the command while ( g_iCurrScriptLineChar < ( int ) strlen ( g_ppstrScript [ g_iCurrScriptLine ] ) ) { // Read the next character from the line cCurrChar = g_ppstrScript [ g_iCurrScriptLine ][ g_iCurrScriptLineChar ]; // If a space (or newline) has been read, the command is complete if ( cCurrChar == ' ' || cCurrChar == '\n' ) break; // Otherwise, append it to the current command pstrDestString [ iCommandSize ] = cCurrChar; // Increment the length of the command ++ iCommandSize;
IMPLEMENTING
A
COMMAND-BASED LANGUAGE
83
// Move to the next character in the current line ++ g_iCurrScriptLineChar; } // Skip the trailing space ++ g_iCurrScriptLineChar; // Append a null terminator pstrDestString [ iCommandSize ] = '\0'; // Convert it all to uppercase strupr ( pstrDestString ); }
Just as expected, this function is little more than a character-reading loop that incrementally builds a new string containing the name of the command. There are a few details to note, however. First of all, note that the loop checks for both single-space and newline characters to determine whether the command is complete. Remember, commands like Newline and WaitForKeyPress don’t accept parameters, so in their cases, the end of the command is also the end of the line. Also, after the loop finishes, you increment the g_iCurrScriptLineChar character index once more. This is because, as you know, a single space separates the command from the first parameter. It’s much easier to simply get this space out of the way and save subsequent calls to the Get*Param () functions from having to worry about it. A null terminator is then appended to the newly created string, and it’s converted to uppercase. By now, it should be clear why g_iCurrScriptLineChar is so important. Because this is a global value that persists between calls to GetCommand () and Get*Param (), each of these three functions can use it to determine where exactly in the current source line you are. This is why repeated calls to the parameter extraction functions always produce the next parameter, because they’re all updating the same global character index.
NOTE You may be wondering why I’m using both strupr () to convert the command string to uppercase, and using stricmp () when comparing it to each command name. stricmp () is all I need to perform a case-insensitive comparison, but I’m a bit anal retentive when it comes to this sort of thing and like to simply convert all human-written input to uppercase for that added bit of cleanliness and order. Now if you’ll excuse me, I’m going to adjust each of the objects on my desk until they’re all at perfect 90degree angles and make sure the oven is still off.
84
3. INTRODUCTION
TO
COMMAND-BASED SCRIPTING
The process followed by GetCommand () is repeated for both GetIntParam () and GetStringParam (), so you should have no trouble following them. The only real difference is that unlike GetCommand (), both of these functions convert their substring in some form to create a “final value” that the command handler will use. For example, integer parameters found in the script will, by their very nature, not be integers. They’ll be strings, and will have to be converted with a call to the atoi () function. This function will return an actual int value, which is the final value the command handler will want. Likewise, even though string parameters are already in string form, their surrounding double-quotes need to be dealt with, because the script writer obviously doesn’t intend them to appear in the final output. In both cases, the substring extracted from the script code must first be converted before returning it to the caller.
GetIntParam () GetIntParam (), like GetCommand (), scans through the current line of code from the initial position of g_iCurrScriptLineChar, all the way until the first space character is encountered. Once this substring has been extracted, atoi () is used to convert it to a true integer value, which is returned to the caller. Have a look at the code: int GetIntParam () { // Create some space for the integer's string representation char pstrString [ MAX_PARAM_SIZE ]; // Keep track of the parameter's length int iParamSize = 0; // Create a space for the current character char cCurrChar; // Read all characters until the next space to isolate the integer while ( g_iCurrScriptLineChar < ( int ) strlen ( g_ppstrScript [ g_iCurrScriptLine ] ) ) { // Read the next character from the line cCurrChar = g_ppstrScript [ g_iCurrScriptLine ][ g_iCurrScriptLineChar ]; // If a space (or newline) has been read, the command is complete if ( cCurrChar == ' ' || cCurrChar == '\n' ) break;
IMPLEMENTING
A
COMMAND-BASED LANGUAGE
// Otherwise, append it to the current command pstrString [ iParamSize ] = cCurrChar; // Increment the length of the command ++ iParamSize; // Move to the next character in the current line ++ g_iCurrScriptLineChar; } // Move past the trailing space ++ g_iCurrScriptLineChar; // Append a null terminator pstrString [ iParamSize ] = '\0'; // Convert the string to an integer int iIntValue = atoi ( pstrString ); // Return the integer value return iIntValue; }
There shouldn’t be any real surprises here, because it’s virtually the same logic found in GetCommand (). Remember that this function must also check for newlines before reading the next character, because the last parameter on the line will not be followed by a space.
GetStringParam () Lastly, there’s GetStringParam (). At this point, the function’s code will almost seem redundant, because it shares so much logic with the last two functions you’ve looked at. You know the drill; dive right in: void GetStringParam ( char * pstrDestString ) { // Keep track of the parameter's length int iParamSize = 0; // Create a space for the current character char cCurrChar;
85
3. INTRODUCTION
86
TO
COMMAND-BASED SCRIPTING<