WebGL改造笔记

洪开诚
2023-12-01

这段时间需要把原有的一个老项目改造成WebGL版本,遇到了一些困难,这里将记录改造的一些情况。

用Node.js来管理游戏内容

var express = require('express');
var app = express();
app.get('/', function (req, res) {
  res.send('Hello World!');
});

app.use(express.static('tttt'));
var server = app.listen(8080, function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('Example app listening at http://%s:%s', host, port);
});

下面是webSocket的通信相关。

由于安全性问题,WebGL不支持TCPSocket,但是支持WebSockets;下面是几个socket的写法。

TCP Socket,用node.js运行:

var net = require('net');

var HOST = 'ipaddr';
var PORT = 123;

var b64encode = data => Buffer.from(data).toString('base64');  
var b64decode = data => Buffer.from(data, 'base64');  

var login1 = "MwAAAAEAAAAHAAAAMTA5NzU5AAIAAAA5ABEAAAA5ZmYzZjA1MzcyOWQ0ZmE0AAEAAAAA";
var login2 = "sgAAACMAAABJAAAAAQYAMTA5NzU5NpZHUC/B+SHjMCVg5U/R0BQzxFqpNRyv0JlnhMMzBXK+x7nT6RiAuuxrijfvanzYHySvXnCPToRHuenl1t/k8iEAAAA0NTY5MjcxRkM2REMyOTJBNUMwNzQxOTcwREMwQjk3OAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";

var client = new net.Socket();
client.connect(PORT, HOST, function() {

    console.log('CONNECTED TO: ' + HOST + ':' + PORT);
    // Write a message to the socket as soon as the client is connected, the server will receive it as message from the client 
    client.write(b64decode(login1));
    client.write(b64decode(login2));
});

// Add a 'data' event handler for the client socket
// data is what the server sent to this socket
client.on('data', function(data) {
    
    console.log('DATA: ' + b64encode(data));
    // Close the client socket completely
    client.destroy();
});

// Add a 'close' event handler for the client socket
client.on('close', function() {
    console.log('Connection closed');
});

WebSocket, 运行在浏览器里面:

hello websocket!!  Please see console to check the result!
<script>
(function(r){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=r()}else if(typeof define==="function"&&define.amd){define([],r)}else{var e;if(typeof window!=="undefined"){e=window}else if(typeof global!=="undefined"){e=global}else if(typeof self!=="undefined"){e=self}else{e=this}e.base64js=r()}})(function(){var r,e,n;return function(){function r(e,n,t){function o(i,a){if(!n[i]){if(!e[i]){var u=typeof require=="function"&&require;if(!a&&u)return u(i,!0);if(f)return f(i,!0);var d=new Error("Cannot find module '"+i+"'");throw d.code="MODULE_NOT_FOUND",d}var c=n[i]={exports:{}};e[i][0].call(c.exports,function(r){var n=e[i][1][r];return o(n?n:r)},c,c.exports,r,e,n,t)}return n[i].exports}var f=typeof require=="function"&&require;for(var i=0;i<t.length;i++)o(t[i]);return o}return r}()({"/":[function(r,e,n){"use strict";n.byteLength=c;n.toByteArray=v;n.fromByteArray=s;var t=[];var o=[];var f=typeof Uint8Array!=="undefined"?Uint8Array:Array;var i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(var a=0,u=i.length;a<u;++a){t[a]=i[a];o[i.charCodeAt(a)]=a}o["-".charCodeAt(0)]=62;o["_".charCodeAt(0)]=63;function d(r){var e=r.length;if(e%4>0){throw new Error("Invalid string. Length must be a multiple of 4")}return r[e-2]==="="?2:r[e-1]==="="?1:0}function c(r){return r.length*3/4-d(r)}function v(r){var e,n,t,i,a;var u=r.length;i=d(r);a=new f(u*3/4-i);n=i>0?u-4:u;var c=0;for(e=0;e<n;e+=4){t=o[r.charCodeAt(e)]<<18|o[r.charCodeAt(e+1)]<<12|o[r.charCodeAt(e+2)]<<6|o[r.charCodeAt(e+3)];a[c++]=t>>16&255;a[c++]=t>>8&255;a[c++]=t&255}if(i===2){t=o[r.charCodeAt(e)]<<2|o[r.charCodeAt(e+1)]>>4;a[c++]=t&255}else if(i===1){t=o[r.charCodeAt(e)]<<10|o[r.charCodeAt(e+1)]<<4|o[r.charCodeAt(e+2)]>>2;a[c++]=t>>8&255;a[c++]=t&255}return a}function l(r){return t[r>>18&63]+t[r>>12&63]+t[r>>6&63]+t[r&63]}function h(r,e,n){var t;var o=[];for(var f=e;f<n;f+=3){t=(r[f]<<16&16711680)+(r[f+1]<<8&65280)+(r[f+2]&255);o.push(l(t))}return o.join("")}function s(r){var e;var n=r.length;var o=n%3;var f="";var i=[];var a=16383;for(var u=0,d=n-o;u<d;u+=a){i.push(h(r,u,u+a>d?d:u+a))}if(o===1){e=r[n-1];f+=t[e>>2];f+=t[e<<4&63];f+="=="}else if(o===2){e=(r[n-2]<<8)+r[n-1];f+=t[e>>10];f+=t[e>>4&63];f+=t[e<<2&63];f+="="}i.push(f);return i.join("")}},{}]},{},[])("/")});

