Main Official Butterfly.net Game Developer's Guide

Official Butterfly.net Game Developer's Guide

0 / 0
How much do you like this book?
What’s the quality of the file?
Download the book for quality assessment
What’s the quality of the downloaded files?
This book details how the unique Butterfly Grid can be implemented in existing and new game projects to minimize the complexity of network programming, allowing the game developer to concentrate on game design and programming.
Year:
2004
Publisher:
Wordware Publishing, Inc.
Language:
english
Pages:
500 / 417
ISBN 10:
1556220448
ISBN 13:
9781556220449
Series:
Wordware Game Developer's Library
File:
PDF, 9.91 MB
Download (pdf, 9.91 MB)
0 comments
 

You can write a book review and share your experiences. Other readers will always be interested in your opinion of the books you've read. Whether you've loved the book or not, if you give your honest and detailed thoughts then people will find new books that are right for them.
1

Discovering Psychology: The Science of Mind

Year:
2013
Language:
english
File:
PDF, 136.22 MB
0 / 0
2

American Passenger Trains and Locomotives Illustrated

Year:
2008
Language:
english
File:
PDF, 90.67 MB
0 / 0
Official Butterfly.net®
Game Developer’s
Guide

Andrew Mulholland

Wordware Publishing, Inc.

Library of Congress Cataloging-in-Publication Data
Mulholland, Andrew.
Official Butterfly.net game developer's guide / by Andrew Mulholland.
p. cm.
Includes index.
ISBN 1-55622-044-8 (pbk.)
1. Computer games—Programming. I. Title.
QA76.76.C672M853 2004
794.8'1526—dc22
2004011572

© 2005, Wordware Publishing, Inc.
All Rights Reserved
2320 Los Rios Boulevard
Plano, Texas 75074

No part of this book may be reproduced in any form or by any means
without permission in writing from Wordware Publishing, Inc.
Printed in the United States of America

ISBN 1-55622-044-8
10 9 8 7 6 5 4 3 2 1
0406

Butterfly.net is a registered trademark and Butterfly Grid is a trademark of Butterfly.net, Inc.
All brand names and product names mentioned in this book are trademarks or service marks of their respective
companies. Any omission or misuse (of any kind) of service marks or trademarks should not be regarded as
intent to infringe on the property of others. The publisher recognizes and respects all marks used by companies,
manufacturers, and developers as a means to distinguish their products.
This book is sold as is, without warranty of any kind, either express or implied, respecting the contents of this
book and any disks or programs that may accompany it, including but not limited to implied warranties for the
book’s quality, performance, merchantability, or fitness for any particular purpose. Neither Wordware
Publishing, Inc. nor its dealers or distributors shall be liable to the purchaser or any other person or entity with
respect to any liability, loss, or damage caused or alleged to have been caused directly or indirectly by this book.

All inquiries for volume purchases of this book should be addressed to Wordware Publishing, Inc.,
at the above address. Telephone inquiries may be made by calling:
(972) 423-0090

Contents

| iii

Contents
Chapter 1 Introduction . . . . . .
Introduction . . . . . . . . . . . .
What Is Massivel; y Multiplayer? . .
Shard Worlds. . . . . . . . . . . .
What Is the Butterfly Grid? . . . .
Key Aspects . . . . . . . . . . . .
Client Libraries. . . . . . . . .
Gateway Server . . . . . . . .
Datastore/Game Server. . . . .
Technology Overview . . . . . . .
The Grid . . . . . . . . . . . .
Network Protocol Stack (NPS).
Locale System . . . . . . . . .
Dead Reckoning . . . . . . . .
Scripting Game Logic . . . . .
Summary. . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .
. .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .

1
1
1
2
3
3
3
3
4
4
4
5
5
5
5
6

Chapter 2 First Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Accessing Your Account via the Internet . . . . . . . . . . . . . . . . . . . . . . . . . 7
Accessing Your Account via SSH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Chapter 3 Getting Started with Crystal Space
Introduction. . . . . . . . . . . . . . . . . .
Getting Crystal Space . . . . . . . . . . . .
Using the CVS Client . . . . . . . . . . . .
Getting WinCVS . . . . . . . . . . . . .
Configuring WinCVS . . . . . . . . . . .
Connecting to the CVS Server . . . . . .
Updating the Local Version . . . . . . . .
Compiling Crystal Space . . . . . . . . . . .
Testing the Installation . . . . . . . . . .
Creating Your Own Project. . . . . . . . . .
Setting the Directories . . . . . . . . . .
Creating and Setting Up the Project . . .
Creating the Project Resource File . . . .
Creating a Simple 2D Application . . . .
Summary . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .
. . .

13
13
13
13
13
14
17
19
20
23
24
24
25
33
36
65

iv |

Contents

Chapter 4 Moving into 3D . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Basics of 3D in Crystal Space . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
Quake 2 Model Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Adding Simple Collision Detection . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Creating and Loading a Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Creating the World. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Saving and Converting the World . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Loading the World into Crystal Space . . . . . . . . . . . . . . . . . . . . . . . . 134
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Chapter 5 Integrating the OMS with an Existing Application . .
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Obtaining the Latest OMS Client Libraries . . . . . . . . . . . . .
Integrating the OMS without the Wrapper . . . . . . . . . . . . .
Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
OMS_EVENT_LOGON_PASS . . . . . . . . . . . . . . .
OMS_EVENT_LOGON_FAIL . . . . . . . . . . . . . . .
OMS_EVENT_IDENT_LIST_CHANGE . . . . . . . . . .
OMS_EVENT_EMBODY_DONE . . . . . . . . . . . . .
OMS_EVENT_EMBODY_FAIL . . . . . . . . . . . . . .
OMS_EVENT_THING_NEW . . . . . . . . . . . . . . .
OMS_EVENT_THING_HERE . . . . . . . . . . . . . . .
OMS_EVENT_THING_SET . . . . . . . . . . . . . . . .
OMS_EVENT_THING_DROP . . . . . . . . . . . . . . .
OMS_EVENT_THING_GONE . . . . . . . . . . . . . . .
OMS_EVENT_MESSAGE_USER_OFFLINE . . . . . . .
OMS_EVENT_MESSAGE_USER_PING . . . . . . . . .
OMS_EVENT_MESSAGE_RECEIVED . . . . . . . . . .
OMS_EVENT_MESSAGE_RECEIVED_SECURE . . . .
Integrating the OMS with the Wrapper . . . . . . . . . . . . . . .
Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
EventThingNew . . . . . . . . . . . . . . . . . . . . . . .
EventAvatarNew . . . . . . . . . . . . . . . . . . . . . . .
EventThingHere . . . . . . . . . . . . . . . . . . . . . . .
EventThingSet . . . . . . . . . . . . . . . . . . . . . . . .
EventThingDrop . . . . . . . . . . . . . . . . . . . . . . .
EventThingGone . . . . . . . . . . . . . . . . . . . . . . .
EventThingSetPosition. . . . . . . . . . . . . . . . . . . .
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

. 149
. 149
. 149
. 151
. 209
. 210
. 210
. 210
. 211
. 218
. 218
. 221
. 221
. 221
. 222
. 222
. 223
. 223
. 226
. 229
. 250
. 250
. 250
. 250
. 250
. 251
. 251
. 251
. 252

Chapter 6 Demo Game Part 1 — Building the GUI
Introduction . . . . . . . . . . . . . . . . . . . . .
Installing Qt . . . . . . . . . . . . . . . . . . . . .
Designing the GUI . . . . . . . . . . . . . . . . .
The Login Dialog . . . . . . . . . . . . . . . .
The Signup Dialog. . . . . . . . . . . . . . . .
The Error Dialog. . . . . . . . . . . . . . . . .
The Chat Dialog . . . . . . . . . . . . . . . . .
Converting the Dialogs . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

. .
. .
. .
. .
. .
. .
. .
. .
. .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

. 253
. 253
. 253
. 256
. 258
. 267
. 269
. 271
. 274

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.
.
.
.

| v

Contents

Testing the GUI . . . . . . . . . .
Adding the Sinks . . . . . . . . .
The Login Method. . . . . . .
The CreatePlayer Method . . .
The SignupCancel Method . .
The DoSignup Method . . . .
The ErrorOk Method . . . . .
The SendChatMessage Method
Summary . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

Chapter 7 Demo Game Part 2 — Signup/Login . . .
Introduction . . . . . . . . . . . . . . . . . . . . . . .
Creating a Skeleton CNetworkHandler Class . . . . . .
Implementing the Signup . . . . . . . . . . . . . . . .
Implementing the Login. . . . . . . . . . . . . . . . .
Summary . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.

. .
. .
. .
. .
. .
. .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.

. 323
. 323
. 323
. 327
. 327
. 332

Chapter 8 Demo Game Part 3 — The World .
Introduction . . . . . . . . . . . . . . . . . .
Adding Player Communication . . . . . . . .
Implementing the Chat . . . . . . . . . . . .
Adding the World . . . . . . . . . . . . . . .
Summary . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.

. .
. .
. .
. .
. .
. .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.

. 333
. 333
. 333
. 345
. 355
. 406

.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

. . .
. . . .
. . . .
. . . .
. . . .
. . . .

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

286
299
315
317
317
318
319
319
321

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407

About the Author
Andrew Mulholland has a BSc (Hons) in Computer Games
Technology and is a partner in a games development company
based in Scotland called Hunted Cow Studios Ltd.
(www.huntedcow.com). Hunted Cow’s current project is an
online gaming website called CowPlay.com, which currently
offers free multiplayer games.

2 |

Chapter 1
Shard Worlds

MMORPGs usually support at least 2,000 players per server. Each player in the
game has some form, such as a character or spaceship (or both), within a persistent world. The aim of these games is generally to be the best. There is no
defined end point for the game — it just goes on forever. As you progress
through the game, you get opportunities to “upgrade” your character or ship
within the game, using some form of experience system. RuneScape is one such
MMORPG, and is available in both free and subscription versions.

'

http://www.runescape.com

One very long running MMORPG is Ultima Online, published by ORIGIN. Its
graphics look a little dated now, but a new version is under development that has
slick 3D graphics. A sample screen shot can be seen in Figure 1-2.

'

http://www.uo.com/

Figure 1-2: Ultima Online

Shard Worlds
Both RuneScape and Ultima Online have shard worlds. A shard world is basically a duplicate of the online world on a different server. For some games, such
as Ultima, when you create your character on a particular shard, it is fixed to
that shard and cannot be played on a different shard. For RuneScape, your character can be played on any shard, but you can only see the players that are also
logged into that shard.

Introduction

| 3

What Is the Butterfly Grid?

Your question now is, presumably, why? The reason for these shard worlds is
purely because of resources. If a game server is handling above x amount of
players, the game is going to take a performance hit and reduce the experience
for all players connected to it.
So, because of this, there is not truly one world, but rather several shard
worlds that are mostly unrelated to each other.

