-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4bd687b
commit 1810052
Showing
10 changed files
with
338 additions
and
174 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./useNavMesh"; | ||
export * from "./useYuka"; | ||
export * from "./navmesh/useNavMesh"; | ||
export * from "./navmesh/useYuka"; | ||
export * from "./navmesh/NavMeshAgent"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* eslint-disable react/prop-types */ | ||
import { BallCollider, CapsuleCollider, RigidBody } from "@react-three/rapier"; | ||
import { useYuka } from "./useYuka"; | ||
import { Vehicle } from "yuka"; | ||
import getRandomArbitrary from "./RandomCalculations"; | ||
import { Vector3 } from "three"; | ||
import { useEffect, useRef } from "react"; | ||
import { useNavMesh } from "./useNavMesh"; | ||
|
||
export function NavMeshAgent({ | ||
name = "agent", | ||
agentId = null, | ||
position = [getRandomArbitrary(0, 60), 2, getRandomArbitrary(0, 60)], | ||
navPoints = [new Vector3(10, 2, 10), new Vector3(60, 2, 60)], | ||
maxSpeed = getRandomArbitrary(3, 10), | ||
maxForce = getRandomArbitrary(30, 60), | ||
isRandomNav = false, | ||
removed = false, | ||
isPlayerDetected = false, | ||
collisionSize = 10, | ||
capsuleColliderSize = [1, 1, 0.2], | ||
...props | ||
}) { | ||
const [refYuka] = useYuka({ | ||
type: Vehicle, | ||
name, | ||
agentId, | ||
position, | ||
navPoints, | ||
isRandomNav, | ||
isPlayerDetected, | ||
maxSpeed, | ||
removed, | ||
maxForce, | ||
...props, | ||
}); | ||
|
||
const actions = useNavMesh((state) => state.actions); | ||
const agentControl = useRef({ playerDetected: false }); | ||
|
||
// const Attacked = (event) => { | ||
// if (event.rigidBodyObject.name == "Player") { | ||
// console.log("Attacked"); | ||
// } | ||
// }; | ||
|
||
useEffect(() => { | ||
return () => console.log("Destroyed", agentId); | ||
}, []); | ||
|
||
return ( | ||
<RigidBody | ||
ref={refYuka} | ||
colliders={false} | ||
linearDamping={0} | ||
type="kinematicPosition" | ||
agentId={agentId} | ||
position={position} | ||
name="Enemy" | ||
lockRotations | ||
> | ||
<CapsuleCollider args={capsuleColliderSize} position={[0, 0, 0]}> | ||
{props.children} | ||
</CapsuleCollider> | ||
<BallCollider | ||
onIntersectionEnter={(object) => { | ||
if ( | ||
!agentControl.current.playerDetected && | ||
object.rigidBodyObject.name == "Player" | ||
) { | ||
actions.agentDetectPlayerTrigger(agentId, true); | ||
} | ||
}} | ||
args={[collisionSize]} | ||
position={[0, 0, 0]} | ||
sensor | ||
/> | ||
</RigidBody> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function getRandomArbitrary(min, max) { | ||
return Math.random() * (max - min) + min; | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import * as THREE from "three"; | ||
import { NavMeshLoader, Vector3 } from "yuka"; | ||
import { create } from "zustand"; | ||
import { createConvexRegionHelper } from "./createConvexRegionHelper"; | ||
|
||
export const useNavMesh = create((set, get) => ({ | ||
navMesh: null, | ||
intersects: new Vector3(), | ||
agentList: [], | ||
mutation: { | ||
mouse: { x: 0, y: 0 }, | ||
}, | ||
level: { | ||
geometry: new THREE.BufferGeometry(), | ||
material: new THREE.MeshBasicMaterial(), | ||
}, | ||
actions: { | ||
loadNavMesh(url) { | ||
const loader = new NavMeshLoader(); | ||
loader.load(url).then((navMesh) => { | ||
const { geometry, material } = createConvexRegionHelper(navMesh); | ||
set({ navMesh }); | ||
set({ level: { geometry, material } }); | ||
}); | ||
}, | ||
|
||
setPosition(position) { | ||
set({ intersects: position }); | ||
}, | ||
|
||
setAgentList(agentList) { | ||
set({ agentList: agentList }); | ||
}, | ||
|
||
/** | ||
* use to make agent follow player | ||
*/ | ||
agentDetectPlayerTrigger(agentId, active) { | ||
const { agentList } = get(); | ||
const selectedAgent = agentList.current.find((agentData) => { | ||
return agentData.agent.agentId === agentId; | ||
}); | ||
selectedAgent.agent.isPlayerDetected = active; | ||
set({ agentList: agentList }); | ||
}, | ||
|
||
/** | ||
* use to stop agent from moving | ||
*/ | ||
freezeAgentTrigger(agentId) { | ||
const { agentList } = get(); | ||
const selectedAgent = agentList.current.find((agentData) => { | ||
return agentData.agent.agentId === agentId; | ||
}); | ||
selectedAgent.followPathBehavior.active = | ||
!selectedAgent.followPathBehavior.active; | ||
selectedAgent.onPathBehavior.active = | ||
!selectedAgent.followPathBehavior.active; | ||
|
||
selectedAgent.agent.navRef.current.setEnabled(false); | ||
selectedAgent.agent.velocity = new Vector3(0, 0, 0); | ||
set({ agentList: agentList }); | ||
}, | ||
|
||
/** | ||
* use to remove agent from agent list | ||
*/ | ||
removeAgent(agentId) { | ||
const { agentList } = get(); | ||
const selectedAgent = agentList.current.find((agentData) => { | ||
return agentData.agent.agentId === agentId; | ||
}); | ||
if (selectedAgent) { | ||
selectedAgent.followPathBehavior.active = false; | ||
selectedAgent.onPathBehavior.active = false; | ||
selectedAgent.agent.navRef.current.setEnabled(false); | ||
selectedAgent.agent.velocity = new Vector3(0, 0, 0); | ||
agentList.current = agentList.current.filter((agentData) => { | ||
return agentData.agent.agentId !== agentId; | ||
}); | ||
} | ||
set({ agentList: agentList }); | ||
}, | ||
}, | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* eslint-disable react/prop-types */ | ||
import { useRef, useEffect, useState, useContext, createContext } from "react"; | ||
import { | ||
GameEntity, | ||
EntityManager, | ||
FollowPathBehavior, | ||
OnPathBehavior, | ||
Vector3, | ||
ObstacleAvoidanceBehavior, | ||
Smoother, | ||
} from "yuka"; | ||
import { useNavMesh } from "./useNavMesh"; | ||
import { useFrame } from "@react-three/fiber"; | ||
import getRandomArbitrary from "./RandomCalculations"; | ||
|
||
const context = createContext(); | ||
|
||
export function Manager({ children }) { | ||
const [mgr] = useState(() => new EntityManager()); | ||
const agentListRef = useRef([]); | ||
const managerControl = useRef({ start: false }); | ||
const navMesh = useNavMesh((state) => state.navMesh); | ||
const agentList = useNavMesh((state) => state.agentList); | ||
const actions = useNavMesh((state) => state.actions); | ||
|
||
useEffect(() => { | ||
if (!navMesh) { | ||
return; | ||
} | ||
const agents = mgr.entities.filter((item) => item.name === "Enemy"); | ||
|
||
agents.forEach((agent) => { | ||
agent.boundingRadius = 1; | ||
agent.smoother = new Smoother(20); | ||
// Set up agent | ||
const followPathBehavior = new FollowPathBehavior(); | ||
const onPathBehavior = new OnPathBehavior(); | ||
|
||
//this avoid agent collide with each other | ||
const obstaclesAvoidenceBehavior = new ObstacleAvoidanceBehavior(agents); | ||
|
||
obstaclesAvoidenceBehavior.active = false; | ||
obstaclesAvoidenceBehavior.brakingWeight = 0.1; | ||
obstaclesAvoidenceBehavior.weight = 5; | ||
obstaclesAvoidenceBehavior.dBoxMinLength = 5; | ||
followPathBehavior.active = false; | ||
onPathBehavior.active = false; | ||
onPathBehavior.radius = 1; | ||
agent.steering.add(obstaclesAvoidenceBehavior); | ||
agent.steering.add(followPathBehavior); | ||
agent.steering.add(onPathBehavior); | ||
|
||
agentListRef.current.push({ | ||
agent: agent, | ||
followPathBehavior: followPathBehavior, | ||
onPathBehavior: onPathBehavior, | ||
obstaclesAvoidenceBehavior: obstaclesAvoidenceBehavior, | ||
}); | ||
}); | ||
|
||
actions.setAgentList(agentListRef); | ||
|
||
useNavMesh.subscribe((intersects) => findPathTo(intersects)); | ||
|
||
const setPaths = (agentDate, from, to) => { | ||
const path = navMesh.findPath(from, to); | ||
agentDate.onPathBehavior.path.clear(); | ||
agentDate.followPathBehavior.path.clear(); | ||
agentDate.onPathBehavior.active = true; | ||
agentDate.followPathBehavior.active = true; | ||
agentDate.obstaclesAvoidenceBehaviorw.active = true; | ||
|
||
for (const point of path) { | ||
agentDate.followPathBehavior.path.add(point); | ||
agentDate.onPathBehavior.path.add(point); | ||
} | ||
}; | ||
|
||
const findPathTo = (target) => { | ||
target.agentList.current.forEach((agentDate) => { | ||
const navPointsCount = agentDate.agent.navPoints.length; | ||
if (agentDate.agent.isPlayerDetected) { | ||
const from = agentDate.agent.position; | ||
const to = new Vector3( | ||
target.intersects.x, | ||
target.intersects.y, | ||
target.intersects.z | ||
); | ||
setPaths(agentDate, from, to); | ||
} else if (navPointsCount > 0) { | ||
const pathData = setRoamingPath(agentDate.agent, navPointsCount); | ||
setPaths(agentDate, pathData.from, pathData.to); | ||
} | ||
}); | ||
}; | ||
}, [navMesh]); | ||
|
||
const setRoamingPath = (agent, navPointsCount) => { | ||
let currentPointIndex = agent.currentNavPoint; | ||
const from = agent.position; | ||
let to = agent.navPoints[currentPointIndex]; | ||
if (from.distanceTo(to) < 0.5 && navPointsCount > 1) { | ||
currentPointIndex = | ||
currentPointIndex < navPointsCount - 1 ? currentPointIndex + 1 : 0; | ||
to = agent.navPoints[currentPointIndex]; | ||
agent.currentNavPoint = currentPointIndex; | ||
} | ||
const toVec3 = new Vector3(to.x, to.y, to.z); | ||
return { from: from, to: toVec3 }; | ||
}; | ||
|
||
useFrame((state, delta) => { | ||
mgr.update(delta); | ||
}); | ||
|
||
return <context.Provider value={mgr}>{children}</context.Provider>; | ||
} | ||
|
||
export function useYuka({ | ||
type = GameEntity, | ||
agentId = null, | ||
position = [getRandomArbitrary(0, 60), 2, getRandomArbitrary(0, 60)], | ||
name = "unnamed", | ||
navPoints = [], | ||
isRandomNav = false, | ||
isPlayerDetected = false, | ||
maxForce, | ||
removed, | ||
maxSpeed, | ||
}) { | ||
// This hook makes set-up re-usable | ||
const ref = useRef(); | ||
const mgr = useContext(context); | ||
const [entity] = useState(() => new type()); | ||
useEffect(() => { | ||
entity.position.set(...position); | ||
entity.agentId = agentId; | ||
entity.name = name; | ||
entity.maxForce = maxForce; | ||
entity.maxSpeed = maxSpeed; | ||
entity.isRandomNav = isRandomNav; | ||
entity.currentNavPoint = 0; | ||
entity.isPlayerDetected = isPlayerDetected; | ||
entity.navPoints = navPoints; | ||
entity.navRef = ref; | ||
entity.removed = removed; | ||
entity.setRenderComponent(ref, (entity) => { | ||
ref.current.setTranslation(entity.position); | ||
ref.current.setRotation(entity.rotation); | ||
}); | ||
console.log("entity", entity); | ||
mgr.add(entity); | ||
return () => { | ||
const removingEntity = mgr.entities.find( | ||
(item) => item.agentId === entity.agentId | ||
); | ||
mgr.remove(removingEntity); | ||
}; | ||
}, []); | ||
return [ref, entity]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.