I have what I believe is a fairly common setup for a ThreeJS scene that emulates a first person shooter world. I have this block of code in my animate loop, which came from one of the ThreeJS "controls" example. I can't remember which one since it's been a while.
const onObject = userIsLookingAtObjects.length > 0;
let delta = (currentTime - prevTime) / 1000;
// Limit long delay gaps, which indicate
// a background task interfered with us and
// we don't want the camera/user
const limitDelta = 0.2;
if (delta > limitDelta) {
delta = Math.min(limitDelta, delta);
console.warn(`${errPrefix}Capping delta time at: ${limitDelta}`);
}
velocity.x -= velocity.x * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
// ROS: The Number() constructor is simply being used
// to convert the TRUE/FALSE move<direction> values
// to a number in the following set of values: [-1, 0, 1]
direction.z = Number(moveForward) - Number(moveBackward);
direction.x = Number(moveRight) - Number(moveLeft);
direction.normalize(); // this ensures consistent movements in all directions
if (moveForward || moveBackward)
velocity.z -= direction.z * 400.0 * delta;
if (moveLeft || moveRight)
velocity.x -= direction.x * 400.0 * delta;
if (onObject === true) {
// ROS: This appears to be part of a check to allow jumping or
// not. See the raycast intersection code above involving
// onObject.
velocity.y = Math.max(0, velocity.y);
canJump = true;
if (g_BreakHerePlease)
// This aids tracing using Chrome DevTools. See pointerlock.js
// for the keystroke that sets g_BreakHerePlease to TRUE.
console.info(`${errPrefix}Set DevTools breakpoint here.`);
}
g_ThreeJsControls.moveRight(-velocity.x * delta);
g_ThreeJsControls.moveForward(-velocity.z * delta);
g_ThreeJsControls.getObject().position.y += (velocity.y * delta); // new behavior
if (g_ThreeJsControls.getObject().position.y < 10) {
velocity.y = 0;
g_ThreeJsControls.getObject().position.y = 10;
canJump = true;
}
Everything works fine, but I noticed something today. If one of my animation models is LERP'ing and I move the camera continuously, like when you "strafe" around an object in an FPS game, the object that is LERP'ing slows way down. So slow, that at first I thought that moving the camera actually stopped other animations. It doesn't, it just brings them to a snail's crawl.
What could be causing this? I'm hoping it's not inherent to moving the camera around a lot because "players" in my world will be moving constantly. Can I fix thi?
Related
I found out while debugging floats and stuff in Unity the values are sometimes off. I.e. I have a float called currentSpeed and when the player is moving this is a value and when he is not it is 0. The problem is when debugging this value while walking the following happens; i.e. 1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1. I found out this is happening with multiple values and Vectors. The weird thing is, when my framerate is lower it doesn't happen (i.e. while having the player selected in the inspector the framerate drops from 60 to 48). I'm using Unity 2018.3 and have no clue how to fix this. I hope someone can help me.
Edit (added code):
void Move(Vector2 inputDir, bool running)
{
if (inputDir != Vector2.zero)
{
// When the player is not pushing we can create a moving direction
if (!playerIsPushing)
{
float targetRotation = Mathf.Atan2(inputDir.x, inputDir.y) * Mathf.Rad2Deg + cameraT.eulerAngles.y;
transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref turnSmoothVelocity, GetModifiedSmoothTime(turnSmoothTime));
}
}
float targetSpeed = ((running) ? runSpeed : movementSpeed) * inputDir.magnitude;
currentSpeed = Mathf.SmoothDamp(currentSpeed, targetSpeed, ref speedSmoothVelocity, GetModifiedSmoothTime(speedSmoothTime));
velocityY += Time.deltaTime * gravity;
Vector3 velocity = transform.forward * currentSpeed + Vector3.up * velocityY;
// Here we cap the players velocity to a maximum speed. In this case the runSpeed. This means the player can never exceed the runSpeed.
if (velocity.x > runSpeed)
{
velocity.x = runSpeed;
}
if (velocity.x < -runSpeed)
{
velocity.x = -runSpeed;
}
if (velocity.z > runSpeed)
{
velocity.z = runSpeed;
}
if (velocity.z < -runSpeed)
{
velocity.z = -runSpeed;
}
controller.Move(velocity * Time.deltaTime);
currentSpeed = new Vector2(controller.velocity.x, controller.velocity.z).magnitude;
// Debugging the players velocity
Debug.Log(currentSpeed);
float animationSpeedPercent = ((running) ? currentSpeed / runSpeed : currentSpeed / movementSpeed * .5f);
Edit 2: I have tested some more and found out my pushforce isn't framerate independant. During pushing an object in my game I see the weird values while debugging i.e. 1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1. I'm working with a CharacterController so adding force to a rigidbody is done via script.
void OnControllerColliderHit(ControllerColliderHit hit)
{
Rigidbody body = hit.collider.attachedRigidbody;
if (body == null || body.isKinematic)
return;
if (hit.moveDirection.y < -.3f)
return;
Vector3 pushDirection = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
body.velocity = pushForce * pushDirection;
}
Might this be the issue for the weird values?
I have a line geometry made with three.js, which I want to move as a spermatozoid (i.e. the head moves first, then all of the points along the tail move accordingly to the head) using GLSL.
Here is a visual representation of what I want:
So as you can see, point G eases to (follows) H, which in turn follows point E, which in turn follows the head.
I have a working examples, being animated on the CPU. Here is the code:
class Boid {
constructor (position) {
this.position = position
this.speed = 0.0009 + Math.random() * 0.0003
this.pointsNum = 12
this.points = []
this.line = null
this.angle = Math.random() * 360
for (let i = 0; i < this.pointsNum; i += 1) {
this.points.push(new THREE.Vector3(1, 1, 1))
}
this.angle = 0
}
update (target, time) {
if (time) {
this.line.geometry.verticesNeedUpdate = true
this.line.geometry.vertices.forEach((p, i) => {
let nextP = this.line.geometry.vertices[i + 1]
if (nextP) {
// if it's not the HEAD point, follow the next point in the geometry vertices
p.x += (nextP.x - p.x) * (time * 8.0)
p.y += (nextP.y - p.y) * (time * 8.0)
p.z += (nextP.z - p.z) * (time * 8.0)
} else {
// if the point is in fact the head, ease it according to some random moving point in our scene (target)
p.x += (target.x - p.x) * time
p.y += (target.y - p.y) * time
p.z += (target.z - p.z) * time
}
})
}
}
}
And here is a working example.
This technique is working, but would like to accomplish the same stuff with GLSL. My question is how should I approach it? Should I pass the next vertex position to the previous one and ease in my vertex shader? How should I keep track of the next's point position?
Any help is more then appreciated, I have been thinking about this a lot without any success.
Is the intent of the TrackballControl to have a "border" outside the trackball that induces roll? I personally dislike it. It is a bit discontinuous, and does't really have a lot of purpose (imho).
If not, the function getMouseProjectionOnBall can be changed similar to the following. This does two things (not necessarily "correctly"):
Normalize the radius to fill both axis
Map z values outside of the ball (ie where z was previously 0)
I find this a lot more natural, personally.
Thoughts?
this.getMouseProjectionOnBall = function(clientX, clientY) {
var xnormalized = (clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft) / (_this.screen.width / 2.0);
var ynormalized = (_this.screen.height * 0.5 + _this.screen.offsetTop - clientY) / (_this.screen.height / 2.0);
var mouseOnBall = new THREE.Vector3(
xnormalized,
ynormalized,
0.0
);
var length = mouseOnBall.length();
var ballRadius = 1.0; // As a fraction of the screen
if (length > ballRadius * 0.70710678118654752440) {
var temp = ballRadius / 1.41421356237309504880;
mouseOnBall.z = temp * temp / length;
// Remove old method.
// This Left z = 0, which meant rotation axis
// becomes z, which is a roll
//mouseOnBall.normalize();
} else {
mouseOnBall.z = Math.sqrt(1.0 - length * length);
}
_eye.copy(_this.object.position).sub(_this.target);
var projection = _this.object.up.clone().setLength(mouseOnBall.y);
projection.add(_this.object.up.clone().cross(_eye).setLength(mouseOnBall.x));
projection.add(_eye.setLength(mouseOnBall.z));
return projection;
};
I want to control my 3D model, move it every direction but I don't know way to do this. Anybody have any idea ?
You only have to provide to the effect the model's world transform.
Matrix World = Matrix.CreateWorld(position, forward, up);
In your update method you can modify the position:
LastPosition = Position;
if (IsKeyDonw(Left) Position -= Vector3.UnitX * Speed * ElapsedTime; ForwardDirty = true;
if (IsKeyDonw(Right) Position += Vector3.UnitX * Speed * ElapsedTime; ForwardDirty = true;
if (IsKeyDonw(Up) Position -= Vector3.UnitZ * Speed * ElapsedTime; ForwardDirty = true;
if (IsKeyDonw(Down) Position += Vector3.UnitZ * Speed * ElapsedTime; ForwardDirty = true;
// the forward is the direction where will point your model.
if (ForwardDirty) {
Forward = Position - LastPosition;
Forward.Normalize();
ForwardDirty = false;
}
You also can base your movement in forward vector, or smooth the angle change interpolating the final forward with the current,...
There are many topics like this, but none with concrete answers. I am drawing a tile-map in the traditional way (two for loops) and keeping my player centered except when the edges of the map is reached. How would I create collision detection? I need to know how to translate tile location in the array to screen coordinates I think.
I will give you the code i wrote for point/tilemap collision detection. The code assumes that you have a point at (xfrom, yfrom) and you want to move it to (xto, yto) and want to see if there is a collision with a block in the tilemap map[Y][X]. I assume a method isSolid(tileId) which will return true if the tile is solid.
/**
* This method returns true if there is a collision between a point and a 2D tilemap map[Y][X].
* The isSolid method must be implemented to indicate if a tile is solid or not
* Assumes the tilemap starts at (0,0) and TILEWIDTH and TILEHEIGHT hold the size of a tile (in pixels)
* #param xfrom the original x-coordinate of the point
* #param yfrom the original y-coordinate of the point
* #param xto the destination x-coordinate of the point
* #param yto the destination y-coordinate of the point
* #param outCollisionPoint output the location where the collision occurs
* #return true if a collision is found
*/
public boolean collisionDetection(int xfrom, int yfrom, int xto, int yto, Point outCollisionPoint){
//Ref: A fast voxel traversal algorithm J.Amanatides, A. Woo
float tMaxX, tMaxY, tDeltaX, tDeltaY, collisionLength;
int X, Y, stepX, stepY, endX, endY, blkX, blkY;
//Calculate direction vector
float dirX = (xto - xfrom);
float dirY = (yto - yfrom);
float length = (float) Math.sqrt(dirX * dirX + dirY * dirY);
//Normalize direction vector
dirX /= length;
dirY /= length;
//tDeltaX: distance in terms of vector(dirX,dirY) between two consecutive vertical lines
tDeltaX = TILEWIDTH / Math.abs(dirX);
tDeltaY = TILEHEIGHT / Math.abs(dirY);
//Determine cell where we originally are
X = xfrom / TILEWIDTH;
Y = yfrom / TILEHEIGHT;
endX = xto / TILEWIDTH;
endY = yto / TILEHEIGHT;
//stepX: Determine in what way do we move between cells
//tMaxX: the distance in terms of vector(dirX,dirY) to the next vertical line
if (xto > xfrom){
blkX = 0;
stepX = 1;
tMaxX = ((X+1) * TILEWIDTH - xfrom) / dirX;
}else{
blkX = 1;
stepX = -1;
tMaxX = (X * TILEWIDTH - xfrom) / dirX;
}
if (yto > yfrom){
blkY = 0;
stepY = 1;
tMaxY = ((Y+1) * TILEHEIGHT - yfrom) / dirY;
}else{
blkY = 1;
stepY = -1;
tMaxY = (Y * TILEHEIGHT - yfrom) / dirY;
}
if (isSolid(map[Y][X])) {
//point already collides
outCollisionPoint = new Point(xfrom, yfrom);
return true;
}
//Scan the cells along the line between 'from' and 'to'
while (X != endX || Y !=endY){
if(tMaxX < tMaxY){
tMaxX += tDeltaX;
X += stepX;
if (isSolid(map[Y][X])) {
collisionLength = ((X + blkX) * TILEWIDTH - xfrom) / dirX;
outCollisionPoint = new Point((int)(xfrom + dirX * collisionLength), (int)(yfrom + dirY * collisionLength));
return true;
}
}else{
tMaxY += tDeltaY;
Y += stepY;
if (isSolid(map[Y][X])) {
collisionLength= ((Y + blkY) * TILEHEIGHT - yfrom) / dirY;
outCollisionPoint = new Point((int)(xfrom + dirX * collisionLength), (int)(yfrom + dirY * collisionLength));
return true;
}
}
}
return false;
}
It depends on the model.
If your model (the data) is a grid, then a collision occurs simply when two incompatible objects occupy the same location. The easiest way to handle this type of collision is just to make sure where you are trying to move a game entity to is "available". If it is, no collision, and update the model. If it wasn't free, then there was a collision.
The screen simply renders the model. With the exception of something like of per-pixel collision detection (think the original lemmings or worms), don't use it for collision detection.
The screen/view is just the rendering agent. While you can have the model tied tightly to the screen (e.g. you only need to update parts of the screen in which things have changed such as when a piece is moved), the screen is not, and should not, generally be considered part of the model. However, with modern computing speed, you might as well simple re-render the entire visible model each frame.
(Yes, I know I repeated myself. It was on purpose.)
Now, to answer the secondary question not mentioned in the title:
When you start rendering, simply draw screen_width/cell_width/2 cells to the left and screen_width/cell_width/2 cells to the right of the player (the player is assumed to take 1x1). Do the same for the up-and-down. Make sure to not cause an Index-Out-Of-Bounds exception. You can run the for-loops with out-of-bounds values, as long long as you clamp/filter before using them. If you wish to only make the character "push" the edge when he gets close, keep track of a current model-to-view reference as well.