// ===================================================================
var b64encode = data => base64js.fromByteArray(data);    
var b64decode = data => base64js.toByteArray(data);  

var login1 = "MwAAAAEAAAAHAAAAMTA5NzU5AAIAAAA5ABEAAAA5ZmYzZjA1MzcyOWQ0ZmE0AAEAAAAA";
var login2 = "sgAAACMAAABJAAAAAQYAMTA5NzU5NpZHUC/B+SHjMCVg5U/R0BQzxFqpNRyv0JlnhMMzBXK+x7nT6RiAuuxrijfvanzYHySvXnCPToRHuenl1t/k8iEAAAA0NTY5MjcxRkM2REMyOTJBNUMwNzQxOTcwREMwQjk3OAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";

var addr = 'ws://127.0.0.1:811';
var ws = new WebSocket(addr);
ws.onopen = function(){
    console.log('Connected to: ' + addr);
    console.log('Try to login!!!');
    ws.send(b64decode(login1));
    ws.send(b64decode(login2));
}
ws.addEventListener("message", function(event) {
    console.log('Chrome Received: ' + b64encode(event.data));
    alert("Login OK!!");
});
</script>

webSocket Server,用Node.js运行:

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 811 });
wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(msg) {
    console.log('received: %s', msg);
    setInterval(() => {
      ws.send(msg);
    }, 1500);
  });
});

C# 端的TCP socket例子:

string serverIP = "192.168.1.1";
int port = 12345;
Socket clientSocket = null;
private IEnumerator oldSendLogin() {
    Debug.Log("try to connect!!");
    clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    clientSocket.Blocking = true;
    var address = new IPEndPoint(IPAddress.Parse(serverIP), port);

    IAsyncResult result = clientSocket.BeginConnect(address, null, null);
    bool success = result.AsyncWaitHandle.WaitOne(5000);
    if (success) {
        clientSocket.EndConnect(result);
    }
    clientSocket.Blocking = false;

    yield return StartCoroutine(trySocketSend());
    yield return StartCoroutine(trySocketReceive());
}

string login_param1 = "MwAAAAEAAAAHAAAAMTA5NzU5AAIAAAA5ABEAAAA5ZmYzZjA1MzcyOWQ0ZmE0AAEAAAAA";
string login_param2 = "sgAAACMAAABJAAAAAQYAMTA5NzU5NpZHUC/B+SHjMCVg5U/R0BQzxFqpNRyv0JlnhMMzBXK+x7nT6RiAuuxrijfvanzYHySvXnCPToRHuenl1t/k8iEAAAA0NTY5MjcxRkM2REMyOTJBNUMwNzQxOTcwREMwQjk3OAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";

