How do I simply prevent my player from moving beyond my playing area?

Hi,

By use this model, i would like how to prevent my player moving beyond my playing area (=> floor) ?

I use a function like this :

if (mesh.position.x > size_floor*.5){
//block the possibility to go up
}

The problem is that my mesh move with the possibility of changing the perspective with orbit.

So how do i recognize the movement that i must blocked ?

Thanks.

I update my codepen to see how i do to prevent my player moving beyond my playing area.
I success to block my player but after my player pass trough the area. Could you help me ?

import * as THREE from "https://threejs.org/build/three.module.js";
import {
	OrbitControls
} from "https://threejs.org/examples/jsm/controls/OrbitControls.js";


// vars
let fwdValue = 0;
let bkdValue = 0;
let rgtValue = 0;
let lftValue = 0;
let tempVector = new THREE.Vector3();
let upVector = new THREE.Vector3(0, 1, 0);
let joyManager;

// for block movement
var forward_blocked = false;
var backward_blocked = false;
var left_blocked = false;
var right_blocked = false;

var width = window.innerWidth,
	height = window.innerHeight;

// Create a renderer and add it to the DOM.
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);
// Create the scene 
var scene = new THREE.Scene();
// Create a camera
var camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 10000);
camera.position.z = 50;
camera.position.y = 50;

scene.add(camera);

// Create a light, set its position, and add it to the scene.
var light = new THREE.PointLight(0xffffff);
light.position.set(-100, 200, 100);
scene.add(light);

// Add OrbitControls so that we can pan around with the mouse.
var controls = new OrbitControls(camera, renderer.domElement);
controls.maxDistance = 100;
controls.minDistance = 100;
//controls.maxPolarAngle = (Math.PI / 4) * 3;
controls.maxPolarAngle = Math.PI / 2;
controls.minPolarAngle = 0;
controls.autoRotate = false;
controls.autoRotateSpeed = 0;
controls.rotateSpeed = 0.4;
controls.enableDamping = false;
controls.dampingFactor = 0.1;
controls.enableZoom = false;
controls.enablePan = false;
controls.minAzimuthAngle = -Math.PI / 2; // radians
controls.maxAzimuthAngle = Math.PI / 4 // radians

// Add axes
var axes = new THREE.AxesHelper(50);
scene.add(axes);

// Add grid
const size = 500;
const divisions = 30;

//variables for floor
let size_floor = 100
let dim_floor = size_floor * .5


const gridHelper = new THREE.GridHelper(size, divisions);
scene.add(gridHelper);

var geometry = new THREE.BoxGeometry(5, 5, 5);
var cubeMaterial = new THREE.MeshNormalMaterial();

var mesh = new THREE.Mesh(geometry, cubeMaterial);
scene.add(mesh);

//parameters for movement
mesh.is_block_direction = true


//var ground = new Object3D()
var geometry_floor = new THREE.BoxGeometry(size_floor, 1, size_floor)
var material_floor = new THREE.MeshPhongMaterial({

	color: 0xfff000,

});

var floor = new THREE.Mesh(geometry_floor, material_floor);
floor.position.y = -5;
//ground.add(floor)
scene.add(floor)
//floor.rotation.x = -Math.PI / 2

resize();
animate();
window.addEventListener('resize', resize);

// added joystick + movement
addJoystick();

function resize() {
	let w = window.innerWidth;
	let h = window.innerHeight;

	renderer.setSize(w, h);
	camera.aspect = w / h;
	camera.updateProjectionMatrix();
}

// Renders the scene
function animate() {

	updatePlayer();
	renderer.render(scene, camera);
	controls.update();

	requestAnimationFrame(animate);
}


function updatePlayer() {
	// move the player
	const angle = controls.getAzimuthalAngle()

	if (fwdValue > 0 && forward_blocked == false) {
		tempVector
			.set(0, 0, -fwdValue)
			.applyAxisAngle(upVector, angle)
		mesh.position.addScaledVector(
			tempVector,
			1
		)
	}

	if (bkdValue > 0 && backward_blocked == false) {
		tempVector
			.set(0, 0, bkdValue)
			.applyAxisAngle(upVector, angle)
		mesh.position.addScaledVector(
			tempVector,
			1
		)
	}

	if (lftValue > 0 && left_blocked == false) {
		tempVector
			.set(-lftValue, 0, 0)
			.applyAxisAngle(upVector, angle)
		mesh.position.addScaledVector(
			tempVector,
			1
		)
	}

	if (rgtValue > 0 && right_blocked === false) {
		tempVector
			.set(rgtValue, 0, 0)
			.applyAxisAngle(upVector, angle)
		mesh.position.addScaledVector(
			tempVector,
			1
		)
	}


	mesh.updateMatrixWorld()

	// for control player in area and avoid player outside area
	check_if_in_area()

	// reposition camera
	camera.position.sub(controls.target)
	controls.target.copy(mesh.position)
	camera.position.add(mesh.position)


};


