麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > HTML > 正文

canvas實現飛機打怪獸射擊小游戲的示例代碼

2024-08-26 00:20:49
字體:
來源:轉載
供稿:網友

接觸 canvas 也只有一個多月,第一次完整實現一個游戲流程,收獲還是挺大的。

canvas,小游戲,代碼

射擊游戲截圖

先上 demo:https://littleyljy.github.io/demo/shootgame/

游戲規則

要求玩家控制飛機發射子彈,消滅會移動的怪獸,如果全部消滅了則游戲成功,如果怪獸移動到底部則游戲失敗。

  • 使用 ← 和 → 操作飛機
  • 使用空格(space)進行射擊
  • 需有暫停功能
  • 多關卡

場景切換

游戲分為幾個場景:

  • 開始游戲(.game-intro)
  • 游戲中(#canvas)
  • 游戲失?。?game-failed)
  • 游戲成功(.game-success)
  • 游戲通關(.game-all-success)
  • 暫停(.game-stop)

實現場景切換,其實是先把所有場景 display: none , 然后通過 js 控制 data-status 分別為 start 、playing 、failed 、success 、all-success 、stop 來實現對應場景 display: block 。

HTML 和 CSS 如下:

  <div id="game" data-status="start">     <div class="game-panel">      <section class="game-intro game-ui">        <h1 class="section-title">射擊游戲</h1>        <p class="game-desc">這是一個令人欲罷不能的射擊游戲,使用 ← 和 → 操作你的飛機,使用空格(space)進行射擊,使用回車(enter)暫停游戲。一起來消滅宇宙怪獸吧!</p>        <p class="game-level">當前Level: 1</p>        <button class="js-play button">開始游戲</button>      </section>      <section class="game-failed game-ui">        <h1 class="section-title">游戲結束</h1>        <p class="game-info-text">最終得分: <span class="score"></span></p>        <button class="js-replay button">重新開始</button>      </section>      <section class="game-success game-ui">        <h1 class="section-title">游戲成功</h1>        <p class="game-next-level game-info-text"></p>        <button class="js-next button">繼續游戲</button>      </section>      <section class="game-all-success game-ui">        <h1 class="section-title">通關成功</h1>        <p class="game-next-level game-info-text">你已經成功地防御了怪獸的所有攻擊。</p>        <button class="js-replay button">再玩一次</button>      </section>      <section class="game-stop game-ui">        <h1 class="section-title">游戲暫停</h1>        <button class="js-stop button">游戲繼續</button>      </section>    </div>    <div class="game-info game-ui">      <span class="title">分數:</span>      <span class="score"></span>    </div>    <canvas id="canvas" width="700" height="600">        <!-- 動畫畫板 -->    </canvas>  </div>
#game{  width: 700px;  height: 600px;  position: relative;  left: 50%;  top: 40px;  margin: 0 0 0 -350px;  background: linear-gradient(-180deg, #040024 0%, #07165C 97%);}.game-ui{  display: none;  padding: 55px;  box-sizing: border-box;    height: 100%;}[data-status="start"] .game-intro {  display: block;  padding-top: 180px;  background: url(./img/bg.png) no-repeat 430px 180px;  background-size: 200px;}[data-status="playing"] .game-info {  display: block;  position: absolute;  top:0;  left:0;  padding:20px;}[data-status="failed"] .game-failed,[data-status="success"] .game-success,[data-status="all-success"] .game-all-success,[data-status="stop"] .game-stop{  display: block;  padding-top: 180px;  background: url(./img/bg-end.png) no-repeat 380px 190px;  background-size: 250px;}

面向對象

canvas,小游戲,代碼

整個游戲可以把怪獸(Enemy)、飛機(Plane)、子彈(Bullet)都當作對象,另外還有配置對象(CONFIG)和控制游戲邏輯的游戲對象(GAME)。

游戲相關配置