private bool isSended = false;
IEnumerator trySocketSend() {
    Debug.Log(">>>>>>> try socketSend!!!!");

    while (true && !isSended) {
        yield return null;
        var ret = clientSocket.Poll(0, SelectMode.SelectWrite);
        if (ret) {
            var buff = Convert.FromBase64String(login_param1);
            int n = clientSocket.Send(buff, 0, buff.Length, SocketFlags.None);
            Debug.Log("Send Data Byte: " + n);
            break;
        }
    }

    while (true && !isSended) {
        yield return null;
        var ret = clientSocket.Poll(0, SelectMode.SelectWrite);
        if (ret) {
            var buff = Convert.FromBase64String(login_param2);
            int n = clientSocket.Send(buff, 0, buff.Length, SocketFlags.None);
            Debug.Log("Send Data Byte: " + n);
            break;
        }
    }
    isSended = true;

}

IEnumerator trySocketReceive() {
    Debug.Log(">>>>>>> try trySocketReceive!!!!");

    while (true) {
        yield return null;
        var ret = clientSocket.Poll(0, SelectMode.SelectRead);
        if (ret) {
            var recvStream = new MemoryStream(64 * 1024);
            var n = clientSocket.Receive(recvStream.GetBuffer(), (int)recvStream.Position, 64 * 1024, SocketFlags.None);
            Debug.Log("Receive Data Byte: " + n);
        }
    }
}

C#端WebSocket的登陆逻辑,使用插件:Simple Web Sockets for Unity WebGL

public IEnumerator webSocketLogin()
{
    var login1 = Convert.FromBase64String(login_param1);
    var login2 = Convert.FromBase64String(login_param2);

    WebSocket w = new WebSocket(new Uri("ws://192.168.1.1:1234"));
    yield return StartCoroutine(w.Connect());

    w.Send(login1);
    w.Send(login2);

    int i = 0;
    while (true) {
        var reply = w.Recv();
        if (reply != null) {
            Debug.LogFormat("Received: {0} ==> {1}", reply.Length, Convert.ToBase64String(reply));
            //   w.SendString("Hi there" + i++);
        }
        if (w.error != null) {
            Debug.LogError("Error: " + w.error);
            break;
        }
        yield return 0;
    }
    w.Close();
}

一个带CORS和https功能的server

var path = require('path');
var express = require('express');

var fs = require('fs');
var http = require('http');
var https = require('https');
var privateKey  = fs.readFileSync('key.pem', 'utf8');
var certificate = fs.readFileSync('cert.pem', 'utf8');

var app = express();
app.get('/', function (req, res) {
  res.send('<a href="/index.html">Enter Game</a>');
});


app.use(express.static(
    path.join('.', "WebBuild"),
    {
        setHeaders: (res) => {
             res.setHeader("Access-Control-Allow-Origin", "*");
				res.setHeader("Access-Control-Allow-Credentials", "*");
				res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT");
				res.setHeader("Access-Control-Allow-Headers", "Content-Type,Accept, X-Access-Token, X-Application-Name, X-Request-Sent-Time");
        }
    }
))


var credentials = {key: privateKey, cert: certificate};

// your express configuration here

var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);

httpServer.listen(8080);
httpsServer.listen(8443);

console.log(`Server Started, http on 8080, https on 8443`);

一个webSocket转socket的服务:

const net = require('net');
const fs = require('fs');
const https = require('https');
const WebSocket = require('ws');

// ============ proxy服务器地址 {{{{ ===============
var HOST = 'xxxxxx';
var PORT = 123;

var WS_PORT = 811;   // webSocket 的监听端口

var SSL_CERT = "./cert.pem";
var SSL_KEY = "./key.pem";
// ============ }}}} proxy服务器地址 ===============

var port = parseInt(process.argv[2]);  
if(port>0) {
    WS_PORT = port;
} else {
    console.log(`port is not set or not a number, use default ${WS_PORT}`);
}

var b64encode = data => Buffer.from(data).toString('base64');
var b64decode = data => Buffer.from(data, 'base64');

