2020年1月2日 星期四

Week17 欣儀的期末作品

 作品名稱:Sky Challenge 

組員:05160035林欣儀、05160160黃偉愷

 Demo影片 

Youtube網址:https://www.youtube.com/watch?v=1C28ph7BvZ0

 介紹: 

首先,開啟遊戲時,因為載入音樂會需要花點時間,所以製作了loading的畫面。接下來玩家可依照個人的喜好選擇小夥伴,確定小夥伴後就可以進入到遊戲畫面。遊戲主要是讓玩家透過按鍵模組來操控小夥伴的方向,讓小夥伴跳在每朵雲上。每一層的雲朵長度不一致,並且在第五層時雲朵會隨機移動。另外,左右兩側會隨機出現小麻雀飛過,玩家必需閃躲,碰到小麻雀或從雲朵上掉落下來,遊戲就結束了。玩家必需要讓小夥伴安全的跳在每朵雲上和閃躲小麻雀,考驗玩家的技術和反應。

 操作: 

利用arduino按鍵模組上的"上鍵"、"左鍵"、"右鍵",來控制小夥伴的方向。

 特色: 

遊戲的設計上,在越高樓層會提高難度與速度,以及增加隨機飛躍的小麻雀,增加遊戲的困難度,讓玩家可以不斷挑戰。介面的設計上,主要以可愛的角落小夥伴當作遊戲主角,並且搭配輕鬆愉快的音樂,增加遊戲整體的豐富度。另外,因為剛開始載入音樂時,會需要花一點時間等待,為了填補這等待的時間,所以增加loading的畫面。

 Code: 

Sumikkogurashi_shaft

import processing.sound.*; // 匯入音樂播放的套件
import processing.serial.*; // 與Arduino連接的套件

GameView gv; // 遊戲畫面
WelcomeView wv; // 歡迎畫面
LoadingView lv; // loading畫面

boolean isGameStart = false; // 紀錄遊戲開始了沒
boolean isLoadingFinsh = false; // 紀錄Loading完了沒

SoundFile player; // 宣告音樂播放器

Serial serial; // 與Arduino連接的東西

String[] characters = {"cat", "dinosaur", "bear", "lizard"};
int nowCharacter = 0; // 紀錄現在選的是哪個角色

void setup() {
  size(500, 800);
  
  lv = new LoadingView();
  
  // 在子線程載入進行遊戲初始化,未初始化前會先顯示載入動畫
  thread("initGame");
}

void initGame() {
  // 初始化與Arduino連接的東西
  serial = new Serial(this, "/dev/cu.wchusbserial1420", 9600); 
  
  // 初始化音樂播放器,並傳入音檔檔名
  player = new SoundFile(this, "Asset/角落.mp3");
  
  gv = new GameView();
  wv = new WelcomeView();
  
  initAllButton();
  
  isLoadingFinsh = true;
}

void initAllButton() {
  
  // 初始化貓咪按鈕
  wv.cat = new Button(5, height/2+200, 120, 120) {
    @Override
      public void onClick() {
        // 按下按鈕開始遊戲
        nowCharacter = 0;
        startGame();
      }
  };
  wv.cat.img = loadImage("Asset/cat.png");
  wv.cat.imgHover = loadImage("Asset/cat_left.png");
  
  // 初始化恐龍按鈕
  wv.dinosaur = new Button(125, height/2+200, 120, 120) {
    @Override
      public void onClick() {
        // 按下按鈕開始遊戲
        nowCharacter = 1;
        startGame();
      }
  };
  wv.dinosaur.img = loadImage("Asset/dinosaur.png");
  wv.dinosaur.imgHover = loadImage("Asset/dinosaur_left.png");
  
  // 初始化白熊按鈕
  wv.bear = new Button(245, height/2+200, 120, 120) {
    @Override
      public void onClick() {
        // 按下按鈕開始遊戲
        nowCharacter = 2;
        startGame();
      }
  };
  wv.bear.img = loadImage("Asset/bear.png");
  wv.bear.imgHover = loadImage("Asset//bear_left.png");
  
  // 初始化蜥蜴按鈕
  wv.lizard = new Button(365, height/2+200, 120, 120) {
    @Override
      public void onClick() {
        // 按下按鈕開始遊戲
        nowCharacter = 3;
        startGame();
      }
  };
  wv.lizard.img = loadImage("Asset/lizard.png");
  wv.lizard.imgHover = loadImage("Asset/lizard_left.png");
}

