成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

使用JavaScript和Canvas寫一個(gè)游戲框架

開發(fā) 前端
下面我們要介紹的JavaScript代碼使用面向?qū)ο蟮姆绞絹砭帉?。?duì)于沒有編寫過多少JavaScript代碼的人來說,恐怕第一眼看到它們會(huì)覺得有點(diǎn)奇怪。

4、寫一個(gè)游戲框架(一)

http://www.brighthub.com/internet/web-development/articles/40512.aspx

在知道了如何使用畫布元素之后,接下來我教大家寫一個(gè)框架,有了這個(gè)框架,我們就可以把它作為基礎(chǔ)來創(chuàng)建游戲。在這第一部分,我們會(huì)介紹前兩個(gè)文件/類。

編寫代碼之前,我們先來看一看隨后幾篇文章將致力于創(chuàng)建的示例Demo。表面上看起來,這個(gè)Demo跟第二篇文章里的那個(gè)沒啥區(qū)別,但如果你看看后臺(tái)(查看網(wǎng)頁源代碼)就會(huì)發(fā)現(xiàn),為了更方便地創(chuàng)建這個(gè)最終效果,一個(gè)凝聚不少心血的基礎(chǔ)框架已經(jīng)寫好了。

 

 

下面我們要介紹的JavaScript代碼使用面向?qū)ο蟮姆绞絹砭帉?。?duì)于沒有編寫過多少JavaScript代碼的人來說,恐怕第一眼看到它們會(huì)覺得有點(diǎn)奇怪。如果你真的不太熟悉JavaScript的面向?qū)ο缶幊?,建議通過Mozilla Developer Network的這個(gè)教程https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript來補(bǔ)補(bǔ)課。這篇教程里解釋了我們稍后會(huì)用到的一些編程技術(shù)。

從設(shè)計(jì)思想上來看,這個(gè)框架可以分成兩部分:與底層的2D引擎交互的類(用于操作畫布、控制渲染循環(huán)、處理輸入等的代碼)和用來創(chuàng)建對(duì)象以便構(gòu)成游戲的類。前者可以歸為引擎類,后者可以歸為應(yīng)用類。由于應(yīng)用類要構(gòu)建于引擎類之上,所以我們需要先來創(chuàng)建引擎類。

Main.js

如果你研究了前面例子中的代碼,就會(huì)發(fā)現(xiàn)Main.js文件中包含了不少代碼。

  1.  
  2. /** 每秒多少幀  
  3.     @type Number  
  4. */  
  5. var FPS = 30;  
  6. /** 兩幀間間隔的秒數(shù)  
  7.     @type Number  
  8. */  
  9. var SECONDS_BETWEEN_FRAMES = 1 / FPS;  
  10. /** GameObjectManager 實(shí)例的全局引用  
  11.     @type GameObjectManager  
  12. */  
  13. var g_GameObjectManager = null;  
  14. /** 應(yīng)用中用到的圖像  
  15.     @type Image  
  16. */  
  17. var g_image = new Image();  
  18. g_image.src = "jsplatformer3-smiley.jpg";  
  19.  
  20. // 將應(yīng)用的入口設(shè)置為init函數(shù)  
  21. window.onload = init;  
  22.  
  23. /**  
  24.     應(yīng)用的入口  
  25. */  
  26. function init()  
  27. {  
  28.     new GameObjectManager().startupGameObjectManager();  
  29. }  

首先是定義全局變量的代碼。然后,跟以前一樣,當(dāng)頁面加載完畢后立即運(yùn)行init函數(shù)。在init函數(shù)里,創(chuàng)建GameObjectManager類的實(shí)例。

這里在GameObjectManager類的實(shí)例上調(diào)用了startupGameObjectManager函數(shù)。這篇文章以及后面的幾篇文章還將多次提到幾個(gè)命名上具有startupClassName形式的函數(shù)。這些函數(shù)實(shí)際上充當(dāng)了各自類的構(gòu)造函數(shù),這樣做有兩個(gè)原因。

首先,JavaScript不支持函數(shù)重載(至少不容易實(shí)現(xiàn))。如果你想讓一個(gè)類有多個(gè)構(gòu)造函數(shù),那么這就成了問題。而通過把構(gòu)造工作分配給另一組函數(shù)(如startupClassName1、startupClassName2),就可以比較容易地定義構(gòu)造類的不同方式了。

