VisualError / Le_Experiment_Game_TS

1 stars 0 forks source link

Separate springs for camera rotation/camera position. #6

Closed github-actions[bot] closed 6 months ago

github-actions[bot] commented 6 months ago

Calculate the camera's rotation based on the accumalated angles.

Apply the rotation CFrame to the camera's position

https://github.com/VisualError/Le_Experiment_Game_TS/blob/88e8eda8cc5e2189ab07813e9902dfea2c43f533/src/client/controllers/CameraController.ts#L206


import { Controller, OnStart } from "@flamework/core";
import { Players, RunService, Workspace, UserInputService } from "@rbxts/services";
import Spring from "@rbxts/spring";
import { EventController } from "./EventController";

type PositionProvider = {
    getPosition(): Vector3;
    getCFrame(): CFrame;
    getVelocity(): Vector3;
    instance: Instance;
};

class BasePartPositionProvider implements PositionProvider {
    constructor(private basePart: BasePart) {
        this.instance = basePart;
    }
    getCFrame(): CFrame {
        return this.basePart.CFrame;
    }
    getPosition(): Vector3 {
        return this.basePart.Position;
    }
    getVelocity(): Vector3 {
        return this.basePart?.AssemblyLinearVelocity || new Vector3();
    }
    instance: Instance;
}

class ModelPositionProvider implements PositionProvider {
    constructor(private model: Model) {
        this.instance = model;
    }
    instance: Instance;
    getCFrame(): CFrame {
        return this.model.PrimaryPart?.CFrame || new CFrame();
    }
    getPosition(): Vector3 {
        return this.model.PrimaryPart?.Position || new Vector3();
    }

    getVelocity(): Vector3 {
        return this.model.PrimaryPart?.AssemblyLinearVelocity || new Vector3();
    }
}
// TODO: Add support for first person mode.
// TODO: Add support for different input types.
// TODO: Make left and right arrow keys to work.
@Controller()
export class CameraController implements OnStart {
    spring?: Spring<Vector3>;
    isRightMouseDown = false; // Track if the right mouse button is down
    oldMousePos?: Vector3; // Track the previous mouse position
    cameraOffset = new Vector3(0, 0, 5); // Initial camera offset
    private static target?: PositionProvider;
    private static dampening = 6;
    private accumulatedHorizontalAngle = 0;
    private accumulatedVerticalAngle = 0;
    private currentIndex = 0;
    private static minDistance = 2;
    private static maxDistance = 100;

    constructor(private eventController: EventController) {}

    onStart(): void {
        this.eventController.maid.GiveTask(RunService.RenderStepped.Connect((dt) => this.Connection(dt)));
        // Listen for right-click to start orbiting
        this.eventController.maid.GiveTask(
            UserInputService.InputBegan.Connect((input, gameProcessedEvent) => {
                if (gameProcessedEvent) return; // Ignore inputs already processed by the game

                if (input.UserInputType === Enum.UserInputType.MouseButton2) {
                    this.isRightMouseDown = true; // Right mouse button is down
                    this.oldMousePos = input.Position;
                }
                if (input.UserInputType === Enum.UserInputType.Keyboard) {
                    if (input.KeyCode === Enum.KeyCode.E) {
                        const children = game.Workspace.GetChildren();
                        if (this.currentIndex >= children.size()) {
                            // Reset the index to 0 if it exceeds the array length
                            this.currentIndex = 0;
                            CameraController.setTarget(Players.LocalPlayer);
                            return;
                        } else {
                            this.currentIndex++;
                        }
                        let randomChild = children[this.currentIndex];
                        print(randomChild);
                        while (!randomChild || !CameraController.setTarget(randomChild)) {
                            this.currentIndex++;
                            if (this.currentIndex >= children.size()) {
                                // Reset the index to 0 if it exceeds the array length
                                this.currentIndex = 0;
                                CameraController.setTarget(Players.LocalPlayer);
                                return;
                            }
                            randomChild = children[this.currentIndex];
                        }
                        print(CameraController.target);
                    }
                    switch (input.KeyCode) {
                        case Enum.KeyCode.F:
                            CameraController.dampening++;
                            print(CameraController.dampening);
                            break;
                        case Enum.KeyCode.G:
                            if (CameraController.dampening === 0) return;
                            CameraController.dampening--;
                            print(CameraController.dampening);
                            break;
                    }
                }
            }),
        );

        this.eventController.maid.GiveTask(
            UserInputService.InputEnded.Connect((input, gameProcessedEvent) => {
                if (gameProcessedEvent) return; // Ignore inputs already processed by the game

                if (input.UserInputType === Enum.UserInputType.MouseButton2) {
                    this.isRightMouseDown = false; // Right mouse button is up
                    this.oldMousePos = undefined;
                }
            }),
        );

        // Listen for mouse wheel scroll to change camera offset
        // TODO: Disable spring when in first person.
        this.eventController.maid.GiveTask(
            UserInputService.InputChanged.Connect((input, gameProcessedEvent) => {
                if (gameProcessedEvent) return; // Ignore inputs already processed by the game

                switch (input.UserInputType) {
                    case Enum.UserInputType.MouseWheel:
                        this.cameraOffset = this.cameraOffset.add(new Vector3(0, 0, -input.Position.Z * 2));
                        this.cameraOffset = new Vector3(
                            this.cameraOffset.X,
                            this.cameraOffset.Y,
                            math.clamp(this.cameraOffset.Z, CameraController.minDistance, CameraController.maxDistance),
                        );
                        break;
                    case Enum.UserInputType.MouseMovement:
                        const newMousePos = input.Position;
                        if (this.oldMousePos) {
                            const delta = newMousePos.sub(this.oldMousePos);
                            this.oldMousePos = newMousePos; // Update oldMousePos for the next delta calculation
                            if (CameraController.target) {
                                // Accumulate the rotation angles
                                const mouseSensitivity = UserSettings().GetService("UserGameSettings").MouseSensitivity;
                                this.accumulatedHorizontalAngle += delta.X * mouseSensitivity;
                                this.accumulatedVerticalAngle += delta.Y * mouseSensitivity;

                                // Clamp the accumulated vertical angle to prevent the camera from turning upside down
                                // This example clamps the vertical angle between -80 and 80 degrees
                                this.accumulatedVerticalAngle = math.clamp(this.accumulatedVerticalAngle, -80, 80);
                            }
                        }
                        break;
                }
            }),
        );

        this.eventController.maid.GiveTask(
            Players.LocalPlayer.CharacterAdded.Connect((character) => {
                CameraController.setTarget(character);
            }),
        );
    }