void startGame() {
  // 按下按鈕開始遊戲
  isGameStart = true;
  gv.initView(
    "Asset/"+characters[nowCharacter]+"_left.png", 
    "Asset/"+characters[nowCharacter]+"_right.png");
  player.loop(); // 播放音檔
  frameRate(75);
}

void draw() {
  if (!isLoadingFinsh) {
    lv.draw();
    return;
  }
  
  //  如果還沒開始遊戲就顯示主畫面
  if (!isGameStart) {
    wv.draw();
    handleChoseCharacters();
    return;
  }
  
  if (!gv.isGameOver()) {
    // 如果玩家還沒掛掉就繼續更新遊戲畫面
    gv.draw();
    //joyStickContorl(); // 搖桿操作
    handleContorl(); // 把手操作
    
  } else {
    // 如果GameOver就不要繼續顯示遊戲畫面和播音樂了
    isGameStart = false;
    player.stop();
  }
}

void keyPressed() {
  switch(keyCode) {
    case LEFT:
      gv.dinosaur.facing = 'L';
      gv.dinosaur.velocity.x = -5f;
      break;
      
    case RIGHT:
      gv.dinosaur.facing = 'R';
      gv.dinosaur.velocity.x = 5f;
      break;  
      
    case UP:
      gv.dinosaur.jump();
      break; 
  }
}

void keyReleased() {
  // 放開鍵盤就煞車
  if (keyCode == RIGHT) {
    gv.dinosaur.velocity.x = 0f;
  }
  if (keyCode == LEFT) {
    gv.dinosaur.velocity.x = 0f;
  }
  if (keyCode == UP) {
    // 放開上鍵後,要記得把isjump設定為false,這樣下次按上鍵時,小恐龍才可以跳耀
    gv.dinosaur.isjump = false;
  }
}

// 搖桿操作
void joyStickContorl() {
  
  float x=0, y=0;
  
  if(serial.available()>0){
    String now= serial.readString();
    String[] xy= splitTokens(now);
    
    x = float(xy[0]);
    y = float(xy[1]);
    
    println("==================");
    println("x: " + x + "   y: " + y);
    
    if (x > 550) {
      gv.dinosaur.facing = 'R';
      gv.dinosaur.velocity.x = 5f;
        
    } else if (x < 500) {
      gv.dinosaur.facing = 'L';
      gv.dinosaur.velocity.x = -5f;
      
    } else {
      gv.dinosaur.velocity.x = 0f;
    }
    
    if (y < 500) {
      gv.dinosaur.jump();
    } else {
      // 放開上鍵後,要記得把isjump設定為false,這樣下次按上鍵時,小恐龍才可以跳耀
      gv.dinosaur.isjump = false;  
    }
  }
}

//  把手操作
void handleContorl() {
  
  if(serial.available()>0){
    float now = float(serial.readString());
    //println(now);
    
    if (now >= 160 && now <= 170) {
      gv.dinosaur.facing = 'R';
      gv.dinosaur.velocity.x = 5f;
        
    } else if (now >= 0 && now <= 10) {
      gv.dinosaur.facing = 'L';
      gv.dinosaur.velocity.x = -5f;
      
    } else if (now >= 30 && now <= 40) {
      gv.dinosaur.jump();
      
    } else {
      gv.dinosaur.velocity.x = 0f;
      // 放開上鍵後,要記得把isjump設定為false,這樣下次按上鍵時,小恐龍才可以跳耀
      gv.dinosaur.isjump = false; 
    }
    
    //if (now >= 30 && now <= 40) {
    //  gv.dinosaur.jump();
    //} else {
    //  // 放開上鍵後,要記得把isjump設定為false,這樣下次按上鍵時,小恐龍才可以跳耀
    //  gv.dinosaur.isjump = false;  
    //}
  }
}

