1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394
|
#include "Board.h"
#include "GameApp.h"
#include "Graphics.h"
// See the Draw method for more information on using the Color class.
#include "Color.h"
// The Image.h file just declares basic functions. All images are either of
// the DDImage or MemoryImage type. For this demo, we will use DDImage
// types, as they are the type returned by the image loading code.
// A DDImage is actually derived from MemoryImage, so where an Image or
// MemoryImage is required, a DDImage will suffice as well. A DDImage
// contains optimized code for use with DirectX 7+.
#include "DDImage.h"
// The Rectangle template, used to specify X, Y, Width, Height
#include "Rect.h"
// We're going to be making a button in this demo so we need to
// include this file.
#include "ButtonWidget.h"
// We're going to add our own button widget, which requires knowing about the
// WidgetManager.
#include "WidgetManager.h"
#include "ImageFont.h"
// The SexyAppFramework resides in the "Sexy" namespace. As a convenience,
// you'll see in all the .cpp files "using namespace Sexy" to avoid
// having to prefix everything with Sexy::
using namespace Sexy;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
Board::Board(GameApp* theApp)
{
mApp = theApp;
// Start off drawing the first frame of mLightningImg
mAnimFrame = 0;
mButton = NULL;
mMouseX = mMouseY = 0;
mLeftDown = mRightDown = mMiddleDown = false;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
Board::~Board()
{
delete mButton;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Update()
{
// Let the parent class update as well. This will increment
// the variable mUpdateCnt which is an integer that indicates
// how many times the Update() method has been called. Since our
// Board class is updated 100 times per second, this variable will
// increment 100 times per second. As you will see in later demos,
// we will use this variable for animation since its value represents
// hundredths of a second, which is for almost all games a good
// enough timer value and doesn't rely on the system clock function
// call.
Widget::Update();
// Let's update our animation frame every 5 update ticks. This
// is equivalent to 20 times per second.
if (mUpdateCnt % 5 == 0)
{
// In GameApp we specified how many rows and columns this image
// had. Thus, if our current frame of animation exceeds the number
// of frames that we have, we should reset the animation frame back
// to 0.
if (++mAnimFrame >= mApp->mLightningImg->mNumRows)
mAnimFrame = 0;
}
// For this and most of the other demos, you will see the function
// below called every Update() call. MarkDirty() tells the widget
// manager that something has changed graphically in the widget and
// that it needs to be repainted. All widgets follow this convention.
// In general, if you don't need
// to update your drawing every time you call the Update method
// (the most common case is when the game is paused) you should
// NOT mark dirty. Why? If you aren't marking dirty every frame,
// then you aren't drawing every frame and thus you use less CPU
// time. Because people like to multitask, or they may be on a laptop
// with limited battery life, using less CPU time lets people do
// other things besides play your game. Of course, everyone
// will want to play your game at all times, but it's good to be
// nice to those rare people that might want to read email or
// do other things at the same time.
// In this particular demo, we
// won't be nice, as the purpose is to bring you up to speed as
// quickly as possible, and so we'll dispense with optimizations
// for now, so you can concentrate on other core issues first.
// In general, this is the last method called in the Update
// function, but that is not necessary. In fact, the MarkDirty
// function can be called anywhere, in any method (although
// calling it in the Draw method doesn't make sense since it is
// already drawing) and even multiple times. Calling it multiple
// times does not do anything: only the first call makes a difference.
MarkDirty();
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Draw(Graphics* g)
{
// The Graphics object, "g", is
// automatically created and passed to this method by the
// WidgetManager and can be thought of as the main screen
// bitmap/canvas upon which all drawing will be done. This object
// is double buffered automatically so you don't need to worry
// about those details. All you need to do is instruct the object
// that you would like to draw something to it, and when the
// WidgetManager gets done letting all widgets draw to the
// Graphics object, it will then blit everything to the screen
// at once.
// First, let's start by clearing the screen to black.
// As you'll recall from Demo1, we set the color with SetColor
// and pass in a Color object that contains either 3 or 4 parameters,
// that represent the r,g,b,a values (alpha is 255 if only 3 specified).
g->SetColor(Color(0, 0, 0));
g->FillRect(0, 0, mWidth, mHeight);
// Now let's try drawing a stretched image. We'll draw the original image
// stretched to twice its size. Drawing a stretched image is exactly like
// drawing a normal image, except that you have two extra parameters:
// the stretched width and height. You can use this to draw a shrunk version
// of the image as well (which we'll do second)
g->DrawImage(mApp->mTurbotImg, 0, 0, mApp->mTurbotImg->GetWidth() * 2, mApp->mTurbotImg->GetHeight() * 2);
g->DrawImage(mApp->mTurbotImg, 0, 275, mApp->mTurbotImg->GetWidth() / 2, mApp->mTurbotImg->GetHeight() / 2);
// The default stretching algorithm (the one used in the two examples above) uses
// the slow stretching method. The slow stretching method anti-aliases the resulting
// image to give a smoother, less pixelated look. While this tends to look a lot
// nicer, it also requires more processing power. Thus, if you either don't need
// or don't care about the anti-aliasing, you can instruct the graphics object
// to use fast stretching. Just remember: unless you turn fast stretching off when
// done, it will remain on. Let's give it a try by drawing the same image
// stretched twice as large using fast stretching.
g->SetFastStretch(true);
g->DrawImage(mApp->mTurbotImg, 275, 0, mApp->mTurbotImg->GetWidth() * 2, mApp->mTurbotImg->GetHeight() * 2);
g->SetFastStretch(false);
// A good use of fast/non fast stretching is to enable the slower stretching method
// for users with a supported 3D card and to disable it for users that have
// to run in software mode. You'll learn more about determining if a user is in
// 3D mode or not in a later demo, but keep this simple optimization in mind
// when creating drawing code for your games.
// Another cool thing we can do is to draw an image mirrored. This is exactly
// like using the DrawImage command except that you use DrawImageMirror
// instead. The optional fourth argument is just a convenience switch: if it's false,
// it will draw the image normally. In 3D mode, this is just as fast as a normal draw.
g->DrawImageMirror(mApp->mTurbotImg, 75, 275);
// But what if you want to draw the image mirrored AND stretched? It's a little different,
// but still easy. The first parameter, like usual, is the image you want to draw.
// The second parameter is a Rect which indicates the destination region you want to draw
// the image to: The XY of the Rect is just what you think it is, it's the pixel coordinate
// the image should be drawn at. The Width, Height of the Rect however is how you accomplish
// the stretching/shrinking. Specify a different width/height from the original image size
// and you've just accomplished resizing the original image! Note that we have a third
// parameter, also a Rect. This is the source rectangle, and indicates the region of
// image you wish to draw. As you'll see in our animation example, you don't have to
// always draw the entire image. You can specify a rectangular region of the source image
// that you wish to draw, which is useful for large strips of animation (more below).
// For our current case, however, we want to draw the entire image. So we specify that
// we should draw from the top left (0,0) coordinate of the source image and we should
// use it's full width/height. Again, just like with DrawImage, you can use the
// SetFastStretch call to set whether you want the nice, slow, smooth scaling, or the quick
// and efficient exact scaling. In 3D mode, this is just as fast as a normal stretched draw.
g->DrawImageMirror(mApp->mTurbotImg,
Rect(200, 275, mApp->mTurbotImg->GetWidth() * 2, mApp->mTurbotImg->GetHeight() * 2),
Rect(0, 0, mApp->mTurbotImg->GetWidth(), mApp->mTurbotImg->GetHeight()));
// Remember that black and white image we made in GameApp::LoadingThreadCompleted?
// How about we draw it now so you can see what the result looks like:
g->DrawImage(mApp->mAlteredImg, 500, 0);
// And now for the exciting part: animation! As you can see in ::Update, we
// increment the animation frame every 5 update ticks. We don't want to use
// DrawImage because that will render the entire image. Instead, we only
// want to draw a particular cel. We do that with the DrawImageCel function,
// specifying the row or column to draw like so:
g->DrawImageCel(mApp->mLightningImg, 0, 540, mAnimFrame);
// If your animation strips contain both multiple rows AND columns,
// you will need to use one of the alternate forms of DrawImageCel.
// DrawImageCel is really just a convenience wrapper around DrawImage.
// As you may have seen, you can tell DrawImage to draw just a particular
// rectangular region of the image. Here is the equivalent function
// call that we could have used in place of DrawImageCel above:
//g->DrawImage(mApp->mLightningImg,
//Rect(0, 540, mApp->mLightningImg->GetWidth(), mApp->mLightningImg->GetCelHeight()),
//Rect(0, mApp->mLightningImg->GetCelHeight() * mAnimFrame, mApp->mLightningImg->GetWidth(), mApp->mLightningImg->GetCelHeight()));
//
// As you can see, DrawImageCel is a lot quicker to type.
// Let's also display the current mouse XY and which button(s) are held down.
// You should recall how to set fonts and change their colors from Demo2.
// You will notice that the X, Y is not updated when the cursor moves over
// the button that we added. This can be explained by reading the comments
// for the MouseMove/MouseDrag/MouseDown/MouseUp methods.
g->SetFont(mApp->mFont);
g->SetColor(Color(255, 255, 255));
g->DrawString(StrFormat(_S("X, Y is %d, %d"), mMouseX, mMouseY), 630, 20);
SexyString buttonStr;
if (mLeftDown)
buttonStr += _S("Left button is down. ");
if (mRightDown)
buttonStr += _S("Right button is down. ");
if (mMiddleDown)
buttonStr += _S("Middle button is down. ");
WriteWordWrapped(g, Rect(630, 40, mWidth - 630, 300), buttonStr, -1, -1);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::AddedToManager(WidgetManager* theWidgetManager)
{
// At this point, the Board class has already been added to the
// widget manager. We should call our parent class' method
// so that it can be sure to perform any needed tasks, first.
Widget::AddedToManager(theWidgetManager);
// Now let's create our first widget: a button. We do that by
// telling the button what integer ID it should be identified with,
// and by passing it a pointer to a button listener. Widgets have
// "listeners" which respond to their particular events. Any class
// can be a listener. For this particular demo, it is convenient for the
// Board class to be the listener, but it is also common to use GameApp
// as the main listener for all application buttons. The choice is up
// to you and your needs.
mButton = new ButtonWidget(1, this);
// Remember how we had to position and size the Board class when we first
// created it? A button is no different, it too is a widget. Let's
// place this button on screen and set it's size now:
mButton->Resize(675, 500, 100, 50);
// Because a button can have a label on it, we should set the font to use:
mButton->SetFont(mApp->mFont);
// And just what should that label be? How about the word "Off".
// We'll make it so that when it's clicked, it changes to "On" and
// back to "Off" again.
mButton->mLabel = _S("Off");
// We can also change some colors, like the label color and the color
// the label gets when moused over using the constants below:
mButton->SetColor(ButtonWidget::COLOR_LABEL, Color(0, 255, 0));
mButton->SetColor(ButtonWidget::COLOR_LABEL_HILITE, Color(0, 255, 0));
// And finally, just like with the Board class, we have to add it
// if we want to do anything with it.
theWidgetManager->AddWidget(mButton);
// If you want, you can also control the placement of the button.
// You can put it in front of another widget, behind another widget,
// at the top of the drawing order, or at the bottom of the
// drawing order. You accomplish that with the WidgetManager's
// PutInFront, PutBehind, BringToFront, BringToBack methods, respectively.
// But wait, what does this button look like? If you don't specify an
// image to use, the default Windows widget look will be used. We'll cover
// images and widgets in a later demo.
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::RemovedFromManager(WidgetManager* theWidgetManager)
{
// This is called after we've been removed from the widget manager.
// Again, we should let our base class do anything it needs to, first.
Widget::RemovedFromManager(theWidgetManager);
// We should now also remove any widgets we are responsible for. In
// our current case, we made a button in AddedToManager. Let's remove
// it now:
theWidgetManager->RemoveWidget(mButton);
// We'll delete it in our destructor.
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::ButtonDepress(int theId)
{
// Because we told our button that we, the Board class, are
// going to listen for its particular events, this method will
// thus be called when our button has any events it wants us
// to know about. In our current case, we only want to know when
// the button is clicked, but you could also respond to a few others.
// Let's change the label on our button whenever it is clicked, from
// "Off" to "On" and back again.
if (theId == 1)
{
// We assigned ID of 1 to our button. You'd ideally want to use
// a constant, but since this is a demo and there's only 1 button,
// we're going to just use a literal instead. When a button
// causes an action, it calls the appropriate listener function and
// let's the listener know who it is via the ID parameter.
if (mButton->mLabel == _S("Off"))
mButton->mLabel = _S("On");
else
mButton->mLabel = _S("Off");
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::MouseMove(int x, int y)
{
// This is called because the mouse cursor changed position and
// the Board class was the widget closest to the cursor.
// We're going to keep track of the XY coordinate whenever we
// get this message for the sake of this demo.
mMouseX = x;
mMouseY = y;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::MouseDrag(int x, int y)
{
// This is called because the mouse cursor changed position while
// a button was down (a drag) and
// the Board class was the widget closest to the cursor.
// We're going to keep track of the XY coordinate whenever we
// get this message for the sake of this demo.
// Note that MouseDrag is called instead of MouseMove under
// this condition.
mMouseX = x;
mMouseY = y;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::MouseDown(int x, int y, int theClickCount)
{
// Let the parent class know about this and perform any actions
// it needs to.
Widget::MouseDown(x, y, theClickCount);
// Let's just keep track of which button is currently held down.
if (theClickCount == 3)
mMiddleDown = true;
else if (theClickCount > 0)
mLeftDown = true;
else if (theClickCount < 0)
mRightDown = true;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::MouseUp(int x, int y, int theClickCount)
{
// Let the parent class know about this and perform any actions
// it needs to.
Widget::MouseUp(x, y, theClickCount);
//You can actually tell if the left, right,
// or middle buttons are currently held down by calling one of these
// WidgetManager methods: IsLeftButtonDown, IsRightButtonDown,
// IsMiddleButtonDown. However, we're going to keep track of this
// manually just to illustrate a point.
if (theClickCount == 3)
mMiddleDown = false;
else if (theClickCount > 0)
mLeftDown = false;
else if (theClickCount < 0)
mRightDown = false;
}
|