因爲我做的時候比較着急,現在寫可能思緒有點亂,大家見諒一下呀!過段時間我好好整理一下,再寫一次。
寫這個app的原始目的,是爲了想要把鯨魚(黃景瑜的照片作爲我項目的資料,這個我就有動力好好寫啦,花癡一下下),好,現在我把我寫代碼的過程簡單描述一下。
1.使用cocos2d框架(導入cocos2d-android.jar包)
- SurfaceView是一個Android系統爲需要繪製複雜畫面的程序員提供的強有力的工具視圖類,和OpenGL沒有直接的關係,它的子類GLSurfaceView負責把OpenGL連接到Android視圖系統。
2.設置一個基本的BaseActivity,在他裏面設置好屏幕顯示的屬性,其他類繼承它就可以使用它裏面設置的全部屬性
- 它用於向WindowManager描述Window的管理策略。(全屏、指定豎屏、允許鎖屏)
22.在歡迎和引導界面中添加一個小女孩的動畫
- 使用定時器和Handler、message來處理(要讓每秒進行一次ui更新,如果不使用handler就不能達到更新ui的效果,我的理解是handler中存在一個隊列問題,可以保證不產生阻塞。
- timer裏面調用scheduleAtFixedRate函數(定時任務、開始時間,週期),用改變圖片的方式做動畫
3.在MainActivity中實現遊戲的處理事件
- 定義一個CCGLSurfaceView和CCScene,用於做遊戲動畫。它的View可以用過CCGLSurfaceView來實現:,cocos2d引擎會把圖形加載該view對象上
- 在銷燬這個activity時,獲取這個場景對象實例,得到它的操作句柄,結束這個句柄。同時通過移除緩存中的全部單例“
- 暫停、恢復、停止都是通過CCDirector的句柄來操作。摧毀比停止多一個移除所有的單例,就是清除內容
4.設計gamePlay:
- 裏面採用CopyOnWriteArrayList<>(); 來存放活動的精靈,因爲(一個線程安全的變體,其中所有可變操作(add、set等等)都是通過對底層數組進行一次新的複製來實現的。
//CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景裏,比如緩存。)
5.gamePlay裏面需要gameSprite(遊戲精靈,就是指在界面上活動的)
- 主要是利用org.cocos2d.nodes.CCSprite;來創建:
//初始化一個精靈對象,sprite()方法會默認到assets目錄下去找名爲path的文件
- gameSprite有生命值(life),座標(x,y),間隔多少時間出現,
4.gamePlay中寫初始化函數,
sharedPrefence = CCDirector.sharedDirector().getActivity().getSharedPreferences("sharedPrefence", Context.MODE_PRIVATE);
WinSize = CCDirector.sharedDirector().displaySize();
setIsTouchEnabled(true);
- 設置背景,按屏幕大小比例放大
- 每隔一段時間執行一次this.schedule("GameSmallLove",0.5f);帶0.5f的參數調用GameSmallLove函數
- 新建一個GameSprite精靈,給他設置號基本屬性(生命值,加載的圖片等)
- 寫移動的函數(降落等),先要得到它圖片的大小和屏幕的大小來計算可以移動的範圍,以及可以下降的速度,根據隨機性來設置:
addChild(small.getCCSprite());
- 使用CCFiniteTimeAction 類定義好在什麼時間範圍完成動作,開啓這個精靈runAction
是有限次執行類,它是最爲普通的行爲,就是按時間順序做一系列事情,做完後行爲結束
CCFiniteTimeAction fs_timeAction = CCMoveTo.action(actualDuration,CGPoint.ccp(actualX, (foe.getCCSprite().getContentSize().height / 2)));// 時間內移動
自定義行爲,效果爲調用一個自定義的方法,通過方法的實現來達到自己想要的效果,會將節點對象作爲參數傳遞給被調用的函數。
CCCallFuncN fs_Over = null;
行爲序列(繼承自CCActionInterval),即將若干個待執行的行爲按順序組合在一起,然後依次執行,如果中間有持續行爲的話則等到前一個行爲執行完畢後再執行下一個(CCCallFunc是並行),因此單獨的CCSequence對象是沒有意義的。
CCSequence fs_actions = CCSequence.actions(fs_timeAction, fs_Over);
foe.getCCSprite().runAction(fs_actions);
現在一個小愛心就會隨機產生了。
6.設計小女孩躲避愛心
- 在不同的狀態下,有兩個圖片,不停的交換,做出動畫的效果。
- 用手指一動小女孩,計算觸屏的位置
//將UIKit座標系中的座標轉換爲OpenGLES座標系中的座標。與之前的原點對其
//下面的代碼獲取了觸摸點的座標,並將其轉換成Cocos2D可以使用的座標。該方法通常用於轉換多點觸摸點的座標。
OpenGl座標系:原點在左下角(0,0),與數據的二維座標系一致
UIKit座標系:又稱爲屏幕座標系,原點在左上角,X軸越右越大,Y軸越下越大;
CGPoint location = CCDirector.sharedDirector().convertToGL(CGPoint.ccp(event.getX(), event.getY()));
public boolean ccTouchesBegan(MotionEvent event)
public boolean ccTouchesMoved(MotionEvent event)
public boolean ccTouchesEnded(MotionEvent event)
- 獲取小女孩的碰撞盒,方便後續進行判斷是否發生碰撞CGRect Rect = _FeiJi_Play.getBoundingBox();
7.移動小女孩就是通過控制檢測到觸屏的位置,用它來設置小女孩的位置
localX = touchPosition.x;
localY = touchPosition.y+girl.getContentSize().height/2+qipao.getContentSize().height/2;
qipao.setPosition(localX,localY);
addChild(qipao);
8.小女孩的氣泡碰到愛心就得分(這個時候氣泡消失)
監聽器的刷新頻率影響界面顯示的效果(慢了,就是碰撞之後,愛心和氣泡還來不及消失)
刷新頻率是在this.schedule("Detection",0.1f);,帶參數的調用函數,後面的參數就是刷新時間間隔(0.1秒,刷新一次)
- 用每個氣泡的矩形框來判斷每個愛心是否被選中,選中了減少生命值,判斷生命值是否爲0,爲0就移除
for(int i=0;i<qipaos.size();i++){
CCSprite qipao = qipaos.get(i);
CGRect rectQipao = qipao.getBoundingBox();
for(int j=0;j<smallLoves.size();j++){
GameSprite small = smallLoves.get(j);
CGRect rectSmall = small.getCCSprite().getBoundingBox();
if(CGRect.intersects(rectQipao, rectSmall)){
- 氣泡消失時又動畫的,這裏使用spritesheet精靈列表來實現,這回提高效率。
(
因爲cocos2d對它進行了優化!如果你使用spritesheet來獲取sprite,那麼當場景中有許多sprite的時候,如果這些sprite共享一個spritesheet,那麼cocos2d就會使用一次OpenGL ES調用來渲染這些sprite。但是,如果是單個的sprite的話,那麼就會有N次OpenGL ES call,這個代價是相當昂貴的。
簡而言之--使用spritesheet會更快,尤其是當你有很多的sprite的時候!(使用spritesheet還可以減少遊戲佔用的內存大小
)
Animations是一個實現android
UI界面動畫效果的API,Animations提供了一系列的動畫效果,可以進行旋轉、縮放、淡入淡出等,這些效果可以應用在絕大多數的控件中。
先要選取顯示框的大小,就是每個愛心的矩形框,然後用spritesheet精靈列表來顯示
for(int y=0;y<1;y++){
for(int x=0;x<num;x++){
CCSpriteFrame frame = CCSpriteFrame.frame(boomSheet.getTexture(), CGRect.make(x*cutX,y*cutY,cutX,cutY), CGPoint.ccp(0, 0)); //cpp(0,0)是偏移量
boomAnimFrames.add(frame);
frameCount++;
if(frameCount==num){
break;
}
}
//創建一個CCAnimation對象,並且指定動畫播放的速度。我們使用0.1來指定每個動畫幀之間的時間間隔
CCAnimation boomAnimation = CCAnimation.animation("", (float) 0.1,boomAnimFrames);
CCAnimate boomAction = CCAnimate.action(boomAnimation);
CCCallFuncN actionAnimateDone = CCCallFuncN.action(this,"SpriteAnimationFinished");
CCSequence actions = CCSequence.actions(boomAction, actionAnimateDone);
sprite.runAction(actions);
9.顯示分數
//分數標籤存在,就移除;重新創建,相當於刷新
if(scoreLabel!=null){
scoreLabel.removeSelf();
}
scoreLabel = CCLabel.makeLabel("Score:", scorePath, 40); //字體類型
scoreLabel.setString("Score:"+score);
scoreLabel.setColor(ccColor3B.ccWHITE);
//標籤放置的位置,距離左下角50dip
scoreLabel.setPosition(50,50);
this.addChild(scoreLabel);
10.設置暫停按鈕
- 在屏幕點擊事件中寫,通過控制判斷點擊位置是否包含暫停開始按鈕,來顯示按鈕的圖形已經它是否可見;
CGRect rectPlay = play.getBoundingBox();
CGRect rectPause = pause.getBoundingBox();
if (CGRect.containsPoint(rectPause, location)||CGRect.containsPoint(rectPlay, location)) {
if (isPause) { //暫停
System.out.println("pause");
play.setVisible(true);
pause.setVisible(false);
CCDirector.sharedDirector().resume();
11.在改變小女孩生命值的時候,如果重新畫生命值,監控器檢測來不及反應,所以顯示把生命值的多少畫好,然後直接使用switch來判斷
schedule的時間會相互影響的
12.當生命爲0時,不能移除小女孩?
- 直接使用StopSchedule();停止刷新()
/**
* 停止持續的方法
*/
private void StopSchedule() {
girl.removeSelf();
this.unschedule("GameSmallLove");
this.unschedule("AddGirl");
this.unschedule("AddQipao");
}
13.生命結束需要彈出對話框,先左一個動畫,小女孩變大說話,然後彈出對話框顯示分數
- 出現小女孩和鯨魚,但是沒有愛心(這是因爲在結束之前小女孩和鯨魚是隱藏的,但是這個時候愛心移動的動畫仍在執行,所以當生命結束,小女孩和鯨魚出現的時候,愛心已經移走了;而且這裏移動的方向也不對,moveBy只能移動直線,現在需要移動斜線)
- 顯示對話框,通過場景的Handler開啓一個線程,在一個場景中添加了VIew
場景通過CCDirector可以得到全局的導演,然後getActivity,使用充氣泵導入佈局,然後可以在佈局中得到想要的控件
14.在菜單頁的Score按鈕中查看歷史記錄的分數
有兩種方式:
- 設計一個數據庫表,放在數據庫表中(這裏只需要保存一個分數值,所以不用數據庫這麼麻煩)
- 保存到配置文件中:
在遊戲場景中是通過導演獲取activity,然後再得到配置文件:前面一個參數是配置文件的名稱
sceneActivity = CCDirector.sharedDirector().getActivity();
sp_wel = sceneActivity.getSharedPreferences("sharedPrefence",Context.MODE_PRIVATE);
然後在從sharedPrefence.xml文件中用關鍵詞讀取分數,通過冒泡排序,不斷更新裏面的分數排列
String scoreArray = sp_wel.getString("scoreArray", "0;0;0;0;0");
最後提交:
sp_wel.edit().putString("scoreArray", s).commit();
- scoreActivity可以通過讀取配置文件的信息得到分數,在用listView顯示出來
sp = getSharedPreferences("sharedPrefence", Context.MODE_PRIVATE);
String s = sp.getString("scoreArray", "0;0;0;0;0");
- 在scoreActivity中,採用SimpleAdapter適配器來進行填充分數條目
首先申明適配器和需要填充的條目已經他們的配置文件(一個xml轉載listView,另一個xml中寫好每個listVIew的Item,這樣就可重複利用了)
list = new ArrayList<HashMap<String, Object>>();
adapter = new SimpleAdapter(this, list, R.layout.score_item, new String[] { "score" },
new int[] { R.id.tv_score });
myListView.setAdapter(adapter);
通過往list中添加Map集合(鍵值對),來填充適配器
HashMap<String, Object> map = new HashMap<String, Object>();
map.put(name, score);
list.add(map);
// 方法進行通知該SimpleAdapter內容已經發生改變。
adapter.notifyDataSetChanged();
15.添加背景音樂以及按鈕的按鍵聲,動畫的聲音
music = MediaPlayer.create(this, R.raw.wel);
music.start();
- 按鈕的按鍵聲,動畫的聲音是短促而且多,使用SoundPool來操作,它可以同時進行多個聲音的播放
public SoundPool soundPool;//聲明一個SoundPool
public int musicId;//定義一個整型用load();來設置suondID
裝載聲音
soundPool= new SoundPool(10, AudioManager.STREAM_SYSTEM, 5);
//第一個參數爲同時播放數據流的最大個數,第二數據流類型,第三爲聲音質量
musicId = soundPool.load(this, R.raw.button_sound, 1);
//播放聲音
soundPool.play(musicId, 1, 1, 0, 0, 1);
//第一個參數指定播放哪個聲音; leftVolume 、 rightVolume 指定左、右的音量: priority 指定播放聲音的優先級,數值越大,優先級越高; loop 指定是否循環, 0 爲不循環, -1 爲循環; rate 指定播放的比率,數值可從 0.5 到 2 , 1 爲正常比率。
15.在遊戲的結束的時候,點back按鈕,回到菜單頁面,再點退出遊戲,它又回到菜單頁面,所以菜單Activity需要使用單任務模式SingleTask
16.修改結束頁面對話框的佈局
- 它可以通過配置文件的shape來定義背景等(實心,邊框,圓角等)
- 在顯示對話框的文件中得到Window的屬性,修改:
Window dialogWindow = myDialog.getWindow();
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
dialogWindow.setGravity(Gravity.RIGHT | Gravity.TOP);
lp.x = 65; // 新位置X座標 ,相對於Gravity的偏移量,都是正的
lp.y = 400;
dialogWindow.setAttributes(lp);
17.提升遊戲難度,當分數導到2000分時,進入第二關,加快小愛心的下落速度,同時加大fish
- 創建一個新的場景
- 當分數大於一定程序時,出現一棵樹,四面發射子彈
19.代碼優化
頁面間的切換有黑屏,代碼整個,還有一個free按鈕的功能沒有做好