第二個(gè)原因(很大程度上也是個(gè)人的問題)是我經(jīng)常會(huì)在構(gòu)造函數(shù)中引用尚未定義的變量。這可能是我使用C++、Java和C#這些語言落下的毛病,在這些語言里,類變量在源代碼中的位置對(duì)其在構(gòu)造函數(shù)中的可見性沒有影響。拿下面這個(gè)C#類為例:

  1. class Test  
  2. {  
  3.     public void Test() {this.a = 5;}  
  4.     public int a;  
  5. }  

這些代碼是合乎語法的,可以正常工作。下面再看看JavaScript中一個(gè)相同的例子:

  1. function Test()  
  2. {  
  3.     this.a = 5;  
  4.     var a;  
  5. }  

這段代碼的問題在于,局部變量a在我們把數(shù)值5賦給它的時(shí)候還不存在。只有運(yùn)行到var a;這一行,變量a才存在。盡管這個(gè)例子有點(diǎn)故意編排的意味,但的確能夠說明我所遇到的問題。通過把類的創(chuàng)建放到一個(gè)類似startupClassName這樣的函數(shù)中完成,并且在構(gòu)造函數(shù)中定義(但不初始化)局部變量,然后當(dāng)我在這些構(gòu)建函數(shù)中引用相應(yīng)的局部變量時(shí),就能夠確保它們一定是存在的。

#p#

