Skip to content

Commit

Permalink
format, clean up comments, refactor world injection
Browse files Browse the repository at this point in the history
  • Loading branch information
HexaField committed Feb 9, 2024
1 parent 0cb4b67 commit bd04d31
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 111 deletions.
65 changes: 39 additions & 26 deletions src/components/BubbleComponent.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,67 @@
import { defineComponent, setComponent, useEntityContext } from '@etherealengine/ecs'

import { defineComponent, useEntityContext, setComponent } from "@etherealengine/ecs"
import { Color, Mesh, MeshStandardMaterial, SphereGeometry } from 'three'
import matches from 'ts-matches'

import { Mesh, SphereGeometry, MeshStandardMaterial, Color } from "three"
import matches from "ts-matches"

import React, { useEffect } from "react"
import { VisibleComponent } from "@etherealengine/spatial/src/renderer/components/VisibleComponent"
import { NameComponent } from "@etherealengine/spatial/src/common/NameComponent"
import { addObjectToGroup } from "@etherealengine/spatial/src/renderer/components/GroupComponent"
import { TransformComponent } from "@etherealengine/spatial/src/transform/components/TransformComponent"
import { NameComponent } from '@etherealengine/spatial/src/common/NameComponent'
import { addObjectToGroup } from '@etherealengine/spatial/src/renderer/components/GroupComponent'
import { VisibleComponent } from '@etherealengine/spatial/src/renderer/components/VisibleComponent'
import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent'
import { useEffect } from 'react'

export const BubbleComponent = defineComponent({
//name: The human-readable label for the component. This will be displayed in the editor and debugging tools.
name: "Bubble Component",
/** name: The human-readable label for the component. This will be displayed in the editor and debugging tools. */
name: 'Bubble Component',

//onInit: Initializer function that is called when the component is added to an entity. The return type of this function defines the
// schema of the component's runtime data.
/**
* onInit: Initializer function that is called when the component is added to an entity.
* The return type of this function defines the schema of the component's runtime data.
*/
onInit: (entity) => {
return {
age: 0 as number
}
},
//onSet: Set function that is called whenever the component's data is updated via the setComponent function. This is where deserialize logic should
// be applied.

/**
* onSet: Set function that is called whenever setComponent is called.
* This is where deserialize logic should be applied.
*/
onSet: (entity, component, json) => {
if (!json) return
matches.number.test(json.age) && component.age.set(json.age)
},
//toJSON: Serializer function that is called when the component is being saved to a scene file or snapshot. This is where serialize logic should
// be applied to convert the component's runtime data into a JSON object.

/**
* toJSON: Serializer function that is called when the component is being saved to a scene file or snapshot.
* This is where serialize logic should be applied to convert the component's runtime data into a JSON object.
*/
toJSON: (entity, component) => {
return {
age: component.age.value
}
},
//reactor: The reactor function is where async reactive logic is defined. Any side-effects that depend upon component data should be defined here.
/**
* reactor: The reactor function is where async reactive logic is defined. Any side-effects that depend upon component data should be defined here.
*/
reactor: () => {
//get the entity using useEntityContext
/** Get the entity for this component instance */
const entity = useEntityContext()

//a useEffect with no dependencies will only run once, when the component is first initialized
/** A useEffect with no dependencies will only run once, when the component is first initialized */
useEffect(() => {
setComponent(entity, VisibleComponent) // Set if the entity is visible
setComponent(entity, NameComponent, "Bubble") // Give the entity a name
setComponent(entity, TransformComponent) // Give the entity a local transform
/** Set if the entity is visible */
setComponent(entity, VisibleComponent)
/** Set the name of the entity */
setComponent(entity, NameComponent, 'Bubble')
/** Give the entity a local transform */
setComponent(entity, TransformComponent)
/** Add a mesh to the object */
const bubbleMesh = new Mesh(new SphereGeometry(), new MeshStandardMaterial())
bubbleMesh.material.color = new Color(0xFFFFFF)
addObjectToGroup(entity, bubbleMesh) // Add GroupComponent and add mesh to Group
bubbleMesh.material.color = new Color(0xffffff)
addObjectToGroup(entity, bubbleMesh)
}, [])

return null
}
})
})
116 changes: 66 additions & 50 deletions src/components/BubbleEmitterComponent.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
import { EntityUUID } from "@etherealengine/common/src/interfaces/EntityUUID"
import { defineComponent, Entity, useEntityContext, useComponent, removeEntity, getComponent, useExecute, createEntity, setComponent, SimulationSystemGroup, getMutableComponent } from "@etherealengine/ecs"