What Is the Butterfly Grid?
The Butterfly Grid is a multilayered architecture that provides an absolute platform-independent window into a massively multiplayer world.
To the gamer, this technology gives a fresh approach to massively multiplayer technology, virtually removing the need for shard worlds and true
platform independence and allowing varied devices to seamlessly connect to the
one world.
To the developer, the Butterfly Grid provides a clean, easy-to-use API and
toolset for integration with a current or future game engine. It removes the need
to develop network code and allows focus to return to the core of the project —
the game.

Key Aspects
Let’s look now at the key aspects that make the Butterfly Grid tick.

Client Libraries
Also known as the Object Management System, the Client Libraries provide the
programming interface to the Butterfly Grid in the form of an API (application
programming interface). The OMS (Object Management System) provides the
interface with the server from within the game to send and receive information
(via the Butterfly Grid Network Protocol Stack — discussed later).
In simple terms, if a client’s player moves, that client will inform the OMS of
the event, i.e., where the player has moved to. The OMS will then transmit this
information to the Gateway Server (the middle tier — see below)
The OMS will be the main focus of this book as we focus on the integration
of it with the open source 3D engine Crystal Space.

Gateway Server
The Gateway Server is the middle tier of the Butterfly Grid and is used to translate the data objects and communication protocols into the correct format for the
current platform. On more advanced and complex platforms, this layer has little
to no work to do. However, on more humble platforms, the Gateway can be
increasingly complex and hence cause data that is not suitable for the current
platform to be lost.

4 |

Chapter 1
Technology Overview

Datastore/Game Server
Once the data objects and communication protocols have been translated by the
Gateway, they are sent to the back end for processing, which consists of the
Datastore and Game Server. This layer contains the objects that represent the
players within the world and also determines what data is relevant to which
client.

Technology Overview
Here we will look briefly at the different areas of the Butterfly Grid that contribute to the overall technology.

The Grid
The Butterfly Grid provides the means to have a single world without any
shards, giving the player the feeling of a seamless world, which is currently
uncommon — if even in existence — with current technology. So why can the
Butterfly Grid handle an unlimited number of players within one game? The
answer is simple — behind the scenes, there are multiple servers, in the form of
a fully meshed server grid, handling all the connected players. So when a player
connects, he or she is in fact connecting to a Gateway Server, which translates
the incoming and outgoing data for that client and sends the messages to the
back end servers.
In the past, each shard world (A though D) would have, for example, 2,000
players and be completely unconnected to the others as shown in Figure 1-3.

Figure 1-3: Four shard worlds

The Grid basically joins these four worlds together,
providing a single world with 8,000 players that is
handled by the four servers and allowing communication between all players on all the different servers.
However, the fact that four servers are used is transparent to the end user.

Figure 1-4: Four shards to
one world

Introduction

| 5

Technology Overview

So what this means is that sections of the game world are handled by different servers. This will be transparent to the end user and also to the developer
once the system is in place.
As the server is in fact a collection of meshed servers, you can think of each
individual server on the network as a node and the network as being the server.

Network Protocol Stack (NPS)
The Network Protocol Stack is a custom protocol based upon standard UDP
(User Datagram Protocol) transmission. The difference is the addition of a
“heartbeat” packet that notifies the sender if a packet is required for retransmission. Packets can also be flagged as reliable to ensure delivery to the receiver.

Locale System
A Locale within the Butterfly Grid is a convex area within the game world,
defined as a convex polygon within the game database, that is then assigned to
the servers within a server configuration file. This is basically what we saw
before. We would define each of the shards A through D in Figure 1-4 as a
Locale, as each can contain players; then we could assign each of the shards to a
different server. For example, if we felt that shards A and B would be much less
populated than C and D, we could combine A and B into a single locale and
assign them to a single server, maintaining C and D each on their own servers.
Note that a player must always be located within a Locale.

Dead Reckoning
The dead reckoning system within the Butterfly Grid is used simply to reduce
the amount of bandwidth consumed for transmitting state information between
players. Each player has his or her own state (position, etc.) recorded and also
other nearby players’ perception of the player’s state. If other players’ perception of the player is off by much, the player’s state is transmitted to the
applicable players. Again, all this is handled internally on the server side and
also the client side (via the OMS).

Scripting Game Logic
The Butterfly Grid also provides advanced mechanisms for scripted AI and
game logic. This process involves creating Python scripts on the server which
can then be invoked via the OMS from the client. This more advanced topic is
not covered within this book; however, more information on this topic and others mentioned within this introduction can be found in the Manifesto provided
on the companion CD-ROM. Also check the official web site for new tutorials.

'

http://www.butterflyguide.net

Team-Fly®

6 |

Chapter 1
Summary

Summary
If the advantages of using the Butterfly Grid are not yet clear, hopefully they
will be by the end of this book after you have performed your first integration of
the OMS and started your own experiments using the Butterfly Grid technology.
In the next chapter, we will look at how to set up a Butterfly.net account and
how to access it via SSH and the web. Then we will look at the basics of the
Crystal Space 3D engine. Once we have mastered that, we will look at integrating the OMS with the 3D engine, and finally we will move on to developing a
mini-game project from scratch.

Chapter 2

First Steps
Introduction
Before you can actually use the Butterfly Grid, you’ll need to create an account
with Butterfly.net, Inc. To do this, visit the following web site and fill in your
details.

'

http://www.butterfly.net/contact/registerform.html

Important When signing up, be sure to select “Other” in the “How did you
Ü hear
about us?” field and note in the Comments box below that you purchased
this book.

One of the Butterfly.net, Inc. sales representatives will contact you within 14
days and help you though the signup process.

Accessing Your Account via the Internet
When your account is activated, you will have access to the online collaboration
tools provided by the Butterfly Lab. These benefits are as follows:
n Private project spaces with role-based access control to manage and maintain design documents, source code, binary files, and art assets exclusively
with specific colleagues on a development team.
n Shared project spaces for community members to collaborate on tools, utilities, and performance optimization strategies.
n A revision control system based on Concurrent Versions System (CVS) that
stores and tracks the versions of evolving code and art assets, managing the
details of distributed or centralized development.
n Mailing list creation and management for project team members to circulate ideas and communicate changes, while newly authorized individuals can
review the project history and get up to speed.
n Issue tracking for testers and other team members to enter issues as they
arise and follow them from concept to resolution. The Lab notifies those
experts best suited to addressing each of the issues and keeps everyone
involved.

7

8 |

Chapter 2
Accessing Your Account via the Internet

Once logged in, using the username and password boxes at the top right, you
will see a browser page similar to the following.

Figure 2-1: The login splash screen

As you can see from the preceding screen shot, the splash page gives you links
to your own project and also any projects you’re currently watching. From here,
click on your own project, which will take you to a screen similar to Figure 2.2.
On the project screen, you have access to the following tools:
n Membership — The membership section allows you to invite and add new
members to your project, as well as assign roles to the new members.
n Mailing lists — The mailing lists section does just what it says — it allows
you to create and manage project mailing lists for different aspects of the
project.
n Source code — The source code section lets you view your current CVS of
the project and also gives the access details for the project. Note there is a
short tutorial in the next chapter on using CVS.
n Issue tracking — This section provides a bug and feature tracking mechanism for your project. You can also assign bugs and features to specific
members of the group and leave details regarding them. Highly useful!

First Steps

| 9

Accessing Your Account via SSH

n File sharing — Allows the upload of files to a shared folder. Good for placing coding styles, design documents, etc., that do not readily fit into the
CVS.
n News — Allows the posting of project news.
n Discussion forums — Standard forum system that can be useful for discussion of project ideas and features before they are committed.

Figure 2-2: The project screen

Accessing Your Account via SSH
Although you will not need this until you move on to the more advanced features of the Butterfly Grid, it’s useful to know how to access your account via
SSH. First, you will need an SSH client. I recommend a neat little application
called PuTTY, which can be downloaded from the following web site.

'

http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html

The download you want is the standard PuTTY program, putty.exe. Once downloaded, run the executable and you will be presented with the configuration
screen. Here you need to first enter the host name of your Butterfly account.
Ours is “wordware.butterfly.net” so we’ve entered that. Then you need to ensure
the SSH radio button is selected.

10 |

Chapter 2
Accessing Your Account via SSH

Figure 2-3: PuTTY Configuration window

After this, click the Open button on the bottom right of the configuration screen.
A connection will then be established to your Butterfly.net account and you will
be asked for your login and password details. Note that these are not the same as
your lab username and password. You will need to obtain them from the Butterfly Technical Support team. The login request screen can be seen in the
following figure:

Figure 2-4: Butterfly login request

First Steps

| 11

Accessing Your Account via SSH

Once you have entered your correct credentials, the screen will look as follows:

Figure 2-5: Logged in

If you now perform the ls command, you will see there are three folders and a
sql log file.

Figure 2-6: Folders

12 |

Chapter 2
Summary

Within the schema folder you will find a SQL file, which is the file used to create the datastore for your game. In our case, this file is called wordware.sql, but
it will vary depending on the name of your project. Note that the Oracle database must be recreated with any changes before they will become live within the
datastore. For more information on the database schema, please refer to the
Manifesto provided on the CD-ROM. Note also that we will be looking at a
recently implemented XML format for creating the schema in the final chapter
of this book.

Summary
In this chapter, we looked at the basics of connecting to your new Butterfly
account via the web and SSH. In the next chapter we get into the code by starting to look at the basics of the Crystal Space engine in preparation for our OMS
integration.

Chapter 3

Getting Started with
Crystal Space
Introduction
In this chapter, you will gain a basic knowledge of how to set up and get started
with the Crystal Space 3D engine. The reason we are going to be using Crystal
Space is because it integrates well with the OMS (Object Management System)
and it is free (which is always a good thing!).

Getting Crystal Space
There are two methods for obtaining the Crystal Space engine. The first and best
method is to use the CVS (Concurrent Versions System) repository, which is
available online. The other way is to simply download the latest CVS snapshot
directly from a web browser.
The latest snapshot is always available from the following link and has the
filename “cs-current-snapshot.zip.”

'

http://crystal.sourceforge.net/cvs-snapshots/zip/

However, it is recommended that you use a CVS client to ensure you always
have the latest version.

Using the CVS Client
Getting WinCVS
To retrieve the latest snapshot of the Crystal Space source code, we need to set
up a CVS client to connect to the CVS server. Therefore, the first step is to
download and install a suitable client. In this book, we use the WinCVS client,
which is available from the following web link (and is also available on the
companion CD-ROM).

'

http://www.collab.net/developers/tools/

NOTE There are other tools available from the above link. While it is up to
Ü you
to decide which one to use, we will only be covering the use of WinCVS here.

13

14 |

Chapter 3
Using the CVS Client

Once you download the zip file (called WinCvs120.zip), simply extract it to a
temporary directory and run setup.exe, following the standard installation.

Configuring WinCVS
Before you run WinCVS, you need to create a directory to store the administrative files that WinCVS will generate. To do this simply create a folder called
cvshome on your c:\ drive:
c:\cvshome

Once this is done, run the WinCVS client. When it starts up, you should see the
following preferences window:

Figure 3-1: The WinCvs Preferences window

