Blame |
Last modification |
View Log
| RSS feed
#include "Board.h"
#include "GameApp.h"
#include "SexyAppFramework/Graphics.h"
// See the Draw method for more information on using the Color class.
#include "SexyAppFramework/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 "SexyAppFramework/DDImage.h"
// The Rectangle template, used to specify X, Y, Width, Height
#include "SexyAppFramework/Rect.h"
// We're going to be making a button in this demo so we need to
// include this file.
#include "SexyAppFramework/ButtonWidget.h"
// We're going to add our own button widget, which requires knowing about the
// WidgetManager.
#include "SexyAppFramework/WidgetManager.h"
#include "SexyAppFramework/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, 0, 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;
}