Skip to content

Commit

Permalink
[camera-animation] Add camera animation on game start
Browse files Browse the repository at this point in the history
  • Loading branch information
Tryferos committed Dec 15, 2024
1 parent e37c5d3 commit 2396d6d
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 48 deletions.
10 changes: 10 additions & 0 deletions resources/js/Constants/camera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as THREE from "three";

export const ORIGIN_CAMERA_LOOK_AT = new THREE.Vector3(0, 7.5, 0);
export const DESTINATION_CAMERA_LOOK_AT = new THREE.Vector3(0, 0, 2.5);

export const DESTINATION_CAMERA_POSITION = new THREE.Vector3(0, 9.5, 8.3);
export const ORIGIN_CAMERA_POSITION = new THREE.Vector3(0, DESTINATION_CAMERA_POSITION.y*1.25, -DESTINATION_CAMERA_POSITION.z*2);


export const CAMERA_ANIMATION_DURATION = 2500;
8 changes: 6 additions & 2 deletions resources/js/Game/Board/BoardControls.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { DESTINATION_CAMERA_LOOK_AT } from "@/Constants/camera";
import { useBoardState } from "@/Store/board_state";
import { OrbitControls } from "@react-three/drei"
import * as THREE from "three";

export const BoardControls = () => {
const state = useBoardState(state => state.gameState.state)
return (
<>
<OrbitControls
target={new THREE.Vector3(0, 0, 2.5)}
enabled={state !== 'Ready' && state !== 'Finished' && state !== 'Starting'}
target={DESTINATION_CAMERA_LOOK_AT}

minPolarAngle={Math.PI * 0.1} maxPolarAngle={Math.PI * 0.35}
minPolarAngle={Math.PI * 0.025} maxPolarAngle={Math.PI * 0.35}
maxAzimuthAngle={Math.PI * 0.05} minAzimuthAngle={Math.PI * -0.05}

makeDefault enableDamping
Expand Down
54 changes: 30 additions & 24 deletions resources/js/Game/Board/GameMap.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
import { useThree } from "@react-three/fiber";
import { useFrame, useThree } from "@react-three/fiber";
import { Lights } from "../Ligths";
import { Board } from "./Board";
import Hand from "./Hand";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { useControls } from "leva";
import { useBoardState } from "@/Store/board_state";
import { useTimerStore } from "@/Store/timer";
import * as THREE from "three";
import { lerp } from "three/src/math/MathUtils.js";
import { CAMERA_ANIMATION_DURATION, DESTINATION_CAMERA_LOOK_AT, DESTINATION_CAMERA_POSITION, ORIGIN_CAMERA_LOOK_AT, ORIGIN_CAMERA_POSITION } from "@/Constants/camera";

const GameMap = () => {

const {y,z} = useControls({
y: {
label: 'Camera Y',
value: 9.5,
min: 0,
max: 30,
step: 0.1,
},
z: {
label: 'Camera Z',
value: 8.3,
min: 0,
max: 30,
step: 0.1,
},
})
const playerIdentifier = useBoardState(state => state.gameState.player_identifier);
const state = useBoardState(state => state.gameState.state);

const {camera} = useThree();
const {time} = useTimerStore();
const timeRef = useRef(0)

useEffect(() => {
useFrame(({camera}, delta) => {
//* Camera Animation
if(time===0 || (state !== 'Ready' && state !== 'Finished' && state !== 'Starting')) return;
timeRef.current = Math.min((CAMERA_ANIMATION_DURATION/1000), timeRef.current + delta);

camera.position.set(0, y, z);
camera.updateProjectionMatrix()
const strength = timeRef.current/(CAMERA_ANIMATION_DURATION/1000);

}, [y,z])
const lerpedLookAt = new THREE.Vector3(
lerp(ORIGIN_CAMERA_LOOK_AT.x, DESTINATION_CAMERA_LOOK_AT.x, strength),
lerp(ORIGIN_CAMERA_LOOK_AT.y, -DESTINATION_CAMERA_LOOK_AT.z*2, strength),
lerp(ORIGIN_CAMERA_LOOK_AT.z, DESTINATION_CAMERA_LOOK_AT.y, strength),
);

const playerIdentifier = useBoardState(state => state.gameState.player_identifier);
const lerpedPosition = new THREE.Vector3(
lerp(ORIGIN_CAMERA_POSITION.x, DESTINATION_CAMERA_POSITION.x, strength),
lerp(ORIGIN_CAMERA_POSITION.y, DESTINATION_CAMERA_POSITION.y, strength),
lerp(ORIGIN_CAMERA_POSITION.z, DESTINATION_CAMERA_POSITION.z, strength),
);

camera.position.set(lerpedPosition.x, lerpedPosition.y, lerpedPosition.z);
camera.lookAt(0, lerpedLookAt.y, 0);
camera.updateProjectionMatrix()
})

return (
<>
Expand Down
8 changes: 6 additions & 2 deletions resources/js/Game/Experience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@ import GameMap from "./Board/GameMap";
import { Interface } from "./Interface";
import { BoardControls } from "./Board/BoardControls";
import * as THREE from "three";
import { ORIGIN_CAMERA_LOOK_AT, ORIGIN_CAMERA_POSITION } from "@/Constants/camera";

const INITIAL_CAMERA_PROPS = {
fov: 60,
fov: 85,
near: 0.1,
far: 200,
position: new THREE.Vector3,
}

export const Experience = memo(
() => {
return (
<>
<Canvas
onCreated={({camera}) => {
camera.position.set(ORIGIN_CAMERA_POSITION.x, ORIGIN_CAMERA_POSITION.y, ORIGIN_CAMERA_POSITION.z);
camera.lookAt(ORIGIN_CAMERA_LOOK_AT.x, ORIGIN_CAMERA_LOOK_AT.y, ORIGIN_CAMERA_LOOK_AT.z);
}}
camera={INITIAL_CAMERA_PROPS}>
<color attach={'background'} args={['#535353']} />
<Perf position="top-left"/>
Expand Down
16 changes: 5 additions & 11 deletions resources/js/Game/Interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useInterval } from "@/Hooks/useInterval";
import { useBoardState } from "@/Store/board_state";
import { useTimerStore } from "@/Store/timer";
import { PlayerIdentifier } from "@/types/piece";
import { useEffect, useMemo, useRef, useState } from "react";
import { useState } from "react";


export const Interface = () => {
Expand All @@ -13,20 +13,14 @@ export const Interface = () => {
const move = useBoardState(state => state.move);
const boardPieces = useBoardState(state => state.boardPieces);
const [timer, setTimer] = useState(0);
const {time,start} = useTimerStore();
const [isStarting, setIsStarting] = useState(false);
const {time} = useTimerStore();

useInterval(() => {
setTimer(Math.round(((Date.now() - (startTime ?? 0))/1000)*10)/10);
}, 100)

const onStartGame = (playerCount: number, playerIdentifier: PlayerIdentifier, playerTurn: PlayerIdentifier) => {
setIsStarting(true);
start(1000);
setTimeout(() => {
setIsStarting(false);
startGame(playerCount, playerIdentifier, playerTurn);
}, 3250)
startGame(playerCount, playerIdentifier, playerTurn);
}
return (
<div className='interface'>
Expand All @@ -43,8 +37,8 @@ export const Interface = () => {
</>
}
</div>
{isStarting && <p className="starting-text">Starting in {3 - time}...</p>}
{!isStarting && <div className="btn-group">
{state === 'Starting' && <p className="starting-text">Starting in {Math.round(3 - (time/10))}...</p>}
{state !== 'Starting' && <div className="btn-group">
{
state === 'Ready' &&
<>
Expand Down
24 changes: 16 additions & 8 deletions resources/js/Store/board_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {subscribeWithSelector} from 'zustand/middleware'
import * as THREE from "three";
import { GameState, MovePayload, PlayerIdentifier } from "@/types/piece";
import { useTimerStore } from "./timer";
import { CAMERA_ANIMATION_DURATION } from "@/Constants/camera";

type State = {
boardRef: THREE.Mesh | null;
Expand Down Expand Up @@ -42,18 +43,25 @@ export const useBoardState = create<BoardState>()((set, get, state) => ({
player_count: 0,
player_identifier: null,
},

canPlay: () => {
return get().gameState.state === 'OwnTurnPlaying'
},
startGame: (player_count, player_identifier, player_turn) => {
set({gameState: {
round: 1,
player_identifier: player_identifier,
player_turn: player_turn,
state: player_identifier === player_turn ? 'OwnTurnPlaying' : 'OpponentTurn',
startTime: Date.now(),
player_count: player_count,
}})
const timer = useTimerStore.getState();
timer.start(100);
set({gameState: {...get().gameState, state: 'Starting'}})
setTimeout(() => {
timer.stop();
set({gameState: {
round: 1,
player_identifier: player_identifier,
player_turn: player_turn,
state: player_identifier === player_turn ? 'OwnTurnPlaying' : 'OpponentTurn',
startTime: Date.now(),
player_count: player_count,
}})
}, CAMERA_ANIMATION_DURATION + 250)
},
lockTurn: () => {
set({gameState: {...get().gameState, state: 'OwnTurnLocked'}})
Expand Down
2 changes: 1 addition & 1 deletion resources/js/types/piece.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type GameState = {
player_turn: PlayerIdentifier;
player_identifier: PlayerIdentifier | null;
player_count: number;
state: 'Ready' | 'Finished' | 'OwnTurnPlaying' | 'OwnTurnLocked' | 'OpponentTurn';
state: 'Ready' | 'Starting' | 'Finished' | 'OwnTurnPlaying' | 'OwnTurnLocked' | 'OpponentTurn';
}

export type OpponentMovePayload = {
Expand Down

0 comments on commit 2396d6d

Please sign in to comment.