If this screen is not visible when you run WinCVS (i.e., it is not the first time
you have run it), simply go to the Admin menu and select the Preferences…
option.
For the CVSROOT, we need to enter the command to connect to the Crystal
Space CVS server, which is:
:pserver:anonymous@cvs.crystal.sourceforge.net:/cvsroot/crystal

Then from the Authentication pull-down list, we need to select the “passwd” file
on cvs server option.
Once this is done, the preferences should look as follows:

Getting Started with Crystal Space

| 15

Using the CVS Client

Figure 3-2: The WinCvs Preferences window – updated General tab

Next, click on the Globals tab. This will display the following options by
default.

Figure 3-3: The WinCvs Preferences window – default Globals tab

Here we need to deselect the “Checkout read-only” option and select the “Use
TCP/IP compression” option. Also, set the compression level to 4 instead of 9.
We do this to make the files readable and writeable once downloaded and to
increase the speed at which the data transfers, respectively.
Once this is done, the preferences should look as follows:

Team-Fly®

16 |

Chapter 3
Using the CVS Client

Figure 3-4: The WinCvs Preferences window – updated Globals tab

Next, we need to set the location where the administrative information for
WinCVS should be saved. Click on the rightmost tab, WinCvs. All we need to
do here is type in (or browse for) our c:\cvshome directory, which we created
earlier, into the HOME folder field. Once this is done, the window should look
as follows:

Figure 3-5: The WinCVS Preferences window – updated WinCvs tab

Now simply click OK. Providing all the steps have been done correctly, the output area of the WinCVS client should look like the following figure.

Getting Started with Crystal Space

| 17

Using the CVS Client

Figure 3-6: The CVSROOT is now set up correctly.

Connecting to the CVS Server
The next step is to actually connect and retrieve the latest Crystal Space snapshot from the server. To do this select the Admin option from the main menu and
then click the Login… option. You will then be presented with a password
dialog.

Figure 3-7: Password authentication dialog

As we’re not actually going to be making any changes to the source code on the
server, only retrieving it, no password is required. Simply click the OK button
without entering a password. Once you do this the output area of WinCVS
should display the following:
cvs -z4 login
(Logging in to anonymous@cvs.crystal.sourceforge.net)
*****CVS exited normally with code 0*****

Once we are connected, we need somewhere for Crystal Space to go, so let’s
now create a folder called crystal on the c:\ drive:
c:\crystal

Now that we have somewhere for Crystal Space to go, we need to point
WinCVS to that directory. This is accomplished by clicking on View in the main
menu, then selecting the Browse Location option and finally the Change…
option. A dialog will then appear, allowing you to browse for a folder. In this
dialog, simply select your newly created crystal directory on the c:\ drive and
click the OK button.
Once this is done, you should see the crystal folder displayed in the
workspace area to the left, as shown in the following screen shot.

18 |

Chapter 3
Using the CVS Client

Figure 3-8: The crystal folder

The next stage is to actually start the transfer from the CVS repository. To do
this, right-click on the crystal folder shown in the left-hand window and then
select the Checkout module… option. Once this is done, the following window
will be visible:

Figure 3-9: Checkout settings window

Getting Started with Crystal Space

| 19

Using the CVS Client

The module name we need to enter in the top text field is “crystal.” Once you
enter this click on the Checkout options tab. All the check boxes on this page
should initially be blank. The only one we need to check here is “If no matching
revision is found, use the most recent one.” And that’s it! Ensure you have an
active connection to the Internet and click the OK button.
You should now start seeing some action in the bottom output area. Note that
this process will take a while (depending on your Internet connection), as, at the
time of writing, the source is around 20MB for Crystal Space.
Once the process is completed, if you expand the crystal folder, followed by
the newly created cs folder, you will notice the workspace window (left-hand
window) now contains several folders.

Figure 3-10: The local copy of Crystal Space

Updating the Local Version
So now you have the current version of Crystal Space on your hard drive. However, as Crystal Space is continually being updated, the remote repository is also
being updated with newer code, such as bug fixes and extra functionality. Therefore, before we look at compiling the Crystal Space engine, let’s finish off this
CVS section by looking at how we update our local copy of the Crystal Space
source code.
First, log in to the remote CVS server as we did in the “Connecting to the
CVS Server” section.
Once logged in successfully, expand the crystal folder, then right-click on the
CS folder. From the pop-up menu, select the Update selection… option. After
you click this, you will be presented with the following window:

20 |

Chapter 3
Compiling Crystal Space

Figure 3-11: The Update settings window

On this window, first check the “Create missing directories that exist in repository” option and then click on the Sticky options tab. In the Sticky options tab
all you need to do is check the “If no matching revision is found, use the most
recent one” option. Then simply click OK and WinCVS will update your local
version of the Crystal Space source code with any applicable changes in the
remote CVS repository.

Compiling Crystal Space
Now that we have the latest version of the Crystal Space source code on our
local machine (either by downloading a snapshot from the web or using CVS), it
is time to compile the source. This will create the libraries we require to develop
our own applications using the Crystal Space engine as well as the premade
example programs that are included in the package.
We are only going to cover compiling Crystal Space on the Windows platform using Visual Studio 6, although it is possible to use other platforms. If you
are planning to use Crystal Space with Visual Studio 7 or a different platform,
refer to the Crystal Space project home page, which is available at the following
link:

'

http://crystal.sourceforge.net/

So where do we start? The first step is to open up Visual Studio 6 and select
File, then Open Workspace…. Next, use the browse dialog to go to the following folder:
c:\crystal\CS\mk\visualc

Once there, you will see a workspace called csall.dsw.

Getting Started with Crystal Space

| 21

Compiling Crystal Space

Figure 3-12: Selecting the workspace

Open this workspace now, as it contains all the projects for the libraries,
plug-ins, and examples that are included in the Crystal Space package.
Before we attempt to compile anything though, there are a few prerequisites.
The first is the zip file that contains all the third-party libraries required by
Crystal Space. This zip file is available on the CD-ROM (cs_current_snapshot.tar.bz2) and from the following link.

'

ftp://ftp.sunsite.dk/projects/crystal/support/win32/msvc_libs_0.96.zip

Once you have acquired the additional libraries, all you need to do is extract it to
the root Crystal Space directory, which is the following if you are using the suggested names in the book.
c:\crystal\CS

You will also need to install the DirectX 9 SDK, which is available from the following link and on the CD-ROM.

'

http://www.msdn.microsoft.com/directx

After this is done, we can compile Crystal Space. However, there are many extra
plug-ins in there that we will not be using, some of which require extra libraries,
etc., that need to be downloaded and installed first. We will unload the following
projects from the workspace before we attempt to compile it:
appcaltocs
appzoo
plgcspython
plgfreefnt2
plgiso

To unload the projects, first switch the workspace to show the FileView of all
the projects by clicking the tab at the bottom of the workspace panel.

22 |

Chapter 3
Compiling Crystal Space

Figure 3-13: The FileView

Once on this view, simply right-click the project you wish to unload and select
the Unload Project option from the pop-up menu. For example, when you do
this to the plgcspython project, it should then read “plgcspython – not loaded”
and have a grayed-out folder to the left of it.
Once we have unloaded the five projects that are not required, we need to set
the active project to be grpall, as when we compile this it will compile all the
projects in the Crystal Space workspace. To set it as the active project, simply
right-click on the grpall files in the list and then select Set as Active Project
from the pop-up menu.
Next, we have to set the active configuration for the project correctly. This is
done by selecting Build from the main menu, followed by the Set Active Configuration… option. Once this is selected, a dialog will appear on which we
want to select the grpall - Win32 Debug configuration.

Figure 3-14: Setting the active project configuration

Getting Started with Crystal Space

| 23

Compiling Crystal Space

We’re ready to compile now, so click on the Build option from the main menu,
and then select the Build option. Then comes the long wait while Visual Studio
compiles all the projects (depending on the speed of your computer, of course).
Depending on the current version you downloaded from the CVS, you may
get some warning messages but as long as there were no errors, everything
should be fine.

Testing the Installation
Now that we have Crystal Space compiled, let’s check that it is working before
we attempt to develop anything using it. As I mentioned before, there are example applications included in the package that are also compiled at the same time
as the plug-ins and libraries, so to test it we will try running one of these examples now.
The best looking of the examples is WalkTest, as it shows a good range of
capabilities for the Crystal Space engine. If you take a look in the root directory
c:\crystal\CS you will find an executable called walktest.exe. Providing you
have followed the steps correctly so far, it should load and place you within a 3D
world in which you can walk about. Here is a sample screen shot of how the
WalkTest example looks.

Figure 3-15: The WalkTest example application

24 |

Chapter 3
Creating Your Own Project

Creating Your Own Project
Setting the Directories
Now that Crystal Space is installed correctly, the next step is to create our own
applications using the engine.
Before we actually create a new project, however, we need to tell Visual Studio where to find the include files and static libraries that were created when we
compiled the Crystal Space engine in the last section. To do this, click on the
Tools option from the main menu, followed by the Options… option.
When you do this, you will be presented with the Options window, which
contains several tabs of options. Next, click the Directories tab and ensure that
the Show directories for pull-down is set to “Include files.”
In the list of directories, there should currently be four listed as follows:
c:\MSSDK\INCLUDE
c:\Program Files\Microsoft Visual Studio\VC98\INCLUDE
c:\Program Files\Microsoft Visual Studio\VC98\MFC\INCLUDE
c:\Program Files\Microsoft Visual Studio\VC98\ATL\INCLUDE

The first of the four is the include directory of the DirectX SDK and may look
slightly different, depending upon which version you installed. However, the
following three are standard and contain the standard include files for Visual
Studio.
What we need to do here is add the include directory for Crystal Space to this
list, so add the following directory to the bottom of the list:
c:\crystal\CS\INCLUDE

The window should now look as follows:

Figure 3-16: Setting the CS include directory

Next, we need to set the directory where the static libraries for CS can be found.
To do this, change the Show directories for pull-down to “Library files.” The list
should now contain something similar to the following:

Getting Started with Crystal Space

| 25

Creating Your Own Project
c:\MSSDK\LIB
c:\Program Files\Microsoft Visual Studio\VC98\LIB
c:\Program Files\Microsoft Visual Studio\VC98\MFC\LIB

You may at first think the obvious directory to add would be c:\crystal\CS\libs;
however, this directory only contains the source code for the libraries. The actual
directory we need to add here is the following:
c:\crystal\CS\MK\VISUALC\CSDEBUG\BIN\LIBS

After we have added this, simply click OK. Note that this step to set the directories will only ever need to be done once, as the settings are global to all projects
created in the Visual Studio IDE.

Creating and Setting Up the Project
Now we are ready to create our actual project. To do this, first open up the
csall.dsw workspace located in the c:\crystal\CS\mk\visualc folder (just as we
did at the start of the chapter when we were compiling the Crystal Space
engine).
The easiest and best way to create your project is to include it in the Crystal
Space workspace, so once the Crystal Space workspace is loaded, click File
from the main menu and select New….
Next, select the Projects tab and then click on Win32 Application in the list to
the left. We now need to give the project a name, so enter appbutterfly in the
Project name field.
Now set the Location field to read:
c:\crystal\CS\mk\visualc

