使用RequestAnim或超时在动画中滞后fps



我试图用恒定的fps获得一个好的动画,但没有任何效果。我使用的是三j,webGL渲染场景,对于动画循环,我找到了两种方法(有第三种?),哪个是requestAnimationframe(...)或settimeout()。两者都不能保证FPS是恒定的,但是我是通过通过window.performance.now()更新对象位置来修复了它。但是我仍然有可以清楚地看到的落后。那我该如何解决呢?显然可能是因为有像《毁灭战士》这样的游戏不会落后。

我的示例与完整的SRC代码可以在此处找到:

http://sc2tube.com:8080/test/three.html

相关代码:

function animate() {
requestAnimationFrame( animate );
// calculate how long the last frame was 
var timefix = (window.performance.now() - last)/(1000/30);
last = window.performance.now();
var oldX = object.position.x;
// calculate updateX including the timefix
var updateX = oldX + (10 / 30 * 100) * dx * timefix;
// update the position of the object
object.position.x = updateX;  
// render the scene
renderer.render(scene, camera);
}

worker.js:

self.addEventListener('message', function(e) {
setInterval(function(){ 
    now = self.performance.now()
    timefix = (now - last)/(1000/100);
    last = now;
    x += 5*timefix*dx;
    self.postMessage(x);
}, 1000/100);
}, false);

var test;  
	var dx = 1, dy = 0;
	var speed = 0.5;
	var activeKey = 0;
    // Set up the scene, camera, and renderer as global variables.
    var scene, camera, renderer;
    init();
    animate();
    // Sets up the scene.
    function init() {
      // Create the scene and set the scene size.
      scene = new THREE.Scene();
      var WIDTH = window.innerWidth - 50,
          HEIGHT = 500;
      // Create a renderer and add it to the DOM.
      renderer = new THREE.WebGLRenderer({antialias:true});
      renderer.setSize(WIDTH, HEIGHT);
      document.body.appendChild(renderer.domElement);
      camera = new THREE.OrthographicCamera( 0, WIDTH, 200, -HEIGHT, 1, 1000 );
      
      camera.position.set(0,0,100);
      scene.add(camera);
      console.log(WIDTH);
      
      
      window.addEventListener('resize', function() {
    	  var WIDTH = window.innerWidth - 50,
          HEIGHT = window.innerHeight - 50;
        renderer.setSize(WIDTH, HEIGHT);
        camera.aspect = WIDTH / HEIGHT;
        camera.updateProjectionMatrix();
        
      });
      renderer.setClearColor();
      var loader = new THREE.ObjectLoader();
  	  
      loader.parse({
    "metadata" : {
        "type" : "Object",
        "version" : 4.3,
        "generator" : "Blender Script"
    },
    "object" : {
        "name" : "red_cube.Material",
        "type" : "Mesh",
        "uuid" : "6071e8f2-79ae-5660-8d2b-aa675c566703",
        "matrix" : [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
        "geometry" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
        "material" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561"
    },
    "geometries" : [{
            "name" : "red_cube.Material",
            "type" : "BufferGeometry",
            "uuid" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
            "data" : {
                "attributes" : {
                    "position" : {
                        "type" : "Float32Array",
                        "itemSize" : 3,
                        "array" : [0.79906648,-0.73424673,-0.87263167,0.79906648,-0.73424661,1.1273682,-1.2009337,-0.73424661,1.1273681,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,-1.2009335,1.2657533,-0.87263179,-1.2009339,1.2657533,1.1273677,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424673,-0.87263167,0.79906696,1.2657533,-0.87263131,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424661,1.1273682,0.79906648,-0.73424661,1.1273682,0.79906583,1.2657533,1.1273688,-1.2009339,1.2657533,1.1273677,-1.2009337,-0.73424661,1.1273681,-1.2009337,-0.73424661,1.1273681,-1.2009339,1.2657533,1.1273677,-1.2009335,1.2657533,-0.87263179,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,0.79906648,-0.73424673,-0.87263167,-1.2009332,-0.73424673,-0.87263215,-1.2009335,1.2657533,-0.87263179]
                    },
                    "normal" : {
                        "type" : "Float32Array",
                        "itemSize" : 3,
                        "array" : [-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,0,1,0,0,1,0,0,1,0,0,1,0,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1]
                    },
                    "index" : {
                        "type" : "Uint32Array",
                        "itemSize" : 1,
                        "array" : [0,1,2,2,3,0,4,5,6,6,7,4,8,9,10,10,11,8,12,13,14,14,15,12,16,17,18,18,19,16,20,21,22,22,23,20]
                    }
                }
            }
        }],
    "materials" : [{
            "name" : "Material",
            "type" : "MeshBasicMaterial",
            "uuid" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561",
            "transparent" : false,
            "opacity" : 1,
            "color" : 10682379
        }]
}, function(object){
       		test = object;
       		object.scale.set(50,50,50);
       		scene.add(object)
       		
      });
      
  	document.addEventListener('keydown', function(e) {
  	    if (activeKey == e.keyCode) return;
  	    activeKey = e.keyCode;
  	    
  	    //left
  	    if (e.keyCode == 37) {
  	        dx = -1;
  	    }
  	    //top
  	    else if (e.keyCode == 38) {
  	        dy = 1;
  	    }
  	    //right
  	    else if (e.keyCode == 39) {
  	        dx = 1;
  	    }
  	    //bottom
  	    else if (e.keyCode == 40) {
  	        dy = -1;
  	    }
  	});
  	document.addEventListener('keyup', function(e) {
  	    switch (e.keyCode) {
  	        case 37: // left
  	        case 39: // right
  	            dx = 0;
  	            break;
  	            
  	        case 38: // up
  	        case 40: // down
  	            dy = 0;
  	            break;
  	    }
  	    
  	    activeKey = 0;
  	});
    }
    
    var start;
    var last;
    function animate() {
        requestAnimationFrame( animate );
    	if(start == null) {
    		start = window.performance.now();
    		last = start;
    	}
    	
    	var timefix = (window.performance.now() - last)/(1000/30);
    	
    	last = window.performance.now();
    	
        if(test != null) {
       	    var oldX = test.position.x;
       	    var oldY = test.position.y;
       	    
       	    var updateX = oldX + (10 / 30 * 100) * dx * speed * timefix;
       	    var updateY = oldY + (10 / 30 * 100) * dy * speed * timefix;  
       	    
       	    if(updateX > 1800 ) {
       	    	dx = -1;
       	    } else if(updateX < 100) {       	    	
       	    	dx = 1;
       	    }
       	    
       	    test.position.x = updateX;  
       	    test.position.y =  oldY + (10 / 30 * 100) * dy * speed * timefix;  
       	    
       	 	var text = document.getElementById('panel');
       	 	text.innerHTML =  timefix;
       		renderer.render(scene, camera);
       	}
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
<body style="margin: 0;">
<div id="panel">TEST
</div>
<br>
</body>

问题是称为游戏tick的东西。

您需要的是线程。一个渲染线程。一个游戏线程。

对于游戏线程,我建议网络工人:

https://developer.mozilla.org/en-us/docs/web/api/web_workers_api/using_web_workers

您让游戏线程每50ms运行一次以更新游戏逻辑。它应该"睡觉"之间。您将内容张贴回渲染线程,该线程更新所有内容并将CurrentPOS的轨迹解释为50 ms的下一个POS。

  • tick 1. 0ms

    • 游戏线程:
      • 在0.0
      • 的Spawn Block
      • 将Gamestate推到渲染线程
    • 渲染线程
      • 收集实体。
      • 如果移动
      • ,则更新位置的实体
      • 在POS
      • 绘制实体
  • tick 2. 50ms

    • 游戏线程:
      • 获取实体
      • 触发更新功能
        • 红色块移动左20
        • 将Gamestate推到渲染线程
    • 渲染线程
      • 收集实体。
      • 如果移动,则更新位置的实体
        • 红色块从0移动到20
        • AVG帧/滴答4
        • 每滴tick的动作,5 px。
        • 更新红色为5
      • 在POS
      • 绘制实体

编辑添加了如何利用渲染线程的示例代码。

基本上,您在游戏线程(网络工程师)中具有与渲染线程相同的对象。唯一的区别是,渲染线程具有渲染指令(ONRENDER),并且游戏循环具有更新说明(ON UPDATE)

所以它们是相同的,但也不同。

看看。

function getInlineJS() {
    var js = document.querySelector('[type="javascript/worker"]').textContent;
    var blob = new Blob([js], {"type": "text/plain"});
    return URL.createObjectURL(blob);
}
var RedCube = function(id) {
    this.cube = null;
    this.type = 'redcube';
    if(typeof id === undefined) {
       this.entityId = generateId();
    }
    else {
        this.entityId = id;
    }
    this.lastX = 0;
    this.x = 0;
}
RedCube.prototype.getType = function() {
   return this.type;
}
RedCube.prototype.onUpdate = function() {
    this.x += 20;
}
RedCube.prototype.loadCube = function(scene, renderer) {
var that = this;
       
        var loader = new THREE.ObjectLoader();
  	        loader.parse({
    "metadata" : {
        "type" : "Object",
        "version" : 4.3,
        "generator" : "Blender Script"
    },
    "object" : {
        "name" : "red_cube.Material",
        "type" : "Mesh",
        "uuid" : "6071e8f2-79ae-5660-8d2b-aa675c566703",
        "matrix" : [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
        "geometry" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
        "material" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561"
    },
    "geometries" : [{
            "name" : "red_cube.Material",
            "type" : "BufferGeometry",
            "uuid" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
            "data" : {
                "attributes" : {
                    "position" : {
                        "type" : "Float32Array",
                        "itemSize" : 3,
                        "array" : [0.79906648,-0.73424673,-0.87263167,0.79906648,-0.73424661,1.1273682,-1.2009337,-0.73424661,1.1273681,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,-1.2009335,1.2657533,-0.87263179,-1.2009339,1.2657533,1.1273677,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424673,-0.87263167,0.79906696,1.2657533,-0.87263131,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424661,1.1273682,0.79906648,-0.73424661,1.1273682,0.79906583,1.2657533,1.1273688,-1.2009339,1.2657533,1.1273677,-1.2009337,-0.73424661,1.1273681,-1.2009337,-0.73424661,1.1273681,-1.2009339,1.2657533,1.1273677,-1.2009335,1.2657533,-0.87263179,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,0.79906648,-0.73424673,-0.87263167,-1.2009332,-0.73424673,-0.87263215,-1.2009335,1.2657533,-0.87263179]
                    },
                    "normal" : {
                        "type" : "Float32Array",
                        "itemSize" : 3,
                        "array" : [-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,0,1,0,0,1,0,0,1,0,0,1,0,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1]
                    },
                    "index" : {
                        "type" : "Uint32Array",
                        "itemSize" : 1,
                        "array" : [0,1,2,2,3,0,4,5,6,6,7,4,8,9,10,10,11,8,12,13,14,14,15,12,16,17,18,18,19,16,20,21,22,22,23,20]
                    }
                }
            }
        }],
    "materials" : [{
            "name" : "Material",
            "type" : "MeshBasicMaterial",
            "uuid" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561",
            "transparent" : false,
            "opacity" : 1,
            "color" : 10682379
        }]
}, function(object){
       		that.cube = object;
       		object.scale.set(50,50,50);
       		scene.add(object)
       		
      });
}
RedCube.prototype.onRender = function(scene, renderer) {
   if(this.cube === null) {
       this.loadCube(scene, renderer);
   }
   
   // Some interprolation logic here to move from lastpos to next pos in average frames
   // per tick.
   this.cube.position.x = this.x;
}
RedCube.prototype.getType = function() {
   return type;
}
RedCube.prototype.generateSyncPacket = function() {
   return {
            type: this.getType(),
            x : this.x
          };
}
RedCube.prototype.parseSyncPacket = function(syncpacket) {
   this.setPosition(syncpacket.x);
}
RedCube.prototype.generateId = function() {
    var d = new Date().getTime();
    if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
        d += performance.now(); //use high-precision timer if available
    }
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
}
RedCube.prototype.getEntityId = function() {
    return this.entityId;
}
RedCube.prototype.beforeDeath = function() {
}
RedCube.prototype.die = function() {
}
RedCube.prototype.setPosition = function(newpos) {
    this.lastX = this.x;
    this.x = newpos;
}
RedCube.prototype.getPosition = function() {
    return this.x;
}
RedCube.prototype.getLastX = function() {
    return this.lastX;
}
var EntityRegistry = function() {
   this.entities = {};
   this.types = {};
}
EntityRegistry.prototype.register = function(entity) {
   this.entities[entity.getEntityId()] = entity;
   
}
EntityRegistry.prototype.callUpdate = function() {
  for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].onUpdate();
      }
   }
}
EntityRegistry.prototype.callOnRender = function(scene, renderer) {
  for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].onRender(scene, renderer);
      }
   }
}
EntityRegistry.prototype.remove = function(entity) {
   entity.beforeDeath();
   delete this.entities[entity.getEntityId()]
   entity.die();
}
EntityRegistry.prototype.registerType = function(name, entityClass) {
    this.types[name] = entityClass;
}
EntityRegistry.prototype.startEntity = function(syncpacket, entityId) {
    var entity = new this.types[syncpacket.type](entityId);
    entity.parseSyncPacket(syncpacket);
    this.register(entity);
}
EntityRegistry.prototype.getSyncData = function() {
   var syncpacket = {};
   for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          syncpacket[entityId] = this.entities[entityId].generateSyncPacket();
      }
   }
   return syncpacket;
}
EntityRegistry.prototype.parseSyncData = function(syncpacket) {
   for(entityId in syncpacket) {
      
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].parseSyncPacket(syncpacket[entityId]);
      }
      else {
          this.startEntity(syncpacket[entityId], entityId);
      }
   }
   return syncpacket;
}
var REGISTRY = new EntityRegistry();
REGISTRY.registerType('redcube', RedCube);
 	var test = "d";  
	var dx = 1, dy = 0;
	var speed = 0.5;
	var activeKey = 0;
    // Set up the scene, camera, and renderer as global variables.
    var scene, camera, renderer;
    var worker = new Worker(getInlineJS());
    worker.postMessage("dasd");
  
    worker.addEventListener('message', function(e) {
    	  REGISTRY.parseSyncData(e.data);
    	}, false);
    
    
    console.log("asd " + test);
    init();
    animate();
    // Sets up the scene.
    function init() {
      // Create the scene and set the scene size.
      scene = new THREE.Scene();
      var WIDTH = window.innerWidth - 50,
          HEIGHT = 500;
      // Create a renderer and add it to the DOM.
      renderer = new THREE.WebGLRenderer({antialias:true});
      renderer.setSize(WIDTH, HEIGHT);
      document.body.appendChild(renderer.domElement);
      camera = new THREE.OrthographicCamera( 0, WIDTH, 200, -HEIGHT, 1, 1000 );
      
      camera.position.set(0,0,100);
      scene.add(camera);
      console.log(WIDTH);
      
      
      window.addEventListener('resize', function() {
    	  var WIDTH = window.innerWidth - 50,
          HEIGHT = window.innerHeight - 50;
        renderer.setSize(WIDTH, HEIGHT);
        camera.aspect = WIDTH / HEIGHT;
        camera.updateProjectionMatrix();
        
      });
      renderer.setClearColor();
     
      
  	document.addEventListener('keydown', function(e) {
  	    if (activeKey == e.keyCode) return;
  	    activeKey = e.keyCode;
  	    
  	    //left
  	    if (e.keyCode == 37) {
  	        dx = -1;
  	    }
  	    //top
  	    else if (e.keyCode == 38) {
  	        dy = 1;
  	    }
  	    //right
  	    else if (e.keyCode == 39) {
  	        dx = 1;
  	    }
  	    //bottom
  	    else if (e.keyCode == 40) {
  	        dy = -1;
  	    }
  	});
  	document.addEventListener('keyup', function(e) {
  	    switch (e.keyCode) {
  	        case 37: // left
  	        case 39: // right
  	            dx = 0;
  	            break;
  	            
  	        case 38: // up
  	        case 40: // down
  	            dy = 0;
  	            break;
  	    }
  	    
  	    activeKey = 0;
  	});
    }
    
    var start;
    var last;
    var timefix, oldX,oldY, updateX,updateY,text;
    
    function animate() {
    	
    	  REGISTRY.callOnRender(scene, renderer);
        renderer.render(scene, camera);   	   
        requestAnimationFrame( animate );
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
<body style="margin: 0;">
<div id="panel">TEST
</div>
<br>
<script type="javascript/worker">
var RedCube = function(id) {
    this.direction = false;
    this.type = 'redcube';
    if(typeof id === 'undefined') {
       this.entityId = this.generateId();
    }
    else {
        this.entityId = id;
    }
    this.lastX = 0;
    this.x = 0;
}
RedCube.prototype.getType = function() {
   return this.type;
}
RedCube.prototype.generateSyncPacket = function() {
   return {
            type: this.getType(),
            x : this.x
          };
}
RedCube.prototype.parseSyncPacket = function(syncpacket) {
   this.setPosition(syncpacket.x);
}
RedCube.prototype.generateId = function() {
    var d = new Date().getTime();
    if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
        d += performance.now(); //use high-precision timer if available
    }
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
}
RedCube.prototype.getEntityId = function() {
    return this.entityId;
}
RedCube.prototype.beforeDeath = function() {
}
RedCube.prototype.die = function() {
}
RedCube.prototype.setPosition = function(newpos) {
    this.lastX = this.x;
    this.x = newpos;
}
RedCube.prototype.getPosition = function() {
    return this.x;
}
RedCube.prototype.getLastX = function() {
    return this.lastX;
}
var EntityRegistry = function() {
   this.entities = {};
   this.types = {};
}
RedCube.prototype.onUpdate = function() {
    if(this.x > 500) {
       this.direction = true;
    }
    if(this.x <= 0) {
       this.direction = false;
    }
    this.x += !this.direction ? 20 : -20;
}
RedCube.prototype.onRender = function(scene, renderer) {
/// this is not a rendering thread. leave it empty
}
EntityRegistry.prototype.register = function(entity) {   
   this.entities[entity.getEntityId()] = entity;
   
}
EntityRegistry.prototype.remove = function(entity) {
   entity.beforeDeath();
   delete this.entities[entity.getEntityId()]
   entity.die();
}
EntityRegistry.prototype.registerType = function(name, entityClass) {
    this.types[name] = entityClass;
}
EntityRegistry.prototype.startEntity = function(entityId, syncpacket) {
    var entity = this.types[syncpacket.type](entityId);
    entity.parseSyncPacket(syncpacket);
    this.register(entity);
}
EntityRegistry.prototype.getSyncData = function() {
   var syncpacket = {};
   
   for(entityId in this.entities) {
      
      if(this.entities.hasOwnProperty(entityId)) {
          syncpacket[entityId] = this.entities[entityId].generateSyncPacket();
      }
   }
   return syncpacket;
}
EntityRegistry.prototype.callUpdate = function() {
  for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].onUpdate();
      }
   }
}
EntityRegistry.prototype.callOnRender = function(scene, renderer) {
  for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].onRender(scene, renderer);
      }
   }
}
EntityRegistry.prototype.parseSyncData = function(syncpacket) {
   for(entityId in syncpacket) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].parseSyncPacket(syncpacket[entityid]);
      }
      else {
          this.startEntity(syncpacket, entityId);
      }
   }
   return syncpacket;
}
var REGISTRY = new EntityRegistry();
var little_red = new RedCube();
REGISTRY.register(little_red);    
var x = 0;
var timefix = 0;
var last = 0;
var dx = 1;
var loopInterval = 0;
loopInterval = setInterval(function(){ 			
    REGISTRY.callUpdate()
		var msg = REGISTRY.getSyncData();
		self.postMessage(msg);
		
	}, 1000/60);
  
self.addEventListener('message', function(e) {
	  
	
}, false);
</script>
</body>

最新更新