//  用把手選角色 
void handleChoseCharacters() {
    
  if(serial.available()>0){
    float now = float(serial.readString());
    //println("-----------");
    //println(now);
    
    
    if (now >= 160 && now <= 170) {
      nowCharacter ++;
      if (nowCharacter >= characters.length) nowCharacter=0;
        
    } else if (now >= 0 && now <= 10) {
      nowCharacter --;
      if (nowCharacter < 0) nowCharacter=characters.length-1;
    }
    
    //println(nowCharacter);
    
    clearButtonChose();
    // 如果按鈕isChose==true就會顯示面向右邊的圖樣
    switch(nowCharacter) {
      case 0:
        wv.cat.isChose = true;
        break;
      case 1:
        wv.dinosaur.isChose = true;
        break;
      case 2:
        wv.bear.isChose = true;
        break;
      case 3:
        wv.lizard.isChose = true;
        break;
    }
    
    // 按下最右邊的按鈕開始遊戲
    if (now >= 350 && now <= 360) {
      startGame();
    }
  }
}

// 把每個按鈕的isChose清除掉,這樣一次才會顯示一隻被選擇
void clearButtonChose() {
  wv.cat.isChose = false;
  wv.dinosaur.isChose = false;
  wv.bear.isChose = false;
  wv.lizard.isChose = false;
}

LoadingView

import java.util.Arrays;
import java.util.List;

class LoadingView {
  PImage[] images; // 存開場動畫的陣列

  LoadingView() {
    
    // 讀入角落小夥伴動圖的路徑
    File temp = new File(sketchPath() + "/Asset/loading_animate/");
    File[] fs = temp.listFiles();
    ArrayList<File> fsList = new ArrayList<File>(Arrays.asList(fs));
    
    
    println("\n\n==============Loading動畫的所有檔案==============");
    for (File f : fsList) {
      println(f);
    }

    // 移除 .DS_Store 檔案
    for (File f : fsList) {
      if (f.getName().equals(".DS_Store")) {
        fsList.remove(f);
        break;
      }
    }
    
    images = new PImage[fsList.size()];
    // 讀入角落小夥伴的動圖
    for (int i=0; i<fsList.size(); i++) {
      // 因為會有圖片順序的問題,因此暫時先寫死
      images[i]=loadImage("Asset/loading_animate/img" + (i+1) + ".png");
    }
  }
  