This will ensure that our project file will be kept along with all the others in the
Crystal Space workspace.
Then we want to select the Add to current workspace option (as opposed to
the Create new workspace option). Once these steps are complete, the window
should look as follows:

Figure 3-17: Creating the new project

Team-Fly®

26 |

Chapter 3
Creating Your Own Project

Next, click the OK button; the following screen should now be visible:

Figure 3-18: Win32 Application options

On this window, ensure that the “An empty project” option is selected and then
click the Finish button.
A New Project Information window will then appear. Simply click OK on
this and your new project will be created for you.
In the workspace FileView to the left, you should now see your new
appbutterfly project listed and it should contain three folders — Source Files,
Header Files, and Resource Files.

Figure 3-19: Our appbutterfly project

At the moment it is just a standard project, although there are many settings for
the project we need to change to enable it to work correctly with Crystal Space.
We’ll go through these settings one at a time. Be sure not to miss any as one little mistake can cause you a world of pain (as I found out the hard way).

Getting Started with Crystal Space

| 27

Creating Your Own Project

The first thing to do is right-click on the appbutterfly files text and then click
on the Settings… option that appears in the right-click pop-up. Once you click
on this, the Project Settings window will become visible.
First, ensure that the Settings For pull-down is set to “Win32 Debug” and
also that you are looking at the General tab on the right-hand side.
In the General tab, check that the Microsoft Foundation Classes pull-down is
set to “Not Using MFC.” For the Intermediate files field we want to enter the
following to replace what is currently there:
csdebug\temp\appbutterfly

In addition, we want to enter this into the Output files field to replace the existing entry.
Once we have done this, the General tab should look as follows.

Figure 3-20: Project Settings 4 General tab

Next, click on the Debug tab. All we need to do on this tab is set the Working
directory field to:
c:\crystal\CS

This will enable us to execute the compiled applications from within Visual Studio as they need to be executed from the root directory of Crystal Space, simply
because the root folder contains all the DLLs for the plug-ins, etc. Once we
make this change, the Debug tab settings should look as follows:

28 |

Chapter 3
Creating Your Own Project

Figure 3-21: Project Settings 4 Debug tab

Next, click on the C/C++ tab and ensure that the Category pull-down is set to
“General.” Here, we need to change the Preprocessor definitions field by deleting the current contents and replacing it with the following:
_DEBUG,_MT,WIN32,_CONSOLE,_MBCS,WIN32_VOLATILE,__CRYSTAL_SPACE__,CS_DEBUG,
CS_STRICT_SMART_POINTERS

This can be seen in the following screen shot.

Figure 3-22: Project Settings 4 C/C++ tab 4 General category

As you can see from the preceding screen shot, the Project Options text area is
automatically updated with the modified preprocessor definitions, so there are
no manual changes necessary.

Getting Started with Crystal Space

| 29

Creating Your Own Project

Next, change the Category pull-down to show “Code Generation.” As Crystal
Space requires multithreading, we need to change the Use run-time library
pull-down to “Debug Multithreaded DLL.”

Figure 3-23: Project Settings 4 C/C++ tab 4 Code Generation category

Now change the Category pull-down to the “Precompiled Headers” option and
then select the “Not using precompiled headers” option that is now visible.

Figure 3-24: Project Settings 4 C/C++ tab 4 Precompiled Headers category

Next, change the Category pull-down to the “Preprocessor” option. Here we
need to set up the additional include directories that our project requires to be
able to find all the required plug-ins and libraries.
Add the following to the Additional include directories field (which should
initially be empty):

30 |

Chapter 3
Creating Your Own Project
..\..\plugins,..\..,..\..\include\cssys\win32,..\..\include,..\..\libs,
..\..\support,..\..\apps

This can be seen in the following screen shot.

Figure 3-25: Project Settings 4 C/C++ tab 4 Preprocessor category

That concludes all the changes to the C/C++ tab, so now click on the Link tab
and ensure that the Category pull-down in this tab is set to the “General” option.
Here we first want to delete the contents of the current Output file name field
and replace it with the following:
csdebug\temp\appbutterfly\butterfly.exe

Then we want to delete everything from the Object/library modules field and
instead enter the following libraries (as this is all we require of the standard
libraries):
shell32.lib gdi32.lib user32.lib advapi32.lib

Again, once this is done the Project Options text area at the bottom will be
updated automatically. This can be seen in the following screen shot.

Getting Started with Crystal Space

| 31

Creating Your Own Project

Figure 3-26: Project Settings 4 Link tab 4 General category

Next, we need to change the Category to the “Input” option. All we need to do
here is add the following to the Additional library path field:
..\..\libs\cssys\win32\libs

This can be seen in the following screen shot.

Figure 3-27: Project Settings 4 Link tab 4 Input category

We are finished with the Link tab, so click on the Resources tab. Here we need
to first set the Resource file name field to the following:
.\csdebug\temp\appbutterfly\appbutterfly.res

Then we need to set the Additional resource include directories field to read as
follows:
..\..\include\cssys\win32;..\..\include

32 |

Chapter 3
Creating Your Own Project

Finally, we need to add CS_DEBUG to the Preprocessor definitions field so it
will then read as follows:
_DEBUG,CS_DEBUG

Once you have followed these steps, the Resources tab should look like this:

Figure 3-28: Project Settings 4 Resources tab

Nearly there! All we need to do now is go to the last tab in the list, called
Post-build step, and add the following four post-build commands:
echo
copy
echo
copy

Moving output to
"$(TargetPath)"
Moving output to
"$(TargetPath)"

CS root.
..\..
MSVC Debug Bin.
csdebug\bin

All this does is move the executable to the root Crystal Space folder (where all
the DLLs are) and the csdebug\bin directory once your application is compiled.
Once this is done correctly, the window should look as follows:

Figure 3-29: Project Settings 4 Post-build step tab

Getting Started with Crystal Space

| 33

Creating Your Own Project

That is all we need to do in the Project Settings window, so simply click OK
now.

Creating the Project Resource File
Now that we have configured the project settings, it’s time to create the resource
file for the project. Although this is not an essential step, it is good from a conformity point of view in that all the other Crystal Space applications contain a
resource detailing the version of the application.
So to do this, first click File in the main menu, followed by New…. Ensure
the File tab is selected on the window and then click on Resource Script in the
left-hand list. Enter the name as appbutterfly in the File name field on the
right-hand side. Also, ensure that the Location field is set to:
c:\crystal\CS\mk\visualc

Figure 3-30: Adding the Resource Script

Once this is done, simply click OK. Then, close all the document windows that
are open within Visual Studio, and drag the appbutterfly.rc file from the Source
files folder into the Resource files folder.

34 |

Chapter 3
Creating Your Own Project

Figure 3-31: Before and after…

Next, we’re going to add the relevant information to the resource file. To do this,
first select the ResourceView tab at the bottom of the workspace view.

Figure 3-32: ResourceView

Once in this view, right-click on appbutterfly resources and then click the
Insert… option from the pop-up menu.

Figure 3-33: Inserting a new resource

Getting Started with Crystal Space

| 35

Creating Your Own Project

When this is done, you will be presented with the Insert Resource dialog. Here
you should select the Version option from the left-hand side and then simply
click the New button.

Figure 3-34: Adding a “Version” resource

You will then be presented with the following screen:

Figure 3-35: The version information

Team-Fly®

36 |

Chapter 3
Creating Your Own Project

As you can see from the preceding figure, I have filled in some sample information; of course, it is up to you what you want to put here. Once you have
finished editing this information, save it, close it, and return to the FileView tab
of the workspace.

Creating a Simple 2D Application
Although as you know Crystal Space is a 3D engine, it also has support for 2D
drawing operations. What we are going to do now is create a basic Crystal Space
application in which you can move a graphic around the screen with the cursor
keys. Sound exciting? Well, the point of doing this is to understand the structure
of Crystal Space applications and see how input, rendering, and everything else
works, as there are some complex(ish) issues to look into before we even think
about rendering anything 3D to the screen using the engine.
Now that we have our project created and set up, the next step is to create a
folder to contain our source code. Let’s now create a folder called butterfly in
the c:\crystal\CS\apps location, giving our new folder the full path of:
c:\crystal\CS\apps\butterfly

Next, go back to Visual Studio and click File, followed by New… and this time,
select C++ Source File from the left-hand list and enter the name of this file as
butterfly in the File name field on the right-hand side. Then, before clicking OK,
change the location to be the folder we just created:
c:\crystal\CS\apps\butterfly

The completed window is shown here:

Figure 3-36: Adding the source file

Click OK and you will notice that our source file has been added into the Source
Files folder within the appbutterfly project in the workspace pane.

Getting Started with Crystal Space

| 37

Creating Your Own Project

We then want to repeat the process for adding the C/C++ Header File, which
will also be called butterfly and located in the folder we just created.
Once these steps are completed, our project should now look as follows:

Figure 3-37: Project complete with source, header, and resource files

The final step before we actually see some code is to add the dependencies to
the project. To do this, click on Project in the main menu and then select the
Dependencies… option.
The Project Dependencies window will appear. Here you want to check all
the Crystal Space library files, which are as follows:
libcsengine
libcsgeom
libcsgfx
libcssys
libcstool
libcsutil
libcsws

So, for reference, the window should now look similar to the following.

Figure 3-38: Setting the dependencies

38 |

Chapter 3
Creating Your Own Project

If you now click the OK button, you should see that they have been added as
what look similar to links under the Resource Files folder.

Figure 3-39: Project dependencies

Now we are ready for the code. What we are going to do is look at the complete
source code for the example, compile it and run it, then look in detail at the code
to see how it all works.
Following are the complete listings for the butterfly.cpp and butterfly.h files.
Listing 3-1: butterfly.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

"cssysdef.h"
"cssys/sysfunc.h"
"iutil/vfs.h"
"csutil/cscolor.h"
"cstool/csview.h"
"cstool/initapp.h"
"cstool/cspixmap.h"
"butterfly.h"
"iutil/eventq.h"
"iutil/event.h"
"iutil/objreg.h"
"iutil/csinput.h"
"iutil/virtclk.h"
"iengine/sector.h"
"iengine/engine.h"
"iengine/camera.h"
"iengine/light.h"
"iengine/statlght.h"
"iengine/texture.h"
"iengine/mesh.h"

Getting Started with Crystal Space

| 39

Creating Your Own Project
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

"iengine/movable.h"
"iengine/material.h"
"imesh/thing/polygon.h"
"imesh/thing/thing.h"
"imesh/object.h"
"ivideo/graph3d.h"
"ivideo/graph2d.h"
"ivideo/txtmgr.h"
"ivideo/texture.h"
"ivideo/material.h"
"ivideo/fontserv.h"
"igraphic/image.h"
"igraphic/imageio.h"
"imap/parser.h"
"ivaria/reporter.h"
"ivaria/stdrep.h"
"csutil/cmdhelp.h"