// 日志相关
var curDay = -1;
Log = msg => {
    var date = new Date();
    var hour = date.getHours();
    var min  = date.getMinutes();
    var sec  = date.getSeconds();
    var mi = date.getMilliseconds();
    var rt = `[${hour >= 10 ? hour : "0" + hour}:${min >= 10 ? min : "0" + min}:${sec >= 10 ? sec : "0" + sec} ${mi >= 100 ? mi : ((mi >= 10 ? "0" : "00") + mi)}]`;
   
    var day = date.getDate();
	if(curDay != day) {  // 隔天,要显示日期信息
        curDay = day;
		var year = date.getFullYear();
		var month = date.getMonth() + 1;
		rt = `************** ${year}-${month >= 10 ? month : "0" + month}-${day >= 10 ? day : "0" + day} *****************\r\n` + rt;
	}
    console.log(`${rt} ${msg}`);
}

// ============ 下面是TcpSocket,辅助连接Proxy服务器和客户端  ===========

// referTo https://nodejs.org/api/net.html
var TcpSocket = function() {  
    var _userId = 0;
    var _wsocket = null;
    var _socket = null;
    var _pendingSend = null;
    var _attachCnt = 0;
    var _alive = false;

    // 进行连接
    function init() {
        _alive = false;
        _socket = new net.Socket();

        _socket.on("connect", () => {
            _alive = true;
            if(_pendingSend!=null) {  // 处理链接期间到来的数据
                while(_pendingSend.length > 0) {
                    _socket.write(_pendingSend.shift());  // TODO 判断状态,并进行异常处理
                }
            }
        });

        _socket.on('data', data => {
            if(_wsocket!=null && _wsocket.readyState === WebSocket.OPEN) {
                _wsocket.send(data); 
            } 
        });

        _socket.on('close', had_err => {
            _alive = false;
        });
    
        _socket.on('error', err => { 
            console.log("Error :: " + err);
        });
    };
    init();

    var _close = () => {
        _attachCnt--;
        if(_attachCnt<=0) {
            if(_socket!=null) {
                _socket.end();  // 通知服务器关闭socket
                _socket = null;
            }
            if(_wsocket!=null) {
                _wsocket.close();   
                _wsocket = null;    
            }
            if(tcpDict[_userId]!=null) {
                delete tcpDict[_userId];
            }
        }
    };

    var _trySend = data => {
        if(_socket == null) {
            return;
        }
        if(_alive) {
            _socket.write(data);   //TODO 进行异常处理
        } else {
            if(!_socket.connecting) {  // 尝试恢复连接
                _socket.connect(PORT, HOST);
            }
            if(_pendingSend == null) {
                _pendingSend = [];
            }
            if(_pendingSend.length < 10) { // 10条之内的消息,做缓存
                _pendingSend.push(data);
            } else {
                console.log("cache is full!!");
            }
        }
    };

    return {
        close : _close,
        send : _trySend,
        get uid() {
            return _userId;
        },
        attach: (ws, uid) => {
            _attachCnt++;
            if(_wsocket!=null) {
                _wsocket.terminate();
            }
            _wsocket = ws;
            _userId = uid;
            if(_socket == null) {
                init();
            }
            if(!_alive && !_socket.connecting) {
                _socket.connect(PORT, HOST);
            }
        }
    };
}

var tcpDict = {};

// ============ 下面是WebSocket,负责和客户端连接 ================
// https://github.com/websockets/ws
var idReg = /\/(\d+)/;
const server = new https.createServer({
    cert: fs.readFileSync(SSL_CERT),
    key: fs.readFileSync(SSL_KEY)
  });
