Skip to content

Commit

Permalink
Merge pull request #155 from candirugame/feat-controller-aim-assist
Browse files Browse the repository at this point in the history
feat: aim assist
  • Loading branch information
AustinJMann authored Nov 28, 2024
2 parents 6ff1140 + 4284c12 commit 91f8e1a
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 28 deletions.
62 changes: 46 additions & 16 deletions src/core/RemotePlayerRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
import { Player } from './Player.ts';
import {acceleratedRaycast, computeBoundsTree} from "three-mesh-bvh";
import {CollisionManager} from "../input/CollisionManager.ts";

THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
THREE.Mesh.prototype.raycast = acceleratedRaycast;
Expand All @@ -27,6 +26,7 @@ interface PlayerToRender {
id: number;
object: THREE.Object3D;
objectUUID: string;
sphere: THREE.Object3D;
nameLabel: THREE.Sprite;
name: string;
}
Expand All @@ -38,9 +38,12 @@ export class RemotePlayerRenderer {
private loader: GLTFLoader;
private dracoLoader: DRACOLoader;

private sphere: THREE.Mesh;
private sphereScene: THREE.Scene;

private raycaster: THREE.Raycaster;
private camera: THREE.Camera;
private scene: THREE.Scene;
private scene: THREE.Scene

private isAnimating: { [id: number]: boolean };
private animationPhase: { [id: number]: number };
Expand All @@ -53,6 +56,7 @@ export class RemotePlayerRenderer {
private localPlayer: Player;
private deltaTime: number = 0; // Initialize deltaTime to avoid Deno error
private static minVelocityToAnimate = 0.1;
private static map: THREE.Mesh = new THREE.Mesh();

private crosshairVec = new THREE.Vector2();

Expand All @@ -71,6 +75,10 @@ export class RemotePlayerRenderer {

this.entityScene = new THREE.Scene();

this.sphere = new THREE.Mesh(new THREE.SphereGeometry(.6), new THREE.MeshBasicMaterial({color: 0xffffff}));
this.sphere.geometry.computeBoundsTree();
this.sphereScene = new THREE.Scene();

this.loader = new GLTFLoader();
this.dracoLoader = new DRACOLoader();
this.dracoLoader.setDecoderPath('/draco/');
Expand Down Expand Up @@ -126,7 +134,7 @@ export class RemotePlayerRenderer {

const existingPlayer = this.playersToRender.find((player) => player.id === remotePlayer.id);
if (existingPlayer) {
this.updatePlayerPosition(existingPlayer.object, playerDataWithQuaternion);
this.updatePlayerPosition(existingPlayer.object, existingPlayer.sphere, playerDataWithQuaternion);
} else {
this.addNewPlayer(playerDataWithQuaternion);
}
Expand All @@ -135,7 +143,7 @@ export class RemotePlayerRenderer {
this.removeInactivePlayers(remotePlayerData);
}

private updatePlayerPosition(playerObject: THREE.Object3D, remotePlayerData: RemotePlayerData): void {
private updatePlayerPosition(playerObject: THREE.Object3D, playerSphere: THREE.Object3D, remotePlayerData: RemotePlayerData): void {
const velocity = Math.sqrt(
Math.pow(remotePlayerData.velocity.x, 2) +
Math.pow(remotePlayerData.velocity.y, 2) +
Expand Down Expand Up @@ -189,6 +197,7 @@ export class RemotePlayerRenderer {

// Set playerObject.position to groundTruthPosition.clone()
playerObject.position.copy(groundTruthPosition);
playerSphere.position.copy(groundTruthPosition.clone());

// Apply animation offsets (e.g., yOffset)
if (this.isAnimating[playerId]) {
Expand All @@ -199,6 +208,7 @@ export class RemotePlayerRenderer {
const yOffset = amplitude * (1 + Math.cos(this.animationPhase[playerId]));

playerObject.position.y += yOffset;
playerSphere.position.y += yOffset;
this.lastRunningYOffset[playerId] = yOffset;

if (velocity <= RemotePlayerRenderer.minVelocityToAnimate && Math.cos(this.animationPhase[playerId]) <= 0) {
Expand Down Expand Up @@ -256,6 +266,7 @@ export class RemotePlayerRenderer {

private addNewPlayer(remotePlayerData: RemotePlayerData): void {
const object = this.possumMesh!.clone();
const sphere = this.sphere.clone();

// Create a text sprite for the player's name
const nameLabel = this.createTextSprite(remotePlayerData.name.toString());
Expand All @@ -264,12 +275,14 @@ export class RemotePlayerRenderer {
id: remotePlayerData.id,
object: object,
objectUUID: object.uuid,
sphere: sphere,
nameLabel: nameLabel,
name: remotePlayerData.name,
};

this.playersToRender.push(newPlayer);
this.entityScene.add(newPlayer.object);
this.sphereScene.add(newPlayer.sphere);
this.entityScene.add(newPlayer.nameLabel);

// Initialize groundTruthPosition for the new player
Expand All @@ -286,6 +299,7 @@ export class RemotePlayerRenderer {
if (!isActive) {
this.entityScene.remove(player.object);
this.entityScene.remove(player.nameLabel);
this.sphereScene.remove(player.sphere);
// Remove associated data for the player
delete this.groundTruthPositions[player.id];
delete this.isAnimating[player.id];
Expand Down Expand Up @@ -367,14 +381,31 @@ export class RemotePlayerRenderer {
private getPlayersInCrosshairWithWalls(): THREE.Object3D[] {
this.raycaster.setFromCamera(this.crosshairVec, this.camera);

const geom = CollisionManager.getColliderGeom();
const map = new THREE.Mesh(geom);
const mapobj = new THREE.Object3D;
mapobj.add(map);

const playerIntersects = this.raycaster.intersectObjects(this.entityScene.children);
this.raycaster.firstHitOnly = true;
const wallIntersects = this.raycaster.intersectObjects([mapobj]);
const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
this.raycaster.firstHitOnly = false;

const filteredIntersects = playerIntersects.filter((playerIntersect) => {
for (const wallIntersect of wallIntersects) {
if (wallIntersect.distance < playerIntersect.distance) {
return false;
}
}
return true;
});

return filteredIntersects.map((intersect) => intersect.object);
}

public getPlayerSpheresInCrosshairWithWalls(): THREE.Object3D[] {
this.raycaster.setFromCamera(this.crosshairVec, this.camera);

this.sphereScene.updateMatrixWorld();

this.raycaster.firstHitOnly = true;
const playerIntersects = this.raycaster.intersectObjects(this.sphereScene.children);
const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
this.raycaster.firstHitOnly = false;

const filteredIntersects = playerIntersects.filter((playerIntersect) => {
Expand All @@ -396,15 +427,10 @@ export class RemotePlayerRenderer {
// Set the raycaster with the offset direction
this.raycaster.set(this.camera.position, offsetDirection);

const geom = CollisionManager.getColliderGeom();
const map = new THREE.Mesh(geom);
const mapobj = new THREE.Object3D;
mapobj.add(map);

// Intersect with all potential targets (players and walls)
const playerIntersects = this.raycaster.intersectObjects(this.playersToRender.map(p => p.object), true);
this.raycaster.firstHitOnly = true;
const wallIntersects = this.raycaster.intersectObjects([mapobj]);
const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
this.raycaster.firstHitOnly = false;

// Filter player intersections based on wall intersections
Expand Down Expand Up @@ -457,4 +483,8 @@ export class RemotePlayerRenderer {
}
return null;
}

public static setMap(map: THREE.Mesh) {
this.map = map;
}
}
4 changes: 4 additions & 0 deletions src/core/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ export class Renderer {
return this.remotePlayerRenderer.getShotVectorsToPlayersInCrosshair();
}

public getPlayerSpheresInCrosshairWithWalls() {
return this.remotePlayerRenderer.getPlayerSpheresInCrosshairWithWalls();
}

public getShotVectorsToPlayersWithOffset(yawOffset: number, pitchOffset: number): { playerID: number, vector: THREE.Vector3, hitPoint: THREE.Vector3 }[] {
return this.remotePlayerRenderer.getShotVectorsToPlayersWithOffset(yawOffset, pitchOffset);
}
Expand Down
14 changes: 6 additions & 8 deletions src/input/CollisionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as THREE from 'three';
import { Player } from '../core/Player.ts';
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, StaticGeometryGenerator, MeshBVH } from 'three-mesh-bvh';
import { InputHandler } from "./InputHandler.ts";
import { RemotePlayerRenderer} from "../core/RemotePlayerRenderer.ts";

THREE.Mesh.prototype.raycast = acceleratedRaycast;
THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
Expand Down Expand Up @@ -29,7 +30,7 @@ export class CollisionManager {
this.deltaVec = new THREE.Vector3();
this.prevPosition = new THREE.Vector3();
this.triNormal = new THREE.Vector3();
this.upVector = new THREE.Vector3(0,1,0)
this.upVector = new THREE.Vector3(0, 1, 0)
this.coyoteTime = 0;
this.jumped = false;
this.collided = false;
Expand All @@ -39,11 +40,11 @@ export class CollisionManager {
if (!CollisionManager.mapLoaded || !CollisionManager.colliderGeom || !CollisionManager.colliderGeom.boundsTree) return; // Add checks
let deltaTime: number = this.clock.getDelta();
let steps: number = 1;
while (deltaTime >= 1/120) {
while (deltaTime >= 1 / 120) {
deltaTime = deltaTime / 2
steps = steps * 2;
}
for (let i = 0; i < steps; i ++) {
for (let i = 0; i < steps; i++) {
this.physics(localPlayer, deltaTime);
}
}
Expand Down Expand Up @@ -129,6 +130,7 @@ export class CollisionManager {
staticGenerator.attributes = ['position'];
this.colliderGeom = staticGenerator.generate();
this.colliderGeom.computeBoundsTree({maxDepth: 1000000, maxLeafTris: 4});
RemotePlayerRenderer.setMap(new THREE.Mesh(this.colliderGeom));
this.mapLoaded = true;
console.timeEnd("Building static geometry BVH");
}
Expand All @@ -137,8 +139,4 @@ export class CollisionManager {
public isPlayerInAir(): boolean {
return !this.collided;
}

public static getColliderGeom() {
return this.colliderGeom!;
}
}
}
15 changes: 11 additions & 4 deletions src/input/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export class InputHandler {
private gamepad: (Gamepad | null) = null;
private readonly gamepadEuler ;
private clock: THREE.Clock;
private forward: THREE.Vector3;
private keys: { [key: string]: boolean } = {};
private leftMouseDown: boolean = false;
private rightMouseDown: boolean = false;
Expand Down Expand Up @@ -40,7 +39,6 @@ export class InputHandler {

this.clock = new THREE.Clock();
this.mouse = new PointerLockControls(this.localPlayer, document.body);
this.forward = new THREE.Vector3(0, 0, -1);

this.gamepadInputs = new GamepadInputs();

Expand Down Expand Up @@ -111,8 +109,9 @@ export class InputHandler {
if (this.gamepadInputs.A) this.jump = true;
if (this.gamepadInputs.leftTrigger > .5) this.aim = true;
if (this.gamepadInputs.rightTrigger > .5) this.shoot = true;
this.gamepadEuler.y -= this.gamepadInputs.rightJoyX * SettingsManager.settings.controllerSense * deltaTime;
this.gamepadEuler.x -= this.gamepadInputs.rightJoyY * SettingsManager.settings.controllerSense * deltaTime;
const aimAdjust = this.calculateAimAssist();
this.gamepadEuler.y -= this.gamepadInputs.rightJoyX * SettingsManager.settings.controllerSense * deltaTime * aimAdjust;
this.gamepadEuler.x -= this.gamepadInputs.rightJoyY * SettingsManager.settings.controllerSense * deltaTime * aimAdjust;
this.gamepadEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.gamepadEuler.x));
this.localPlayer.lookQuaternion.setFromEuler(this.gamepadEuler);
}
Expand Down Expand Up @@ -282,6 +281,14 @@ export class InputHandler {
this.gamepadInputs.rightShoulder= gamepad.buttons[5].pressed
}

private calculateAimAssist(): number {
if ((Math.abs(this.gamepadInputs.rightJoyX) >= .1 || Math.abs(this.gamepadInputs.rightJoyY) >= .1)) {
if (this.renderer.getPlayerSpheresInCrosshairWithWalls().length > 0) {
return .5;
}
}
return 1;
}
}

class GamepadInputs {
Expand Down

0 comments on commit 91f8e1a

Please sign in to comment.