CS_IMPLEMENT_APPLICATION
// Application Specific...
csPixmap* logoImg;
int logo_x, logo_y;
csRef<iFont> font;
// [END] Application Specific

// The global pointer to our application...
Butterfly *butterfly;

Butterfly::Butterfly (iObjectRegistry* object_reg)
{
Butterfly::object_reg = object_reg;
}

Butterfly::~Butterfly ()
{
}

bool Butterfly::LoadPixMaps ()
{
csRef<iImage> ifile = loader->LoadImage ("/lib/butterfly/logo.jpg");
csRef<iTextureHandle> txt = txtmgr->RegisterTexture (ifile, CS_TEXTURE_2D);
txt->Prepare ();
logoImg = new csSimplePixmap (txt);
return true;
}

40 |

Chapter 3
Creating Your Own Project
void Butterfly::SetupFrame ()
{
// Check input...
if (kbd->GetKeyState (CSKEY_LEFT))
{
if(logo_x > 1)
logo_x-=2;
}
if (kbd->GetKeyState (CSKEY_RIGHT))
{
if(logo_x < 639-logoImg->Width())
logo_x+=2;
}
if (kbd->GetKeyState (CSKEY_UP))
{
if(logo_y > 1)
logo_y-=2;
}
if (kbd->GetKeyState (CSKEY_DOWN))
{
if(logo_y < 479-logoImg->Height())
logo_y+=2;
}

// Begin 2D rendering...
if (!g2d->BeginDraw ())
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Failed to begin 2D frame");
return;
}
g2d->Clear(0);
if(logoImg)
{
logoImg->DrawScaled (g3d, logo_x, logo_y, logoImg->Width(),
logoImg->Height());
}
// draw text...
int fntcol = g2d->FindRGB (255, 255, 0);

char buf[256];
sprintf(buf, "Butterfly Grid");
g2d->Write(font, 10,10, fntcol, -1, buf);

Getting Started with Crystal Space

| 41

Creating Your Own Project
sprintf(buf, "Logo at (%i,%i)", logo_x, logo_y);
g2d->Write(font, 10,25, fntcol, -1, buf);
}

void Butterfly::FinishFrame ()
{
g2d->FinishDraw ();
g2d->Print (NULL);
}

bool Butterfly::HandleEvent (iEvent& ev)
{
if (ev.Type == csevBroadcast && ev.Command.Code == cscmdProcess)
{
butterfly->SetupFrame ();
return true;
}
else if (ev.Type == csevBroadcast && ev.Command.Code == cscmdFinalProcess)
{
butterfly->FinishFrame ();
return true;
}
else if (ev.Type == csevKeyDown && ev.Key.Code == CSKEY_ESC)
{
csRef<iEventQueue> q (CS_QUERY_REGISTRY (object_reg, iEventQueue));
if (q) q->GetEventOutlet()->Broadcast (cscmdQuit);
return true;
}
return false;
}

bool Butterfly::SimpleEventHandler (iEvent& ev)
{
return butterfly->HandleEvent (ev);
}

bool Butterfly::Initialize ()
{
if (!csInitializer::RequestPlugins (object_reg,
CS_REQUEST_VFS,
CS_REQUEST_SOFTWARE3D,
CS_REQUEST_ENGINE,
CS_REQUEST_FONTSERVER,
CS_REQUEST_IMAGELOADER,
CS_REQUEST_LEVELLOADER,
CS_REQUEST_REPORTER,
CS_REQUEST_REPORTERLISTENER,
CS_REQUEST_END))
{

42 |

Chapter 3
Creating Your Own Project
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Can't initialize plugins!");
return false;
}
if (!csInitializer::SetupEventHandler (object_reg, SimpleEventHandler))
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Can't initialize event handler!");
return false;
}
// Check for commandline help.
if (csCommandLineHelper::CheckHelp (object_reg))
{
csCommandLineHelper::Help (object_reg);
return false;
}
// The virtual clock.
vc = CS_QUERY_REGISTRY (object_reg, iVirtualClock);
if (vc == NULL)
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Can't find the virtual clock!");
return false;
}
// Find the pointer to engine plug-in
engine = CS_QUERY_REGISTRY (object_reg, iEngine);
if (engine == NULL)
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"No iEngine plugin!");
return false;
}
loader = CS_QUERY_REGISTRY (object_reg, iLoader);
if (loader == NULL)
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"No iLoader plugin!");
return false;
}
g2d = CS_QUERY_REGISTRY (object_reg, iGraphics2D);
if (!g2d)
{

Getting Started with Crystal Space

| 43

Creating Your Own Project
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"No iGraphics2D plugin!");
return false;
}
g3d = CS_QUERY_REGISTRY (object_reg, iGraphics3D);
if (g3d == NULL)
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"No iGraphics3D plugin!");
return false;
}
kbd = CS_QUERY_REGISTRY (object_reg, iKeyboardDriver);
if (kbd == NULL)
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"No iKeyboardDriver plugin!");
return false;
}
// Open the main system. This will open all the previously loaded plug-ins.
if (!csInitializer::OpenApplication (object_reg))
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Error opening system!");
return false;
}

txtmgr = g3d->GetTextureManager ();
// Load in our pixmaps (for 2D)
LoadPixMaps();
font = g2d->GetFontServer()->LoadFont(CSFONT_LARGE);

return true;
}

void Butterfly::Start ()
{
csDefaultRunLoop (object_reg);
}

int main (int argc, char* argv[])
{

44 |

Chapter 3
Creating Your Own Project
iObjectRegistry* object_reg = csInitializer::CreateEnvironment (argc, argv);
butterfly = new Butterfly (object_reg);
if(butterfly->Initialize ())
butterfly->Start ();
delete butterfly;
font = NULL;
csInitializer::DestroyApplication (object_reg);
return 0;
}

Listing 3-2: butterfly.h
#ifndef __BUTTERFLY_H__
#define __BUTTERFLY_H__
#include <stdarg.h>
#include "csutil/ref.h"
struct
struct
struct
struct
struct
struct
struct
struct
struct
struct

iObjectRegistry;
iEngine;
iLoader;
iGraphics2D;
iGraphics3D;
iKeyboardDriver;
iVirtualClock;
iEvent;
iView;
iTextureManager;

class Butterfly
{
private:
iObjectRegistry* object_reg;
csRef<iEngine> engine;
csRef<iLoader> loader;
csRef<iGraphics2D> g2d;
csRef<iGraphics3D> g3d;
csRef<iKeyboardDriver> kbd;
csRef<iVirtualClock> vc;
csRef<iView> view;
csRef<iTextureManager> txtmgr;
static bool SimpleEventHandler (iEvent& ev);
bool HandleEvent (iEvent& ev);
void SetupFrame ();
void FinishFrame ();
bool LoadPixMaps ();
void DrawFrame2D ();
public:

Getting Started with Crystal Space

| 45

Creating Your Own Project
Butterfly (iObjectRegistry* object_reg);
~Butterfly ();
bool Initialize ();
void Start ();
};
#endif // __BUTTERFLY_H__

Yes, it is quite a lot of code, but 99% of it is for setting up Crystal Space. This
will compile now but before you execute it, you will need to copy butterfly.zip
from the companion CD into the following folder (don’t worry — I’ll explain
later what it is and how it works):
c:\crystal\CS\data

Additionally, you will need to add the following two lines to the vfs.cfg file,
located in the root Crystal Space folder (c:\crystal\CS).
; Added for Butterfly
VFS.Mount.lib/butterfly

= $@data$/butterfly.zip

Once this is done, you can either execute it from the root Crystal Space directory
(c:\crystal\CS\butterfly.exe) or simply run it from Visual Studio. Either way, you
should get something that looks similar to the following on the screen.

Figure 3-40: Our 2D example

Try moving the logo around with the cursor keys and note how the position of
the logo is updated in the top left corner of the screen.

Team-Fly®

46 |

Chapter 3
Creating Your Own Project

It’s time now to look into the code, so we will start by looking at the contents
of the header file, butterfly.h.
First, we include stdarg.h, which as you probably know contains the macros
(such as va_start) to access arguments of functions that have an undefined
amount of parameters. Then we include the ref.h header, which is contained
within the csutil folder. This can be seen here:
#include <stdarg.h>
#include "csutil/ref.h"

The reason for the ref.h file is so we can use a feature that is relatively new to
Crystal Space (since version 0.95) called “smart pointers.”
Before the advent of smart pointers, it was your responsibility to ensure the
internal reference counting was accurate. This was achieved by means of methods called IncRef and DecRef (if you have previous experience with DirectX,
you may be familiar with this type of system when working with COM objects).
So, in previous versions, to create a reference to a graphics object you would
have had to write something similar to this for the definition of your class:
class MainClass
{
iGraphics3D g3d;
}
MainClass::MainClass()
{
// constructor…
g3d = NULL;
}
MainClass::~ MainClass ()
{
// destructor…
if (engine) engine->DecRef ();
}

Then when you came to acquire the iGraphics3D interface, you would have used
the following code:
g3d = CS_QUERY_REGISTRY (object_reg, iGraphics3D);
if (g3d == NULL)
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"No iGraphics3D plugin!");
return false;
}

The key points to note here are the initial setting of the g3d variable to NULL in
the constructor and the call to the DecRef method in the destructor.
When using smart pointers, this is not required. Instead of declaring the g3d
object in our class like this:
iGraphics3D g3d;

Getting Started with Crystal Space

| 47

Creating Your Own Project

we would now declare it as follows:
csRef<iGraphics3D> g3d;

Doing it this way means there is no need to initially set it to NULL and there is
no need to call the DecRef method when cleaning up, as this is handled automatically when the class that contains it is deleted. Note also here that it is possible
to use a smart pointer just as you would an ordinary pointer, in that the code to
actually acquire the iGraphics3D interface would still be the same as before.
Don’t be too concerned about smart pointers; just be aware of them as they
do make life a lot easier, especially when your application has multiple exit
points.
So back to the code — after the include files, we then declare all the structures we will be using in our class and main code using the following lines:
struct
struct
struct
struct
struct
struct
struct
struct
struct
struct

iObjectRegistry;
iEngine;
iLoader;
iGraphics2D;
iGraphics3D;
iKeyboardDriver;
iVirtualClock;
iEvent;
iView;
iTextureManager;

The first is the iObjectRegistry, which acts as a registry for all other objects (and
plug-ins) within your applications. We will see how this is created and used
when we look at the butterfly.cpp source file.
Next, we have the iEngine interface, which is used to control the 3D engine
(although we will not be making use of this interface in this example as we will
be sticking to 2D for the moment).
Then we have the iLoader interface, which is used to handle the loading of
maps, textures, images, and sounds within a Crystal Space application.
The iGraphics2D and iGraphics3D interfaces handle all 2D and 3D rendering
as we will see later in this section.
The iKeyboardDriver, not surprisingly, handles keyboard input or, more accurately, places keyboard events into the event queue that again we will see later in
this section.
Next, we have the iVirtualClock interface, which provides a high-resolution
virtual game clock for use within your application. (We will not directly use this
in this example, but we will in future examples in this book.)
Then we have iEvent, which is an interface used to handle all hardware and
software events, such as the mouse and keyboard input.
The iView interface is not used in this example. However, it is the top-level
rendering interface and allows access to facilities such as the camera and 2D
screen-clipping rectangle.
Finally, we have the iTextureManager, which is used to prepare all the images
loaded into the correct format for the 3D renderer and similar tasks, such as
mipmap generation.

