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