import { getState, NO_PROXY } from "@etherealengine/hyperflux"
import { Color, Vector3, Mesh, BufferGeometry, MeshStandardMaterial, MathUtils } from "three"
import { BubbleComponent } from "./BubbleComponent"

import React, { useEffect } from "react"
import { ECSState } from "@etherealengine/ecs/src/ECSState"
import { GroupComponent } from "@etherealengine/spatial/src/renderer/components/GroupComponent"
import { EntityTreeComponent } from "@etherealengine/spatial/src/transform/components/EntityTree"

import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID'
import {
createEntity,
defineComponent,
Entity,
getComponent,
getMutableComponent,
removeEntity,
setComponent,
SimulationSystemGroup,
useComponent,
useEntityContext,
useExecute
} from '@etherealengine/ecs'

import { getState, NO_PROXY } from '@etherealengine/hyperflux'
import { BufferGeometry, Color, MathUtils, Mesh, MeshStandardMaterial, Vector3 } from 'three'
import { BubbleComponent } from './BubbleComponent'

import { ECSState } from '@etherealengine/ecs/src/ECSState'
import { GroupComponent } from '@etherealengine/spatial/src/renderer/components/GroupComponent'
import { EntityTreeComponent } from '@etherealengine/spatial/src/transform/components/EntityTree'
import { useEffect } from 'react'

