天天看點

用MSC庫寫貪吃蛇——記我的第一個遊戲

打開這篇部落格的時候,心裡請默念:梨花花超可愛!

菜雞梨花花因為隻會一點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文檔在以上的網站中都有,就不詳細解釋了

具體效果是這樣的(還算是挺好看的吧

用MSC庫寫貪吃蛇——記我的第一個遊戲

重點來了,接下來要設計蛇。

既然不想做遠古時期隻會上下左右動的小蚯蚓,想做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中随着>後面的值的不同可以實作不同的效果。

一節一節的貪吃蛇!

用MSC庫寫貪吃蛇——記我的第一個遊戲

更加圓滑的貪吃蛇(強迫症患者的福音,密恐患者慎玩

用MSC庫寫貪吃蛇——記我的第一個遊戲

第二個思路(未實作)

事實上蛇再朝一個方向運動的時候,身體有呈直線向着運動的方向的趨勢,在第一個思路的一節一節的貪吃蛇中,在結點的數目達到幾十個的時候,很容易出現一個情況,若蛇的一部分圍繞着下一個節點旋轉,那麼從那個被圍繞的節點開始的後面的部分,是不會運動的。那麼這其實是不合理的。

那麼在蛇運動的過程中,除蛇頭以外的節點,都要添加一個以上一個結點的圓心為圓心,旋轉至運動方向的函數,有了這個函數,這條蛇才能成為真正的靈活的蛇,妖豔的蛇。

由于時間有限,梨花并未實作這個函數,但把思路儲存了下來,希望有讀者替我實作,然後告訴我那是一條怎麼樣的蛇。

折中的解決方案是把判斷是否運動的兩點之間的距離改小,這樣就有了強迫症患者喜愛的柔軟光滑的蛇,在沒有修改算法的情況下,柔軟光滑的蛇顯然比傳統的一節一節的貪吃蛇更招人喜歡。

随機在地圖上生成小圓點

使用了百度中的方法生成随機數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 ;
}
           

感謝閱讀,請再默念一遍:梨花花超可愛!