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"
// Why are we including ImageFont.h and not Font.h? Font.h is just a generic
// base class. ImageFont creates fonts from an image that contains all the
// text characters as well as a text file that indicates character widths
// and kerning information, as well as some more advanced features not used
// in this tutorial such as font layers, etc.
#include "SexyAppFramework/ImageFont.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"
// 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 by fully glowing and making the pulse decrease
mPulseAmt = 255;
mIncPulse = false;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
Board::~Board()
{
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
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();
// Every frame (100 times per second) let's change the pulse amount by
// 1. When we reach either 0 or 255 (the lower/upper bounds of
// a color value) we'll reverse the pulsing direction. See the Draw()
// method for its usage: we're going to draw an image with varying intenstiy
// over time.
if (mIncPulse)
{
if (++mPulseAmt >= 255)
{
mIncPulse = false;
mPulseAmt = 255;
}
}
else
{
if (--mPulseAmt <= 0)
{
mIncPulse = true;
mPulseAmt = 0;
}
}
// Let's play the timer sound effect once every 2 second.
// Since this method is called 100 times per second, just mod
// mUpdateCnt by 200. We do that by calling GameApp's PlaySample()
// method and specifying the integral ID of the sound to play. This
// ID is the same as we used when loading the sound in GameApp::LoadingThreadProc()
if (mUpdateCnt % 200 == 0)
mApp->PlaySample(1);
// What about playing a sound but making it play on either the left or
// right speaker? And what about pitch shifting? These are all possible,
// but we won't learn about them until a later demo.
// 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)
{
// And now for the good stuff! 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 print some text. In order to do so, we need to instruct the
// graphics object as to which font we want to use. We do that with
// the SetFont method. Then we call the DrawString method and pass in
// a normal string and give it an X, Y to draw at. But wait! If we just
// draw a string right now, we won't see it. Can you guess why?
// The graphics object is still using the color, white, which we
// set a few lines above. Thus, when it goes to draw the string,
// it will draw it in white. How do you change the text color?
// Easy: it's just like you learned for drawing those primitives:
// just use SetColor and you're done! It doesn't matter whether you
// set the color before or after you set the font, fyi.
g->SetColor(Color(255, 0, 0));
g->SetFont(mApp->mTextFont);
g->DrawString(_S("Woo! Text! Ya!"), 10, 10);
// Wait a minute, didn't we say to draw at a Y value of 10? Then why does it
// appear to be drawing at about 0? That's because the text is offset
// by the ascent of the font. A discussion of font terms is beyond the
// scope of this demo, but basically just remember that if you want to
// display the text at the exact Y coordinate, that you should add
// the value of the font's ascent. You can get the ascent by calling
// GetAscent() on the font in question. I'm going to use the same
// Y value of 10 just to show you the slight difference using GetAscent makes.
g->SetColor(Color(55, 90, 255));
// We already set the font, no need to set it again unless we want a new one
g->DrawString(_S("I like Techno and Drum n' Bass"), 170, 10 + mApp->mTextFont->GetAscent());
// And to help you out visually, let's draw a line at Y coordinate 10:
g->SetColor(Color(255, 255, 255));
g->DrawLine(0, 10, mWidth, 10);
// Just for fun, let's change the font. This font, as you can see from its image,
// only contains digits and a few symbols. As an example, I will draw a string
// containing only those characters as well as a string containing characters that
// the font can't represent to show you what would happen.
g->SetFont(mApp->mNumberFont);
g->SetColor(Color(255, 255, 0));
g->DrawString(_S("+200"), 10, 40);
g->DrawString(_S("+200 pts"), 10, 60);
// You can also get the width of a string in pixels for any given font. In addition, you
// can use a printf style function, StrFormat, which is defined in Common.h, to
// format strings. You might want to look at Common.h for some...common...and handy functions.
g->SetColor(Color(0, 255, 255));
SexyString myString = StrFormat(_S("You got %d points!"), 147);
g->SetFont(mApp->mTextFont);
g->DrawString(myString, 10, 80);
g->SetColor(Color(0, 255, 0));
g->DrawString(_S(" I am to the right of that previous string."), 10 + mApp->mTextFont->StringWidth(myString), 80);
// What about some other common text functions, like justifying and word wrapping?
// Not a problem. The Widget class contains some useful features.
// For justification, the easiest is to use the WriteString method.
// The valid justification values are:
// -1: Left justified (same as DrawString)
// 0: Centered
// 1: Right justified
//
// For centered/right justified, you can also specify a width indicating the size of the region you
// are printing in. The sole purpose of that is to control the centering/right aligning.
g->SetColor(Color(255, 255, 255));
WriteString(g, _S("Left justified at X of 200"), 200, 95, -1, -1);
WriteString(g, _S("Centered using app width"), 0, 110, mWidth, 0);
WriteString(g, _S("Centered using width of 200, X of 200"), 200, 125, 200, 0);
// And now for a word wrapping example. With this function, you specify a rectangular region
// that represents the "box" in which you want the text to be displayed. You can also
// set the line spacing, or use -1 for the default font height, and you can also set
// the justification using the same values as for WriteString. We'll also draw the "box" using
// the DrawRect function, for illustrative purposes. You may notice that I didn't offset the
// Y coordinate of the text by the font's ascent: that's because WriteWordWrapped automatically
// does that for you.
g->SetColor(Color(255, 255, 255));
g->DrawRect(30, 140, 200, 400);
g->SetColor(Color(0, 255, 0));
WriteWordWrapped(g, Rect(30, 140, 200, 400), _S("This is some text that is wrapped inside of the rectangle \
at X of 30, Y of 140, width of 200, height of 400. It has \
been wrapped using the default left justification and the \
default line spacing."), -1, -1);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Enough talk, let's draw images!
// The simplest way to draw an image is with, coincidentally, the DrawImage method.
// Just specify the image and the X, Y to draw at and boom! It's done.
g->DrawImage(mApp->mTurbotImg, 230, 140);
// What about changing the color of an image? This is called colorization, and it works just like
// all the previous examples: You call SetColor with the color you want. But wait! There's
// an extra step: Because colorizing an image on the fly is slower than normal drawing, you have
// to explicity tell the graphics object that you want to enable it. This is done by calling the
// SetColorizeImages method of the Graphics class with a parameter value of "true".
// IMPORTANT: You should call SetColorizeImages again but with a parameter of "false" when you
// are done drawing your colorized image! Otherwise all subsequent image drawing calls will
// be colorized using the most recent value from SetColor. You may call SetColor either before
// or after SetColorizeImages, it doesn't matter. For this example, we'll draw an image normally
// and then colorized. We'll also use the GetHeight() method to draw this image just below the
// previous one.
g->DrawImage(mApp->mOpaqueBeamImg, 230, 140 + mApp->mTurbotImg->GetHeight());
g->SetColor(Color(255, 0, 0));
g->SetColorizeImages(true);
g->DrawImage(mApp->mOpaqueBeamImg, 230 + mApp->mOpaqueBeamImg->GetWidth(), 140 + mApp->mTurbotImg->GetHeight());
g->SetColorizeImages(false);
// You may have noticed that part of the previous two images was drawn offscreen. The graphics object by
// default sets a clipping region to the size of the widget, and anything drawn outside of it
// is clipped, or not drawn. You'll see some more advanced uses of clipping and widgets in a later demo.
// There's another cool effect you can do to images: Drawing additively instead of normally.
// Additive drawing is just what the name implies: when you draw the image, instead of
// drawing using the exact RGBA values of the image, it actually combines them with whatever
// pixel values there are in the location in which it is placed. One common use of this is
// to draw the same image, but brighter: To do so, you'd just draw the image normally first,
// then draw the same image on top of it but additively. Let's try an example and draw
// our first image, mApp->mTurbotImg, additively to get a bright effect.
g->DrawImage(mApp->mTurbotImg, 230 + mApp->mTurbotImg->GetWidth(), 140);
// Enable additive drawing with the call below. IMPORTANT: You should set the
// drawmode back to Graphics::DRAWMODE_NORMAL when done, so that other images
// don't get drawn additively. Additive drawing is slower than colorization,
// so keep that in mind if you are concerned about your game's performance.
g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
g->DrawImage(mApp->mTurbotImg, 230 + mApp->mTurbotImg->GetWidth(), 140);
g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
// You can even draw an image colorized and additively. This is comonly used to
// implement a pulsing sort of effect. Let's do just that: the variable,
// mPulseAmt, is updated in our Update method and controls the RGB value
// that we'll use to make our image pulse. Again, be sure to set things back
// to normal when you are done. Also, it matters not in which order the
// SetDrawMode, SetColor, and SetColorizeImages calls are made.
g->DrawImage(mApp->mTurbotImg, 230 + mApp->mTurbotImg->GetWidth() * 2, 140);
g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
g->SetColor(Color(mPulseAmt, mPulseAmt, mPulseAmt));
g->SetColorizeImages(true);
g->DrawImage(mApp->mTurbotImg, 230 + mApp->mTurbotImg->GetWidth() * 2, 140);
g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
g->SetColorizeImages(false);
// And finally, let's just have a simple example of alpha blending. We'll
// Draw our first image, a picture of a moon, normally, and draw
// our friend Turbot at reduced opacity on top of it. The result is you can
// see through turbot to the moon below. Notice that we set the color
// for turbot to r=255, g=255, b=255, alpha=128: This results in the
// RGB values not being changed, only the alpha. Using a value of 255 for
// a colorization amount basically means that the color component will not
// be altered.
g->DrawImage(mApp->mMoonImg, 400, 300);
g->SetColor(Color(255, 255, 255, 60));
g->SetColorizeImages(true);
g->DrawImage(mApp->mTurbotImg, 400, 300);
g->SetColorizeImages(false);
}