function check_if_in_area() {
	if (mesh.position.x > -dim_floor && mesh.position.x < dim_floor && mesh.position.z > -dim_floor && mesh.position.z < dim_floor) {
		console.log("in area")
		reset_directions()
	} else {
		console.log("not in area")
		block_direction()
	}
}

function reset_directions() {
	forward_blocked = false
	backward_blocked = false
	left_blocked = false
	right_blocked = false
	mesh.is_block_direction = true
}

function block_direction() {
	if (mesh.is_block_direction == true) {
		mesh.is_block_direction = false;
		if (forward > 0) {
			forward_blocked = true
			fwdValue = 0
		} else {
			console.log("bas")
			backward_blocked = true
			bkdValue = 0;
		}
		if (turn > 0) {
			right_blocked = true
			console.log("droite")
			rgtValue = 0;
		} else {
			console.log("gauche")
			left_blocked = true
			lftValue = 0;
		}
	}
}

function addJoystick() {
	const options = {
		zone: document.getElementById('joystickWrapper1'),
		size: 120,
		multitouch: true,
		maxNumberOfNipples: 2,
		mode: 'static',
		restJoystick: true,
		shape: 'circle',
		// position: { top: 20, left: 20 },
		position: {
			top: '60px',
			left: '60px'
		},
		dynamicPage: true,
	}


	joyManager = nipplejs.create(options);

	joyManager['0'].on('move', function (evt, data) {
		const forward = data.vector.y
		const turn = data.vector.x

		if (forward > 0) {
			fwdValue = Math.abs(forward)
			bkdValue = 0
		} else if (forward < 0) {
			fwdValue = 0
			bkdValue = Math.abs(forward)
		}

		if (turn > 0) {
			lftValue = 0
			rgtValue = Math.abs(turn)
		} else if (turn < 0) {
			lftValue = Math.abs(turn)
			rgtValue = 0
		}
	})

	joyManager['0'].on('end', function (evt) {
		bkdValue = 0
		fwdValue = 0
		lftValue = 0
		rgtValue = 0
	})

}

Success ! hope it help beginners like me :wink:

1 Like

I think there’s still a loophole there.
If you go diagonally to the corner, you can leave the playing field.

Some time ago I did something similar.
Circular control used for walkable areas control

1 Like

@espace3d Maybe .clamp() will help.

Ok, i understand the problem. If my player is oriented, it is not simply backward,forward,right or left that i must block. I have to block the direction according to the smallest vector compared to the concerned limit, cfr my drawing.

This is surely what you mention via Clamp … but I do not see how to implement it?
Could I have a concrete example please?

Hi,

I have find this example who works good :

var UNITWIDTH = 90; // Width of a cubes in the maze
var UNITHEIGHT = 15; // Height of the cubes in the maze
var PLAYERSPEED = 600.0; // How fast the player moves
var PLAYERCOLLISIONDISTANCE = 20; // How far away the player can get from collidabel objects

var clock;
var loader = new THREE.JSONLoader();
var camera, controls, scene, renderer;
var mapSize;

var totalCubesWide;
var collidableObjects = [];

// Flag to determine if the player can move and look around
var controlsEnabled = false;

// Flags to determine which direction the player is moving
var moveForward = false;
var moveBackward = false;
var moveLeft = false;
var moveRight = false;

// Velocity vectors for the player and 
var playerVelocity = new THREE.Vector3();

// HTML elements to be changed
var blocker = document.getElementById('blocker');
var instructions = document.getElementById('instructions');

// Take control of the mouse for controls
getPointerLock();
// Set up the game
init();

function getPointerLock() {
  document.onclick = function () {
    container.requestPointerLock();
  }

  document.addEventListener('pointerlockchange', lockChange, false);
}

function lockChange() {
  if (document.pointerLockElement === container) {
    blocker.style.display = "none";
    controls.enabled = true;
  } else {
    blocker.style.display = "";
    controls.enabled = false;
  }
}