GameObjectManager.js

  1.  
  2. /**  
  3.     管理游戲中所有對(duì)象的管理器  
  4.     @class  
  5. */  
  6. function GameObjectManager()  
  7. {  
  8.     /** 保存游戲中對(duì)象的數(shù)組  
  9.         @type Arary  
  10.     */  
  11.     this.gameObjects = new Array();  
  12.     /** 上一次幀被渲染的時(shí)間  
  13.         @type Date  
  14.     */  
  15.     this.lastFrame = new Date().getTime();  
  16.     /** x軸的全局滾動(dòng)值  
  17.         @type Number  
  18.     */  
  19.     this.xScroll = 0;  
  20.     /** y軸的全局滾動(dòng)值  
  21.         @type Number  
  22.     */  
  23.     this.yScroll = 0;  
  24.     /** 對(duì)ApplicationManager實(shí)例的引用  
  25.         @type ApplicationManager  
  26.     */  
  27.     this.applicationManager = null;  
  28.     /** 對(duì)畫布元素的引用  
  29.         @type HTMLCanvasElement  
  30.     */  
  31.     this.canvas = null;  
  32.     /** 對(duì)畫布元素2D上下文的引用  
  33.         @type CanvasRenderingContext2D  
  34.     */  
  35.     this.context2D = null;  
  36.     /** 對(duì)內(nèi)存中用作后臺(tái)緩沖區(qū)的畫布的引用  
  37.         @type HTMLCanvasElement  
  38.     */  
  39.     this.backBuffer = null;  
  40.     /** 對(duì)后臺(tái)緩沖畫布的2D上下文的引用  
  41.         @type CanvasRenderingContext2D  
  42.     */  
  43.     this.backBufferContext2D = null;  
  44.  
  45.     /**  
  46.         初始化這個(gè)對(duì)象  
  47.         @return A reference to the initialised object  
  48.     */  
  49.     this.startupGameObjectManager = function()  
  50.     {  
  51.         // 設(shè)置引用this對(duì)象的全局指針  
  52.         g_GameObjectManager = this;  
  53.  
  54.         // 取得畫布元素及其2D上下文的引用  
  55.         this.canvas = document.getElementById('canvas');  
  56.         thisthis.context2D = this.canvas.getContext('2d');  
  57.         this.backBuffer = document.createElement('canvas');  
  58.         thisthis.backBuffer.width = this.canvas.width;  
  59.         thisthis.backBuffer.height = this.canvas.height;  
  60.         thisthis.backBufferContext2D = this.backBuffer.getContext('2d');  
  61.  
  62.         // 創(chuàng)建一個(gè)新的ApplicationManager  
  63.         this.applicationManager = new ApplicationManager().startupApplicationManager();  
  64.  
  65.         // 使用setInterval來調(diào)用draw函數(shù)  
  66.         setInterval(function(){g_GameObjectManager.draw();}, SECONDS_BETWEEN_FRAMES);  
  67.  
  68.         return this;  
  69.     }  
  70.  
  71.     /**  
  72.         渲染循環(huán)  
  73.     */  
  74.     this.draw = function ()  
  75.     {  
  76.         // 計(jì)算從上一幀到現(xiàn)在的時(shí)間  
  77.         var thisFrame = new Date().getTime();  
  78.         var dt = (thisFrame - this.lastFrame)/1000;  
  79.         this.lastFrame = thisFrame;  
  80.  
  81.         // 清理繪制上下文  
  82.         this.backBufferContext2D.clearRect(0, 0, this.backBuffer.width, this.backBuffer.height);  
  83.         this.context2D.clearRect(0, 0, this.canvas.width, this.canvas.height);  
  84.  
  85.         // 首先更新所有游戲?qū)ο? 
  86.         for (x in this.gameObjects)  
  87.         {  
  88.             if (this.gameObjects[x].update)  
  89.             {  
  90.                 this.gameObjects[x].update(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  91.             }  
  92.         }  
  93.  
  94.         // 然后繪制所有游戲?qū)ο? 
  95.         for (x in this.gameObjects)  
  96.         {  
  97.             if (this.gameObjects[x].draw)  
  98.             {  
  99.                 this.gameObjects[x].draw(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  100.             }  
  101.         }  
  102.  
  103.         // 將后臺(tái)緩沖復(fù)制到當(dāng)前顯示的畫布  
  104.         this.context2D.drawImage(this.backBuffer, 0, 0);  
  105.     };  
  106.  
  107.     /**  
  108.         向gameObjects集合中添加一個(gè)GameObject  
  109.         @param gameObject The object to add  
  110.     */  
  111.     this.addGameObject = function(gameObject)  
  112.     {  
  113.         this.gameObjects.push(gameObject);  
  114.         this.gameObjects.sort(function(a,b){return a.zOrder - b.zOrder;})  
  115.     };  
  116.  
  117.     /**  
  118.         從gameObjects集合中刪除一個(gè)GameObject  
  119.         @param gameObject The object to remove  
  120.     */  
  121.     this.removeGameObject = function(gameObject)  
  122.     {  
  123.         this.gameObjects.removeObject(gameObject);  
  124.     }  
  125. }  

首先看一看GameObjectManager類。GameObjectManager是一個(gè)引擎類,用于管理畫布的繪制操作,還負(fù)責(zé)分派GameObject類(下一篇文章里介紹)的事件。

GameObjectManager類的startupGameObjectManager函數(shù)的代碼如下:

  1.  
  2. /**  
  3.     初始化這個(gè)對(duì)象  
  4.     @return A reference to the initialised object  
  5. */  
  6. this.startupGameObjectManager = function()  
  7. {  
  8.     // 設(shè)置引用this對(duì)象的全局指針  
  9.     g_GameObjectManager = this;  
  10.  
  11.     // 取得畫布元素及其2D上下文的引用  
  12.     this.canvas = document.getElementById('canvas');  
  13.     thisthis.context2D = this.canvas.getContext('2d');  
  14.     this.backBuffer = document.createElement('canvas');  
  15.     thisthis.backBuffer.width = this.canvas.width;  
  16.     thisthis.backBuffer.height = this.canvas.height;  
  17.     thisthis.backBufferContext2D = this.backBuffer.getContext('2d');  
  18.  
  19.     // 創(chuàng)建一個(gè)新的ApplicationManager  
  20.     this.applicationManager = new ApplicationManager().startupApplicationManager();  
  21.  
  22.     // 使用setInterval來調(diào)用draw函數(shù)  
  23.     setInterval(function(){g_GameObjectManager.draw();}, SECONDS_BETWEEN_FRAMES);  
  24.  
  25.     return this;  
  26. }  

前面已經(jīng)說過,我們會(huì)把每個(gè)類的初始化工作放在startupClassName函數(shù)中來做。因此,GameObjectManager類將由startupGameObjectManager函數(shù)進(jìn)行初始化。

而引用這個(gè)GameObjectManager實(shí)例的全局變量g_GameObjectManager經(jīng)過重新賦值,指向了這個(gè)新實(shí)例。

  1. // 設(shè)置引用this對(duì)象的全局指針  
  2. g_GameObjectManager = this;  

對(duì)畫布元素及其繪圖上下文的引用也同樣保存起來:

  1. // 取得畫布元素及其2D上下文的引用  
  2. this.canvas = document.getElementById('canvas');  
  3. thisthis.context2D = this.canvas.getContext('2d');  

在前面的例子中,所有繪圖操作都是直接在畫布元素上完成的。這種風(fēng)格的渲染一般稱為單緩沖渲染。在此,我們要使用一種叫做雙緩沖渲染的技術(shù):任意游戲?qū)ο蟮乃欣L制操作,都將在一個(gè)內(nèi)存中的附加畫布元素(后臺(tái)緩沖)上完成,完成后再通過一次操作把它復(fù)制到網(wǎng)頁上的畫布元素(前臺(tái)緩沖)。

雙緩沖技術(shù)(http://www.brighthub.com/internet/web-development/articles/11012.aspx)通常用于減少畫面抖動(dòng)。我自己在測(cè)試的時(shí)候從沒發(fā)現(xiàn)直接向畫布元素上繪制有抖動(dòng)現(xiàn)象,但我在網(wǎng)上的確聽別人念叨過,使用單緩沖渲染會(huì)導(dǎo)致某些瀏覽器在渲染時(shí)發(fā)生抖動(dòng)。

不管怎么說,雙緩沖還是能夠避免最終用戶看到每個(gè)游戲?qū)ο笤诶L制過程中最后一幀的組合過程。在通過JavaScript執(zhí)行某些復(fù)雜繪制操作時(shí)(例如透明度、反鋸齒及可編程紋理),這種情況是完全可能發(fā)生的。

使用附加緩沖技術(shù)占用的內(nèi)存非常少,多執(zhí)行一次圖像復(fù)制操作(把后臺(tái)緩沖繪制到前臺(tái)緩沖)導(dǎo)致的性能損失也可以忽略不計(jì),可以說實(shí)現(xiàn)雙緩沖系統(tǒng)沒有什么缺點(diǎn)。

如果將在HTML頁面中定義的畫布元素作為前臺(tái)緩沖,那就需要再創(chuàng)建一個(gè)畫布來充當(dāng)后臺(tái)緩沖。為此,我們使用了document.createElement函數(shù)在內(nèi)存里創(chuàng)建了一個(gè)畫布元素,把它用作后臺(tái)緩沖。

  1. this.backBuffer = document.createElement('canvas');  
  2. thisthis.backBuffer.width = this.canvas.width;  
  3. thisthis.backBuffer.height = this.canvas.height;  
  4. thisthis.backBufferContext2D = this.backBuffer.getContext('2d');  

接下來,我們創(chuàng)建了ApplicationManager類的一個(gè)新實(shí)例,并調(diào)用startupApplicationManager來初始化它。這個(gè)ApplicationManager類將在下一篇文章中介紹。

  1. // 創(chuàng)建一個(gè)新的ApplicationManager  
  2. this.applicationManager = new ApplicationManager().startupApplicationManager();  

最后,使用setInterval函數(shù)重復(fù)調(diào)用draw函數(shù),這個(gè)函數(shù)是渲染循環(huán)的核心所在。

  1. // 使用setInterval來調(diào)用draw函數(shù)  
  2. setInterval(function(){g_GameObjectManager.draw();}, SECONDS_BETWEEN_FRAMES);  

#p#

下面來看一看draw函數(shù)。

  1. /**  
  2.     渲染循環(huán)  
  3. */  
  4. this.draw = function ()  
  5. {  
  6.     // 計(jì)算從上一幀到現(xiàn)在的時(shí)間  
  7.     var thisFrame = new Date().getTime();  
  8.     var dt = (thisFrame - this.lastFrame)/1000;  
  9.     this.lastFrame = thisFrame;  
  10.  
  11.     // 清理繪制上下文  
  12.     this.backBufferContext2D.clearRect(0, 0, this.backBuffer.width, this.backBuffer.height);  
  13.     this.context2D.clearRect(0, 0, this.canvas.width, this.canvas.height);  
  14.  
  15.     // 首先更新所有游戲?qū)ο? 
  16.     for (x in this.gameObjects)  
  17.     {  
  18.         if (this.gameObjects[x].update)  
  19.         {  
  20.             this.gameObjects[x].update(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  21.         }  
  22.     }  
  23.  
  24.     // 然后繪制所有游戲?qū)ο? 
  25.     for (x in this.gameObjects)  
  26.     {  
  27.         if (this.gameObjects[x].draw)  
  28.         {  
  29.             this.gameObjects[x].draw(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  30.         }  
  31.     }  
  32.  
  33.     // 將后臺(tái)緩沖復(fù)制到當(dāng)前顯示的畫布  
  34.     this.context2D.drawImage(this.backBuffer, 0, 0);  
  35. };  

這個(gè)draw函數(shù)就是所有渲染循環(huán)的核心。在前面的例子中,渲染循環(huán)的函數(shù)會(huì)直接修改要繪制到屏幕上的對(duì)象(笑臉)。如果只需繪制一個(gè)對(duì)象,這樣做沒有問題。但是,一個(gè)游戲要由幾十個(gè)單獨(dú)的對(duì)象組成,所以這個(gè)draw函數(shù)并沒有直接在渲染循環(huán)中直接處理要繪制的對(duì)象,而是維護(hù)了一個(gè)保存著這些對(duì)象的數(shù)組,讓這些對(duì)象自己來更新和繪制自己。

首先,計(jì)算自上一幀渲染所經(jīng)過的時(shí)間。即便我們?cè)诖a里寫了每秒鐘調(diào)用30次draw函數(shù),但誰也無法保證事實(shí)如此。通過計(jì)算自上一幀渲染所經(jīng)過的時(shí)間,可以做到盡可能讓游戲的執(zhí)行與幀速率無關(guān)。

  1. // 計(jì)算從上一幀到現(xiàn)在的時(shí)間  
  2. var thisFrame = new Date().getTime();  
  3. var dt = (thisFrame - this.lastFrame)/1000;  
  4. this.lastFrame = thisFrame;  

接著清理繪制上下文。

  1. // 清理繪制上下文  
  2. this.backBufferContext2D.clearRect(0, 0, this.backBuffer.width, this.backBuffer.height);  
  3. this.context2D.clearRect(0, 0, this.canvas.width, this.canvas.height);  

然后,就是調(diào)用游戲?qū)ο?這些對(duì)象是由GameObject類定義的,下一篇文章將介紹該類)自己的更新(update)和繪制(draw)方法。注意,這兩個(gè)方法是可選的(這也是我們?cè)谡{(diào)用它們之前先檢查它們是否存在的原因),但差不多每一個(gè)對(duì)象都需要更新和繪制自已。

  1. // 首先更新所有游戲?qū)ο? 
  2. for (x in this.gameObjects)  
  3. {  
  4.     if (this.gameObjects[x].update)  
  5.     {  
  6.         this.gameObjects[x].update(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  7.     }  
  8. }  
  9.  
  10. // 然后繪制所有游戲?qū)ο? 
  11. for (x in this.gameObjects)  
  12. {  
  13.     if (this.gameObjects[x].draw)  
  14.     {  
  15.         this.gameObjects[x].draw(dt, this.backBufferContext2D, this.xScroll, this.yScroll);  
  16.     }  
  17. }  

最后,把后臺(tái)緩沖復(fù)制到前臺(tái)緩沖,最終用戶就可以看到下一幀了。

  1. // 將后臺(tái)緩沖復(fù)制到當(dāng)前顯示的畫布  
  2. this.context2D.drawImage(this.backBuffer, 0, 0);  

理解了draw函數(shù),下面再分別講一講addGameObject和removeGameObject函數(shù)。

  1. /**  
  2.     向gameObjects集合中添加一個(gè)GameObject  
  3.     @param gameObject The object to add  
  4. */  
  5. this.addGameObject = function(gameObject)  
  6. {  
  7.     this.gameObjects.push(gameObject);  
  8.     this.gameObjects.sort(function(a,b){return a.zOrder - b.zOrder;})  
  9. };  
  10.  
  11. /**  
  12.     從gameObjects集合中刪除一個(gè)GameObject  
  13.     @param gameObject The object to remove  
  14. */  
  15. this.removeGameObject = function(gameObject)  
  16. {  
  17.     this.gameObjects.removeObject(gameObject);  
  18. }  

利用addGameObject和removeGameObject(在Utils.js文件里通過擴(kuò)展Array.prototype添加)函數(shù),可以在GameObjectManager所維護(hù)的GameObject集合(即gameObjects變量)中添加和刪除游戲?qū)ο蟆?/p>