    static setTarget<T extends Instance>(this: void, target_?: T): boolean {
        print("called!");
        if (!target_ && Players.LocalPlayer.Character) {
            CameraController.target = new ModelPositionProvider(Players.LocalPlayer.Character);
            print("Player");
        } else if (target_) {
            if (target_.IsA("BasePart")) {
                CameraController.target = new BasePartPositionProvider(target_);
                print("BasePart");
                return true;
            } else if (target_.IsA("Player")) {
                CameraController.target = new ModelPositionProvider(target_.Character!);
                print("Player");
                return true;
            } else if (target_.IsA("Model")) {
                CameraController.target = new ModelPositionProvider(target_);
                print("Model");
                return true;
            } else {
                CameraController.target = new ModelPositionProvider(Players.LocalPlayer.Character!);
                print("Player");
                return false;
            }
        }
        print("returning false");
        return false;
    }

    // Example method to get the position of the target
    static getTargetPosition(this: void): Vector3 | undefined {
        return CameraController.target?.getPosition();
    }

    static setDampening(this: void, number: number): void {
        CameraController.dampening = number;
    }

    // TODO: Separate springs for camera rotation/camera position.
    Connection(dt: number): void {
        if (CameraController.target) {
            const targetPosition = CameraController.target.getCFrame().Position;
            const targetRotation = CameraController.target.getCFrame().Rotation; // Not using this yet..
            // Calculate the camera's rotation based on the accumalated angles.
            const rotationCFrame = new CFrame()
                .mul(CFrame.Angles(0, math.rad(this.accumulatedHorizontalAngle), 0))
                .mul(CFrame.Angles(math.rad(this.accumulatedVerticalAngle), 0, 0));
            let goal = targetPosition.add(rotationCFrame.mul(new CFrame(this.cameraOffset)).Position);
            if (Workspace.CurrentCamera) {
                const params = new RaycastParams();
                params.FilterType = Enum.RaycastFilterType.Exclude;
                params.AddToFilter(CameraController.target.instance);
                //params.AddToFilter(Players.LocalPlayer.Character!);

                const result = game.Workspace.Raycast(targetPosition, goal.sub(targetPosition), params);

                // If a part is hit, adjust the goal position to stop before the part
                if (result && result.Instance) {
                    const hitPosition = result.Position;
                    const directionToGoal = goal.sub(targetPosition).Unit;
                    const distanceToHit = hitPosition.sub(targetPosition).Magnitude;
                    goal = targetPosition.add(directionToGoal.mul(distanceToHit * 0.8)); // The 0.8 is here coz it clips and idk why.
                }
            }
            if (!this.spring) this.spring = new Spring(targetPosition, 50, goal, CameraController.dampening);
            this.spring.goal = goal;
            this.spring.dampingRatio = CameraController.dampening;
            if (this.spring.dampingRatio > 0) {
                this.spring.update(dt);
            } else {
                if (this.spring.position !== this.spring.goal) this.spring.resetToPosition(this.spring.goal);
            }

            if (Workspace.CurrentCamera) {
                Workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable;
                // Calculate the rotation CFrame based on the accumulated rotation angles
                // Apply the rotation CFrame to the camera's position
                const springToggle = this.spring.dampingRatio > 0 ? this.spring.position : goal;
                const finalPosition = rotationCFrame.add(springToggle);
                // Apply the rotation CFrame to the new camera position

                Workspace.CurrentCamera.CFrame = finalPosition;
            }
        }
    }
}
github-actions[bot] commented 6 months ago

Closed in 099485230fce8f4a7b3ffe8c2ce0e6b59055f089