var wss = new WebSocket.Server({ server });
wss.on('connection', (ws, req) => {
    var uid = parseInt(req.url.replace(idReg, "$1"));
    Log(`${wss.clients.size} @ <${uid}> ${req.connection.remoteAddress}`);  // Connected!
    
    if(uid <= 0) {  // uid is not correct!
        console.log("userid is not correct!!!");
        ws.terminate();
        return;
    } 

    var tcp = tcpDict[uid];
    if(tcp==null) {
        tcp = new TcpSocket();
    } 
    tcpDict[uid] = tcp;
    tcp.attach(ws, uid);

    ws.on('message', msg => { 
        tcp.send(msg);
    });
    
    ws.on("close", () => {
        tcp.close();
        Log(`${wss.clients.size} ~ <${tcp.uid}> ${req.connection.remoteAddress}`);  // Disconnect
    });
});

server.listen(WS_PORT);
Log(`WebSocket Server listen on : ${WS_PORT}`);

===================下面是浏览器中相关编程 ==========================

从网路下载文本的方式

var url = "https://192.168.1.194:8443/StreamingAssets/AssetsBundles/LocalBundleMap.txt";
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType
xhr.responseType = 'json';
xhr.onload = function (evt) {
  if (xhr.status == 200) {
    var json = xhr.response;
    if (json) {
        console.log(json);
    } 
  } else {
    console.error("error:",  xhr.status);
  }
};
xhr.send();



var url = "https://192.168.1.194:8443/StreamingAssets/AssetsBundles/LocalBundleMap.txt";
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType
xhr.responseType = 'blob';
xhr.onload = function (evt) {
  if (xhr.status == 200) {
    var val = xhr.response;
    if (val) {
        var reader = new FileReader();
        reader.addEventListener("loadend", function() {
            console.log(reader.result);
        });
        reader.readAsText(val);  // 读取成text
    } 
  } else {
    console.error("Error: ",  xhr.status);
  }
};
xhr.send();

IndexDB相关操作

参考:https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB

// md5 function
!function (n) { "use strict"; function t(n, t) { var r = (65535 & n) + (65535 & t); return (n >> 16) + (t >> 16) + (r >> 16) << 16 | 65535 & r } function r(n, t) { return n << t | n >>> 32 - t } function e(n, e, o, u, c, f) { return t(r(t(t(e, n), t(u, f)), c), o) } function o(n, t, r, o, u, c, f) { return e(t & r | ~t & o, n, t, u, c, f) } function u(n, t, r, o, u, c, f) { return e(t & o | r & ~o, n, t, u, c, f) } function c(n, t, r, o, u, c, f) { return e(t ^ r ^ o, n, t, u, c, f) } function f(n, t, r, o, u, c, f) { return e(r ^ (t | ~o), n, t, u, c, f) } function i(n, r) { n[r >> 5] |= 128 << r % 32, n[14 + (r + 64 >>> 9 << 4)] = r; var e, i, a, d, h, l = 1732584193, g = -271733879, v = -1732584194, m = 271733878; for (e = 0; e < n.length; e += 16)i = l, a = g, d = v, h = m, g = f(g = f(g = f(g = f(g = c(g = c(g = c(g = c(g = u(g = u(g = u(g = u(g = o(g = o(g = o(g = o(g, v = o(v, m = o(m, l = o(l, g, v, m, n[e], 7, -680876936), g, v, n[e + 1], 12, -389564586), l, g, n[e + 2], 17, 606105819), m, l, n[e + 3], 22, -1044525330), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 4], 7, -176418897), g, v, n[e + 5], 12, 1200080426), l, g, n[e + 6], 17, -1473231341), m, l, n[e + 7], 22, -45705983), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 8], 7, 1770035416), g, v, n[e + 9], 12, -1958414417), l, g, n[e + 10], 17, -42063), m, l, n[e + 11], 22, -1990404162), v = o(v, m = o(m, l = o(l, g, v, m, n[e + 12], 7, 1804603682), g, v, n[e + 13], 12, -40341101), l, g, n[e + 14], 17, -1502002290), m, l, n[e + 15], 22, 1236535329), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 1], 5, -165796510), g, v, n[e + 6], 9, -1069501632), l, g, n[e + 11], 14, 643717713), m, l, n[e], 20, -373897302), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 5], 5, -701558691), g, v, n[e + 10], 9, 38016083), l, g, n[e + 15], 14, -660478335), m, l, n[e + 4], 20, -405537848), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 9], 5, 568446438), g, v, n[e + 14], 9, -1019803690), l, g, n[e + 3], 14, -187363961), m, l, n[e + 8], 20, 1163531501), v = u(v, m = u(m, l = u(l, g, v, m, n[e + 13], 5, -1444681467), g, v, n[e + 2], 9, -51403784), l, g, n[e + 7], 14, 1735328473), m, l, n[e + 12], 20, -1926607734), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 5], 4, -378558), g, v, n[e + 8], 11, -2022574463), l, g, n[e + 11], 16, 1839030562), m, l, n[e + 14], 23, -35309556), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 1], 4, -1530992060), g, v, n[e + 4], 11, 1272893353), l, g, n[e + 7], 16, -155497632), m, l, n[e + 10], 23, -1094730640), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 13], 4, 681279174), g, v, n[e], 11, -358537222), l, g, n[e + 3], 16, -722521979), m, l, n[e + 6], 23, 76029189), v = c(v, m = c(m, l = c(l, g, v, m, n[e + 9], 4, -640364487), g, v, n[e + 12], 11, -421815835), l, g, n[e + 15], 16, 530742520), m, l, n[e + 2], 23, -995338651), v = f(v, m = f(m, l = f(l, g, v, m, n[e], 6, -198630844), g, v, n[e + 7], 10, 1126891415), l, g, n[e + 14], 15, -1416354905), m, l, n[e + 5], 21, -57434055), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 12], 6, 1700485571), g, v, n[e + 3], 10, -1894986606), l, g, n[e + 10], 15, -1051523), m, l, n[e + 1], 21, -2054922799), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 8], 6, 1873313359), g, v, n[e + 15], 10, -30611744), l, g, n[e + 6], 15, -1560198380), m, l, n[e + 13], 21, 1309151649), v = f(v, m = f(m, l = f(l, g, v, m, n[e + 4], 6, -145523070), g, v, n[e + 11], 10, -1120210379), l, g, n[e + 2], 15, 718787259), m, l, n[e + 9], 21, -343485551), l = t(l, i), g = t(g, a), v = t(v, d), m = t(m, h); return [l, g, v, m] } function a(n) { var t, r = "", e = 32 * n.length; for (t = 0; t < e; t += 8)r += String.fromCharCode(n[t >> 5] >>> t % 32 & 255); return r } function d(n) { var t, r = []; for (r[(n.length >> 2) - 1] = void 0, t = 0; t < r.length; t += 1)r[t] = 0; var e = 8 * n.length; for (t = 0; t < e; t += 8)r[t >> 5] |= (255 & n.charCodeAt(t / 8)) << t % 32; return r } function h(n) { return a(i(d(n), 8 * n.length)) } function l(n, t) { var r, e, o = d(n), u = [], c = []; for (u[15] = c[15] = void 0, o.length > 16 && (o = i(o, 8 * n.length)), r = 0; r < 16; r += 1)u[r] = 909522486 ^ o[r], c[r] = 1549556828 ^ o[r]; return e = i(u.concat(d(t)), 512 + 8 * t.length), a(i(c.concat(e), 640)) } function g(n) { var t, r, e = ""; for (r = 0; r < n.length; r += 1)t = n.charCodeAt(r), e += "0123456789abcdef".charAt(t >>> 4 & 15) + "0123456789abcdef".charAt(15 & t); return e } function v(n) { return unescape(encodeURIComponent(n)) } function m(n) { return h(v(n)) } function p(n) { return g(m(n)) } function s(n, t) { return l(v(n), v(t)) } function C(n, t) { return g(s(n, t)) } function A(n, t, r) { return t ? r ? s(t, n) : C(t, n) : r ? m(n) : p(n) } "function" == typeof define && define.amd ? define(function () { return A }) : "object" == typeof module && module.exports ? module.exports = A : n.md5 = A }(this);

// In the following line, you should include the prefixes of implementations you want to test.
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
// DON'T use "var indexedDB = ..." if you're not in a function.
// Moreover, you may need references to some window.IDB* objects:
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || { READ_WRITE: "readwrite" }; // This line should only be needed if it is needed to support the object's constants for older browsers
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
// (Mozilla has never prefixed these objects, so we don't need window.mozIDB*)

