先來看看作業要求
在連連看游戲中,會有⼀個地圖,地圖上有許多不同種類的⽅塊,通過將相同種類的兩個⽅塊相連,可以將這兩個⽅塊消除,⽤戶獲得分數。
在整個連連看的過程中,除了處理⽤戶的操作之外,還有⼏個⽐較特殊的部分特別需要注意:
隨機地圖的⽣成
判斷兩個⽅塊是否可以通過兩次以內的折線進⾏連接
判斷剩余⽅塊是否還有解
除此之外,具體的功能要求如下:
RPG 機制
激活:當⻆⾊處於⽅塊旁且再次向⽅塊⽅向移動,會激活該⽅塊(請使⽤某種⽅式表示該⽅塊被激活)。
消除:如果此次激活的⽅塊和上次激活的⽅塊是同種類,且可以通過兩次以內的折線連接,則該兩個⽅塊被消除,玩家獲得分數。(請繪制出將兩個⽅塊連接在⼀起的折線) 否則,上次激活的⽅塊被⾃動變為未激活狀態,換句話說,每個⻆⾊在地圖中只有 0 個(游戲剛開始時,或剛剛消除完⼀對⽅塊時)或者 1 個激活的⽅塊。
計分
不同種類的⽅塊可以有不同的分值,具體規則可以⾃⾏制定。界⾯中應時刻顯示玩家的分數。
倒計時和游戲結束
有兩個情況可以導致游戲結束:
倒計時結束;
沒有可消除的⽅塊對(所有⽅塊均被消除也屬於這⼀種)。
界⾯中應時刻顯示游戲的倒計時。
開始菜單
⾄少包括以下按鈕:
開始新游戲
可選擇游戲模式:單⼈模式、雙⼈模式(具體看后⽂)
載⼊游戲
退出游戲
單⼈模式
游戲開始時,會隨機⽣成地圖,並隨機玩家⻆⾊位置。隨后玩家可控制⻆⾊移動,以激活和消除⽅塊。
道具
道具通過隨機⽅式出現在地圖的空地上,當⻆⾊與道具出現在同⼀位置時,該⻆⾊觸發道具效,道具消失。
+1s:延⻓剩余時間 30s
Shuffle:所有⽅塊位置重排
Hint: 10s 內會⾼亮⼀對可能鏈接的⽅塊,被消除后會⾼亮下⼀對,直到 10s 時間結束
Flash: 5s 內允許通過⿏標單擊移動⻆⾊位置,⻆⾊移動到的位置必須通過空地可到達,否則點擊不產⽣任何效果。如果點擊到⽅塊,且⻆⾊可以移動到該⽅塊旁,則⻆⾊移動到該⽅塊旁,且該⽅塊被激活。如果⽅塊四周有多個位置可以讓⻆⾊停留,則⻆⾊移動到其中任何⼀個位置均可。
雙⼈模式
兩個玩家的兩個⻆⾊在相同的地圖上進⾏游戲,以結束游戲時雙⽅的分數決定誰為贏家。道具 在單⼈模式的基礎上,增加:
Freeze:對⼿ 3s 內⽆法移動
Dizzy:對⼿ 10s 內移動⽅向顛倒(上下左右顛倒) 此外,
Hint 道具的效果對兩個玩家均可⻅;
+1s 道具的效果對兩個玩家均有效。
雙⼈模式下,沒有 Flash 道具
暫停和存檔
在暫停時,可以保存游戲(Save)和載⼊游戲(Load)保存游戲會將當前游戲的所有狀態以任意格式保存到磁盤上的⽂件載⼊游戲時,讀取⽂件,並從中恢復狀態
我完成作業的順序是:
-
做出菜單窗口,幾個窗口的跳轉
-
在主窗口上顯示按鈕和人物(都是通過建立button,在其上顯示貼圖實現)
-
鍵盤事件實現人物移動和人物激活按鈕
-
實現按鈕消除
-
實現繪制按鈕之間的連線
-
實現雙人模式
-
實現計時,道具,分數,背景音樂等
-
實現存檔加載
在整個做完后,回頭看自己其實走了不少彎路,但整個過程還是比較有成就感,就像看見自己一步步養大一個小baby一樣。
這里總結一些自己踩過的坑:
-
button要顯示必須要聲明為Mainwindow的全局變量,不然顯示不出來(這是剛剛接觸qt的我糾結好久的問題)
-
mainwindow里面需要加入一個widget不然無法顯示
-
可能我選擇的實現方式的問題,用button放置圖片,導致一些操作的實現特別困難,比如繪制連線。但所幸我最后解決了這個問題,只能說車到山前必有路。或許用Qpainter繪制更簡單一些,但我沒有這樣做也不知道是否更好些
-
我的完成順序還是有些問題的,把雙人模式放在最后要改動的東西十分多,應該提前解決(也可能是我沒想到合適的解決方法)
這里就按照我的順序逐步講解
做出菜單窗口,幾個窗口的跳轉
先搭好框架,再往里面填充內容,這和我們的任務書推薦順序稍有不同,但我覺得把這一步放在前面挺正確的。
這一步我主要參考文檔的內容:http://shouce.jb51.net/qt-beginning/5.html
在編寫窗口之前,首先需要理解以下概念:信號和槽函數
在我的理解里,一個窗口就像一個人,他可以喊話,聽到他話的人作出反應。在這個過程里,喊話就是一個窗口發送信號,做出反應就是另一個窗口響應槽函數,這樣就完成了一個信號的傳遞。
發送的主體和執行主體也可以是同一個窗口,就像人大腦給肢體發送信息一樣。
這里先實現三個窗口之間的互相切換(我是用到了三個窗口,一個開始窗口,一個模式選擇窗口,一個主窗口)
-
添加窗口
-
添加dialog窗口(初學建議Qt設計師ui類)
-
進入dialog的ui編輯界面,拖入一個pushbutton
雙擊pushbutton,可以修改按鈕文字,修改為“開始”,右鍵單擊,點擊“轉到槽”
在彈出的轉到槽對話框中選擇
clicked()
信號並按下確定按鈕。這時會跳轉到編輯模式dialog.cpp
文件的on_pushButton_clicked()
函數處,這個就是自動生成的槽,它已經在dialog.h
文件中進行了聲明。我們只需要更改函數體即可。這里更改為:void Dialog::on_pushButton_clicked() { this->hide(); emit mainShow(); }
我們讓當前窗口隱藏,讓主窗口顯示,我們需要在
dialog.h
里面定義mainShow()
信號這樣點擊“開始”按鈕后,dialog窗口就能發送信號並將自身隱藏,下一步我們需要讓mainwindow響應信號顯示自身。
-
鏈接信號和槽函數
在
main.cpp
創建dialog對象,並鏈接dialog對象的發送信號和mainwindow的相應槽函數QObject::connect(&dlg, SIGNAL(mainShow()), &w, SLOT(show()));
這樣就可以實現點擊開始按鈕后,dialog窗口隱藏,主窗口顯示。如果想要隱藏主窗口顯示dialog窗口,那就在mainwindow里創建一個按鈕,像之前一樣設置按鍵槽函數->發送信號->鏈接信號和槽函數->設置槽函數
如此一來就可以實現幾個界面的跳轉了
在主窗口上顯示按鈕和人物
首先了解一下布局和widget
先對qt的組件層次關系有一個總體上的把握,大體的層次關系是這樣,mainwindow上面需要有一個主widget,主widget上可以有若干個布局layout, 可以是水平布局(QHBoxLayout)、垂直布局(QVBoxLayout)、網格布局(QGridLayout)。布局里可以放各種組件,像按鈕,對話框之類。當然這個關系不是絕對包含的,比如layout里面也可以加入widget。
map是記錄地圖的int數組,0表示空地,正數表示圖片的編號
在對層次關系了解后,我們在 mainwindow.h 上依次新建widget、layout和button們
QWidget *windows; // 主widget ImageButton *image[X+2][Y+2];// 存儲二態圖片的按鈕 QGridLayout *gridLayout; // 主布局
當然要包含QGridLayout
庫,另外的ImageButton
是我自己創建的一個類,目的是讓按鈕擁有普通態和激活態兩種顯示圖片狀態,為之后的選中后高亮顯示做准備。
先說說我的imagebutton
的實現。
實現了這些功能 {初始設置兩個可以相互切換的圖標(我的用途是切換圖標的激活與未激活)、清空圖標(把圖片換成純灰色)、設置新的圖片(方便player移動到這個按鈕)、交換兩個按鈕的圖片(后續的重排shuffle功能用到)}
imagebutton.h
#ifndef IMAGEBUTTON_H #define IMAGEBUTTON_H #include <QToolButton> #include <QIcon> class ImageButton : public QToolButton { Q_OBJECT private: QIcon normalState; // 普通狀態 QIcon activeState; // 激活狀態 QIcon clearState; // 空白狀態 bool currentIcon; //當前為普通態還是激活態 private: public: ImageButton(); ~ImageButton(){} // 切換激活態和普通態 void changeIcon(); // 初始設置按鈕 void setButtonIcon(QIcon normal, QIcon active); // 設置為player圖片 void setPlayer(QIcon icon); // 消除圖片后清空按鈕,或者player移動后清空原來按鈕 void clearIcon(); void exchangeIcon(ImageButton *); void flushIcon(); }; #endif // IMAGEBUTTON_H imagebutton.cpp #include "imagebutton.h" ImageButton::ImageButton() { setDown(false); setFocusPolicy(Qt::NoFocus); // 空白圖片 QPixmap map(":/new/prefix1/image/group1/1-1 (0).png"); clearState = QIcon(map); } void ImageButton::changeIcon() { if(currentIcon) { setIcon(normalState); currentIcon = 0; } else { setIcon(activeState); currentIcon = 1; } } void ImageButton::setButtonIcon(QIcon normal, QIcon active) { normalState = normal; activeState = active; setIcon(normal); currentIcon = 0; } void ImageButton::setPlayer(QIcon icon) { setIcon(icon); } void ImageButton::clearIcon() { setIcon(clearState); } void ImageButton::exchangeIcon(ImageButton *m) { QIcon tmp1 = activeState; QIcon tmp2 = normalState; activeState = m->activeState; normalState = m->normalState; m->activeState = tmp1; m->normalState = tmp2; } void ImageButton::flushIcon() { setIcon(normalState); }
創建完成之后,需要在mainwindow.cpp
里實例化並賦值信息
windows = new QWidget(); gridLayout = new QGridLayout; for(int i = 0; i < X+2; ++i) for(int j = 0; j < Y+2; ++j) { image[i][j] = createImageButton( texts1[buttonNum], texts2[buttonNum]); gridLayout->addWidget(image[i][j], i, j, Qt::AlignCenter);
這里是根據地圖的大小創建了imagebutton,x+2*Y+2最外面一圈是陸地,中間是待消除的圖標。這里調用了creatImageButton
函數,實現如下
ImageButton *MainWindow::createImageButton(const QString &str1, const QString &str2) { // 加載兩張圖片 QPixmap img1, img2; img1.load(str1); img2.load(str2); ImageButton *button = new ImageButton; // 設置大小和圖片 // button->setGeometry(0, 0, 0, 0); button->setFixedSize(int(img1.width()/2.15), int(img1.height()/2.15)); button->setButtonIcon(QIcon(img1), QIcon(img2)); button->setIconSize(QSize(img1.width()/2, img1.height()/2)); button->setStyleSheet("border:1px rgba(237, 241, 255, 100);" "border-radius:8px;" "padding:0px;" "border-style: outset;"); return button; }
接收兩個圖片地址字符串,把圖片加載進來,實例化imagebutton,設置imgbutton大小和背景格式,返回一個Imagebutton類型
button的setIcon()
方法接收一個QIcon
對象,我在讀入圖片的時候選擇先用QPixmap
格式讀入,再用QIcon (img)
轉為QIcon
格式
人物圖片的讀入也一樣,在.h文件下創建對象,在.cpp文件中讀入就ok了
鍵盤事件實現人物移動和人物激活按鈕
首先在.h文件聲明對keyPressEvent的重寫void keyPressEvent(QKeyEvent *event);
我的實現如下
void MainWindow::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_W: { // 到達邊界就break if(loc_player1.x == 0) break; // 碰到圖片就發送激活信號 if(map[loc_player1.x - 1][loc_player1.y] > 0) emit activateImg(loc_player1.x - 1, loc_player1.y); else if(loc_player1.x) { loc_player1.x --; flushPlayerLoc(); } if(map[loc_player1.x][loc_player1.y] < 0) propHandler(map[loc_player1.x][loc_player1.y]); break; } case Qt::Key_A: { if(loc_player1.y == 0) break; if(map[loc_player1.x][loc_player1.y - 1] > 0) emit activateImg(loc_player1.x,loc_player1.y - 1); else if(loc_player1.y) { loc_player1.y --; flushPlayerLoc(); } if(map[loc_player1.x][loc_player1.y] < 0) propHandler(map[loc_player1.x][loc_player1.y]); break; } case Qt::Key_S: { if(loc_player1.x == X+1) break; if(map[loc_player1.x + 1][loc_player1.y] > 0) emit activateImg(loc_player1.x + 1, loc_player1.y); else if(loc_player1.x < X+1){ loc_player1.x ++; flushPlayerLoc(); } if(map[loc_player1.x][loc_player1.y] < 0) propHandler(map[loc_player1.x][loc_player1.y]); break; } case Qt::Key_D: { if(loc_player1.y == Y+1) break; if(map[loc_player1.x][loc_player1.y + 1] > 0) emit activateImg(loc_player1.x, loc_player1.y + 1); else if(loc_player1.y < Y+1) { loc_player1.y ++; flushPlayerLoc(); } if(map[loc_player1.x][loc_player1.y] < 0) propHandler(map[loc_player1.x][loc_player1.y]); break; } }
根據按鍵來判斷運動方向:
- 如果超出就break
- 如果碰到圖標就發送激活這個圖標的信號(當然這個信號得在.h文件定義並提前鏈接到處理的槽函數中)
- 如果可以移動,那就改變人物的坐標
發送激活信號后,處理槽函數change(int, int)
接收到激活的位置,實現如下
void MainWindow::change(int x, int y) { enum{first, second}; // 第幾次激活 static int visitTime = first; if (visitTime == first) { // 第一次激活直接激活 loc_first[0] = loc(x, y); visitTime = second; image[x][y]->changeIcon(); flushPlayerLoc(); sound[1]->play(); }else { // 第二次激活 loc_second[0] = loc(x, y); visitTime = first; if (x == loc_first[0].x && y == loc_first[0].y) { // 如果是同一個按鈕,變回去 image[x][y]->changeIcon(); sound[1]->play(); } else{ if(checkFeasibility(0)) { // 如果兩個按鈕可消除 map[x][y] = 0; //消除圖片,並把map更新為空地 map[loc_first[0].x][loc_first[0].y] = 0; image[x][y]->changeIcon(); flushLine(0); sound[2]->play(); imgNum -= 2; score[0] +=2; QString tmp = "score:\n" + QString::number(score[0]); if(mode) tmp+=":"+QString::number(score[1]); scores->setText(tmp); curSol->setText("可消除"); if (imgNum == 0) { QMessageBox tmp; tmp.setText("恭喜你,呱唧呱唧!"); tmp.exec(); emit gameover(); } }else { // 不可消除就去除激活態 image[loc_first[0].x][loc_first[0].y]->changeIcon(); curSol->setText("不可消除"); sound[1]->play(); } } if(checkRemain()) remainSol->setText("有解"); else { remainSol->setText("無解"); QMessageBox tmp; tmp.setText("很遺憾,無解"); tmp.exec(); emit gameover(); } } }
通過創造一個靜態變量來記錄訪問的次數
實現按鈕消除
這算是游戲的核心算法,其實本質就是BFS,用dfs不行,我是參考https://www.cnblogs.com/HappyAngel/archive/2010/12/25/1916731.html,用dfs消除是可以實現的,但要記錄路徑就很麻煩了,上面的文章我看了好幾遍才搞清楚,建議多看幾遍。這里貼上檢驗代碼。
// 檢查兩個圖標是否可消除 bool MainWindow::checkFeasibility(int i) { // 如果兩張圖片不一樣,不可消除 if(map[loc_first[i].x][loc_first[i].y] != map[loc_second[i].x][loc_second[i].y]) { return false; } //先把visited數組清零 solvability = false; memset(visited, 0, sizeof(visited)); queue.clear(); for (int s = 0; s < X+2; ++s) for(int j = 0; j < Y+2; ++j) path[s][j] = loc(-1, -1); queue.enqueue(loc(loc_first[i].x, loc_first[i].y)); while(!queue.empty()) { loc tmp = queue.front(); queue.pop_front(); bfs(tmp.x, tmp.y, visited[tmp.x][tmp.y]+1, i); if (solvability) return true; } return false; } // 廣搜函數,x1 y1 第一個圖片坐標; cnt 轉折點次數; void MainWindow::bfs(int x1, int y1,int cnt, int i) { int tmp_x = x1, tmp_y = y1; int cnt_limit = 3; // 向上搜索 while (-- tmp_x >=0 && !map[tmp_x][tmp_y]) { if (!visited[tmp_x][tmp_y] && cnt <= cnt_limit) { queue.enqueue(loc(tmp_x, tmp_y)); visited[tmp_x][tmp_y] = cnt; path[tmp_x][tmp_y] = loc(x1, y1); } } if (tmp_x == loc_second[i].x && tmp_y == loc_second[i].y) { solvability = true; path[tmp_x][tmp_y] = loc(x1, y1); return; } tmp_x = x1; // 向下搜索 while (++ tmp_x < X+2 && !map[tmp_x][tmp_y]) { if (!visited[tmp_x][tmp_y] && cnt <= cnt_limit) { queue.enqueue(loc(tmp_x, tmp_y)); visited[tmp_x][tmp_y] = cnt; path[tmp_x][tmp_y] = loc(x1, y1); } } if (tmp_x == loc_second[i].x && tmp_y == loc_second[i].y) { solvability = true; path[tmp_x][tmp_y] = loc(x1,y1); return; } tmp_x = x1; while (-- tmp_y >= 0 && !map[tmp_x][tmp_y]) { if (!visited[tmp_x][tmp_y] && cnt <= cnt_limit) { queue.enqueue(loc(tmp_x, tmp_y)); visited[tmp_x][tmp_y] = cnt; path[tmp_x][tmp_y] = loc(x1, y1); } } if (tmp_x == loc_second[i].x && tmp_y == loc_second[i].y) { solvability = true; path[tmp_x][tmp_y] = loc(x1, y1); return; } tmp_y = y1; while (++ tmp_y < Y+2 && !map[tmp_x][tmp_y]) { if (!visited[tmp_x][tmp_y] && cnt <= cnt_limit) { queue.enqueue(loc(tmp_x, tmp_y)); visited[tmp_x][tmp_y] = cnt; path[tmp_x][tmp_y] = loc(x1, y1); } } if (tmp_x == loc_second[i].x && tmp_y == loc_second[i].y) { solvability = true; path[tmp_x][tmp_y] = loc(x1, y1); return; } }
實現繪制按鈕之間的連線
這是最難為我的一步,因為我采用的方法是用button顯示圖片,但是Qt不允許在控件上面畫,也就是不能疊加顯示,我和老師交流了一下,很沒有頭緒
在千思萬慮后我想到了解決辦法——把連線做成圖片,只需要做從左到右、從上到下、從左到上下,從右到上下六種圖片,就可以拼接形成路徑了。但還是挺難,這也就導致畫線函數成了整個程序最長的函數。方法是這樣的:
-
從起點開始搜索
-
每一個點記錄的是路線的上一個keyPoint,可以理解為拐點
-
畫路徑的時候從終點開始向起點回溯
-
如果沒太理解,再看看https://www.cnblogs.com/HappyAngel/archive/2010/12/25/1916731.html
這里貼出畫線的代碼
// // 可消除狀態下的路線更新,路徑上的方塊都轉換圖像。path里記錄的是拐點信息 // void MainWindow::flushLine(int c) { // 從第二個img開始往回溯 loc cur = loc_second[c]; loc next = path[cur.x][cur.y]; while (!(cur == loc_first[c])) { // 兩個關鍵點在同一列 if (cur.x == next.x) { if(cur.y < next.y) { // 把關鍵點之間的空地改為直線 for (int i = cur.y + 1; i < next.y; ++i) { // l2r代表left_to_right, u2d代表up_to_down image[cur.x][i]->setPlayer(QIcon(pathPic[l2r])); // 如果和人物的位置重合,就換圖片 lineOnPlayer(loc(cur.x, i), p1_l2r, p2_l2r, p_l2r); } // 拐點部分單獨處理 //要根據下一個拐點具體判斷 if (!(next == loc_first[c])) { if(path[next.x][next.y].x < next.x) { image[next.x][next.y]->setPlayer(QIcon(pathPic[u2l])); lineOnPlayer(next, p1_u2l, p2_u2l, p_u2l); } else { // std::cerr << "d" ; image[next.x][next.y]->setPlayer(QIcon(pathPic[d2l])); lineOnPlayer(next, p1_d2l, p2_d2l, p_d2l); } } }else { for (int i = next.y + 1; i < cur.y; ++i) { image[cur.x][i]->setPlayer(QIcon(pathPic[l2r])); lineOnPlayer(loc(cur.x, i), p1_l2r, p2_l2r, p_l2r); } if (!(next == loc_first[c])) { if(path[next.x][next.y].x < next.x) { image[next.x][next.y]->setPlayer(QIcon(pathPic[u2r])); lineOnPlayer(next, p1_u2r, p2_u2r, p_u2r); } else { // std::cerr << "d" ; image[next.x][next.y]->setPlayer(QIcon(pathPic[d2r])); lineOnPlayer(next, p1_d2r, p2_d2r, p_d2r); } } } // 兩關鍵點在同一行 }else { if(cur.x < next.x) { for (int i = cur.x + 1; i < next.x; ++i) { image[i][cur.y]->setPlayer(QIcon(pathPic[u2d])); lineOnPlayer(loc(i, cur.y), p1_u2d, p2_u2d, p_u2d); } if (!(next == loc_first[c])) { if(path[next.x][next.y].y < next.y) { image[next.x][next.y]->setPlayer(QIcon(pathPic[u2l])); lineOnPlayer(next, p1_u2l, p2_u2l, p_u2l); } else { image[next.x][next.y]->setPlayer(QIcon(pathPic[u2r])); lineOnPlayer(next, p1_u2r, p2_u2r, p_u2r); } } }else { for (int i = next.x + 1; i < cur.x; ++i) { image[i][cur.y]->setPlayer(QIcon(pathPic[u2d])); lineOnPlayer(loc(i, cur.y), p1_u2d, p2_u2d, p_u2d); } if (!(next == loc_first[c])) { if(path[next.x][next.y].y < next.y) { image[next.x][next.y]->setPlayer(QIcon(pathPic[d2l])); lineOnPlayer(next, p1_d2l, p2_d2l, p_d2l); } else { image[next.x][next.y]->setPlayer(QIcon(pathPic[d2r])); lineOnPlayer(next, p1_d2r, p2_d2r, p_d2r); } } } } // 更新當前點和下一個點,進入下一次回溯 cur = next; next = path[cur.x][cur.y]; } }
為了避免畫線遮掉人物,還另做了6*3張圖片(p1,p2,p1+p2乘以6種),另寫了一個判斷函數
// 處理路徑顯示和人物重合 // void MainWindow::lineOnPlayer(loc next, int index1, int index2, int index3) { if (next == loc_player1) image[next.x][next.y]->setPlayer(QIcon(pathPic[index1])); if (next == loc_player2) image[next.x][next.y]->setPlayer(QIcon(pathPic[index2])); if (next == loc_player1 && next == loc_player2) image[next.x][next.y]->setPlayer(QIcon(pathPic[index3])); }
實現雙人模式
雙人模式要加的東西有:
-
鍵盤事件
-
判斷函數
鍵盤事件沒啥說的,和player1完全一致,但判斷函數不能復用很麻煩。老師在實現建議里面提到將player實現為一個類,兩個player對應兩個實例,我一開始沒那么做,后面后悔可要改的東西太多了
最后的道具時間音樂等都比較簡單,在做到這一步以后(倒裝)。代碼基本可以復制。文件操作也比較簡單,這里就不多提了,代碼我放在了github,鏈接:https://github.com/kenor5/project1_-
小灶:
用ps做連連看圖標
網上找不到現成成套的圖標,就自己用photoshop制作了一套
-
在搜索引擎上找一套小圖標
-
下載一張導入ps
-
用矩形選框工具(快捷鍵M)框選出一個小寶貝(按住shift不放,框選為正方形),然后ctrl+j復制一層,隱藏掉下面一層
-
用魔棒工具,選擇寶貝周圍的白色像素,按delete刪除
-
新建一個圖層2,拖動到圖層1下面,用吸管工具(快捷鍵I)吸取寶貝的主題色
-
按alt+delete填充,按住CTRL同時選中圖層一二,按ctrl+e合並圖層,這樣就得到一個正方形的寶貝了。可以右鍵單擊導出為png
如此重復,你就可以得到自己的寶貝們了
界面美化
我覺得美化的重點就是矩形倒圓角、調整背景半透明、設置好看的背景圖片,這是花時間最少但最出效果的部分
倒圓角&&設置三態按鈕的方法:設置樣式表,代碼如下
button->setStyleSheet("QPushButton{" "border-image:url(:/new/prefix1/image/pause.png);"//填充圖片 "border:2px rgb(237, 241, 255);" "border-radius:50px;"//倒圓角尺寸 "padding:2px 3px;" "border-style: outset;" "}" "QPushButton:hover{" //懸浮態 " " "color: black;" "}" "QPushButton:pressed{"//點擊態 "" "border-style: inset;" "}");
設置背景半透明:
scores->setStyleSheet(""//最后一位透明度 "border:2px rgb(237, 241, 255);" "border-radius:7px;" "padding:2px 3px;" "font:bold 20px;" "color:rgba(0,0,0,100);" "border-style: outset;");
還有重要的一點:在button里邊設置背景圖片時,想要實現半透明,圖片必須是PNG格式(jpg格式不附帶透明度信息),而且png的不透明度必須小於100%,否則顯示出來還是不透明,改不透明度還是在ps里,方法為:在圖層的右上角更改圖層透明度,然后存儲為PNG格式,這樣得到的圖片就是半透明的,設置在button上就可以隱約顯示出背景了。
還有一些小問題,比如正方形的圖標放在倒過圓角的button里邊不契合,還是會顯示出整個方形圖片。這個問題需要在ps里面對原始圖片倒圓角。
制作封面
-
網上找一張圖片拖進ps(字體仿制官方海報的綠底白字)
-
標題打上去,選擇合適的大小和喜歡的字體
-
用多邊形套索工具勾勒出翅膀的形狀,填充為白色
-
用框選工具調整字體間距,CTRL+j復制一層,填充為綠色,用自由變換(ctrl+t)變大一些
-
把綠色圖層中部分空白框選出來填充
-
把綠色的圖層復制一層,填充為黑色,放大一些,再填充更多
-
把三個圖層同時選中,ctrl+e合並,然后ctrl+t自由變換,右鍵單擊畫面,選擇變形
-
拖拽成你想要的樣子