Skip to content

Commit

Permalink
feat: attach/detach, loop animation, stop animation/sound (#770)
Browse files Browse the repository at this point in the history
* feat: attach/detach, loop animation, stop animation/sound

* feat: upgrade @dcl/asset-packs
  • Loading branch information
cazala authored Oct 5, 2023
1 parent 6a85a0f commit 90f5538
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 292 deletions.
467 changes: 234 additions & 233 deletions packages/@dcl/inspector/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/@dcl/inspector/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"@babylonjs/inspector": "^6.18.0",
"@babylonjs/loaders": "^6.18.0",
"@babylonjs/materials": "^6.18.0",
"@dcl/asset-packs": "^0.0.0-20231004215238.commit-830aa6e",
"@dcl/asset-packs": "^0.0.0-20231005184607.commit-bcc54dd",
"@dcl/ecs": "file:../ecs",
"@dcl/ecs-math": "2.0.2",
"@dcl/mini-rpc": "^1.0.7",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { Item } from 'react-contexify'
import { AiFillDelete as DeleteIcon } from 'react-icons/ai'
import { VscQuestion as QuestionIcon, VscTrash as RemoveIcon, VscInfo as InfoIcon } from 'react-icons/vsc'
import { VscQuestion as QuestionIcon, VscTrash as RemoveIcon } from 'react-icons/vsc'
import { Action, ActionType, getActionTypes, getJson, ActionPayload, getActionSchema } from '@dcl/asset-packs'
import { ReadWriteByteBuffer } from '@dcl/ecs/dist/serialization/ByteBuffer'
import { Popup } from 'decentraland-ui/dist/components/Popup/Popup'
Expand Down Expand Up @@ -31,6 +31,8 @@ import { getDefaultPayload, getPartialPayload, isStates } from './utils'
import { Props } from './types'

import './ActionInspector.css'
import { AvatarAnchorPointType } from '@dcl/ecs'
import { PlayAnimationAction } from './PlayAnimationAction'

export default withSdk<Props>(
withContextMenu<Props & WithSdkProps>(({ sdk, entity: entityId, contextMenuId }) => {
Expand Down Expand Up @@ -161,6 +163,7 @@ export default withSdk<Props>(
const conditionalActions: Partial<Record<string, () => boolean>> = useMemo(
() => ({
[ActionType.PLAY_ANIMATION]: () => hasAnimations,
[ActionType.STOP_ANIMATION]: () => hasAnimations,
[ActionType.SET_STATE]: () => hasStates,
[ActionType.INCREMENT_COUNTER]: () => hasCounter,
[ActionType.DECREASE_COUNTER]: () => hasCounter,
Expand Down Expand Up @@ -194,12 +197,10 @@ export default withSdk<Props>(
}, [addAction])

const handleChangeAnimation = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>, idx: number) => {
(value: ActionPayload<ActionType.PLAY_ANIMATION>, idx: number) => {
modifyAction(idx, {
...actions[idx],
jsonPayload: getJson<ActionType.PLAY_ANIMATION>({
animation: value
})
jsonPayload: getJson<ActionType.PLAY_ANIMATION>(value)
})
},
[modifyAction, actions]
Expand Down Expand Up @@ -249,6 +250,18 @@ export default withSdk<Props>(
[modifyAction, actions]
)

const handleChangeAnchorPoint = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>, idx: number) => {
modifyAction(idx, {
...actions[idx],
jsonPayload: getJson<ActionType.ATTACH_TO_PLAYER>({
anchorPointId: parseInt(value)
})
})
},
[modifyAction, actions]
)

const handleChangeType = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>, idx: number) => {
modifyAction(idx, {
Expand Down Expand Up @@ -323,40 +336,15 @@ export default withSdk<Props>(
)
}

const renderSelectAnimationMoreInfo = () => {
return (
<Popup
content={
<>
Learn more about animations in the <a href="">docs</a>.
</>
}
trigger={<InfoIcon size={16} />}
position="top center"
on="hover"
hideOnScroll
hoverable
/>
)
}

const renderAction = (action: Action, idx: number) => {
switch (action.type) {
case ActionType.PLAY_ANIMATION: {
return hasAnimations ? (
<div className="row">
<div className="field">
<label>Select Animation {renderSelectAnimationMoreInfo()}</label>
<Dropdown
options={[
{ value: '', text: 'Select an Animation' },
...animations.map((animation) => ({ text: animation.name, value: animation.name }))
]}
value={getPartialPayload<ActionType.PLAY_ANIMATION>(action)?.animation}
onChange={(e) => handleChangeAnimation(e, idx)}
/>
</div>
</div>
<PlayAnimationAction
value={getPartialPayload<ActionType.PLAY_ANIMATION>(action)}
animations={animations}
onUpdate={(value: ActionPayload<ActionType.PLAY_ANIMATION>) => handleChangeAnimation(value, idx)}
/>
) : null
}
case ActionType.SET_STATE: {
Expand Down Expand Up @@ -423,6 +411,26 @@ export default withSdk<Props>(
</div>
)
}
case ActionType.ATTACH_TO_PLAYER: {
return (
<div className="row">
<div className="field">
<label>Select Anchor Point</label>
<Dropdown
options={[
{ value: '', text: 'Select an Anchor Point' },
{ value: AvatarAnchorPointType.AAPT_RIGHT_HAND, text: 'Right Hand' },
{ value: AvatarAnchorPointType.AAPT_LEFT_HAND, text: 'Left Hand' },
{ value: AvatarAnchorPointType.AAPT_NAME_TAG, text: 'Name Tag' },
{ value: AvatarAnchorPointType.AAPT_POSITION, text: 'Avatar Position' }
]}
value={getPartialPayload<ActionType.ATTACH_TO_PLAYER>(action)?.anchorPointId}
onChange={(e) => handleChangeAnchorPoint(e, idx)}
/>
</div>
</div>
)
}
default: {
// TODO: handle generic schemas with something like <JsonSchemaField/>
return null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.PlayAnimationActionContainer {
width: 100%;
}

.PlayAnimationActionContainer .SelectField {
width: 100%;
}

.PlayAnimationActionContainer .SelectField {
width: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useCallback, useEffect, useState } from 'react'
import { ActionPayload, ActionType } from '@dcl/asset-packs'
import { recursiveCheck } from 'jest-matcher-deep-close-to/lib/recursiveCheck'
import { SelectField } from '../../SelectField'
import { Dropdown } from '../../../Dropdown'
import { isValid } from './utils'
import type { Props } from './types'

import './PlayAnimationAction.css'

enum PLAY_MODE {
PLAY_ONCE = 'play-once',
LOOP = 'loop'
}

const playModeOptions = [
{
label: 'Play Once',
value: PLAY_MODE.PLAY_ONCE
},
{
label: 'Loop',
value: PLAY_MODE.LOOP
}
]

const PlaySoundAction: React.FC<Props> = ({ value, animations, onUpdate }: Props) => {
const [payload, setPayload] = useState<Partial<ActionPayload<ActionType.PLAY_ANIMATION>>>({
...value
})

useEffect(() => {
if (!recursiveCheck(payload, value, 2) || !isValid(payload)) return
onUpdate(payload)
}, [payload, onUpdate])

const handleChangeAnimation = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
setPayload({ ...payload, animation: value })
},
[payload, setPayload]
)

const handleChangePlayMode = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLSelectElement>) => {
setPayload({ ...payload, loop: value === PLAY_MODE.LOOP })
},
[payload, setPayload]
)

return (
<div className="PlayAnimationActionContainer">
<div className="row">
<div className="field">
<label>Select Animation</label>
<Dropdown
options={[
{ value: '', text: 'Select an Animation' },
...animations.map((animation) => ({ text: animation.name, value: animation.name }))
]}
value={payload.animation}
onChange={handleChangeAnimation}
/>
</div>
<div className="field">
<label>Play Mode</label>
<SelectField
value={payload.loop ? PLAY_MODE.LOOP : PLAY_MODE.PLAY_ONCE}
options={playModeOptions}
onChange={handleChangePlayMode}
/>
</div>
</div>
</div>
)
}

export default React.memo(PlaySoundAction)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import PlayAnimationAction from './PlayAnimationAction'
export { PlayAnimationAction }
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AnimationGroup } from '@babylonjs/core'
import { ActionPayload, ActionType } from '@dcl/asset-packs'

export interface Props {
value: Partial<ActionPayload<ActionType.PLAY_ANIMATION>>
animations: AnimationGroup[]
onUpdate: (value: ActionPayload<ActionType.PLAY_ANIMATION>) => void
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ActionPayload, ActionType } from '@dcl/asset-packs'

export function isValid(
payload: Partial<ActionPayload<ActionType.PLAY_ANIMATION>>
): payload is ActionPayload<ActionType.PLAY_ANIMATION> {
return !!payload.animation
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ enum PLAY_MODE {
LOOP = 'loop'
}

const options = [
const playModeOptions = [
{
label: 'Play Once',
value: PLAY_MODE.PLAY_ONCE
Expand Down Expand Up @@ -126,7 +126,7 @@ const PlaySoundAction: React.FC<Props> = ({ value, onUpdate }: Props) => {
<label>Play Mode</label>
<SelectField
value={payload.loop ? PLAY_MODE.LOOP : PLAY_MODE.PLAY_ONCE}
options={options}
options={playModeOptions}
onChange={handleChangePlayMode}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ export function isValidTween(tween: ActionPayload['start_tween']) {
!isNaN(parseInt(tween.end.z)) &&
tween.relative !== undefined &&
!!tween.interpolationType &&
!!tween.duration
!isNaN(Number(tween.duration))
)
}
28 changes: 8 additions & 20 deletions packages/@dcl/inspector/src/lib/sdk/operations/add-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,7 @@ import {
PBAudioSource,
LastWriteWinElementSetComponentDefinition
} from '@dcl/ecs'
import {
ActionType,
Actions,
ComponentName,
Trigger,
TriggerType,
Triggers,
getJson,
getNextId,
getPayload
} from '@dcl/asset-packs'
import { ActionType, Actions, ComponentName, Triggers, getJson, getNextId, getPayload } from '@dcl/asset-packs'
import { CoreComponents } from '../components'
import updateSelectedEntity from './update-selected-entity'
import { addChild } from './add-child'
Expand Down Expand Up @@ -80,14 +70,8 @@ export function addAsset(engine: IEngine) {
switch (componentName) {
case CoreComponents.GLTF_CONTAINER: {
const gltfContainer = values.get(componentName) as PBGltfContainer
if (components[ComponentName.TRIGGERS]) {
const triggers = values.get(ComponentName.TRIGGERS) as Triggers
// fix collision masks if the asset has an ON_CLICK trigger
if (triggers.value.some((trigger: Trigger) => trigger.type === TriggerType.ON_CLICK)) {
gltfContainer.visibleMeshesCollisionMask ??= 1
gltfContainer.invisibleMeshesCollisionMask ??= 2
}
}
gltfContainer.visibleMeshesCollisionMask ??= 1
gltfContainer.invisibleMeshesCollisionMask ??= 2
values.set(componentName, { ...gltfContainer, src: gltfContainer.src.replace('{assetPath}', base) })
break
}
Expand Down Expand Up @@ -142,7 +126,11 @@ export function addAsset(engine: IEngine) {
}
} else {
// if the asset is just a path to a model, create a gltf container for it (this is the case for assets dropped from the local files tab)
GltfContainer.create(child, { src: `${base}/${src}` })
GltfContainer.create(child, {
src: `${base}/${src}`,
visibleMeshesCollisionMask: 1,
invisibleMeshesCollisionMask: 2
})
}

// update selection
Expand Down

0 comments on commit 90f5538

Please sign in to comment.