48 |

Chapter 3
Creating Your Own Project

Now that we have defined the required structures, we then start the definition
of our main class, which in this example is called Butterfly. In the class definition, we first create smart pointer references for all the interfaces we just
defined, with the exception of iObjectRegistry, which works a little different.
The start of the class definition can be seen here:
class Butterfly
{
private:
iObjectRegistry* object_reg;
csRef<iEngine> engine;
csRef<iLoader> loader;
csRef<iGraphics2D> g2d;
csRef<iGraphics3D> g3d;
csRef<iKeyboardDriver> kbd;
csRef<iVirtualClock> vc;
csRef<iView> view;
csRef<iTextureManager> txtmgr;

Next, we add two function prototypes that will be implemented to provide event
handling within our application. Note the first is a static method called
SimpleEventHandler, as the Crystal Space event handler expects to call a C-style
function. This function is then used to call our non-static HandleEvent method.
These two prototypes can be seen in the following two lines of code:
static bool SimpleEventHandler (iEvent& ev);
bool HandleEvent (iEvent& ev);

We then define another two non-static methods that will be called directly before
and after the rendering process. These methods are called SetupFrame and
FinishFrame and can be seen in the following two lines of code:
void SetupFrame ();
void FinishFrame ();

The final two private methods of our application class are LoadPixMaps and
DrawFrame2D. The first is used to load in the 2D image (known as a pixmap in
Crystal Space) of the Butterfly Grid logo that we are going to move around the
screen and the second is used to actually render it to the screen. The prototypes
for these methods can be seen here:
bool LoadPixMaps ();
void DrawFrame2D ();

Now that we have prototyped the private methods, let’s look at the public methods. First, we have the constructor and destructor methods, which are defined as
follows:
public:
Butterfly(iObjectRegistry* object_reg);
~Butterfly();

Notice how the constructor takes in a pointer to the iObjectRegistry interface;
we’ll see the point of this very shortly.
Finally, we have created a method to initialize and a method to start the application. This can be seen here:

Getting Started with Crystal Space

| 49

Creating Your Own Project
bool Initialize();
void Start();

Let’s now look into the source file, butterfly.cpp.
Starting at the top, you will notice that the first include file is cssysdef.h. This
should always be listed before any other include files in a Crystal Space application and should be listed at the top of all source files in your application.
Next, we have a large list of included header files that are required by the
application and the Crystal Space libraries used within the application. The easiest way to find out which header files you require is to reference the public API
documentation for Crystal Space, as it lists all the headers required at the bottom
of each section.
After including the header files, we place the CS_IMPLEMENT_APPLICATION definition before any source code to specify that this will be a Crystal
Space application. Failing to add this line could cause the following link errors
headache:
Linking...
libcstool_d.lib(initapp.obj) : error LNK2001: unresolved external symbol "void
__cdecl cs_static_var_cleanup(void (__cdecl*)(void))"
(?cs_static_var_cleanup@@YAXP6AXXZ@Z)
libcssys_d.lib(findlib.obj) : error LNK2001: unresolved external symbol "void
__cdecl cs_static_var_cleanup(void (__cdecl*)(void))"
(?cs_static_var_cleanup@@YAXP6AXXZ@Z)
MSVCRTD.lib(crtexew.obj) : error LNK2001: unresolved external symbol _WinMain@16
libcssys_d.lib(win32.obj) : error LNK2001: unresolved external symbol "struct
HINSTANCE__ * ModuleHandle" (?ModuleHandle@@3PAUHINSTANCE__@@A)
libcssys_d.lib(win32.obj) : error LNK2001: unresolved external symbol "int
ApplicationShow" (?ApplicationShow@@3HA)
csdebug\temp\appbutterfly/appbutterfly.exe : fatal error LNK1120: 4 unresolved
externals
Error executing link.exe.
appbutterfly.exe - 6 error(s), 0 warning(s)

After we have defined that this is a Crystal Space application, we then add some
variables specific to this example. The first is a pointer to a csPixmap class
called logoImg. As briefly mentioned before, the csPixmap class contains useful
inline methods for dealing with simple 2D sprites. We will be using this logoImg
pixmap to hold the Butterfly Grid logo. Then we declare integer variables to
hold the current x, y position of the logo on the screen. This can be seen here:
int logo_x, logo_y;

Finally, we declare a smart pointer reference to the iFont interface called font,
which we will use when we want to render text to the screen. Here is the declaration of the font variable:
csRef<iFont> font;

Let’s now jump to the application’s entry-point (i.e., the main method) and see
how a standard Crystal Space application flows.
We start with a standard main method, which takes the usual argv and argc
parameters:

50 |

Chapter 3
Creating Your Own Project
int main (int argc, char* argv[])
{

The first step is to create the environment by calling the static CreateEnvironment method of the csInitializer class, which does all the required initial setup of
the Crystal Space engine and then returns a pointer to the object registry for our
application, which we store in a temporary variable called object_reg. This can
be seen here:
iObjectRegistry* object_reg = csInitializer::CreateEnvironment (argc, argv);

Once we have the environment set up, we then proceed by creating an instance
of our application class Butterfly by passing the iObjectRegistry pointer we
obtained from the CreateEnviroment method into the constructor of our class:
butterfly = new Butterfly (object_reg);

So our actual class constructor looks as follows:
Butterfly::Butterfly (iObjectRegistry* object_reg)
{
Butterfly::object_reg = object_reg;
}

As you can see, all we are doing here is storing a local copy of the passed-in
object registry pointer in our private class member object_reg.
So after our butterfly object is created, we call the Initialize method as
follows:
if(butterfly->Initialize())

Let’s now take a look into the Initialize method that we have defined.
The first thing we do in the Initialize method is make a call to the
RequestPlugins method, which is a static member of the csInitializer class. What
this method actually does is load in the most standard Crystal Space plug-ins,
read in the standard configuration file and command line, and load in any
plug-ins specified in the argument list. Each plug-in required must be specified
by its name, Shared Class Facility ID number, and its version number. However,
there are useful macros that allow this to be done in a very neat fashion.
Before we look at the plug-ins we have loaded in, now is really a good time
to take a step to the side and find out about the Shared Class Facility.

Getting Started with Crystal Space

| 51

Creating Your Own Project

The Shared Class Facility (SCF)
Unless you are going to be contributing to the Crystal Space engine, you
don’t need to know much about this other than it works, and works well.
The idea behind the Shared Class Facility is to have all the methods you
wish to be accessible from your object in a C++ structure (or completely
public class), all defined as virtual methods.
So, as an example, let’s say we wanted to create an Alien plug-in for
Crystal Space (don’t ask why! J). And in this plug-in, all we wanted to do
was give the alien the ability to speak (by means of setting what it was
meant to say and also allowing it to “speak” it). We would then define the
interface for the alien as follows:
struct iAlien
{
virtual void SetPhrase(char *phrase);
virtual void Speak();
}

This would be saved into a header file named ialien.h. Next, we would
create an implementation for this structure (interface) as follows:
#include "ialien.h"
class Alien : public iAlien
{
private:
char *phrase;
public:
virtual void SetPhrase(char *phrase);
virtual void Speak();
}
void Alien::SetPhrase(char *phrase)
{
this->phrase = phrase;
}
void Alien::Speak()
{
printf("%s", phrase);
}

We now have our implementation of the iAlien interface, but this will
never be included in any application; only the iAlien interface (structure)
will be included. In this interface, we also need to provide a static method
to create a new Alien object. This is commonly known as the class factory
and would look similar to the following:
static iAlien* Create_iAlien()
{
return new Alien();
}

52 |

Chapter 3
Creating Your Own Project

So finally, to then use it in an application we would first need to load in
the library that contained the implementation and then use the class factory method we created in the interface to obtain a reference to the Alien
object. Once we had this, we could use the methods just as we could with
a normal pointer to an object:
#include "ialien.h"
LoadLibrary("alienlibrary.dll");
iAlien (*Create_iAlien)() = GetLibrarySymbol("Create_iAlien");
iAlien* myAlien = Create_iAlien();
myAlien->SetPhrase("Hi! I’m an alien");
myAlien->Speak();

There is too much information on the Shared Class Facility to cover in this
book, but if you refer to the section on it in the Crystal Space documentation, there is much more detail on its inner workings. However, as I
mentioned before, unless you intend to contribute to the Crystal Space
project, there is no need to look into it in detail.

Now let’s go back to the code where we left off. For the RequestPlugins method,
we first pass it our object_reg pointer, followed by a list of plug-ins that we
require for our application. The plug-in macros are as follows:
CS_REQUEST_VFS,
CS_REQUEST_SOFTWARE3D,
CS_REQUEST_ENGINE,
CS_REQUEST_FONTSERVER,
CS_REQUEST_IMAGELOADER,
CS_REQUEST_LEVELLOADER,
CS_REQUEST_REPORTER,
CS_REQUEST_REPORTERLISTENER,
CS_REQUEST_END

The first, CS_REQUEST_VFS, requests the Virtual File System (VFS) plug-in,
which we will be using to load in our image (and in later examples to load in a
3D level). We will look into the VFS in detail later in this section.
Next, we have CS_REQUEST_SOFTWARE3D, which is the software rendering plug-in.
The CS_REQUEST_ENGINE requests the core Crystal Space engine (but is
not actually used in this example).
Then we have CS_REQUEST_FONTSERVER, which allows us to access
fonts from within the application.
Next up we request the CS_REQUEST_IMAGELOADER and CS_REQUEST_LEVELLOADER plug-ins, which provide functionality to load in
images and levels (pretty obvious really ;)).
Then finally we have CS_REQUEST_REPORTER and CS_REQUEST_
REPORTERLISTENER, which allow us to report error and information messages to the console window with ease.

Getting Started with Crystal Space

| 53

Creating Your Own Project

Note that the final macro in the list must always be CS_REQUEST_END;
otherwise, this method will break.
If this method fails, we can report the error by calling the csReport macro
(defined in the header file for the csReporterHeader class), passing in the object
registry followed by the severity of the report, using one of the following
macros:
CS_REPORTER_SEVERITY_BUG
CS_REPORTER_SEVERITY_ERROR
CS_REPORTER_SEVERITY_WARNING
CS_REPORTER_SEVERITY_NOTIFY
CS_REPORTER_SEVERITY_DEBUG

This is followed by a message ID in the next parameter (i.e., where the message
originated from) and finally we pass in a description of what needs to be
reported. In this case, we call the macro as follows:
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Can't initialize plugins!");
return false;
}

This means it’s an error that occurred within the butterfly application stating that
the plug-ins could not be initialized. If we force this to occur by making the if
statement fail, you should see something similar to the following screen shot:

Figure 3-41: A fatal error