/**  * 游戲相關配置  * @type {Object}  */var CONFIG = {  status: 'start', // 游戲開始默認為開始中  level: 1, // 游戲默認等級  totalLevel: 6, // 總共6關  numPerLine: 7, // 游戲默認每行多少個怪獸  canvasPadding: 30, // 默認畫布的間隔  bulletSize: 10, // 默認子彈長度  bulletSpeed: 10, // 默認子彈的移動速度  enemySpeed: 2, // 默認敵人移動距離  enemySize: 50, // 默認敵人的尺寸  enemyGap: 10,  // 默認敵人之間的間距  enemyIcon: './img/enemy.png', // 怪獸的圖像  enemyBoomIcon: './img/boom.png', // 怪獸死亡的圖像  enemyDirection: 'right', // 默認敵人一開始往右移動  planeSpeed: 5, // 默認飛機每一步移動的距離  planeSize: {    width: 60,    height: 100  }, // 默認飛機的尺寸,  planeIcon: './img/plane.png'};

定義父類

因為怪獸(Enemy)、飛機(Plane)、子彈(Bullet)都有相同的 x, y, size, speed 屬性和 move() 方法,所以可以定義一個父類 Element,通過子類繼承父類的方式實現。

/*父類:包含x y speed move() draw()*/var Element = function (opts) {        this.opts = opts || {};        //設置坐標、尺寸、速度        this.x = opts.x;        this.y = opts.y;        this.size = opts.size;        this.speed = opts.speed;};Element.prototype.move = function (x, y) {        var addX = x || 0;        var addY = y || 0;        this.x += addX;        this.y += addY;};//繼承原型的函數function inheritPrototype(subType, superType) {        var proto = Object.create(superType.prototype);        proto.constructor = subType;        subType.prototype = proto;}

move(x, y) 方法根據傳入的 (x, y) 值自疊加。

定義怪獸

怪獸包含特有屬性:怪獸狀態、圖像、控制爆炸狀態持續的 boomCount ,和 draw()、down()、direction()、booming() 方法。

/*敵人*/var Enemy = function (opts) {    this.opts = opts || {};    //調用父類屬性    Element.call(this, opts);    //特有屬性狀態和圖像    this.status = 'normal';//normal、booming、noomed    this.enemyIcon = opts.enemyIcon;    this.enemyBoomIcon = opts.enemyBoomIcon;    this.boomCount = 0;};//繼承Element方法inheritPrototype(Enemy, Element);//方法:繪制敵人Enemy.prototype.draw = function () {    if (this.enemyIcon && this.enemyBoomIcon) {        switch (this.status) {            case 'normal':                var enemyIcon = new Image();                enemyIcon.src = this.enemyIcon;                ctx.drawImage(enemyIcon, this.x, this.y, this.size, this.size);                break;            case 'booming':                var enemyBoomIcon = new Image();                enemyBoomIcon.src = this.enemyBoomIcon;                ctx.drawImage(enemyBoomIcon, this.x, this.y, this.size, this.size);                break;            case 'boomed':                ctx.clearRect(this.x, this.y, this.size, this.size);                break;            default:                break;        }    }    return this;};//方法:down 向下移動Enemy.prototype.down = function () {    this.move(0, this.size);    return this;};//方法:左右移動Enemy.prototype.direction = function (direction) {    if (direction === 'right') {        this.move(this.speed, 0);    } else {        this.move(-this.speed, 0);    }    return this;};//方法:敵人爆炸Enemy.prototype.booming = function () {    this.status = 'booming';    this.boomCount += 1;    if (this.boomCount > 4) {        this.status = 'boomed';    }    return this;}
  • draw() 主要是根據怪獸的狀態繪制不同的圖像。
  • down() 調用父類 move() 方法,傳入 y 值控制怪獸向下移動。
  • direction() 根據傳入的方向值控制左/右移動。
  • booming() 讓爆炸狀態持續4幀,4幀后再消失。

定義子彈

子彈有 fly() 、draw() 方法。

/*子彈*/var Bullet = function (opts) {    this.opts = opts || {};    Element.call(this, opts);};inheritPrototype(Bullet, Element);//方法:讓子彈飛Bullet.prototype.fly = function () {    this.move(0, -this.speed);    return this;};//方法:繪制子彈Bullet.prototype.draw = function () {    ctx.beginPath();    ctx.strokeStyle = '#fff';    ctx.moveTo(this.x, this.y);    ctx.lineTo(this.x, this.y - CONFIG.bulletSize);    ctx.closePath();    ctx.stroke();    return this;};
  • fly() 調用父類 move() 方法,傳入 y 值控制子彈向上移動。
  • draw() 因為子彈其實就是一條長度為 10 的直線,通過繪制路徑的方式畫出子彈。

定義飛機

飛機對象包含特有屬性:狀態、寬高、圖像、橫坐標最大最小值,有 hasHit()、draw()、direction()、shoot()、drawBullets() 方法。

/*飛機*/var Plane = function (opts) {    this.opts = opts || {};    Element.call(this, opts);    //特有屬性狀態和圖像    this.status = 'normal';    this.width = opts.width;    this.height = opts.height;    this.planeIcon = opts.planeIcon;    this.minX = opts.minX;    this.maxX = opts.maxX;    //子彈相關    this.bullets = [];    this.bulletSpeed = opts.bulletSpeed || CONFIG.bulletSpeed;    this.bulletSize = opts.bulletSize || CONFIG.bulletSize;};//繼承Element方法inheritPrototype(Plane, Element);//方法:子彈擊中目標Plane.prototype.hasHit = function (enemy) {    var bullets = this.bullets;    for (var i = bullets.length - 1; i >= 0; i--) {        var bullet = bullets[i];        var isHitPosX = (enemy.x < bullet.x) && (bullet.x < (enemy.x + enemy.size));        var isHitPosY = (enemy.y < bullet.y) && (bullet.y < (enemy.y + enemy.size));        if (isHitPosX && isHitPosY) {            this.bullets.splice(i, 1);            return true;        }    }    return false;};//方法:繪制飛機Plane.prototype.draw = function () {    this.drawBullets();    var planeIcon = new Image();    planeIcon.src = this.planeIcon;    ctx.drawImage(planeIcon, this.x, this.y, this.width, this.height);    return this;};//方法:飛機方向Plane.prototype.direction = function (direction) {    var speed = this.speed;    var planeSpeed;    if (direction === 'left') {        planeSpeed = this.x < this.minX ? 0 : -speed;    } else {        planeSpeed = this.x > this.maxX ? 0 : speed;    }    console.log('planeSpeed:', planeSpeed);    console.log('this.x:', this.x);    console.log('this.minX:', this.minX);    console.log('this.maxX:', this.maxX);    this.move(planeSpeed, 0);    return this;//方便鏈式調用};//方法:發射子彈Plane.prototype.shoot = function () {    var bulletPosX = this.x + this.width / 2;    this.bullets.push(new Bullet({        x: bulletPosX,        y: this.y,        size: this.bulletSize,        speed: this.bulletSpeed    }));    return this;};//方法:繪制子彈Plane.prototype.drawBullets = function () {    var bullets = this.bullets;    var i = bullets.length;    while (i--) {        var bullet = bullets[i];        bullet.fly();        if (bullet.y <= 0) {            bullets.splice(i, 1);        }        bullet.draw();    }};
  • hasHit() 判斷飛機發射的子彈是否擊中怪獸,主要是判斷子彈的橫坐標是否在[怪獸橫坐標,怪獸橫坐標+怪獸高度]范圍內,同時子彈的縱坐標在[怪獸縱坐標,怪獸縱坐標+怪獸寬度]范圍內,擊中返回 true,并移除該子彈。
  • draw() 繪制子彈和飛機。
  • direction() 因為飛機移動范圍有左右邊界,需要判斷飛機橫坐標是否到達邊界,如果到達邊界 planeSpeed 為 0,不再移動。
  • shoot() 創建子彈對象,保存到 bullets 數組,子彈橫坐標為飛機橫坐標加上飛機寬度的一半。
  • drawBullets() 繪制子彈,從數組最后往回遍歷子彈對象數組,調用子彈 fly() 方法,如果子彈向上飛出屏幕,則移除這顆子彈。

定義鍵盤事件

鍵盤事件有以下幾種狀態:

  • keydown:用戶在鍵盤上按下某按鍵時發生。一直按著某按鍵則會不斷觸發(opera 瀏覽器除外)。
  • keypress:用戶按下一個按鍵,并產生一個字符時發生(也就是不管類似 shift、alt、ctrl 之類的鍵,就是說用戶按了一個能在屏幕上輸出字符的按鍵 keypress 事件才會觸發)。一直按著某按鍵則會不斷觸發。
  • keyup:用戶釋放某一個按鍵是觸發。

因為飛機需要按下左鍵(keyCode=37)右鍵(keyCode=39)時(keydown)一直移動,釋放時 keyup 不移動。按下空格(keyCode=32)或上方向鍵(keyCode=38)時(keydown)發射子彈,釋放時 keyup 停止發射。另外按下回車鍵(keyCode=13)暫停游戲。所以,需要定義一個 KeyBoard 對象監聽 onkeydown 和 onkeyup 是否按下或釋放某個鍵。

因為左右鍵是矛盾的,為保險起見,按下左鍵時需要把右鍵 設為 false。右鍵同理。

//鍵盤事件var KeyBoard = function () {  document.onkeydown = this.keydown.bind(this);  document.onkeyup = this.keyup.bind(this);};//KeyBoard對象KeyBoard.prototype = {  pressedLeft: false,  pressedRight: false,  pressedUp: false,  heldLeft: false,  heldRight: false,  pressedSpace: false,  pressedEnter: false,  keydown: function (e) {    var key = e.keyCode;    switch (key) {      case 32://空格-發射子彈        this.pressedSpace = true;        break;      case 37://左方向鍵        this.pressedLeft = true;        this.heldLeft = true;        this.pressedRight = false;        this.heldRight = false;        break;      case 38://上方向鍵-發射子彈        this.pressedUp = true;        break;      case 39://右方向鍵        this.pressedLeft = false;        this.heldLeft = false;        this.pressedRight = true;        this.heldRight = true;        break;      case 13://回車鍵-暫停游戲        this.pressedEnter = true;        break;    }  },  keyup: function (e) {    var key = e.keyCode;    switch (key) {      case 32:        this.pressedSpace = false;        break;      case 37:        this.heldLeft = false;        this.pressedLeft = false;        break;      case 38:        this.pressedUp = false;        break;      case 39:        this.heldRight = false;        this.pressedRight = false;        break;      case 13:        this.pressedEnter = false;        break;    }  }};

游戲邏輯

游戲對象(GAME)包含了整個游戲的邏輯,包括init(初始化)、bindEvent(綁定按鈕)、setStatus(更新游戲狀態)、play(游戲中)、stop(暫停)、end(結束)等,在此不展開描述。也包含了生成怪獸、繪制游戲元素等函數。

// 整個游戲對象var GAME = {  //一系列邏輯函數  //游戲元素函數}

1、初始化

初始化函數主要是定義飛機初始坐標、飛機移動范圍、怪獸移動范圍,以及初始化分數、怪獸數組,創建 KeyBoard 對象,只執行一次。

/**   * 初始化函數,這個函數只執行一次   * @param  {object} opts    * @return {[type]}      [description]   */init: function (opts) {    //設置opts    var opts = Object.assign({}, opts, CONFIG);//合并所有參數    this.opts = opts;    this.status = 'start';    //計算飛機對象初始坐標    this.planePosX = canvasWidth / 2 - opts.planeSize.width;    this.planePosY = canvasHeight - opts.planeSize.height - opts.canvasPadding;    //飛機極限坐標    this.planeMinX = opts.canvasPadding;    this.planeMaxX = canvasWidth - opts.canvasPadding - opts.planeSize.width;    //計算敵人移動區域    this.enemyMinX = opts.canvasPadding;    this.enemyMaxX = canvasWidth - opts.canvasPadding - opts.enemySize;    //分數設置為0    this.score = 0;    this.enemies = [];    this.keyBoard = new KeyBoard();    this.bindEvent();    this.renderLevel();  },

2、綁定按鈕事件

因為幾個游戲場景中包含開始游戲(playBtn)、重新開始(replayBtn)、下一關游戲(nextBtn)、暫停游戲繼續(stopBtn)幾個按鈕。我們需要給不同按鈕執行不同事件。

首先定義 var self = this; 的原因是 this 的用法。在 bindEvent 函數中, this 指向 GAME 對象,而在 playBtn.onclick = function () {}; 中 this 指向了 playBtn ,這顯然不是我們希望的,因為 playBtn 沒有 play() 事件,GAME 對象中才有。因此需要把GAME 對象賦值給一個變量 self ,然后才能在 playBtn.onclick = function () {}; 中調用 play() 事件。

需要注意的是 replayBtn 按鈕在闖關失敗和通關場景都有出現,因此獲取的是所有 .js-replay 的集合。然后 forEach 遍歷每個 replayBtn 按鈕,重置關卡和分數,調用 play() 事件。

bindEvent: function () {    var self = this;    var playBtn = document.querySelector('.js-play');    var replayBtn = document.querySelectorAll('.js-replay');    var nextBtn = document.querySelector('.js-next');    var stopBtn = document.querySelector('.js-stop');    // 開始游戲按鈕綁定    playBtn.onclick = function () {      self.play();    };    //重新開始游戲按鈕綁定    replayBtn.forEach(function (e) {      e.onclick = function () {        self.opts.level = 1;        self.play();        self.score = 0;        totalScoreText.innerText = self.score;      };    });    // 下一關游戲按鈕綁定    nextBtn.onclick = function () {      self.opts.level += 1;      self.play();    };    // 暫停游戲繼續按鈕綁定    stopBtn.onclick = function () {      self.setStatus('playing');      self.updateElement();    };  },

3、生成飛機

createPlane: function () {  var opts = this.opts;  this.plane = new Plane({      x: this.planePosX,      y: this.planePosY,      width: opts.planeSize.width,      height: opts.planeSize.height,      minX: this.planeMinX,      speed: opts.planeSpeed,      maxX: this.planeMaxX,      planeIcon: opts.planeIcon    });}

4、生成一組怪獸

因為怪獸都是成組出現的,每一關的怪獸數量也不同,兩個 for 循環的作用就是生成一行怪獸,根據關數(level)增加 level 行怪獸?;蛘咴黾庸肢F的速度(speed: speed + i,)來提高每一關難度等。

//生成敵人  createEnemy: function (enemyType) {    var opts = this.opts;    var level = opts.level;    var enemies = this.enemies;    var numPerLine = opts.numPerLine;    var padding = opts.canvasPadding;    var gap = opts.enemyGap;    var size = opts.enemySize;    var speed = opts.enemySpeed;    //每升級一關敵人增加一行    for (var i = 0; i < level; i++) {      for (var j = 0; j < numPerLine; j++) {      //綜合元素的參數        var initOpt = {          x: padding + j * (size + gap),          y: padding + i * (size + gap),          size: size,          speed: speed,          status: enemyType,          enemyIcon: opts.enemyIcon,          enemyBoomIcon: opts.enemyBoomIcon        };        enemies.push(new Enemy(initOpt));      }    }    return enemies;  },

5、更新怪獸

獲取怪獸數組的 x 值,判斷是否到達畫布邊界,如果到達邊界則怪獸向下移動。同時也要監聽怪獸狀態,正常狀態下的怪獸是否被擊中,爆炸狀態下的怪獸,消失的怪獸要從數組剔除,同時得分。

//更新敵人狀態  updateEnemeis: function () {    var opts = this.opts;    var plane = this.plane;    var enemies = this.enemies;    var i = enemies.length;    var isFall = false;//敵人下落    var enemiesX = getHorizontalBoundary(enemies);    if (enemiesX.minX < this.enemyMinX || enemiesX.maxX >= this.enemyMaxX) {      console.log('enemiesX.minX', enemiesX.minX);      console.log('enemiesX.maxX', enemiesX.maxX);      opts.enemyDirection = opts.enemyDirection === 'right' ? 'left' : 'right';      console.log('opts.enemyDirection', opts.enemyDirection);      isFall = true;    }    //循環更新敵人    while (i--) {      var enemy = enemies[i];      if (isFall) {        enemy.down();      }      enemy.direction(opts.enemyDirection);      switch (enemy.status) {        case 'normal':          if (plane.hasHit(enemy)) {            enemy.booming();          }          break;        case 'booming':          enemy.booming();          break;        case 'boomed':          enemies.splice(i, 1);          this.score += 1;          break;        default:          break;      }    }  },

getHorizontalBoundary 函數的作用是遍歷數組每個元素的 x 值,篩選出更大或更小的值,從而獲得數組最大和最小的 x 值。

//獲取數組橫向邊界function getHorizontalBoundary(array) {  var min, max;  array.forEach(function (item) {    if (!min && !max) {      min = item.x;      max = item.x;    } else {      if (item.x < min) {        min = item.x;      }      if (item.x > max) {        max = item.x;      }    }  });  return {    minX: min,    maxX: max  }}

6、更新鍵盤面板

按下回車鍵執行 stop() 函數,按下左鍵執行飛機左移,按下右鍵執行飛機右移,按下空格執行飛機發射子彈,為了不讓子彈連成一條直線,在這里設置 keyBoard.pressedUp 和 keyBoard.pressedSpace 為 false。

  updatePanel: function () {    var plane = this.plane;    var keyBoard = this.keyBoard;    if (keyBoard.pressedEnter) {      this.stop();      return;    }    if (keyBoard.pressedLeft || keyBoard.heldLeft) {      plane.direction('left');    }    if (keyBoard.pressedRight || keyBoard.heldRight) {      plane.direction('right');    }    if (keyBoard.pressedUp || keyBoard.pressedSpace) {      keyBoard.pressedUp = false;      keyBoard.pressedSpace = false;      plane.shoot();    }  },

7、繪制所有元素

draw: function () {    this.renderScore();    this.plane.draw();    this.enemies.forEach(function (enemy) {      //console.log('draw:this.enemy',enemy);      enemy.draw();    });  },

8、更新所有元素

首先判斷怪獸數組長度是否為 0 ,為 0 且 level 等于 totalLevel 說明通關,否則顯示下一關游戲準備畫面;如果怪獸數組 y 坐標大于飛機 y 坐標加怪獸高度,顯示游戲失敗。

canvas 動畫的原理就是不斷繪制、更新、清除畫布。

游戲暫停的原理就是阻止 requestAnimationFrame() 函數執行,但不重置元素。因此判斷 status 的狀態為 stop 時跳出函數。

  //更新所有元素狀態  updateElement: function () {    var self = this;    var opts = this.opts;    var enemies = this.enemies;    if (enemies.length === 0) {      if (opts.level === opts.totalLevel) {        this.end('all-success');      } else {        this.end('success');      }      return;    }    if (enemies[enemies.length - 1].y >= this.planePosY - opts.enemySize) {      this.end('failed');      return;    }    //清理畫布    ctx.clearRect(0, 0, canvasWidth, canvasHeight);    //繪制畫布    this.draw();    //更新元素狀態    this.updatePanel();    this.updateEnemeis();    //不斷循環updateElement    requestAnimationFrame(function () {      if(self.status === 'stop'){        return;      }else{        self.updateElement();      }    });  },

寫在最后

通過以上幾個步驟,游戲的基本功能就完成了,其他一些游戲流程控制,包括開始、結束、得分計算等在此就不敘述了。

可以優化的地方:在按住空格鍵的時候,可以連續發射子彈。但是,這時再按一下方向鍵,發現無法再發射子彈了。最好是能移動的時候,也能保持著子彈的發射。

canvas 做游戲還是比較有趣的,另外還可以把這個游戲加以擴展,改成手機版,畫布尺寸通過獲取屏幕寬高確定,鍵盤部分改成觸摸事件(touchstart、touchmove、touchend),怪獸出現方式也可以改成從屏幕頂端隨機下落,怪獸增加血量(如射擊4次才消失)等。

下載地址:https://github.com/littleyljy/shoot

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到HTML教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: av电影手机在线看 | 91看片淫黄大片欧美看国产片 | 国产精品免费看 | 亚洲一区二区在线视频 | 中文字幕在线日韩 | 精品亚洲一区二区三区 | 2021免费日韩视频网 | 色综合久久久久久久久久久 | 国产精品久久久久久久久久久久午夜 | 成人综合免费视频 | 精品一区二区三区免费爱 | 久久久麻豆 | 国产精品久久久久久久久久久久久久久 | a黄在线观看 | 亚洲最大的成人网 | 国产福利不卡一区二区三区 | 91成人免费电影 | 免费色片 | www久久艹| 欧美国产日韩在线 | 亚洲午夜久久久精品一区二区三区 | 亚洲婷婷日日综合婷婷噜噜噜 | 特一级黄色毛片 | 黑人一区 | 国内一区 | 欧美成人精品h版在线观看 久久久久久三区 | 久久精品亚洲一区二区 | 免费观看一区 | 一级免费观看 | 欧美一级aa免费毛片 | 久久艳片 | 国产精品一区在线免费观看 | 视频www| 九一传媒在线观看 | 一级黄片毛片免费看 | 亚洲精华液久久含羞草 | 精品亚洲视频在线 | 亚洲xxx视频 | 黄色片免费在线播放 | 成人在线免费观看小视频 | 毛片在线免费观看网址 |