if (!window.indexedDB) {
    window.alert("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
} else {
    console.log("the browser is OK!! --- ");
}

const DB_NAME = '/idbfs';
const DB_STORE = "FILE_DATA";

var db;
var req = window.indexedDB.open(DB_NAME);
var baseDir = `/idbfs/${md5(window.location.href.replace(/(https?.*)\/[^\/]*/, "$1"))}`;
console.log(baseDir);

function bin2Str(array) {
    var result = "";
    for (var i = 0; i < array.length; ++i) {
        result += (String.fromCharCode(array[i]));
    }
    return result;
}

req.onsuccess = function () {
    // Better use "this" than "req" to the result to 
    // avoid problems with garbage collection
    db = this.result;
    var fn = `${baseDir}/AssetsBundles/LocalBundleMap.txt`;
    console.log("==> " + fn);
    db.transaction(DB_STORE).objectStore(DB_STORE).get(fn).onsuccess = evt => {
        var obj = evt.target.result;
        var d = JSON.parse(bin2Str(obj.contents));
        //     console.log(JSON.stringify(d));
    };
};
req.onerror = evt => {
    console.error("Error openDB:", evt.target.errorCode);
};

req.onupgradeneeded = evt => {
    console.log("openDb.onupgradeneeded");
    // var store = evt.currentTarget.result.createObjectStore(
    //     DB_STORE, {keyPath: 'id', autoIncrement: true });
    // store.createIndex('title', 'title', { unique: false });
};

JavaScript中的协程的用法:

参考:https://x.st/javascript-coroutines/

function coroutine(f) {
    var o = f(); // instantiate the coroutine
    o.next(); // execute until the first yield
    return function(x) {
        o.next(x);
    }
}

var doJob = coroutine(function*(){
    console.log("init");
    var x = yield;
    console.log("First I got : " + x);
    var y = yield;
    console.log("Then I got: " + y);
});

doJob('a dog');
doJob('a cat');

JavaScript的枚举:

    var JobStatus = Object.freeze({
        UnInited:1,  
        DBReady:2, 
        BundleMapReady:3,
        LocalResReady: 4,
        });

Promise的用法 :

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}
//Now let's use it:

get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
})

Promise的动态串联:

getJson('story.json').then(function (story) {
    addHtmlToPage(story.heading);

    return story.chapterUrls.reduce(function (chain, chapterUrl) {
        // Once the last chapter's promise is done…
        return chain.then(function () {
            // …fetch the next chapter
            return getJson(chapterUrl);
        }).then(function (chapter) {
            // and add it to the page
            addHtmlToPage(chapter.html);
        });
    }, Promise.resolve());
}).then(function () {
    // And we're all done!
    addTextToPage("All done");
}).catch(function (err) {
    // Catch any error that happened along the way
    addTextToPage("Argh, broken: " + err.message);
}).then(function () {
    // Always hide the spinner
    document.querySelector('.spinner').style.display = 'none';
});

async函数的基本写法

参考:点击打开链接

// async的写法:
async function logFetch(url) {
  try {
    const response = await fetch(url);
    console.log(await response.text());
  }
  catch (err) {
    console.log('fetch failed', err);
  }
}
// 相对于的promise的写法
function logFetch(url) {
  return fetch(url)
    .then(response => response.text())
    .then(text => {
      console.log(text);
    }).catch(err => {
      console.error('fetch failed', err);
    });
}

 

批量下载例子:

 

参考:https://developers.google.com/web/fundamentals/primers/async-functions

In following examples, the URLs are fetched and read in parallel, but the "smart" reduce bit is replaced with a standard, boring, readable for-loop.

 

// 方法1:
function logInOrder(urls) {
  // fetch all the URLs
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text());
  });

  // log them in order
  textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise)
      .then(text => console.log(text));
  }, Promise.resolve());
}

// 方法2:
async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

 

================

 