If you were to change the severity down a level to CS_REPORTER_SEVERITY_WARNING, the message box is not displayed. Note that the
CS_REPORTER_SEVERITY_NOTIFY severity level is also useful to simply
output information to the console window.
After we have loaded the required plug-ins, we proceed by setting up a
method to handle the event queue. We do this by calling the static method
SetupEventHandler, which is also a member of the csInitializer class. As

54 |

Chapter 3
Creating Your Own Project

previously mentioned, this method requires a standard C-style function pointer,
so we first pass on our object registry (object_reg), followed by a pointer to our
static class method SimpleEventHandler, which we will look at shortly. Here is
the code used to set this up:
if (!csInitializer::SetupEventHandler (object_reg, SimpleEventHandler))
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Can't initialize event handler!");
return false;
}

After this, we call the static CheckHelp method of the csCommandLineHelper
class, which examines the command line to detect if the -help command was
issued. If it was, it calls the static Help method of the csCommandLineHelper
class, which will display any relevant help for the plug-ins that have been
loaded. This can be seen here:
// Check for commandline help.
if (csCommandLineHelper::CheckHelp (object_reg))
{
csCommandLineHelper::Help (object_reg);
return false;
}

Next, we attempt to obtain a pointer to the virtual clock. This is done by means
of the CS_QUERY_REGISTRY macro by passing in the object registry
(object_reg) and the interface we wish to obtain a pointer to. So for the virtual
clock we use the following code:
vc = CS_QUERY_REGISTRY (object_reg, iVirtualClock);
if (vc == NULL)
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Can't find the virtual clock!");
return false;
}

Note that because we are using smart pointers, we do not have to worry if it
failed, as the references are automatically decremented (by means of the DecRef
method).
After we have obtained the virtual clock, we attempt to acquire pointers to
the rest of the modules by using a similar technique.
Once we have acquired pointers to all that we require, we call the static
OpenApplication method, which is a member of the csInitializer class, passing
in the pointer to our object registry. This is done with the following segment of
code:
if (!csInitializer::OpenApplication (object_reg))
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Error opening system!");

Getting Started with Crystal Space

| 55

Creating Your Own Project
return false;
}

Then, the next step is to acquire a pointer to the texture manager, which we will
be using in a moment to load in our image. We can do this by calling the
GetTextureManager method of the iGraphics3D interface, which we have an
object of as a member of our class called g3d. This can be seen here:
txtmgr = g3d->GetTextureManager ();