// Set up the game
function init() {

  // Set clock to keep track of frames
  clock = new THREE.Clock();
  // Create the scene where everything will go
  scene = new THREE.Scene();

  // Set render settings
  renderer = new THREE.WebGLRenderer();
  renderer.setClearColor(scene.fog.color);
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);

  // Render to the container
  var container = document.getElementById('container');
  container.appendChild(renderer.domElement);

  // Set camera position and view details
  camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000);
  camera.position.y = 20; // Height the camera will be looking from
  camera.position.x = 0;
  camera.position.z = 0;

  // Add the camera to the controller, then add to the scene
  controls = new THREE.PointerLockControls(camera);
  scene.add(controls.getObject());

  // Check to see if keys are being pressed to move the player
  listenForPlayerMovement();

  // Add ground plane
  createGround();
  // Add boundry walls that surround the maze
  createPerimWalls();

  // Call the animate function so that animation begins after the model is loaded
  animate();
};

// Listen for if the window changes sizes
window.addEventListener('resize', onWindowResize, false);

// Add event listeners for player movement key presses
function listenForPlayerMovement() {
  // Listen for when a key is pressed
  // If it's a specified key, mark the direction as true since moving
  var onKeyDown = function (event) {
    switch (event.keyCode) {
      case 38: // up
      case 87: // w
        moveForward = true;
        break;

      case 37: // left
      case 65: // a
        moveLeft = true;
        break;

      case 40: // down
      case 83: // s
        moveBackward = true;
        break;

      case 39: // right
      case 68: // d
        moveRight = true;
        break;
    }
  };

  // Listen for when a key is released
  // If it's a specified key, mark the direction as false since no longer moving
  var onKeyUp = function (event) {
    switch (event.keyCode) {
      case 38: // up
      case 87: // w
        moveForward = false;
        break;

      case 37: // left
      case 65: // a
        moveLeft = false;
        break;

      case 40: // down
      case 83: // s
        moveBackward = false;
        break;

      case 39: // right
      case 68: // d
        moveRight = false;
        break;
    }
  };

  // Add event listeners for when movement keys are pressed and released
  document.addEventListener('keydown', onKeyDown, false);
  document.addEventListener('keyup', onKeyUp, false);
}

// Create the ground plane that the maze sits on top of
function createGround() {
  // Create the ground based on the map size the matrix/cube size produced
  // mapSize = totalCubesWide * UNITWIDTH;
  mapSize = 100
  // ground
  var groundGeo = new THREE.PlaneGeometry(mapSize, mapSize);
  var groundMat = new THREE.MeshPhongMaterial({
    color: 0xA0522D,
    side: THREE.DoubleSide,
    shading: THREE.FlatShading
  });

  var ground = new THREE.Mesh(groundGeo, groundMat);
  ground.position.set(0, 1, 0);
  ground.rotation.x = degreesToRadians(90);
  scene.add(ground);
}

// Make the four perimeter walls for the maze
function createPerimWalls() {
  var halfMap = mapSize / 2;  // Half the size of the map
  var sign = 1;               // Used to make an amount positive or negative

  // Loop through twice, making two perimeter walls at a time
  for (var i = 0; i < 2; i++) {
    var perimGeo = new THREE.PlaneGeometry(mapSize, UNITHEIGHT);
    // Make the material double sided
    var perimMat = new THREE.MeshPhongMaterial({
      color: 0x464646,
      transparent: true,
      opacity: 0,
      side: THREE.DoubleSide
    });
    // Make two walls
    var perimWallLR = new THREE.Mesh(perimGeo, perimMat);
    var perimWallFB = new THREE.Mesh(perimGeo, perimMat);

    // Create left/right walls
    perimWallLR.position.set(halfMap * sign, UNITHEIGHT / 2, 0);
    perimWallLR.rotation.y = degreesToRadians(90);
    scene.add(perimWallLR);
    collidableObjects.push(perimWallLR);
    // Create front/back walls
    perimWallFB.position.set(0, UNITHEIGHT / 2, halfMap * sign);
    scene.add(perimWallFB);
    collidableObjects.push(perimWallFB);

    sign = -1; // Swap to negative value
  }
}

// Update the camera and renderer when the window changes size
function onWindowResize() {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);

}

function animate() {
  render();
  requestAnimationFrame(animate);
  // Get the change in time between frames
  var delta = clock.getDelta();
  // Update our frames per second monitor

  animatePlayer(delta);
}

// Render the scene
function render() {
  renderer.render(scene, camera);
}

