打開這篇部落格的時候,心裡請默念:梨花花超可愛!
菜雞梨花花因為隻會一點C++又想寫遊戲,是以當俱樂部封裝好MSC後迫不及待使用了這個庫寫了一個貪吃蛇,沒有實體引擎沒有那些隻是聽說過的巴拉巴拉架構,僅僅是一個與滑鼠進行互動的勉強稱之為遊戲的動畫QAQ。
感謝俱樂部的各位對我的支援和指導!
正文!
首先是配置編譯環境,在https://www.kancloud.cn/guaguagua/msc_cpp_library/438586網站上有詳細的配置方法。又笨又懶的梨花由于不會配置自己常用的codeblocks上的環境,隻好跟着網站使用DEVCPP作為編輯器QAQ。網站上教程很詳細,不多說啦~
由于知乎上很多大大的第一個遊戲都是從貪吃蛇開始的,于是梨花也就跟個風。但是我不想做那個醜醜的隻會上下左右動的貪吃蛇,我要做個妖豔賤貨,于是我朝那個貪吃蛇大作戰裡的貪吃蛇看齊。
第一步,在寫貪吃蛇前,我們需要一個背景,一個記分器作為基本的UI界面。
在此安利一個配色網站http://www.colorhunt.co/hot 這個貪吃蛇的配色方案來自這個網站。
void countwindow()
{
countw.setSize(,);
countw.setFill();
countw.setZIndex();
countw.setBorderColor();
countw.setBorderWidth();
countw.put(,);
countwtxt.setText("counting board");
countwtxt.setFontSize();
countwtxt.setBold();
countwtxt.setZIndex();
countwtxt.setColor();
countwtxt.put(,);
txt_score.setText("SCORE:");
txt_score.setZIndex();
txt_score.setFontSize();
txt_score.setColor();
txt_score.put(,);
}
void setscore()
{
stringstream sstr;
char str[];
sstr << score;
sstr >> str;
scoretxt.setText(str);
scoretxt.setZIndex();
scoretxt.setFontSize();
scoretxt.setColor();
scoretxt.put(,);
}
API文檔在以上的網站中都有,就不詳細解釋了
具體效果是這樣的(還算是挺好看的吧

重點來了,接下來要設計蛇。
既然不想做遠古時期隻會上下左右動的小蚯蚓,想做360°無死角轉動的真正的蛇,那麼要如何才能實作呢?參考手遊《貪吃蛇大作戰》裡的搖杆操作,猜想是使蛇頭向搖杆的方向運動,來帶動蛇身的運動。那麼在PC中,有什麼硬體可以實作360°無死角運動的呢?最明顯的答案就是,滑鼠。
于是第一個思路就浮現出來了。
讓蛇頭跟随滑鼠運動,讓蛇身跟着蛇身的上一個結點運動。
這其實是一個遞歸的思想,每一個結點的運動都取決于上一個結點,包括蛇頭的運動取決于滑鼠的運動,而滑鼠的運動軌迹是可人為控制,且可以實作任意軌迹的,那麼蛇整體的運動也是可以取決于滑鼠實作任意軌迹的運動。
然而蛇的每一個結點都要于上一個結點保持一定的距離,否則就會與上一個結點重疊
這是我第一次失敗的教訓。
附上代碼來詳解:
void Node::move(float x,float y,int d)
{
float ix=it.getX();
float iy=it.getY();
float dx=x-ix,dy=y-iy;
float length=sqrt(dx*dx+dy*dy);//計算距離
dx=d*dx/length;
dy=d*dy/length;//計算橫向和縱向的速度
if (length>)//判斷距離
{
it.move(dx,dy);
p.move(dx,dy);//調用運動函數
}
}
這是每個節點的運動函數,主要由計算距離,計算x方向和y方向的速度三個子產品組成,這三個子產品共同作用下實作了節點向上一個結點運動的抽象概念。不過這裡失誤就是,梨花花在定義Node類的時候直接把圖形庫裡的Cirecle類作為公有成員調用了,這裡更好的做法應該是使Node類繼承自Cirecle類(之前學習繼承這個概念的時候沒有經過實踐,真正到了自己寫小遊戲的時候才發現繼承是一種多麼聰明的做法)。
判斷距離的if中随着>後面的值的不同可以實作不同的效果。
一節一節的貪吃蛇!
更加圓滑的貪吃蛇(強迫症患者的福音,密恐患者慎玩
第二個思路(未實作)
事實上蛇再朝一個方向運動的時候,身體有呈直線向着運動的方向的趨勢,在第一個思路的一節一節的貪吃蛇中,在結點的數目達到幾十個的時候,很容易出現一個情況,若蛇的一部分圍繞着下一個節點旋轉,那麼從那個被圍繞的節點開始的後面的部分,是不會運動的。那麼這其實是不合理的。
那麼在蛇運動的過程中,除蛇頭以外的節點,都要添加一個以上一個結點的圓心為圓心,旋轉至運動方向的函數,有了這個函數,這條蛇才能成為真正的靈活的蛇,妖豔的蛇。
由于時間有限,梨花并未實作這個函數,但把思路儲存了下來,希望有讀者替我實作,然後告訴我那是一條怎麼樣的蛇。
折中的解決方案是把判斷是否運動的兩點之間的距離改小,這樣就有了強迫症患者喜愛的柔軟光滑的蛇,在沒有修改算法的情況下,柔軟光滑的蛇顯然比傳統的一節一節的貪吃蛇更招人喜歡。
随機在地圖上生成小圓點
使用了百度中的方法生成随機數https://www.cnblogs.com/afarmer/archive/2011/05/01/2033715.html
有一個細節:應将随機數的類型設定為無符号 (都是淚
簡單的生成随機小圓點後發現了新的問題,小圓點跑到邊緣上去了,還跑到計分闆上去了。我的解決方法是判斷生成的随機數向量是否合法,不合法就重新生成随機數直到合法。
float x,y;
do
{
x=rand()/(RAND_MAX/);
y=rand()/(RAND_MAX/);
}while(x>&&x<&&y>&&y<||x<||x>||y<||y>);
Circle *temp=new Circle;
temp->put(x,y);
temp->setRadius();
temp->setBorderWidth();
temp->setBorderColor();
temp->setFill();
temp->setZIndex();
beenque.push_back(temp);
将生成的小圓點的位址儲存在
判斷碰撞和失敗條件
判斷碰撞的第一個思路(未實作)
我的第一個思路是在蛇頭上設定幾個取色點來跟随蛇頭運動,然後每一幀對取色點上的顔色進行判斷,若不是蛇頭顔色和背景顔色疊加之後的顔色(特地把蛇的顔色設定為95%的透明度),則判斷碰撞,執行相應操作。但被機會主席告知并沒有這個操作以後,很遺憾的隻能放棄這個思路,但我将這個思路記錄在這篇部落格上,希望以後能用到(這種方法的判斷碰撞的時間複雜度是常數級的)
判斷碰撞的第二個思路
第二個思路便是将地圖上的碰撞點放入一個動态數組中,每一幀依次與蛇頭計算距離,若兩個圓的圓心之間的距離小于兩圓半徑之和,則判斷碰撞,執行接下來的操作:删除地圖上的小圓點,在貪吃蛇尾部添加一個結點,積分闆上分數+1,在地圖上生成小圓點。
這裡的分數+1有個小技巧,由于Text類顯示的是字元串,是以可以用字元串流把整型的分數轉化成字元串,再顯示。
stringstream sstr;
char str[];
sstr << score;
sstr >> str;
scoretxt.setText(str);
在删除地圖上的結點的時候遇到了一個問題,由于地圖上的結點是由new建立的,在函數中用delete釋放的時候遊戲會卡死。地圖上的圓點的指針儲存在vector中,不知道是不是在調用vector的erase()函數的時候出了問題。
将以上部分組合,一個可愛的貪吃蛇小遊戲就出來啦~
源碼如下(醜醜的
#include "MSC.h" // 包含MSC封裝頭檔案
#include <sstream>
#include <cmath>
#include <vector>
#include <iostream>
#include<cstdlib>
#include<ctime>
using namespace std;
using namespace MSC;
Text txt_score;
Rectangle countw;
Text countwtxt;
int score=;
Text scoretxt;
int speed=;
int wspeed=;
Rectangle failblock;
Text failtxt;
vector <Circle*> beenque;
class Node
{
public:
Node* prev;
Circle it;
Circle p;
Node(Node* former);
~Node();
void move(float x,float y,int d);
void rotate(float x,float y,int w);
float pointX();
float pointY();
bool isImpact(float x,float y);
};
vector <Node* > que;
void countwindow()
{
countw.setSize(,);
countw.setFill();
countw.setZIndex();
countw.setBorderColor();
countw.setBorderWidth();
countw.put(,);
countwtxt.setText("counting board");
countwtxt.setFontSize();
countwtxt.setBold();
countwtxt.setZIndex();
countwtxt.setColor();
countwtxt.put(,);
txt_score.setText("SCORE:");
txt_score.setZIndex();
txt_score.setFontSize();
txt_score.setColor();
txt_score.put(,);
}
void setscore()
{
stringstream sstr;
char str[];
sstr << score;
sstr >> str;
scoretxt.setText(str);
scoretxt.setZIndex();
scoretxt.setFontSize();
scoretxt.setColor();
scoretxt.put(,);
}
Node::Node(Node* former)
{
prev=former;
if (prev==)
{
it.setRadius();
it.setFill();
it.put(,);
it.setZIndex();
}
else
{
prev=former;
it.setRadius();
it.setFill();
it.put(prev->it.getX(),prev->it.getY()+);
it.setZIndex();
}
p.setFill();
p.setRadius();
p.put(pointX(),pointY()-);
p.setZIndex();
}
Node::~Node()
{
it.remove();
}
float Node::pointX()
{
return it.getX()+;
}
float Node::pointY()
{
return it.getY()+;
}
void Node::move(float x,float y,int d)
{
float ix=it.getX();
float iy=it.getY();
float dx=x-ix,dy=y-iy;
float length=sqrt(dx*dx+dy*dy);
dx=d*dx/length;
dy=d*dy/length;
if (length>)
{
it.move(dx,dy);
p.move(dx,dy);
}
}
bool Node::isImpact(float x,float y)
{
float length=sqrt(pow(pointX()-x,)+pow(pointY()-y,));
return length<?true:false;
}
bool isfail(Node *head)
{
int x=head->pointX();
int y=head->pointY();
if (x<||x>||y<||y>)
return true;
for (int i=;i<que.size();i++)
if(head->isImpact(que[i]->pointX(),que[i]->pointY()))
return true;
return false;
}
void gamming(int ms)
{
if(!isfail(que[]))
{
int d=speed*ms/;
que[]->move(getMouseX(),getMouseY(),d);
for (int i=;i<que.size();i++)
que[i]->move(que[i-]->it.getX(),que[i-]->it.getY(),d);
for (int i=;i<beenque.size();i++)
{
if (que[]->isImpact(beenque[i]->getX()+,beenque[i]->getY()+))
{
score++;
setscore();
que.push_back(new Node(que[que.size()-]));
beenque[i]->remove();
beenque.erase(beenque.begin()+i);
float x,y;
do
{
x=rand()/(RAND_MAX/);
y=rand()/(RAND_MAX/);
}while(x>&&x<&&y>&&y<||x<||x>||y<||y>);
Circle *temp=new Circle;
temp->put(x,y);
temp->setRadius();
temp->setBorderWidth();
temp->setBorderColor();
temp->setFill();
temp->setZIndex();
beenque.push_back(temp);
}
}
}
else
{
failblock.put(,);
failtxt.put(,);
}
}
int main()
{
MSCinit();
createWindow(, , "Retro Snake");
setBackground();
countwindow();
setscore();
failblock.setFill();
failblock.setSize(,);
failblock.setZIndex();
failtxt.setText("YOU ARE FAIL");
failtxt.setFontSize();
failtxt.setZIndex();
failtxt.setColor();
que.push_back(new Node());
int n=;
while (n--)
que.push_back(new Node(que[que.size()-]));
srand((unsigned)time(NULL));
for (int i=;i<;i++)
{
float x,y;
do
{
x=rand()/(RAND_MAX/);
y=rand()/(RAND_MAX/);
}while(x>&&x<&&y>&&y<||x<||x>||y<||y>);
Circle *temp=new Circle;
temp->put(x,y);
temp->setRadius();
temp->setBorderWidth();
temp->setBorderColor();
temp->setFill();
temp->setZIndex();
beenque.push_back(temp);
}
gameLoop(gamming);
return ;
}
感謝閱讀,請再默念一遍:梨花花超可愛!