Next, we make a call to our LoadPixMaps method, which will be used to load in
our Butterfly Grid logo. Let’s take a look at this method now.
In this method, we first call the LoadImage method of the loader object,
which we acquired a pointer to in the Initialize method. This method returns a
pointer to an iImage interface that we create a local smart pointer reference to
called ifile. This can be seen here:
bool Butterfly::LoadPixMaps ()
{
csRef<iImage> ifile = loader->LoadImage ("/lib/butterfly/logo.jpg");

Here is the important part that confused me at first, although it is actually really
simple. Notice the path of the file we have specified here — /lib/butterfly/
logo.jpg.
What we are actually doing here is accessing the image from the Virtual File
System. The actual logo.jpg is stored in a zip file called butterfly.zip, which we
have placed in the c:\crystal\CS\data directory. The reason it can find the file
with the path specified is because we have made /lib/butterfly/ a mount point for
the Virtual File System.
If you remember earlier in this chapter we appended the vfs.cfg file (located
in the root CS directory) with the following two lines:
; Added for Butterfly
VFS.Mount.lib/butterfly

= $@data$/butterfly.zip

As you probably guessed, the first line is simply a comment. However, the second line makes it so that our butterfly.zip file is virtually mounted under a
lib/butterfly folder, meaning we can access our compressed data file as easily as
we would a normal directory.
So, back to the code now. Once we have loaded in our image, we register it as
a 2D texture by calling the RegisterTexture method of the texture manager,
which we previously acquired in the Initialize method:
csRef<iTextureHandle> txt = txtmgr->RegisterTexture (ifile, CS_TEXTURE_2D);

As you can see, this method returns our texture as an iTextureHandle interface.
Once we have this, we call the Prepare method as follows:
txt->Prepare();

And finally we can create our actual pixmap by passing this iTextureHandle, txt,
into the constructor of the csSimplePixmap class:
logoImg = new csSimplePixmap (txt);

Team-Fly®

56 |

Chapter 3
Creating Your Own Project

This concludes the LoadPixMaps method, so we now return to the Initialize
method, which we complete by acquiring one of the standard fonts from the font
server within the Crystal Space engine. Note that we use the iGraphics2D interface to first acquire the font server by means of the GetFontServer method,
which we then use to load a standard font by means of the LoadFont method:
font = g2d->GetFontServer()->LoadFont(CSFONT_LARGE);

So, after the Initialize method finished successfully (i.e., returns true), the Start
method of our Butterfly class is called. Let’s look at the Start method now.
Well, there isn’t much to look at actually as there is only one single line. The
entire method can be seen here:
void Butterfly::Start ()
{
csDefaultRunLoop (object_reg);
}

All we are doing here is starting the main loop of the Crystal Space application.
From here on the application is completely event driven, so let’s now look at
how the event queue works.
If you remember when we looked at the Initialize method, we specified that
our SimpleEventHandler method would be the static method, which would handle all the events, using the following code segment:
if (!csInitializer::SetupEventHandler (object_reg, SimpleEventHandler))
{
csReport (object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Can't initialize event handler!");
return false;
}

When a hardware or software event occurs, it is passed into the event handler
method (which in our example is SimpleEventHandler) as a reference to an
iEvent interface object. Once an event arrives into this method, we call the
instance method of our class called HandleEvent, passing in the reference to the
iEvent object. This is done simply to allow access to the instance members of
our Butterfly class, as they would obviously be inaccessible from a static
method. Here you can see the complete definition for the SimpleEventHandler
method:
bool Butterfly::SimpleEventHandler (iEvent& ev)
{
return butterfly->HandleEvent(ev);
}

Let’s now take a look at the HandleEvent method, which actually deals with the
incoming events. The first thing we should examine when an event arrives is the
type of event it is. Table 3-1 lists possible event types that can occur. These
events are discussed later in the chapter.

Getting Started with Crystal Space

| 57

Creating Your Own Project

Table 3-1: Event types
Event Type

Purpose

csevNothing

No event or unknown event.

csevKeyDown

A key has been pressed.

csevKeyUp

A key has been released.

csevMouseMove

The mouse has been moved.

csevMouseDown

A button has been pressed on the mouse.

csevMouseUp

A button has been released on the mouse.

csevMouseClick

A button has been pressed and released.

csevMouseDoubleClick

A button has been pressed and released twice quickly.

csevJoystickMove

The joystick has been moved.

csevJoystickDown

A button on the joystick has been pressed.

csevJoystickUp

A button on the joystick has been released.

csevCommand

The event is a command.

csevBroadcast

The event was broadcast to all event listeners.

csevNetwork

A network event, such as an incoming packet.

csevMouseEnter

The mouse has entered the application window.

csevMouseExit

The mouse has exited the application window.

csevLostFocus

The application lost keyboard focus.

csevGainFocus

The application gained keyboard focus.

csevGroupOff

A component in a group has been selected, and everyone else
should go to their off state.

csevFrameStart

The frame is about to draw.

Once the event type is determined, it is possible to find out more information
specific to the event. This is possible because the csEvent class (which uses the
iEvent interface) contains many structures all contained within a union.
The first event type we handle is a csevBroadcast event, which is a command
event. So since we know that it is a command, we then examine the Code value
within the Command structure and test this against the value cscmdProcess. As
mentioned later in Table 3-4, the cscmdProcess command is sent every time a
frame should be rendered to the screen. So when this command event is
received, we simply call the SetupFrame method of our Butterfly class and then
return true to show that the event has been consumed (i.e., dealt with). This if
statement can be seen here:
if (ev.Type == csevBroadcast && ev.Command.Code == cscmdProcess)
{
butterfly->SetupFrame ();
return true;
}

We’ll look into the SetupFrame method in a moment. Next in the events that we
handle is another command event — this time the cscmdFinalProcess command,
which is always sent after the cscmdPostProcess (which is always sent after the
cscmdProcess command).

58 |

Chapter 3
Creating Your Own Project

When we receive the cscmdFinalProcess command, we make a call to our
FinishFrame method, which handles the final drawing of the scene to the back
buffer (again, we’ll look at this method in a moment).
The handling for the cscmdFinalProcess command can be seen here:
else if (ev.Type == csevBroadcast && ev.Command.Code == cscmdFinalProcess)
{
butterfly->FinishFrame ();
return true;
}

Finally, in the HandleEvent method, we handle a csevKeyDown key event,
which is sent each time a key is pressed on the keyboard. If this event is
detected, we then check the value of Code for the key that was pressed, accessing the Code variable from within the Keyboard structure in the iEvent interface.
If we then find that the Escape key was pressed, we attempt to acquire a pointer
to the iEventQueue interface, which we can use to send and broadcast messages.
Once we have a pointer to the iEventQueue interface, we can call the
GetEventOutlet method, which returns a pointer to an iEventOutlet interface.
Once we have this, we can then easily call the Broadcast method, passing in the
command we require, which in this example is the cscmdQuit command as we
wish to terminate the application.
This can be seen in full in the following code segment:
else if (ev.Type == csevKeyDown && ev.Key.Code == CSKEY_ESC)
{
csRef<iEventQueue> q (CS_QUERY_REGISTRY (object_reg, iEventQueue));
if (q) q->GetEventOutlet()->Broadcast(cscmdQuit);
return true;
}

Next, let’s look into the SetupFrame method. In this method, we first check the
states of the arrow keys by making calls to the GetKeyState method of the
iKeyboardDriver interface we created in the Initialize method called kbd. By
using this method, we can determine if a key is down or up by examining the
true or false return value, respectively.
We first check the state of the left arrow key, which is defined by the macro
CSKEY_LEFT. If the key is found to be pressed, we then check whether the
logo is still within the bounds of the application; if so, we move it 2 pixels to the
left. This can be seen in the following segment of code:
if(kbd->GetKeyState (CSKEY_LEFT))
{
if(logo_x > 1)
logo_x-=2;
}

We then repeat this for the other three directions using the following code:
if (kbd->GetKeyState (CSKEY_RIGHT))
{
if(logo_x < 639-logoImg->Width())
logo_x+=2;
}

Getting Started with Crystal Space

| 59

Creating Your Own Project
if (kbd->GetKeyState (CSKEY_UP))
{
if(logo_y > 1)
logo_y-=2;
}
if (kbd->GetKeyState (CSKEY_DOWN))
{
if(logo_y < 479-logoImg->Height())
logo_y+=2;
}

After we have adjusted the coordinates of the logo based upon the keyboard
input, we proceed by calling the BeginDraw method of the iGraphics2D interface, which we have called g2d. This method will return true if the graphics
context is ready to start being drawn on. The call to this method can be seen
here:
if(!g2d->BeginDraw())
{
csReport(object_reg, CS_REPORTER_SEVERITY_ERROR,
"crystalspace.application.butterfly",
"Failed to begin 2D frame");
return;
}

After this, our canvas should be ready to draw on, so the next call we make is to
the Clear method of the g2d interface, which basically clears the back buffer
reading for drawing to (note that you can pass a color into this method; however,
0 represents black, which is usually suitable for using to clear the back buffer).
This method can be seen here:
g2d->Clear(0);

Next, we ensure that our logoImg csPixmap pointer is valid, and then we call the
DrawScaled method of the csPixmap class, which will draw the image to the
current canvas (back buffer). This can be seen here:
if(logoImg)
{
logoImg->DrawScaled (g3d, logo_x, logo_y, logoImg->Width(), logoImg->Height(),
100);
}

Note that the first parameter is a reference to the iGraphics3D interface, which is
then followed by the x, y screen coordinates at which to draw the image. Then
finally we have the scaled width and height at which the image should be drawn.
Note how we have used the Width and Height methods of the csPixmap class to
obtain the original width and height of the image.
In addition to this method, there are several other useful methods that can be
used within the csPixmap class. If you do not require scaling, you can simply
use the draw method, which has the following prototype:
Draw(iGraphics3D *g3d, int sx, int sy, uint8 Alpha=0)

60 |

Chapter 3
Creating Your Own Project

In addition, there is another useful method that allows you to tile your image
over a specified area. This method can be seen here:
DrawTiled(iGraphics3D *g3d, int sx, int sy, int w, int h, uint8 Alpha=0)

Note also that each of these methods can take an alpha parameter in the range of
0 to 255, where 0 is fully opaque and 255 is fully transparent.
So, after our image is rendered to the back buffer, we proceed by outputting
some useful information, such as the logo’s x, y position, to the screen.
To do this, we first get a suitable color to draw our text in. We do this by calling the FindRGB method of the iGraphics2D interface, which returns an integer
representation of the color we requested. In our example we are retrieving a yellow color and storing it in a variable called fntcol. This can be seen in the
following line of code:
int fntcol = g2d->FindRGB (255, 255, 0);

Next, we create a char buffer of length 256 to temporarily hold a string, then we
use the sprintf function to write our string to the buffer, which we have called
buf. We then make a call to the Write method of the iGraphics2D interface.
The Write method takes the font as the first parameter, which we previously
acquired in the Initialize method and stored in a pointer to an iFont interface
called font. So we pass this in, followed by the x, y coordinate of where to draw
the string, then the foreground color of the font (which we retrieved with the
FindRGB method of the iGraphics2D interface). Next is the background color
(which we have set to –1, which tells the method not to draw a background) and
finally we pass in the actual string to draw (which is stored in our buf variable).
This can all be seen in the following segment of code:
char buf[256];
sprintf(buf, "Butterfly Grid");
g2d->Write(font, 10,10, fntcol, -1, buf);

As you can see, all we printed there was the text “Butterfly Grid.” In the next
segment, we actually print the coordinates of the logo. All we need to do is use
sprintf to generate the correct string from the values and then output the text
slightly below the other text by moving the y coordinate down. This can be seen
in the following code:
sprintf(buf, "Logo at (%i,%i)", logo_x, logo_y);
g2d->Write(font, 10,25, fntcol, -1, buf);

This concludes the SetupFrame method, so let’s now look at the FinishFrame
method, as it will be called directly after the SetupFrame method due to the way
our event handling is structured.
All we actually do in the FinishFrame method is first make a call to the
FinishDraw method of the iGraphics2D interface, which can be seen here:
g2d->FinishDraw();

Then we complete the rendering process by flipping the video page, making
what we have just rendered to the back buffer visible on the screen. This is
achieved by making a call to the Print method of the iGraphics2D interface,
passing in NULL as a parameter to signify that we wish to flip the entire area:

Getting Started with Crystal Space

| 61

Creating Your Own Project
g2d->Print(NULL);

Finally, now that we have seen all the methods, let’s take a look at what happens
once the cscmdQuit command has been broadcast.
Recall from earlier that when the Start method is called, we call the following
Crystal Space method:
csDefaultRunLoop(object_reg);

This handles the main loops and controls the event queue. So when the
cscmdQuit command is broadcast, this method returns and the execution then
resumes in the main method, where the next line of code is:
delete butterfly;

As you know, this deallocates the memory assigned to our class when it was created, and because we are using smart pointers for the interfaces, the appropriate
DecRef method is called on all our class instance smart pointers.
Next, if you remember we also used a smart pointer for the iFont interface;
however, we declared this outside the class, so we can deal with this simply by
setting its value to NULL, as this will make the smart pointer decrement the reference count internally. This can be seen here:
font = NULL;

Finally, we make a call to the static DestroyApplication method, which is a
member of the csInitializer class. All we need to do is pass in our object_reg
variable, which we still have a pointer to in the main method, and this will clean
up and terminate the application. This final line of code can be seen here:
csInitializer::DestroyApplication (object_reg);

Now let’s explain the possible events that can be sent.

Key Events
First we have the csevKeyDown and csevKeyUp events. If either of these
occurs, we can find out more information by using the Key structure defined in
the iEvent interface as follows:
struct
{
int Code;
int Char;
int Modifiers;
} Key;

// Key code
// Character code
// Control key state

So, if you wanted to test if the “p” key was pressed, you could use the following
if statement:
if (ev.Type == csevKeyDown && ev.Key.Code == 'p')

As you can see, we first check if the Type of the event is csevKeyDown; if it is,
we know the Key struct will contain the information we require, so we test the
value of the Key.Code against the character value of “p”.
As there is no character to represent certain keys, such as the Escape key and
the cursor keys, we can use any of the following macros for comparing to the

62 |

Chapter 3
Creating Your Own Project

Key.Code value to determine which key has been pressed. Table 3-2 lists the
available control key codes.
Table 3-2: Key code macros
Key Code Macro

Actual Key

CSKEY_ESC

Escape key (Esc)

CSKEY_ENTER

Return key

CSKEY_TAB

Tab key

CSKEY_BACKSPACE

Backspace key

CSKEY_SPACE

Spacebar

CSKEY_UP

Up Arrow key

CSKEY_DOWN

Down Arrow key

CSKEY_LEFT

Left Arrow key

CSKEY_RIGHT

Right Arrow key

CSKEY_PGUP

Page Up key

CSKEY_PGDN

Page Down key

CSKEY_HOME

Home key

CSKEY_END

End key

CSKEY_INS

Insert key

CSKEY_DEL

Delete key

CSKEY_CTRL

Control key

CSKEY_ALT

Alt key

CSKEY_SHIFT

Shift key

CSKEY_F1

Function key F1

CSKEY_F2

Function key F2

CSKEY_F3

Function key F3

CSKEY_F4

Function key F4

CSKEY_F5

Function key F5

CSKEY_F6

Function key F6

CSKEY_F7

Function key F7

CSKEY_F8

Function key F8

CSKEY_F9

Function key F9

CSKEY_F10

Function key F10

CSKEY_F11

Function key F11

CSKEY_F12

Function key F12

CSKEY_CENTER

Center key (“5” on numeric keypad)

CSKEY_PADPLUS

numeric keypad + key

CSKEY_PADMINUS

Numeric keypad – key

CSKEY_PADMULT

Numeric keypad * key

CSKEY_PADDIV

Numeric keypad / key

CSKEY_FIRST

Numeric value of the first control key code

CSKEY_LAST

Numeric value of the last control key code

Getting Started with Crystal Space

| 63

Creating Your Own Project

NOTE Although the CSKEY_FIRST and CSKEY_LAST macros are not actually
Ü related
to keys as such, they provide a useful means of cycling through all the
possible control keys as they will always be set to the first and last codes for the
control keys.

In addition to the list in Table 3-2, we can check for modifiers, such as the Ctrl
key being pressed at the same time as the “p” key. This is really useful and also
really simple to do. The modifiers are actually stored as a bitfield, so for example, if we wanted to see if the user pressed Ctrl+n, we would use the following if
statement:
if (ev.Type == csevKeyDown && ev.Key.Code == 'n' && ev.Key.Modifiers &
CSMASK_CTRL)

Or, if we wanted to see if Ctrl+Shift+E was pressed, we could use the following
if statement:
if (ev.Type == csevKeyDown && ev.Key.Code == 'p' && ev.Key.Modifiers &
CSMASK_CTRL && ev.Key.Modifiers & CSMASK_SHIFT)

Table 3-3 shows all the possible modifier key masks that can be used in combination with the key codes.
Table 3-3: Modifier key masks
Modifier Key Mask

Actual Key

CSMASK_SHIFT

Shift key

CSMASK_CTRL

Control key (Ctrl)

CSMASK_ALT

Alt key

CSMASK_ALLSHIFTS

Shift, Ctrl, or Alt key

CSMASK_FIRST

This is a special case that only returns true if this is the first time a
key has been pressed (i.e., it is not an event caused by key repeat
from it being held down).

Mouse Events
The next event type is mouse events, which are csevMouseMove, csevMouseDown, csevMouseUp, csevMouseClick, and csevMouseDoubleClick.
When any of these events are received, it is possible to retrieve additional
information by accessing the Mouse structure, which is within the Union in the
iEvent interface. The definition of the Mouse structure can be seen here:
struct
{
int x,y;
// Mouse coords
int Button;
// Button number: 1-left, 2-right, 3-middle
int Modifiers; // Control key state
} Mouse;

As you can see, within this structure we can grab useful information such as the
x, y position of the mouse, which button was pressed (if a csevMouseDown
event occurred), and if any control keys were held down at the same time (see
Table 3-3 for a list of key modifiers).

64 |

Chapter 3
Creating Your Own Project

Joystick Events
Next in the list of event types are the joystick events: csevJoystickMove,
csevJoystickDown, and csevJoystickUp. If any of these events occur, we can
access the Joystick structure, which again is contained within the Union in the
iEvent interface. The definition for this structure can be seen here:
struct
{
int number;
int x, y;
int Button;
int Modifiers;
} Joystick;

//
//
//
//

Joystick number (1, 2, ...)
Joystick x, y
Joystick button number
Control key state

As you can see, this is very similar to the mouse events, with the addition of the
number variable, which determines which joystick the event actually came from
(if there is more than one joystick connected).

Command Events
When a command event is received, such as csevCommand or csevBroadcast,
we need to access the Command structure from within the Union. The Command structure looks as follows:
struct
{
Code;
void *Info;
} Command;

// Command code
// Command info

Unlike the previous structures for events we have seen, like the key and mouse
events, the Command structure actually has its own sub-list of possible commands, which are stored in the Code unsigned integer within the Command
structure when a csevCommand or csevBroadcast event is received. Table 3-4
has a complete list of possible values for Code.
Table 3-4: Command code macros
Command Code Macro

Purpose

cscmdNothing

There was no command.

cscmdQuit

Used to terminate the application.

cscmdFocusChanged

The application has either lost or gained focus. If this command
is received, the Info pointer in the structure will point to a Boolean
value, where true means the window has gained focus and false
means it has lost focus.