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;
}