Web3D編程入門總結——面向對象的基礎Web3D框架

来源:http://www.cnblogs.com/ljzc002/archive/2016/01/29/5156850.html
-Advertisement-
Play Games

本篇主要通過分析Tony Parisi的sim.js庫(原版代碼托管於:https://github.com/tparisi/WebGLBook/tree/master/sim),總結基礎Web3D框架的編寫方法。在上一篇的基礎上,要求讀者具有簡短英文閱讀或者查字典的能力。 限於水平和時間,本文難免


本篇主要通過分析Tony Parisi的sim.js庫(原版代碼托管於:https://github.com/tparisi/WebGLBook/tree/master/sim),總結基礎Web3D框架的編寫方法。在上一篇的基礎上,要求讀者具有簡短英文閱讀或者查字典的能力。

限於水平和時間,本文難免出現錯誤與遺漏,您在閱讀過程中如果遇到錯誤或者疑問請在評論區中指出,我將儘快回覆。

為提高JavaScript編程效率,建議使用WebStorm工具進行網頁程式編寫,WebStorm官網:http://www.jetbrains.com/webstorm/。 

上一篇中,我們把程式的所有文件放在同一個目錄下,這種文件組織方式適用於簡單的功能測試,但當文件數量更多時則會變得混亂不堪,我們在編寫一般規模的Web3D程式時可參考下圖進行文件組織:

 

該組織方式把JavaScript文件分為LIB和PAGE兩部分,LIB保存一般不做修改的庫文件,PAGE保存為特定頁面編寫的js文件,如果頁面js較多可在PAGE中再分離出子文件夾。

MODEL下的每個文件夾都是一個JSON類型的模型,可以看到其中有保存紋理信息的jpg文件和保存頂點數組、法線向量、紋理坐標的文本文件。

上一篇的代碼中,我們把所有需要多次調用的對象設為了全局變數和全局函數,當代碼量增多時這種“全局管理”方式將面臨巨大的挑戰,隨然我們可以用規範的變數命名或者變數數組來儘可能避免變數名重覆,但全局管理方式仍缺少對變數間關係的描述方法,這時使用“面向對象”的變數管理方法似乎是唯一的選擇。

下麵進入正題:

  1 //代碼截取自https://github.com/tparisi/WebGLBook/tree/master/sim,在那裡Tony Parisi的Sim庫依照舊版Three.js庫編寫,為了使用新版本Three.js庫我對Sim.js進行了部分修改,修改點附近以“@@”標記
  2 // Sim.js - A Simple Simulator for WebGL (based on Three.js)
  3 //Sim.js是一個基於Three.js的WebGL簡單框架
  4 Sim = {};//Sim是一個自包含對象,庫中的其他變數和函數都是這個自包含對象的屬性,可以在庫的外部通過“Sim.”的方式調用庫內的方法。
  5 
  6 // Sim.Publisher - base class for event publishers
  7 //Publish/Subscribe消息通信,用來優化多個對象之間的消息傳遞,事實上Tony Parisi的WebGL著作里並沒有真正使用這種消息傳遞方法,關於Publish/Subscribe的簡單常式可以參考:http://www.mamicode.com/info-detail-502782.html
  8 Sim.Publisher = function() {
  9     this.messageTypes = {};
 10 }
 11 
 12 Sim.Publisher.prototype.subscribe = function(message, subscriber, callback) {
 13     var subscribers = this.messageTypes[message];
 14     if (subscribers)
 15     {
 16         if (this.findSubscriber(subscribers, subscriber) != -1)
 17         {
 18             return;
 19         }
 20     }
 21     else
 22     {
 23         subscribers = [];
 24         this.messageTypes[message] = subscribers;
 25     }
 26 
 27     subscribers.push({ subscriber : subscriber, callback : callback });
 28 }
 29 
 30 Sim.Publisher.prototype.unsubscribe =  function(message, subscriber, callback) {
 31     if (subscriber)
 32     {
 33         var subscribers = this.messageTypes[message];
 34 
 35         if (subscribers)
 36         {
 37             var i = this.findSubscriber(subscribers, subscriber, callback);
 38             if (i != -1)
 39             {
 40                 this.messageTypes[message].splice(i, 1);
 41             }
 42         }
 43     }
 44     else
 45     {
 46         delete this.messageTypes[message];
 47     }
 48 }
 49 
 50 Sim.Publisher.prototype.publish = function(message) {
 51     var subscribers = this.messageTypes[message];
 52 
 53     if (subscribers)
 54     {
 55         for (var i = 0; i < subscribers.length; i++)
 56         {
 57             var args = [];
 58             for (var j = 0; j < arguments.length - 1; j++)
 59             {
 60                 args.push(arguments[j + 1]);
 61             }
 62             subscribers[i].callback.apply(subscribers[i].subscriber, args);
 63         }
 64     }
 65 }
 66 
 67 Sim.Publisher.prototype.findSubscriber = function (subscribers, subscriber) {
 68     for (var i = 0; i < subscribers.length; i++)
 69     {
 70         if (subscribers[i] == subscriber)
 71         {
 72             return i;
 73         }
 74     }
 75     
 76     return -1;
 77 }
 78 
 79 // Sim.App - application class (singleton)
 80 //Sim.App屬性對“繪製環境”的封裝(這裡認為一個canvas里只有一個繪製環境)
 81 Sim.App = function()
 82 {
 83     Sim.Publisher.call(this);
 84     //call表示this(Sim.App)繼承自Sim.Publisher,意指在Sim.App的上下文環境使用Sim.Publisher的“構造方法”,也就是使用Sim.App與Sim.Publisher重疊的屬性(這裡沒有)執行了this.messageTypes = {};語句,為App對象建立了消息一個隊列。
 85     
 86     this.renderer = null;
 87     this.scene = null;
 88     this.camera = null;
 89     this.objects = [];
 90     //可見App對象包含了canvas的上下文、與顯卡的交互介面、相機設置、物體數組
 91 }
 92 
 93 Sim.App.prototype = new Sim.Publisher;
 94 //prototype表示Sim.App擴展自new Sim.Publisher,當調用Sim.App中的某個未定義的方法時,編譯器會嘗試到prototype中去尋找,如App.subscribe
 95 //prototype.init表示使用init方法對Sim.App進行原型拓展,這樣所有的var myApp=new Sim.App都會自動具有init方法(找不到時去prototype中找);這與"Sim.App.init"是不同的,如果後著的init不在App的“構造方法”中定義,myApp是不會具有init方法的。
 96 Sim.App.prototype.init = function(param)//繪圖環境初始化
 97 {
 98     param = param || {};    
 99     var container = param.container;
100     var canvas = param.canvas;
101     
102     // Create the Three.js renderer, add it to our div
103     //@@這一段是我自己改的,加入了沒有顯卡時的軟體渲染選擇,可惜CanvasRenderer只支持部分的Three.js功能,並且沒有找到去除圖元邊線的方法。
104     
105     function webglAvailable()//是否可用webgl
106     {
107         try{
108             var canvas=document.createElement("canvas");
109             return !!(window.WebGLRenderingContext
110             &&(canvas.getContext("webgl")||canvas.getContext("experimental-webgl"))
111             );
112         }catch(e){
113             return false;
114         }
115     }
116     if(webglAvailable()){
117         var renderer=new THREE.WebGLRenderer({ antialias: true, canvas: canvas });
118     }else{
119         var renderer=new THREE.CanvasRenderer({ antialias: true, canvas: canvas });//對於支持html5但不支持webgl的情況,使用更慢一些的2Dcanvas來軟體實現webgl的效果
120     }
121     //var renderer = new THREE.WebGLRenderer( { antialias: true, canvas: canvas } );
122     //@@
123     
124     renderer.setClearColor( 0xffffff );//@@舊版本中這個是預設的
125     renderer.setSize(container.offsetWidth, container.offsetHeight);
126     container.appendChild( renderer.domElement );
127     container.onfocus=function(){
128         renderer.domElement.focus();//@@保持焦點!!
129     }
130     //在部分瀏覽器中canvas不具備保持焦點的能力,點擊canvas時焦點會被設置在外面的container上,影響交互效果
131     
132     // Create a new Three.js scene
133     var scene = new THREE.Scene();
134     scene.add( new THREE.AmbientLight( 0x505050 ) );
135     scene.data = this;
136 
137     // Put in a camera at a good default location
138     camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 10000 );
139     camera.position.set( 0, 0, 3.3333 );
140 
141     scene.add(camera);
142     
143     // Create a root object to contain all other scene objects
144     //建立了一個“根物體”,來存放場景中的其他物體,也就是根物體移動時所有其他物體會和它一同移動
145     var root = new THREE.Object3D();
146     scene.add(root);
147     
148     // Create a projector to handle picking
149     //建立一個“投影器”來處理三維空間中的點選,@@新版本中去掉了這個屬性,這裡的定義是多餘的
150     var projector = new THREE.Projector();
151     
152     // Save away a few things
153     //把上面的屬性設為App對象的“公有”屬性,var則是App對象的“私有”屬性
154     this.container = container;
155     this.renderer = renderer;
156     this.scene = scene;
157     this.camera = camera;
158     this.projector = projector;
159     this.root = root;
160     
161     // Set up event handlers
162     //啟動事件響應功能
163     this.initMouse();
164     this.initKeyboard();
165     this.addDomHandlers();
166 }
167 
168 //Core run loop
169 //核心迴圈
170 Sim.App.prototype.run = function()
171 {
172     this.update();
173     this.renderer.render( this.scene, this.camera );
174     var that = this;//之所以使用that是為了保存此時的this狀態,requestAnimationFrame會在“瀏覽器認為合適”的時候重調,而那時的“this”可能已經發生變化了。
175     //requestAnimationFrame(function() { that.run(); });
176     requestAnimFrame(function() { that.run(); });//@@換用了另一個幀動畫庫
177 }
178 
179 // Update method - called once per tick
180 //場景更新方法,這裡的代碼邏輯運行在瀏覽器端,是CPU資源的主要消耗者
181 Sim.App.prototype.update = function()
182 {
183     var i, len;
184     len = this.objects.length;
185     for (i = 0; i < len; i++)
186     {//將App的update轉化為其所包含的objects的update
187         this.objects[i].update();
188     }
189 }
190 
191 // Add/remove objects
192 //在場景中添加或刪除一個物體
193 //添加
194 Sim.App.prototype.addObject = function(obj)
195 {
196     this.objects.push(obj);//將物體對象添加到前面建立的物體數組裡
197 
198     // If this is a renderable object, add it to the root scene
199     //Three.js對於場景中object3D類型的對象提供了“parent/children ”式的關聯鏈,Sim.js封裝了這一關聯
200     if (obj.object3D)
201     {
202         this.root.add(obj.object3D);
203     }
204 }
205 //刪除
206 Sim.App.prototype.removeObject = function(obj)
207 {
208     var index = this.objects.indexOf(obj);
209     if (index != -1)
210     {
211         this.objects.splice(index, 1);
212         // If this is a renderable object, remove it from the root scene
213         
214         if (obj.object3D)
215         {
216             this.root.remove(obj.object3D);
217         }
218     }
219 }
220 
221 // Event handling
222 //事件處理
223 //初始化滑鼠響應
224 Sim.App.prototype.initMouse = function()
225 {
226     var dom = this.renderer.domElement;//取得canvas
227     
228     //添加監聽
229     var that = this;
230     dom.addEventListener( 'mousemove', 
231             function(e) { that.onDocumentMouseMove(e); }, false );
232     dom.addEventListener( 'mousedown', 
233             function(e) { that.onDocumentMouseDown(e); }, false );
234     dom.addEventListener( 'mouseup', 
235             function(e) { that.onDocumentMouseUp(e); }, false );
236     
237     //中鍵滾動
238     $(dom).mousewheel(
239             function(e, delta) {
240                 that.onDocumentMouseScroll(e, delta);
241             }
242         );
243     
244     //滑鼠懸停的物體
245     this.overObject = null;
246     //被點擊到的物體
247     this.clickedObject = null;
248 }
249 //初始化鍵盤響應
250 Sim.App.prototype.initKeyboard = function()
251 {
252     var dom = this.renderer.domElement;
253     
254     var that = this;
255     dom.addEventListener( 'keydown', 
256             function(e) { that.onKeyDown(e); }, false );
257     dom.addEventListener( 'keyup', 
258             function(e) { that.onKeyUp(e); }, false );
259     dom.addEventListener( 'keypress', 
260             function(e) { that.onKeyPress(e); }, false );
261 
262     // so it can take focus
263     //這樣設置之後canvas可以通過Tab鍵獲得焦點,@@但這個設置並不完美,仍需要修改
264     dom.setAttribute("tabindex", 1);
265     dom.style.outline='none';
266     dom.focus();
267 }
268 
269 Sim.App.prototype.addDomHandlers = function()
270 {
271     var that = this;
272     //監聽瀏覽器視窗大小的變化
273     window.addEventListener( 'resize', function(event) { that.onWindowResize(event); }, false );
274 }
275 
276 //如果監聽到滑鼠移動
277 Sim.App.prototype.onDocumentMouseMove = function(event)
278 {
279     event.preventDefault();//阻止瀏覽器的預設響應
280     
281     if (this.clickedObject && this.clickedObject.handleMouseMove)
282     {//如果已經有選中的物體,並且被選中的物體具有自己的handleMouseMove方法
283         var hitpoint = null, hitnormal = null;//三維空間中的“點擊點”和“點擊法線”(滑鼠在3D物體上的點擊方向)設為空
284         var intersected = this.objectFromMouse(event.pageX, event.pageY);
285         //在三維空間中通過瀏覽器中的二維坐標,找到滑鼠所在的物體,稍後詳細分析該方法
286         if (intersected.object == this.clickedObject)
287         {//如果滑鼠所在的物體確實是被選中的物體,
288             hitpoint = intersected.point;
289             hitnormal = intersected.normal;
290         }
291         this.clickedObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal);
292         //執行這個被選中的物體的滑鼠移動方法,比如拖拽變形之類
293     }
294     else
295     {//如果沒有被選中的物體
296         var handled = false;
297         
298         var oldObj = this.overObject;//暫存舊的“懸停物體”
299         var intersected = this.objectFromMouse(event.pageX, event.pageY);
300         this.overObject = intersected.object;//將懸停物體設為滑鼠所在的物體
301     
302         if (this.overObject != oldObj)//如果這是一個新物體,也就是說滑鼠從一個物體上移到另一物體上
303         {
304             if (oldObj)
305             {//如果存在舊的物體,則要觸發舊物體的“滑鼠移出”事件
306                 this.container.style.cursor = 'auto';//取巧用CSS來處理游標變化,是2D網頁和3Dcanvas的結合運用
307                 
308                 if (oldObj.handleMouseOut)
309                 {
310                     oldObj.handleMouseOut(event.pageX, event.pageY);
311                 }
312             }
313     
314             if (this.overObject)
315             {
316                 if (this.overObject.overCursor)
317                 {
318                     this.container.style.cursor = this.overObject.overCursor;//游標設置
319                 }
320                 
321                 if (this.overObject.handleMouseOver)
322                 {
323                     this.overObject.handleMouseOver(event.pageX, event.pageY);
324                 }
325             }
326             
327             handled = true;//表示物體的handleMouseOver執行完畢
328         }
329     
330         if (!handled && this.handleMouseMove)
331         {
332             this.handleMouseMove(event.pageX, event.pageY);
333             //如果物體沒有執行handleMouseOver,且環境(App)能夠響應handleMouseOver,則執行環境的滑鼠移動響應,在應用中可體現為移動視角之類
334         }
335     }
336 }
337 //滑鼠按下
338 Sim.App.prototype.onDocumentMouseDown = function(event)
339 {
340     event.preventDefault();
341         
342     var handled = false;
343 
344     var intersected = this.objectFromMouse(event.pageX, event.pageY);
345     if (intersected.object)
346     {
347         if (intersected.object.handleMouseDown)
348         {
349             intersected.object.handleMouseDown(event.pageX, event.pageY, intersected.point, intersected.normal);
350             this.clickedObject = intersected.object;
351             handled = true;
352         }
353     }
354     
355     if (!handled && this.handleMouseDown)
356     {
357         this.handleMouseDown(event.pageX, event.pageY);
358     }
359 }
360 
361 Sim.App.prototype.onDocumentMouseUp = function(event)
362 {
363     event.preventDefault();
364     
365     var handled = false;
366     
367     var intersected = this.objectFromMouse(event.pageX, event.pageY);
368     if (intersected.object)
369     {
370         if (intersected.object.handleMouseUp)
371         {
372             intersected.object.handleMouseUp(event.pageX, event.pageY, intersected.point, intersected.normal);
373             handled = true;
374         }
375     }
376     
377     if (!handled && this.handleMouseUp)
378     {
379         this.handleMouseUp(event.pageX, event.pageY);
380     }
381     
382     this.clickedObject = null;
383 }
384 
385 Sim.App.prototype.onDocumentMouseScroll = function(event, delta)
386 {
387     event.preventDefault();
388 
389     if (this.handleMouseScroll)
390     {
391         this.handleMouseScroll(delta);
392     }
393 }
394 
395 Sim.App.prototype.objectFromMouse = function(pagex, pagey)
396 {
397     // Translate page coords to element coords
398     //把瀏覽器頁面中的位置轉化為canvas中的坐標
399     var offset = $(this.renderer.domElement).offset();    
400     var eltx = pagex - offset.left;
401     var elty = pagey - offset.top;
402     
403     // Translate client coords into viewport x,y
404     //把canvas中的坐標轉化為3D場景中的坐標
405     var vpx = ( eltx / this.container.offsetWidth ) * 2 - 1;
406     var vpy = - ( elty / this.container.offsetHeight ) * 2 + 1;
407     
408     var vector = new THREE.Vector3( vpx, vpy, 0.5 );//補充一個z軸坐標,形成三維空間中靠原點外側的一個點(在Three.js中“點”分為Points和Vector兩種,前者具有顏色、大小、材質是真正可以被顯示出來的物體,後著是數學意義上的點或者向量)
409     
410     //this.projector.unprojectVector( vector, this.camera );
411     vector.unproject(this.camera);//@@新版本中去掉投影矩陣影響的方法,不要忘記3D場景中看到的東西都是經過投影矩陣變形過的,所以要先把“看到的位置”轉化為“實際的位置”再進行位置計算
412     
413     //@@這裡是Sim.js中版本差異最大的地方
414     //在三維空間中取得物體的原理:從相機到“滑鼠所在的點”畫一條射線,通過Three.js封裝的方法取得這條射線穿過的所有物體,第一個穿過的物體被認為是“滑鼠所在的物體”
415     
416     //var ray = new THREE.Ray( this.camera.position, vector.subSelf( this.camera.position ).normalize() );
417     //var intersects = ray.intersectScene( this.scene );
418     var raycaster = new THREE.Raycaster(this.camera.position,vector.subVectors(vector,this.camera.position).normalize());
419     //@@Raycaster是新版Three.js專門為“穿過檢測”定義的一種對象,與Ray分別開來,第一個參數是射線的端點,第二個參數是一個標準化(長度為一)的向量
420     var intersects = raycaster.intersectObjects(this.scene.children,true);
421     //true表示考慮物體的子物體,這裡必須加上,被“穿過到”的物體被存入了一個數組    
422     
423     if ( intersects.length > 0 ) {        
424         
425         /*var i = 0;
426         while(!intersects[i].object.visible)
427         {
428             i++;
429         }
430         
431         var intersected = intersects[i];
432         var mat = new THREE.Matrix4().getInverse(intersected.object.matrixWorld);
433         var point = mat.multiplyVector3(intersected.point);
434         
435         return (this.findObjectFromIntersected(intersected.object, intersected.point, intersected.face.normal)); */  
436         //@@
437         for(var i=0;i<intersects.length;i++)
438         {
439             if(intersects[i].object.visible&&intersects[i].face)
440             {//物體可見並且”有面“(剔除了穿過線物體和點物體的情況)
441                 var intersected = intersects[i];
442                 var mat = new THREE.Matrix4().getInverse(intersected.object.matrixWorld);
443                 var point=intersected.point.applyMatrix4( mat );//可見intersected.point是相對坐標,加上物體所在的姿態矩陣之後變成了3D空間中的絕對坐標
444                 return (this.findObjectFromIntersected(intersected.object, intersected.point, intersected.face.normal));
445             }
446         }
447         return { object : null, point : null, normal : null };//沒有找到符合條件的物體
448     }
449     else
450     {
451         return { object : null, point : nul

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • RFC一致性 Methods GET: 獲取某個資源,冪等且無副作用。 POST: 創建一個新的資源。 PUT: 替換某個已有的資源。冪等有副作用。 PATCH: 修改某個已有的資源。 DELETE:刪除某個資源。冪等有副作用。 Headers Accept:伺服器需要返回什麼樣的content。
  • 我在監理別的項目時,經常提出這樣的要求,所有的列表都要有排序機制。最近又看到一個數據倉庫項目中所有的維度表都沒有排序欄位。結果報表上所有維度都是按Caption屬性的字母排序。很多維度在用戶那裡都是有業務排序需求的,然而我提出這個問題後,設計者居然說這個不重要,不說了(他們就這水平了)和我沒有關係的
  • 本文講解一種常見的CSV文件標準,設定文件中的行分隔符,列分隔符,如何處理值中的單引號,雙引號,逗號,換行符等。
  • 第1步:分析問題 我這邊的處理方式是根據傳入的數據條數,和需要顯示的頁碼數,自動生成頁碼。舉個例子,如果傳入的參數為{pageSize:10,totalRow:200} 那麼就一共有20頁。 首次生成的頁碼樣式截圖: 第2步:點擊操作 點擊 2 或者下一頁按鈕的樣式截圖: 第3步:生成新頁面 這裡可
  • 當完成一項前端的工作之後,許多人都會忘記該項目的結構與細節。然而代碼並不是馬上就能完全定型,在餘下的時間里還有不斷的維護工作,而這些工作也許不會是你自己完成。所以,結構優良的代碼能很大程度上優化它的可維護性。下麵列出五種提高CSS文件可維護性的方法,也就是一種較好的CSS樣式指南。1.分解你的樣式對
  • http://120.24.90.140:2368/emberru-men-zhi-nan-jiao-cheng-mu-lu/
  • 首先是angular-ui-router的基本用法。■ 如何引用依賴angular-ui-router angular.module('app',["ui.router"]) .config(function($stateProvider){ $stateProvider.state(stateNa
  • 來源:w3cplus - 南北(@ping4god) 網址:http://www.w3cplus.com/tools/dev-tips.html 谷歌瀏覽器如今是Web開發者們所使用的最流行的網頁瀏覽器。伴隨每六個星期一次的發佈周期和不斷擴大的強大的開發功能,Chrome變成了一個必須的工具。大多數
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...