SDL2 游戏开发日记(三)
俄罗斯方块
方块的表示
用二维数组表示方块。
#pragma once
#include <cstdlib>
#include <ctime>
#include <cassert>
using namespace std;
#define theTetrisArray TetrisArray::Instance()
struct Tetris{
int M[4][4];
};
class TetrisArray{
private:
Tetris mTetriss[8];
TetrisArray(const TetrisArray &){}
void operator=(const TetrisArray &){}
TetrisArray();
public:
~TetrisArray(){
}
static TetrisArray & Instance(){
static TetrisArray instance;
return instance;
}
//随机生成方块
Tetris GetArray(){
int index = rand() % 8;
return mTetriss[index];
}
int GetRandomValue(int min, int max){
assert(max != 0);
int v = rand() % max + min;
return v;
}
};
//TetrisArray.cpp
#include "stdafx.h"
#include "TetrisArray.h"
#include <string>
TetrisArray::TetrisArray(){
srand((unsigned int)time(0));
//
Tetris t[8] = {
{
0, 0, 0, 0,
1, 1, 1, 1,
0, 0, 0, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 0, 1, 1,
0, 0, 0, 1,
0, 0, 0, 1
},
{
0, 0, 0, 0,
0, 1, 1, 0,
0, 0, 1, 1,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 0, 1, 1,
0, 1, 1, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 0, 1, 1,
0, 0, 1, 0,
0, 0, 1, 0
},
{
0, 0, 0, 0,
0, 0, 1, 0,
0, 1, 1, 1,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 1, 1, 1,
0, 1, 0, 1,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 0, 0, 0
}
};
for (int i = 0; i < 8; i++){
mTetriss[i] = t[i];
}
}
方块的渲染
根据4x4的数组去绘制对应的方块
#pragma once
#include "Renderable.h"
#include "TetrisArray.h"
class TetrisRenderable : public Renderable {
private :
Tetris mRenderTetris;
public :
TetrisRenderable(){
}
~TetrisRenderable(){
}
virtual void Render();
virtual void SetPos(int x, int y){
if (mRenderRect != NULL){
mRenderRect->x = x;
mRenderRect->y = y;
mRenderRect->w = mTextureWidth * 4;
mRenderRect->h = mTextureHeight * 4;
}
}
void SetArray(Tetris Tetris){
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
if (Tetris.M[i][j] != 1)
Tetris.M[i][j] = 0;
mRenderTetris.M[i][j] = Tetris.M[i][j];
}
}
}
//方块旋转
Tetris GetRotateTetris(int &, int &);
//获取数组
Tetris GetRenderTetris(int &,int &);
};
void TetrisRenderable::Render(){
if (mIsVisible){
int left = -1, top = -1, right = -1, bottom = -1;
for (int i = 0; i < 4; i++){
for (int j = 0; j < 4; j++){
if (mRenderTetris.M[i][j] > 0){
//获取渲染矩形
if (left == -1)
left = j;
else if (left > j)
left = j;
if (top == -1)
top = i;
else if (top > i)
top = i;
if (right == -1)
right = j;
else if (right < j)
right = j;
if (bottom == -1)
bottom = i;
else if (bottom < i)
bottom = i;
}
}
}
for (int i = top; i < 4; i++){
for (int j = left; j < 4; j++){
if (mRenderTetris.M[i][j] > 0){
SDL_Rect rect = { mRenderRect->x + (j - left) * mTextureWidth, mRenderRect->y + (i-top) * mTextureHeight,
mTextureWidth, mTextureHeight };
SDL_RenderCopyEx(theGame.getRenderer(), mTexture, mClipRect, &rect, 0, NULL, SDL_FLIP_NONE);
}
}
}
}
}
主界面
用一个15 * 21的数组表示整个区域,对应的值如果为1,绘制方块。
#pragma once
#include "Renderable.h"
#include <cassert>
#include "TetrisArray.h"
#include "TetrisRenderable.h"
#include <vector>
using namespace std;
class TetrisWorldRenderable : public Renderable{
private :
vector<int> *mRenderArray;
int mArrayWidth, mArrayHeight,mArrayLength;
//可移动方块的坐标
int mMoveTetrisX, mMoveTetrisY;
public:
TetrisWorldRenderable(int w, int h) :Renderable(){
mRenderArray = new vector<int>(w*h);
mArrayWidth = w;
mArrayHeight = h;
mArrayLength = w * h;
ResetArray();
}
virtual ~TetrisWorldRenderable(){
delete mRenderArray;
}
virtual void Render();
virtual void SetPos(int x,int y){
if (mRenderRect != NULL){
mRenderRect->x = x;
mRenderRect->y = y;
mRenderRect->w = mTextureWidth * mArrayWidth;
mRenderRect->h = mTextureHeight * mArrayHeight;
}
}
virtual void Update(float time_step);
//重新开始
void ResetArray(){
for (int i = 0; i < mArrayLength; i++){
(*mRenderArray)[i] = 0;
}
//初始化在中间
SetTetrisStartPos();
}
//重置可控制的方块的位置
void SetTetrisStartPos(){
mMoveTetrisX = 6;
mMoveTetrisY = 0;
}
int GetWorldWidth(){
return mTextureWidth * mArrayWidth;
}
int GetWorldHeight(){
return mTextureHeight * mArrayHeight;
}
//判断是否可以移动
bool IsCanMove(int x, int y,TetrisRenderable *Tetris);
bool Rotate(TetrisRenderable *Tetris);
bool MoveLeft(TetrisRenderable *Tetris);
bool MoveRight(TetrisRenderable *Tetris);
//
bool MoveDown(TetrisRenderable *Tetris);
// 当可控制的方块不能移动时,判断是否需要消行
int LineDelete(){
int lineCount = 0;
for (int i = mArrayHeight - 1; i >= 0;){
bool IsFull = true;
for (int j = 0; j < mArrayWidth; j++){
int pos = mArrayWidth * i + j;
if ((*mRenderArray)[pos] == 0)
{
IsFull = false;
break;
}
}
if (IsFull){
lineCount++;
MoveLine(i);
}
else{
i--;
}
}
return lineCount;
}
//消去一行后,上面的行往下移
void MoveLine(int lineIndex){
for (int i = lineIndex - 1; i >= 0; i--){
for (int j = 0; j < mArrayWidth; j++){
int pos = mArrayWidth * i + j;
int nextLine = mArrayWidth * (i+1) + j;
(*mRenderArray)[nextLine] = (*mRenderArray)[pos];
}
}
}
//判断是否满屏,在消完行之后进行判断
bool IsGameOver(){
for (int i = 0; i < mArrayWidth; i++){
if ((*mRenderArray)[i] > 0){
return true;
}
}
return false;
}
};
方块场景
#pragma once
#include <SDL.h>
#include "Scene.h"
#include "TetrisRenderable.h"
#include "TextRenderable.h"
#include "TetrisWorldRenderable.h"
class TetrisScene : public Scene{
private :
TextRenderable *mTextScore;
TextRenderable *mTextLineCount;
TextRenderable *mTextLevel;
TetrisWorldRenderable *mTetrisWorld;
//当前可控制的方块和下一个将出现的方块
TetrisRenderable *mCurrentTetris, *mNextTetris;
int mLevel, mLineCount, mScore;
//下落速度,数字越小,速度越快
float mDownSpeed;
//经过的时间,如果时间大于downSpeed,可控制的方块往下移动一行
float mTimeElapse;
public:
TetrisScene(){
mTextLevel = NULL;
mTextLineCount = NULL;
mTextLevel = NULL;
mTetrisWorld = NULL;
mCurrentTetris = NULL;
mNextTetris = NULL;
mLevel = 1;
mDownSpeed = 0.5f;
mTimeElapse = 0.0f;
mLineCount = 0;
mScore = 0;
}
~TetrisScene(){}
virtual void LoadScene();
virtual void HandleEvent(SDL_Event &_event);
virtual void Update(float time_step);
void UpdateSocre(int line);
};
#include "stdafx.h"
#include "Common.h"
#include "TetrisScene.h"
#include "SDLGame.h"
#include "Colors.h"
//加载场景,3个文字显示,一个方块主界面,两个方块(当前,下一个)。
void TetrisScene::LoadScene(){
mTetrisWorld = new TetrisWorldRenderable(15, 21);
int wX = 0, wY = 0;
if (mTetrisWorld->LoadTexture(theGame.GetResourcePath() + "tetris.png")){
wX = (theGame.GetWindowWidth() - mTetrisWorld->GetWorldWidth()) / 2;
wY = (theGame.GetWindowHeight() - mTetrisWorld->GetWorldHeight()) / 2;
mTetrisWorld->SetPos(wX, wY);
wX += mTetrisWorld->GetWorldWidth() + 20;
wY += 20;
this->AddRenderable(mTetrisWorld);
}
else {
SAFE_DELETE(mTetrisWorld);
}
mCurrentTetris = new TetrisRenderable();
if (mCurrentTetris->LoadTexture(theGame.GetResourcePath() + "tetris.png")){
mCurrentTetris->SetArray(theTetrisArray.GetArray());
mCurrentTetris->SetVisible(false);
this->AddRenderable(mCurrentTetris);
}
else{
SAFE_DELETE(mCurrentTetris);
}
mNextTetris = new TetrisRenderable();
if (mNextTetris->LoadTexture(theGame.GetResourcePath() + "tetris.png")){
mNextTetris->SetPos(wX, wY);
mNextTetris->SetArray(theTetrisArray.GetArray());
this->AddRenderable(mNextTetris);
}
else{
SAFE_DELETE(mNextTetris)
}
int margin_top = 200;
mTextLevel = new TextRenderable();
char tempString[50];
memset(tempString, 0, sizeof(tempString));
SDL_Color color = Colors.WHITE;
sprintf_s(tempString, sizeof(tempString), "Lv.%d", mLevel);
if (mTextLevel->SetText(tempString,20,color)){
mTextLevel->SetPos(wX + 10, wY + margin_top);
this->AddRenderable(mTextLevel);
}
else{
SAFE_DELETE(mTextLevel);
}
mTextLineCount = new TextRenderable();
mTextScore = new TextRenderable();
memset(tempString, 0, sizeof(tempString));
sprintf_s(tempString, sizeof(tempString), "Score:%d", mScore);
if (mTextScore->SetText(tempString,20,color)){
mTextScore->SetPos(wX + 10, wY + margin_top + 30);
this->AddRenderable(mTextScore);
}
else{
SAFE_DELETE(mTextScore);
}
memset(tempString, 0, sizeof(tempString));
sprintf_s(tempString, sizeof(tempString), "Line:%d", mLineCount);
if (mTextLineCount->SetText(tempString, 20, color)){
mTextLineCount->SetPos(wX + 10, wY + margin_top + 60);
this->AddRenderable(mTextLineCount);
}
else{
SAFE_DELETE(mTextLineCount);
}
}
//方块控制
void TetrisScene::HandleEvent(SDL_Event &_event){
if (_event.type == SDL_KEYDOWN){
if (_event.key.keysym.sym == SDLK_k){
mTetrisWorld->Rotate(mCurrentTetris);
}
else if (_event.key.keysym.sym == SDLK_a || _event.key.keysym.sym == SDLK_LEFT){
mTetrisWorld->MoveLeft(mCurrentTetris);
}
else if (_event.key.keysym.sym == SDLK_d || _event.key.keysym.sym == SDLK_RIGHT){
mTetrisWorld->MoveRight(mCurrentTetris);
}
else if (_event.key.keysym.sym == SDLK_s || _event.key.keysym.sym == SDLK_DOWN){ //如果当前的方块不能再往下移动了
if (!mTetrisWorld->MoveDown(mCurrentTetris)){
int w, h;
//下一个方块的值给当前方块
mCurrentTetris->SetArray(mNextTetris->GetRenderTetris(w, h));
//随机生成下一个方块
mNextTetris->SetArray(theTetrisArray.GetArray());
//重设当前方块的位置
mTetrisWorld->SetTetrisStartPos();
//消行,是否结束,更新得分
int LineCount = mTetrisWorld->LineDelete();
if (mTetrisWorld->IsGameOver()){
mTetrisWorld->ResetArray();
LineCount = -1;
}
UpdateSocre(LineCount);
}
}
}
}
//
void TetrisScene::Update(float time_step){
mTimeElapse += time_step;
//自动下落
if (mTimeElapse >= mDownSpeed){
mTimeElapse -= mDownSpeed;
if (!mTetrisWorld->MoveDown(mCurrentTetris)){
int w, h;
mCurrentTetris->SetArray(mNextTetris->GetRenderTetris(w, h));
mNextTetris->SetArray(theTetrisArray.GetArray());
mTetrisWorld->SetTetrisStartPos();
int LineCount = mTetrisWorld->LineDelete();
if (mTetrisWorld->IsGameOver()){
mTetrisWorld->ResetArray();
LineCount = -1;
}
UpdateSocre(LineCount);
}
}
}
void TetrisScene::UpdateSocre(int line){
//一次性消除的行越多分数越高
if (line > 0){
int score = 100;
switch (line)
{
case 2:
score = 300;
break;
case 3:
score = 800;
break;
case 4:
score = 1500;
break;
default:
break;
}
mScore += score;
mLineCount += line;
char tempString[50];
memset(tempString, 0, sizeof(tempString));
sprintf_s(tempString, sizeof(tempString), "Score:%d", mScore);
mTextScore->SetText(tempString, 20, Colors.WHITE);
memset(tempString, 0, sizeof(tempString));
sprintf_s(tempString, sizeof(tempString), "Line:%d", mLineCount);
mTextLineCount->SetText(tempString, 20, Colors.WHITE);
}
else if (line == -1){
//-1表示重置得分和等级
char tempString[50];
mScore = 0;
mLineCount = 0;
mLevel = 1;
memset(tempString, 0, sizeof(tempString));
sprintf_s(tempString, sizeof(tempString), "Score:%d", mScore);
mTextScore->SetText(tempString, 20, Colors.WHITE);
memset(tempString, 0, sizeof(tempString));
sprintf_s(tempString, sizeof(tempString), "Line:%d", mLineCount);
mTextLineCount->SetText(tempString, 20, Colors.WHITE);
memset(tempString, 0, sizeof(tempString));
sprintf_s(tempString, sizeof(tempString), "Lv.%d", mLevel);
mTextLevel->SetText(tempString, 20, Colors.WHITE);
}
}
测试
测试了移动,旋转。估计应该都没有什么问题。下一步将加入游戏音效。
int main(int argc, _TCHAR* argv[])
{
//获取程序所在路径
char path[MAX_PATH];
memset(path, 0, MAX_PATH);
DWORD pathSize = GetModuleFileNameA(NULL, path, MAX_PATH);
PathRemoveFileSpecA(path);
//设置游戏资源所在路径
string strPath = charToUTF8(path)+"\\Res\\";
theGame.SetResourcePath(strPath);
string title = charToUTF8("俄罗斯方块");
theGame.Init(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT);
//加载字体
theGame.LoadGameFont(theGame.GetResourcePath() + "simkai.ttf");
//创建场景
Scene *loadingScene = new LoadingScene();
loadingScene->LoadScene();
theSceneManager.AddScene(LOADING, loadingScene);
//标题
Scene* startScene = new StartScene();
startScene->LoadScene();
theSceneManager.AddScene(START, startScene);
//俄罗斯方块
Scene*mainScene = new TetrisScene();
mainScene->LoadScene();
theSceneManager.AddScene(MAIN, mainScene);
//直接显示俄罗斯方块界面
theSceneManager.ChangeScene(MAIN);
theGame.Run();
return 0;
}