export const BubbleEmitterComponent = defineComponent({
//name: The human-readable label for the component. This will be displayed in the editor and debugging tools.
name: "Bubble Emitter Component",
name: 'Bubble Emitter Component',
//jsonID: The serialized name of the component. This is used to identify the component in the serialized scene data
jsonID: "bubbleEmitter",
//onInit: Initializer function that is called when the component is added to an entity. The return type of this function defines the
jsonID: 'bubbleEmitter',
//onInit: Initializer function that is called when the component is added to an entity. The return type of this function defines the
// schema of the component's runtime data.
onInit: (entity) => {
return {
color: new Color(0xFFFFFF),
color: new Color(0xffffff),
direction: new Vector3(0, 1, 0),
speed: .1,
speed: 0.1,
bubbleEntities: [] as Entity[] | null
}
},
Expand All @@ -45,7 +56,7 @@ export const BubbleEmitterComponent = defineComponent({
},

//reactor: The reactor function is where async reactive logic is defined. Any side-effects that depend upon component data should be defined here.
//reactive:
//reactive:
reactor: () => {
// get the entity using useEntityContext
const entity = useEntityContext()
Expand All @@ -57,7 +68,7 @@ export const BubbleEmitterComponent = defineComponent({
// it's return statement will run when the component is unmounted
useEffect(() => {
return () => {
for(let ent of emitterComponent.bubbleEntities.value!) {
for (let ent of emitterComponent.bubbleEntities.value!) {
removeEntity(ent)
}
}
Expand All @@ -66,11 +77,11 @@ export const BubbleEmitterComponent = defineComponent({
// a useEffect with dependencies will run whenever the dependencies change
// Whenever the color is changed this will rerun and update all child bubble materials
useEffect(() => {
for(let ent of emitterComponent.bubbleEntities.value!) {
for (let ent of emitterComponent.bubbleEntities.value!) {
const groupComponent = getComponent(ent, GroupComponent)
const obj = groupComponent[0]
obj.traverse((obj: Mesh<BufferGeometry, MeshStandardMaterial>) => {
if(obj.isMesh) {
if (obj.isMesh) {
const material = obj.material as MeshStandardMaterial
material.color.copy(emitterComponent.color.value)
}
Expand All @@ -81,34 +92,38 @@ export const BubbleEmitterComponent = defineComponent({
// useExecute is a way we can define a System within a reactive context
// Systems will run once per frame
// You must explicitly say where you want your system to run(i.e. after SimulationSystemGroup)
useExecute(() => {

const deltaSeconds = getState(ECSState).deltaSeconds
ageEmitterBubbles(entity, deltaSeconds) // This function is accumulating the age of every bubble with the time from deltaSeconds.
// deltaSeconds is the time since the last system execute occured

// Spawning a single bubble as an example
// [Exercise 1]: Using this system. Spawn multiple bubbles with varying x,z Localtransform positons
// [Exercise 3]: Remove them if they are too old(bubble.age > N seconds)[This can be done in a couple ways(reactively and within this sytem synchronosly)]
if(emitterComponent.bubbleEntities.value!.length < 1) { //For example ensuring there is only one bubble being added
const bubbleEntity = createEntity()
setComponent(bubbleEntity, BubbleComponent)
setComponent(bubbleEntity, EntityTreeComponent, {
parentEntity: entity,
uuid: MathUtils.generateUUID() as EntityUUID
})
emitterComponent.bubbleEntities.merge([bubbleEntity])
}
useExecute(
() => {
const deltaSeconds = getState(ECSState).deltaSeconds
ageEmitterBubbles(entity, deltaSeconds) // This function is accumulating the age of every bubble with the time from deltaSeconds.
// deltaSeconds is the time since the last system execute occured

// Spawning a single bubble as an example
// [Exercise 1]: Using this system. Spawn multiple bubbles with varying x,z Localtransform positons
// [Exercise 3]: Remove them if they are too old(bubble.age > N seconds)[This can be done in a couple ways(reactively and within this sytem synchronosly)]
if (emitterComponent.bubbleEntities.value!.length < 1) {
//For example ensuring there is only one bubble being added
const bubbleEntity = createEntity()
setComponent(bubbleEntity, BubbleComponent)
setComponent(bubbleEntity, EntityTreeComponent, {
parentEntity: entity,
uuid: MathUtils.generateUUID() as EntityUUID
})
emitterComponent.bubbleEntities.merge([bubbleEntity])
}

const bubble = getComponent(emitterComponent.bubbleEntities.value![0], BubbleComponent)
const bubble = getComponent(emitterComponent.bubbleEntities.value![0], BubbleComponent)

if(bubble.age >= 5) { // Delete one bubble after its age is greater than 5 seconds
removeBubble(entity,emitterComponent.bubbleEntities.value![0])
}
}, { after: SimulationSystemGroup })
if (bubble.age >= 5) {
// Delete one bubble after its age is greater than 5 seconds
removeBubble(entity, emitterComponent.bubbleEntities.value![0])
}
},
{ after: SimulationSystemGroup }
)

return null
},
}
})

// These functions are not to be changed (unless there's a bug in them and I messed up.)
Expand All @@ -119,8 +134,9 @@ export const BubbleEmitterComponent = defineComponent({
export function removeBubble(emitterEntity: Entity, bubbleEntity: Entity): void {
const emitter = getMutableComponent(emitterEntity, BubbleEmitterComponent) // Reactive incase someone wants to use it reactively
const currEntities = emitter.bubbleEntities.get(NO_PROXY)!
const index = currEntities.indexOf(bubbleEntity);
if (index > -1) { // only splice array when item is found
const index = currEntities.indexOf(bubbleEntity)
if (index > -1) {
// only splice array when item is found
currEntities.splice(index, 1) // deletes one entiry from array in place. 2nd Parameter means remove only one
emitter.bubbleEntities.set(currEntities)
removeEntity(bubbleEntity) // removes the given entity from the ECS
Expand All @@ -132,9 +148,9 @@ export function removeBubble(emitterEntity: Entity, bubbleEntity: Entity): void
*/
export function ageEmitterBubbles(emitterEntity: Entity, deltaSeconds: number): void {
const emitter = getComponent(emitterEntity, BubbleEmitterComponent)
for(const bubbleEntity of emitter.bubbleEntities!) {
for (const bubbleEntity of emitter.bubbleEntities!) {
const bubble = getMutableComponent(bubbleEntity, BubbleComponent) // getMutable gets the reactified version of the component that will respond to effects(if you want to try checking age reactively)
const currAge = bubble.age.get(NO_PROXY)
bubble.age.set(currAge+deltaSeconds) // increment individual bubble age.
bubble.age.set(currAge + deltaSeconds) // increment individual bubble age.
}
}
}
22 changes: 14 additions & 8 deletions src/editors/BubbleComponentNodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import React from 'react'

import { EditorComponentType, commitProperty, updateProperty } from '@etherealengine/editor/src/components/properties/Util'
import { BubbleEmitterComponent } from '../components/BubbleEmitterComponent'
import NodeEditor from '@etherealengine/editor/src/components/properties/NodeEditor'
import InputGroup from '@etherealengine/editor/src/components/inputs/InputGroup'
import { useComponent } from '@etherealengine/ecs'
import { ColorInput } from '@etherealengine/editor/src/components/inputs/ColorInput'
import AlbumIcon from '@mui/icons-material/Album';
import InputGroup from '@etherealengine/editor/src/components/inputs/InputGroup'
import NumericInput from '@etherealengine/editor/src/components/inputs/NumericInput'
import Vector3Input from '@etherealengine/editor/src/components/inputs/Vector3Input'
import { useComponent } from '@etherealengine/ecs'
import NodeEditor from '@etherealengine/editor/src/components/properties/NodeEditor'
import {
EditorComponentType,
commitProperty,
updateProperty
} from '@etherealengine/editor/src/components/properties/Util'
import AlbumIcon from '@mui/icons-material/Album'
import { BubbleEmitterComponent } from '../components/BubbleEmitterComponent'

export const BubbleNodeEditor: EditorComponentType = (props) => {
const emitterComponent = useComponent(props.entity, BubbleEmitterComponent)
return <NodeEditor description={'Description'} {...props}>
return (
<NodeEditor description={'Description'} {...props}>
<InputGroup name="Color" label="Bubble Color">
<ColorInput
value={emitterComponent.color.value}
Expand All @@ -35,5 +40,6 @@ export const BubbleNodeEditor: EditorComponentType = (props) => {
/>
</InputGroup>
</NodeEditor>
)
}
BubbleNodeEditor.iconComponent = AlbumIcon
BubbleNodeEditor.iconComponent = AlbumIcon
7 changes: 7 additions & 0 deletions src/editors/RegisterBubbleEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ComponentShelfCategories } from '@etherealengine/editor/src/components/element/ElementList'
import { EntityNodeEditor } from '@etherealengine/editor/src/functions/ComponentEditors'
import { BubbleEmitterComponent } from '../components/BubbleEmitterComponent'
import { BubbleNodeEditor } from './BubbleComponentNodeEditor'

EntityNodeEditor.set(BubbleEmitterComponent, BubbleNodeEditor)
ComponentShelfCategories.Misc.push(BubbleEmitterComponent)
18 changes: 9 additions & 9 deletions src/systems/BubbleSystem.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { defineQuery, defineSystem, SimulationSystemGroup, getComponent } from "@etherealengine/ecs"
import { Vector3 } from "three"
import { BubbleEmitterComponent } from "../components/BubbleEmitterComponent"
import { TransformComponent } from "@etherealengine/spatial/src/transform/components/TransformComponent"
import { SimulationSystemGroup, defineQuery, defineSystem, getComponent } from '@etherealengine/ecs'
import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent'
import { Vector3 } from 'three'
import { BubbleEmitterComponent } from '../components/BubbleEmitterComponent'

const bubbleEmitterQuery = defineQuery([BubbleEmitterComponent])
const velocity = new Vector3(0,0,0)
const velocity = new Vector3(0, 0, 0)

export const BubbleSystem = defineSystem({
uuid: "BubbleSystem",
uuid: 'BubbleSystem',
insert: { after: SimulationSystemGroup },
execute: () => {
for (const entity of bubbleEmitterQuery()) {
// [Exercise 2]: Using the below basic setup. Move every bubble not just the first one
const tempvector = new Vector3(0,0,0)
const tempvector = new Vector3(0, 0, 0)
const emitterComponent = getComponent(entity, BubbleEmitterComponent)
const localTransform = getComponent(emitterComponent.bubbleEntities![0], TransformComponent)
if(!localTransform) continue;
if (!localTransform) continue
velocity.copy(emitterComponent.direction).multiplyScalar(emitterComponent.speed)
tempvector.addVectors(localTransform.position, velocity)
localTransform.position.copy(tempvector)
Expand All @@ -24,4 +24,4 @@ export const BubbleSystem = defineSystem({
// Detect if the player is near a bubble and remove it
}
}
})
})
25 changes: 7 additions & 18 deletions src/worldInjection.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
import { ComponentShelfCategories } from '@etherealengine/editor/src/components/element/ElementList'
import { EntityNodeEditor } from '@etherealengine/editor/src/functions/ComponentEditors'
import { getState } from "@etherealengine/hyperflux";
import { BubbleEmitterComponent } from "./components/BubbleEmitterComponent";
import { BubbleNodeEditor } from "./editors/BubbleComponentNodeEditor";
import { BubbleSystem } from "./systems/BubbleSystem";
import { isClient } from '@etherealengine/common/src/utils/getEnvironment';
import { EngineState } from '@etherealengine/spatial/src/EngineState';
import { isClient } from '@etherealengine/common/src/utils/getEnvironment'

export default async function worldInjection() {
if (isClient) {
if (getState(EngineState).isEditing)
{
EntityNodeEditor.set(BubbleEmitterComponent, BubbleNodeEditor)
ComponentShelfCategories.Misc.push(BubbleEmitterComponent)
}
}
}
/** Ensure bubble system is imported */
import './systems/BubbleSystem'

export { BubbleSystem }
/** Ensure bubble editor is imported only on the client */
if (isClient) import('./editors/RegisterBubbleEditor')

export default async function worldInjection() {}

0 comments on commit bd04d31

Please sign in to comment.