C# 和  JavaScript的交互中的回调函数 

参考: emscripten代码 emscripten文档  unity论坛   一个日文的文章    官方解决方案(SendMessage)

C#端:

// C#端对javascript的申明
[DllImport("__Internal")]
public static extern void callFuncStalendp(string strEx, System.Action<System.IntPtr> func);

// 回调函数
[MonoPInvokeCallback(typeof(System.Action))]
public static void callback(System.IntPtr ptr) {
    var val = Marshal.PtrToStringAuto(ptr);
    Debug.LogError("The function is called!! " + val);
}

// 调用
FacebookHelper.callFuncStalendp("C#端", callback);

jslib端:

var LibraryFackbookHelper = {
    
    callFuncStalendp : function(msgEx, callback) {
        var msg = Pointer_stringify(msgEx);
        var hello = "hello world! 你好!";
        if (callback) {
            var stack = Runtime.stackSave();
            Runtime.dynCall('vi', callback, [allocate(intArrayFromString(msg + hello), 'i8', ALLOC_STACK)]);
            Runtime.stackRestore(stack);
        }
    }

};

mergeInto(LibraryManager.library, LibraryFackbookHelper);

关于Jslib的注意点,

var LibraryWonHelper = {
    $WonCommon: {
        str2Ptr : function(str) {
            return str && allocate(intArrayFromString(str), 'i8', ALLOC_STACK) || null;
        },
        invoke: function (callback, methodId, isOK, returnVal) {
            var stack = Runtime.stackSave();
            Runtime.dynCall('viii', callback, [methodId, isOK, WonCommon.str2Ptr(returnVal)]);
            Runtime.stackRestore(stack);
        }
    },
    wbgl_won_login: function (wonId, callback) { // TYPE 0
        WonWrapper.login(Pointer_stringify(wonId)).then(function (id, token) {
            WonCommon.invoke(callback, 0, 1, JSON.stringify([id, token]));
        }).catch(function (err) {
            WonCommon.invoke(callback, 0, 0);
            console.error(err);
        });
    },
    wbgl_won_token: function () {
        return WonCommon.str2Ptr(WonWrapper.getToken());
    }
};
autoAddDeps(LibraryWonHelper, '$WonCommon');
mergeInto(LibraryManager.library, LibraryWonHelper);

函数的声明,

1. 都要使用function(...){} 的形式,不能用 (param1, parram2) => {} 这种形式。

2. 导出的函数(在c#中 用[DllImport("__Internal")] 申明的),会在js代码中生成对应的函数(但是函数名前面加了"_")。比如wbgl_won_tokens生成的函数名是 _wbgl_won_tokens。这个代码是生成在mudule中的,在javascript不能够被直接调用。如果想直接调用,在合适的时机,把这些函数绑定到全局变量中。没有被导出的函数,是不会生成js代码的。

关于合适的时机,可以写一个函数,在unity启动的时候调用,并进行代码的注册。

3. jslib中的公用代码,参考上面例子中的 $WonCommon(注意要用autoAddDeps注册一下)

==============

Cookie的辅助类

let CookieHelper = {
    setCookie : (cname, cvalue, exdays) => {
        let d = new Date();
        d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
        document.cookie = `${cname}=${cvalue};expires=${d.toUTCString()};path=/`;
    }, 
    getCookie : cname => {
        return document.cookie.replace(new RegExp(`(?:(?:^|.*;\\s*)${cname}\\s*=\\s*([^;]*).*$)|^.*$`, 'i'), "$1");
    },
    delCookie : cname => {
        document.cookie = `${cname}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
    }
};

=============

异步加载类

let loadScript = (src, id) => {
    return new Promise(function (resolve, reject) {
        if (document.getElementById(id)) {
            reject("already loaded");
        }  else {
            let s = document.createElement('script');
            s.id = id;
            s.src = src;
            s.type = 'text/javascript';
            s.onload = resolve;
            s.onerror = reject;
            document.head.appendChild(s);
        }
    });
}

===========

单例模式

var UnityLoader = UnityLoader || { .... }

 

 类似资料: