我正在尝试使用常量fps获得一个好的动画,但没有任何效果。我正在使用threejs,webgl来渲染场景,对于动画循环,我发现了两种方式(有第三种吗?),它是requestAnimationFrame(...)或者是setTimeOut()。两者都不保证fps是常量,但是我通过使用window.performance.now()的timedelta更新对象位置来修复它。但是我仍然可以清楚地看到lagspikes。那么我该如何解决这个问题呢?显然可能是因为像厄运这样的游戏不会滞后。
我的完整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>
答案 0 :(得分:1)
问题在于游戏滴答声。
你需要的是线程。 一个渲染线程。 一个游戏线程。
对于游戏主题我建议一个webworker:
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
让游戏线程每隔50ms运行一次以更新游戏逻辑。它应该睡觉&#34;插图中。 你将东西发布回渲染线程,它会更新所有东西,并在50毫秒内将当前事件的轨迹提供给下一个pos。
tick 1. 0MS
tick 2. 50ms
修改强> 添加了如何利用渲染线程的示例代码。
基本上,游戏线程(webworker)中的对象与渲染线程中的对象相同。 唯一的区别是,渲染线程有渲染指令(onRender),游戏循环有更新指令(更新时)
所以它们是相同的,但也不同。
看看。
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 );
}
&#13;
<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>
&#13;
答案 1 :(得分:0)