  void draw() {
    frameRate(7);
    background(#555555);
    showText("Loading...");
    image(images[frameCount%images.length], 65, 250);
  }
  
  void showText(String text) {
    // 顯示文字
    fill(#EEFFEE); // 文字顏色
    textSize(50); // 文字大小
    textAlign(CENTER, CENTER); // 文字置中對齊
    text(text, width/2, height/2 + 150); // 寫文字
  }
}

WelcomeView

import java.util.Arrays;
import java.util.List;

class WelcomeView {
  PImage bg, title; // 背景圖和標題
  PImage[] images; // 存開場動畫的陣列
  Button bt; // 按鈕
  
  Button cat, bear, dinosaur, lizard;
  
  int frameIndex = 0; // 動畫的偵號

  WelcomeView() {

    // 讀入背景和標題圖
    bg = loadImage("Asset/welcomeView/sky.jpg");
    title = loadImage("Asset/welcomeView/Sky_challenge.png");
    
    // 讀入角落小夥伴動圖的路徑
    File temp = new File(sketchPath() + "/Asset/animate/");
    File[] fs = temp.listFiles();
    ArrayList<File> fsList = new ArrayList<File>(Arrays.asList(fs));
    
    println("\n\n==============開場動畫的所有檔案==============");
    for (File f : fsList) {
      println(f);
    }

    // 移除 .DS_Store 檔案
    for (File f : fsList) {
      if (f.getName().equals(".DS_Store")) {
        fsList.remove(f);
        break;
      }
    }
    
    images = new PImage[fsList.size()];
    // 讀入角落小夥伴的動圖
    for (int i=0; i<fsList.size(); i++) {
      // 因為會有圖片順序的問題,因此暫時先寫死
      images[i]=loadImage("Asset/animate/img" + (i+1) + ".png");
    }
  }
  
  void draw() {
    frameRate(24);
    //background(#D2EDF7);
    image(bg, 0, 0);
    image(title, width/2-350, height/2-440, 640, 360);
    image(images[frameIndex], 65, 250);
    
    // 原本一秒四偵,現在一秒24偵,變六倍了,每六偵更新一次動畫
    if (frameCount % 6 == 0) {
      frameIndex ++;
      if (frameIndex >= images.length) frameIndex = 0;
    }
    
    // 畫全部的按鈕
    cat.draw();
    lizard.draw();
    bear.draw();
    dinosaur.draw();
  }
}

Background

class Background {
  ArrayList<Cloud> cloudList;
  float y = -150;
  PImage bg; // 背景圖
  
  Background() {
    cloudList = new ArrayList<Cloud>();
    bg = loadImage("Asset/sky.jpg"); // 讀入背景圖
    
    // 初始化所有的雲朵
    for (int i=0; i<6; i++) {
      cloudList.add(new Cloud((int)random(0, width-200), y+=150));
    }
  }
  
  void drawCloud() {
    // 滾動畫面上的雲朵
    for (int i=0; i<6; i++) {
      // 如果雲朵滾超出畫面就再畫一個新的雲朵
      if (cloudList.get(i).locate.y >= height) {
        cloudList.remove(i);
        cloudList.add(new Cloud((int)random(0, width-200), -100));
        score ++; // 分數加1
        moveRate += 0.01; // 速度加0.01
      }
      cloudList.get(i).locate.y += 1+moveRate;
      cloudList.get(i).draw();
    }
  }
  
  void draw() {
    //background(128, 170, 217);
    image(bg, 0, 0); // 畫背景圖
    showScore();
    drawCloud();
  }
  
  void showScore() {
    // 顯示文字
    fill(#EEFFEE); // 文字顏色
    textSize(175); // 文字大小
    textAlign(CENTER, CENTER); // 文字置中對齊
    text((score/6)+"F", width/2, height/2-50); // 寫文字
  }
}

GameView

import java.util.Collections;

float moveRate = 0; // 畫面滾動的速度
int score = 6; // 玩家的分數,每六朵雲上升一層樓

class GameView {
  Dinosaur dinosaur;
  Background bg;
  Bird bird;
  
  //GameView() {
  //  initView();
  //}
   
  void initView(String playerImgDir_L, String playerImgDir_R) {
    bg = new Background();
    dinosaur = new Dinosaur(bg.cloudList.get(1).locate.x, 0, 'R', playerImgDir_L, playerImgDir_R);
    dinosaur.locate.y = bg.cloudList.get(1).locate.y + 10 - dinosaur.dinosaurH;
    moveRate = 0;
    score = 6;
    
    bird = new Bird('L');
    bird.locate.x = -100;
  }
  
  void draw() {
    if (isGameOver()) return; // 如果GameOver了就不要繼續更新畫面了
    
    bg.draw();
    
    dinosaur.draw();
    if (dinosaur.touchBird) {
      dinosaur.onCloud = false;
    } else {
      dinosaurOnCloud();
    }
    
    
    drawBird();
    dinosaurTouchBird();
  }
  
  void drawBird() {
    if ((score%6 == 0 && score>6) && 
        (bird.locate.x < -500 || bird.locate.x >= width+500)) {
      float temp = random(0, 2);
      if (temp < 1) {
        bird = new Bird('R');
      } else {
        bird = new Bird('L');
      }
    }
    bird.draw();
  }
  
  void dinosaurOnCloud() {
    for (Cloud cloud : bg.cloudList) {
      if (dinosaur.locate.x >= (cloud.locate.x - dinosaur.dinosaurW + 20) &&
          dinosaur.locate.x <= (cloud.locate.x + cloud.cloudW - 20) &&
          dinosaur.locate.y + dinosaur.dinosaurH >= (cloud.locate.y) &&
          dinosaur.locate.y + dinosaur.dinosaurH <= (cloud.locate.y + cloud.cloudH) &&
          dinosaur.velocity.y >= 0) {
            dinosaur.onCloud = true;
            dinosaur.locate.y = (cloud.locate.y + 10) - dinosaur.dinosaurH;
            break;
      } else {
        dinosaur.onCloud = false;
      }
    }
  }
  
  // 看恐龍有沒有碰到鳥,碰到鳥就掛掉往下掉
  void dinosaurTouchBird() {
    if (((bird.locate.x+bird.birdW/2) >= dinosaur.locate.x && 
        (bird.locate.x+bird.birdW/2) <= (dinosaur.locate.x+dinosaur.dinosaurW)) &&
        ((bird.locate.y+bird.birdH/2) >= dinosaur.locate.y && 
        (bird.locate.y+bird.birdH/2) <= (dinosaur.locate.y+dinosaur.dinosaurH))) {
          dinosaur.touchBird = true;
    }
  }
  
  boolean isGameOver() {
    if (dinosaur.locate.y > height + 50) return true;
    else return false;
  }
}

Dinosaur

class Dinosaur {
  PImage dinosaurLeft, dinosaurRight;
  float dinosaurW=70, dinosaurH=70; // 恐龍的大小
  PVector locate; // 恐龍的位置
  PVector velocity; // 恐龍移動的速度
  char facing; // 恐龍的面向
  boolean onCloud = true; // 紀錄恐龍有沒有在雲上
  boolean isjump = false; // 紀錄恐龍有沒有正在跳耀
  boolean touchBird = false; // 紀錄恐龍有沒有碰到鳥
  
  Dinosaur(float x, float y, char facingROL, String playerImgDir_L, String playerImgDir_R) {
    dinosaurLeft = loadImage(playerImgDir_L);
    dinosaurRight = loadImage(playerImgDir_R);
    locate = new PVector(x, y);
    velocity = new PVector(0, 0);
    facing = facingROL;
  }
  
  void draw() {
    
    // 讓小恐龍在橫向的方向移動時不會超出畫面
    if (locate.x > width-dinosaurW) locate.x = width-dinosaurW;
    if (locate.x < 0) locate.x = 0; 
    
    switch (facing) {
      case 'R':
        image(dinosaurRight, locate.x, locate.y, dinosaurW, dinosaurH);
        break;
      
      case 'L':
        image(dinosaurLeft, locate.x, locate.y, dinosaurW, dinosaurH);
        break;
    }
    
    // 把小恐龍的位置加上在XY軸方向的加速度
    locate.add(velocity);
    
    if (!onCloud) {
      // 如果恐龍沒有在雲上就往下掉,給恐龍向下的加速度
      if (velocity.y <= 30) velocity.y += 0.98;
    } else {
      locate.y += 1+moveRate; // 如果恐龍在雲上就要隨著雲向下移動
      if (velocity.y >= 0) velocity.y = 0; // 如果恐龍在雲上就可以不用給他向下的加速度
    }
  }
  
  void jump() {
    // 如果小恐龍在雲上且沒有正在跳耀才可以做跳的動作(避免連環跳的情況發生)
    if (onCloud && !isjump) {
      isjump = true;
      velocity.y = -20+moveRate;
    }
  }
}

Cloud

class Cloud {
  PImage cloud;
  PVector locate; // 雲朵的位置
  int cloudW=200, cloudH=50; // 雲朵的寬度跟高度
  int moveDirection; // 雲的移動方向
  
  Cloud(float x, float y) {
    cloud = loadImage("Asset/cloud.png");
    locate = new PVector(x, y);
     
    moveDirection = round(random(-2, 2)); // 隨機移動雲的方向
    cloudW = round(random(150, 200)); // 隨機初始化雲的大小
  }
  
  void draw() {
    
    if (score/6 >= 5) {
      // 當雲撞到邊界就反彈
      if (locate.x > width-cloudW) moveDirection = round(random(-2, 0));
      if (locate.x < 0) moveDirection = round(random(0, 2));
      locate.x += moveDirection; // 移動雲
    }
    
    image(cloud, locate.x, locate.y, cloudW, cloudH);
  }
}

Bird

class Bird {
  PVector locate; // 鳥的位置
  PVector velocity; // 鳥移動的速度
  float birdW = 60, birdH = 60; // 鳥的長寬
  PImage birdRight;
  PImage birdLeft;
  char facing; // 鳥的面向
  
  Bird(char facingROL) {
    birdRight = loadImage("Asset/bird_right.png");
    birdLeft = loadImage("Asset/bird_left.png");
    
    facing = facingROL;
    
    switch (facing) {
      case 'R':
        velocity = new PVector(round(random(3, 6)), 0);
        locate = new PVector(0, round(random(0, height/2)));
        break;
      
      case 'L':
        velocity = new PVector(-round(random(3, 6)), 0);
        locate = new PVector(width, round(random(0, height/2)));
        break;
    }
  }
  
  void draw() {
    switch (facing) {
      case 'R':
        image(birdRight, locate.x, locate.y, birdW, birdH);
        break;
      
      case 'L':
        image(birdLeft, locate.x, locate.y, birdW, birdH);
        break;
    }
    
    
    // 把小鳥的位置加上在XY軸方向的加速度
    locate.add(velocity);
  }
}

Button

abstract class Button {
  PVector pos;
  float w, h;
  PImage imgHover, img;
  int value = 0;
  int state; // 紀錄畫Hover還是沒有Hover的按鈕
  boolean isChose = false; //紀錄把手有沒有選到
  //PImage pointer; // 顯示現在選到哪個角色用的
  

  Button(float x, float y, float btnW, float btnH) {
    pos = new PVector(x, y);
    w = btnW;
    h = btnH;
    //pointer = loadImage("Asset/fried_shrimp.png"); // 用炸蝦的圖片當指標
  }

  void draw() {
    checkClick();
    mouseHover();
    
    if (state == 0) {
      image (img, pos.x, pos.y, w, h);
      
    } else if (state == 1) {
      image (imgHover, pos.x, pos.y, w, h);
      //image (pointer, pos.x+(w/2)-35, pos.y+h, 70, 70);
    }
  }

  void mouseHover() {
    if (isChose) {
      state = 1;
      
    } else if (mouseX > pos.x && mouseX < pos.x+w &&
      mouseY > pos.y && mouseY < pos.y+h) {
      state = 1;
      nowCharacter = -1;
    } else {
      state = 0;
    }
  }
 
  void checkClick() {
    if (mousePressed && 
      mouseX > pos.x && mouseX < pos.x+w &&
      mouseY > pos.y && mouseY < pos.y+h) {
      onClick();
    }
  }

  public abstract void onClick();
}

沒有留言:

張貼留言

alanhc 互動技術-week17 [final]

回顧這學期的作品:  期中作業:LANDING:PLANET 賣點&特點: 炫麗的特效 物理(星球重力及降落)及粒子系統(噴射) 世界地圖可根據視角縮放 困難點: 重寫3次最終改寫成物件導向的CLASS寫法...