function animatePlayer(delta) {
  // Gradual slowdown
  playerVelocity.x -= playerVelocity.x * 10.0 * delta;
  playerVelocity.z -= playerVelocity.z * 10.0 * delta;

  // If no collision and a movement key is being pressed, apply movement velocity
  if (detectPlayerCollision() == false) {
    if (moveForward) {
      playerVelocity.z -= PLAYERSPEED * delta;
    }
    if (moveBackward) {
      playerVelocity.z += PLAYERSPEED * delta;
    }
    if (moveLeft) {
      playerVelocity.x -= PLAYERSPEED * delta;
    }
    if (moveRight) {
      playerVelocity.x += PLAYERSPEED * delta;
    }

    controls.getObject().translateX(playerVelocity.x * delta);
    controls.getObject().translateZ(playerVelocity.z * delta);
  } else {
    // Collision or no movement key being pressed. Stop movememnt
    playerVelocity.x = 0;
    playerVelocity.z = 0;
  }
}

//  Determine if the player is colliding with a collidable object
function detectPlayerCollision() {
  // The rotation matrix to apply to our direction vector
  // Undefined by default to indicate ray should coming from front
  var rotationMatrix;
  // Get direction of camera
  var cameraDirection = controls.getDirection(new THREE.Vector3(0, 0, 0)).clone();
  console.log(cameraDirection, "cameraDirection")
  // Check which direction we're moving (not looking)
  // Flip matrix to that direction so that we can reposition the ray
  if (moveBackward) {
    rotationMatrix = new THREE.Matrix4();
    rotationMatrix.makeRotationY(degreesToRadians(180));
  }
  else if (moveLeft) {
    rotationMatrix = new THREE.Matrix4();
    rotationMatrix.makeRotationY(degreesToRadians(90));
  }
  else if (moveRight) {
    rotationMatrix = new THREE.Matrix4();
    rotationMatrix.makeRotationY(degreesToRadians(270));
  }

  // Player is moving forward, no rotation matrix needed
  if (rotationMatrix !== undefined) {
    cameraDirection.applyMatrix4(rotationMatrix);
  }

  // Apply ray to player camera
  var rayCaster = new THREE.Raycaster(controls.getObject().position, cameraDirection);
  console.log(controls.getObject().position, "obejct ")

  // If our ray hit a collidable object, return true
  if (rayIntersect(rayCaster, PLAYERCOLLISIONDISTANCE)) {
    return true;
  } else {
    return false;
  }
}

function rayIntersect(ray, distance) {
  var intersects = ray.intersectObjects(collidableObjects);
  for (var i = 0; i < intersects.length; i++) {
    if (intersects[i].distance < distance) {
      return true;
    }
  }
  return false;
}


// Converts degrees to radians
function degreesToRadians(degrees) {
  return degrees * Math.PI / 180;
}

// Converts radians to degrees
function radiansToDegrees(radians) {
  return radians * 180 / Math.PI;
}

I try to implement to my project.
The problem is only with this two variables :

`var cameraDirection = controls.getDirection(new THREE.Vector3(0, 0, 0)).clone();`
	 var rayCaster = new THREE.Raycaster(camera.getObject().position, cameraDirection);

these variables are implement with pointerlock controls in the first example and i don’t know how to transfer this with my example where the cube (mesh) is moving with controls ?

Could you help me ? And i think that this solution would be good for me…

I purposely commented this line in my codepen so as not to have any errors. The code therefore does not work for the moment to generate collisions with my peripheral walls.

// if (detectPlayerCollision() == false) {

Now I have more or less managed to implement it but the code doesn’t work all the time, sometimes my player can get out of the area

What am I forgetting?

Is the navigation area always going to be a rectangle? If not, you may find it easier to use something like three-pathfinding and its clampStep method.

2 Likes

Thanks @donmccurdy but your solutions seems to be complex to implement…for me beginner :slight_smile:

I think another way is to create a dummy object of my mesh and test if the movement could move my mesh out of my rectangular area or not, and if not allow movement

if(pointX > rectX && pointX < rectX + rectWidth){
	if(pointY > rectY && pointY < rectY + rectHeight){
		//the point is inside the rectangle	
	}
}

YES and YES i success with my method above :slight_smile: :slight_smile: :slight_smile:

To share.

2 Likes

This is the approach I used for the rectangle as well. It’s similar for a circle and a triangle, if that’s what you need.

See function withinWalkableAreas( ... ) ...
https://hofk.de/main/threejs/showroom/AreaControl.js
used in AreaControl

1 Like

@hofk Waouw your snippet is great ! It’s powerfull abstract and solve the job ;).