GameObjectManager類是我們這個(gè)游戲框架中最復(fù)雜的一個(gè)類。在下一篇文章中,我們會(huì)講解游戲框架的另外幾個(gè)類:GameObject、VisualGameObject、Bounce和ApplicationManager。

好了,現(xiàn)在放松一下,看一看Demo吧。

原文:http://www.brighthub.com/content/matthewcaspersonshubfoliohasmoved.aspx

譯文:http://www.cn-cuckoo.com/2011/08/14/game-development-with-javascript-and-the-canvas-element-3-2604.html

【編輯推薦】

  1. 使用JavaScript和Canvas開發(fā)游戲之認(rèn)識(shí)Canvas
  2. HTML 5 Canvas(畫布)教程之圖像處理
  3. HTML 5新特性Canvas入門秘籍
  4. 15個(gè)不可思議的HTML 5 Canvas應(yīng)用欣賞
  5. 使用JavaScript和Canvas開發(fā)游戲之使用Canvas
責(zé)任編輯:陳貽新 來源: 李松峰博客
相關(guān)推薦

2011-08-12 08:56:31

JavaScript

2011-08-11 09:16:50

JavaScript

2015-06-29 11:30:07

JavaScript小烏龜推箱子

2022-08-10 18:14:49

國際象棋游戲位字段C語言

2019-05-14 12:30:07

PythonPygame游戲框架

2011-11-03 09:13:27

JavaScript

2016-11-29 13:31:52

JavaScriptsetTimeout定時(shí)執(zhí)行

2011-10-21 09:10:12

JavaScript

2021-03-30 05:58:01

JavascriptCss3轉(zhuǎn)盤小游戲

2014-02-14 09:37:01

JavascriptDOM

2013-01-14 09:44:58

JavaScriptJSJS框架

2021-02-05 16:03:48

JavaScript游戲?qū)W習(xí)前端

2020-11-30 06:20:13

javascript

2017-06-08 15:53:38

PythonWeb框架

2015-06-02 04:13:23

Python乒乓球類游戲

2021-09-08 08:36:50

ncursesLinux猜謎游戲

2022-09-01 11:48:45

JavaScript框架

2024-01-15 00:35:23

JavaScript框架HTML

2023-03-01 10:19:23

2021-04-13 06:35:13

Elixir語言編程語言軟件開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产91一区二区三区 | 性一交一乱一透一a级 | 99精品免费视频 | 国产成人99久久亚洲综合精品 | 婷婷色在线播放 | 亚洲三级av | 日日摸日日碰夜夜爽2015电影 | 天天综合国产 | 亚洲视频在线观看 | 日韩欧美在线视频 | 成人毛片在线观看 | 在线观看国产91 | 久久久高清 | 亚洲一区国产 | 国产精品久久久久久久久久免费看 | www.久久.com| 国产日韩欧美一区二区 | 日韩精品免费 | 国产美女久久 | 日韩在线电影 | 日本高清中文字幕 | 国产一级免费视频 | 精品不卡 | 亚洲午夜av久久乱码 | 欧洲在线视频 | 天天人人精品 | 男女免费在线观看视频 | 久久国产激情视频 | 久久久91精品国产一区二区三区 | 福利视频一区二区 | 国产分类视频 | 中文字幕免费视频 | 国产精品99久久久久久动医院 | 久久久久久亚洲精品 | 久久精品小短片 | 久久久国产一区二区三区 | 艹逼网 | 欧美激情精品久久久久久变态 | 国产片淫级awww | 亚洲国产精品久久 | 亚洲成色777777在线观看影院 |