Commit e1e017de authored by Chunchi Che's avatar Chunchi Che

Merge branch 'react-babylonjs' into 'main'

React babylonjs

See merge request !52
parents a6c3fb4f 024c981b
Pipeline #18905 passed with stages
in 4 minutes and 48 seconds
This diff is collapsed.
...@@ -38,9 +38,10 @@ const CardModal = () => { ...@@ -38,9 +38,10 @@ const CardModal = () => {
<Meta title={name} /> <Meta title={name} />
<p>{desc}</p> <p>{desc}</p>
</Card> </Card>
{interactivies.map((interactive) => { {interactivies.map((interactive, idx) => {
return ( return (
<Button <Button
key={idx}
onClick={() => { onClick={() => {
sendSelectIdleCmdResponse(interactive.response); sendSelectIdleCmdResponse(interactive.response);
dispatch(setCardModalIsOpen(false)); dispatch(setCardModalIsOpen(false));
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui"; import * as CONFIG from "../../config/ui";
export default (scene: BABYLON.Scene) => { const Cemetery = () => {
// 墓地
const shape = CONFIG.CemeterySlotShape(); const shape = CONFIG.CemeterySlotShape();
const cemetery = BABYLON.MeshBuilder.CreateBox("cemetery", shape); const position = new BABYLON.Vector3(
// 位置
cemetery.position = new BABYLON.Vector3(
3.2, 3.2,
shape.depth / 2 + CONFIG.Floating, shape.depth / 2 + CONFIG.Floating,
-2.0 -2.0
); );
// 旋转 const rotation = CONFIG.CemeterySlotRotation();
cemetery.rotation = CONFIG.CemeterySlotRotation();
// 材质 return (
const cemeteryMaterial = new BABYLON.StandardMaterial( <box
"cemeteryMaterial", name="cemetery"
scene width={shape.width}
height={shape.height}
depth={shape.depth}
position={position}
rotation={rotation}
>
<standardMaterial
name="cemetery-mat"
diffuseColor={CONFIG.CemeteryColor()}
/>
</box>
); );
cemeteryMaterial.diffuseColor = CONFIG.CemeteryColor();
cemetery.material = cemeteryMaterial;
}; };
export default Cemetery;
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
export default (scene: BABYLON.Scene) => {
// 卡组
const deck = BABYLON.MeshBuilder.CreateBox(
"deck",
CONFIG.DeckSlotShape(),
scene
);
// 位置
deck.position = new BABYLON.Vector3(
3.2,
CONFIG.DeckSlotShape().depth / 2 + CONFIG.Floating,
-3.3
);
// 旋转
deck.rotation = CONFIG.DeckSlotRotation();
// 材质
const deckMaterial = new BABYLON.StandardMaterial("deckMaterial", scene);
deckMaterial.diffuseColor = CONFIG.DeckColor();
deck.material = deckMaterial;
// 额外卡组
const extraDeck = BABYLON.MeshBuilder.CreateBox(
"exraDeck",
CONFIG.ExtraDeckSlotShape(),
scene
);
// 位置
extraDeck.position = new BABYLON.Vector3(
-3.3,
CONFIG.ExtraDeckSlotShape().depth / 2 + CONFIG.Floating,
-3.3
);
// 旋转
extraDeck.rotation = CONFIG.DeckSlotRotation();
// 材质
const extraDeckMaterial = new BABYLON.StandardMaterial(
"extraDeckMaterial",
scene
);
extraDeckMaterial.diffuseColor = CONFIG.ExtraDeckColor();
extraDeck.material = extraDeckMaterial;
};
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
const Deck = () => (
<>
<CommonDeck />
<ExtraDeck />
</>
);
const CommonDeck = () => {
const shape = CONFIG.DeckSlotShape();
const position = new BABYLON.Vector3(
3.2,
shape.depth / 2 + CONFIG.Floating,
-3.3
);
const rotation = CONFIG.DeckSlotRotation();
return (
<box
name="common-deck"
width={shape.width}
height={shape.height}
depth={shape.depth}
position={position}
rotation={rotation}
>
<standardMaterial
name="common-deck-mat"
diffuseColor={CONFIG.DeckColor()}
/>
</box>
);
};
const ExtraDeck = () => {
const shape = CONFIG.ExtraDeckSlotShape();
const position = new BABYLON.Vector3(
-3.3,
shape.depth / 2 + CONFIG.Floating,
-3.3
);
const rotation = CONFIG.DeckSlotRotation();
return (
<box
name="extra-deck"
width={shape.width}
height={shape.height}
depth={shape.depth}
position={position}
rotation={rotation}
>
<standardMaterial
name="extra-deck-mat"
diffuseColor={CONFIG.ExtraDeckColor()}
/>
</box>
);
};
export default Deck;
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
export default (scene: BABYLON.Scene) => {
// 除外区
const shape = CONFIG.ExclusionSlotShape();
const exclusion = BABYLON.MeshBuilder.CreateBox("exclusion", shape);
// 位置
exclusion.position = new BABYLON.Vector3(3.2, CONFIG.Floating, -0.7);
// 旋转
exclusion.rotation = CONFIG.ExclusionSlotRotation();
// 材质
const exclusionMaterial = new BABYLON.StandardMaterial(
"exclusionMaterial",
scene
);
exclusionMaterial.diffuseColor = CONFIG.ExclusionColor();
exclusion.material = exclusionMaterial;
};
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
const Exclusion = () => {
const shape = CONFIG.ExclusionSlotShape();
const position = new BABYLON.Vector3(3.2, CONFIG.Floating, -0.7);
const rotation = CONFIG.ExclusionSlotRotation();
return (
<box
name="exclusion"
width={shape.width}
height={shape.height}
depth={shape.depth}
position={position}
rotation={rotation}
>
<standardMaterial
name="exclusion-mat"
diffuseColor={CONFIG.ExclusionColor()}
></standardMaterial>
</box>
);
};
export default Exclusion;
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
export default (scene: BABYLON.Scene) => {
const xs = [-1.1, 1];
const shape = CONFIG.CardSlotShape();
for (let i in xs) {
const slot = BABYLON.MeshBuilder.CreatePlane(
`extraMonster${i}`,
shape,
scene
);
// 位置
slot.position = new BABYLON.Vector3(
xs[i],
shape.depth / 2 + CONFIG.Floating,
0
);
// 旋转
slot.rotation = CONFIG.CardSlotRotation();
// 材质
const extraMonsterMaterial = new BABYLON.StandardMaterial(
"extraMonsterMaterial",
scene
);
extraMonsterMaterial.diffuseTexture = new BABYLON.Texture(
`http://localhost:3030/images/card_slot.png`
);
extraMonsterMaterial.diffuseTexture.hasAlpha = true;
slot.material = extraMonsterMaterial;
}
};
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui"; import * as CONFIG from "../../config/ui";
export default (scene: BABYLON.Scene) => { const Field = () => {
// 墓地
const shape = CONFIG.FieldSlotShape(); const shape = CONFIG.FieldSlotShape();
const field = BABYLON.MeshBuilder.CreateBox("field", shape); const position = new BABYLON.Vector3(
// 位置
field.position = new BABYLON.Vector3(
-3.3, -3.3,
shape.depth / 2 + CONFIG.Floating, shape.depth / 2 + CONFIG.Floating,
-2.0 -2.0
); );
// 旋转 const rotation = CONFIG.FieldSlotRotation();
field.rotation = CONFIG.FieldSlotRotation();
// 材质 return (
const fieldMaterial = new BABYLON.StandardMaterial("fieldMaterial", scene); <box
fieldMaterial.diffuseColor = CONFIG.FieldColor(); name="field"
field.material = fieldMaterial; width={shape.width}
height={shape.height}
depth={shape.depth}
position={position}
rotation={rotation}
>
<standardMaterial name="field-mat" diffuseColor={CONFIG.FieldColor()} />
</box>
);
}; };
export default Field;
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
import { Card, InteractType } from "../../reducers/duel/util";
import {
setCardModalImgUrl,
setCardModalIsOpen,
setCardModalText,
setCardModalInteractivies,
} from "../../reducers/duel/mod";
import { store } from "../../store";
export default (hands: Card[], scene: BABYLON.Scene) => {
const handShape = CONFIG.HandShape();
hands.forEach((item, idx, _) => {
const hand = BABYLON.MeshBuilder.CreatePlane(
`hand${idx}`,
handShape,
scene
);
// 位置&旋转
setupHandTransform(hand, item);
// 材质
setupHandMaterial(hand, item, scene);
// 事件管理
setupHandAction(hand, item, idx, scene);
});
};
function setupHandTransform(mesh: BABYLON.Mesh, state: Card) {
mesh.position = new BABYLON.Vector3(
state.transform.position?.x,
state.transform.position?.y,
state.transform.position?.z
);
mesh.rotation = new BABYLON.Vector3(
state.transform.rotation?.x,
state.transform.rotation?.y,
state.transform.rotation?.z
);
}
function setupHandMaterial(
mesh: BABYLON.Mesh,
state: Card,
scene: BABYLON.Scene
) {
const handMaterial = new BABYLON.StandardMaterial(
`handMaterial${state.meta.id}`,
scene
);
// 材质贴纸
handMaterial.diffuseTexture = new BABYLON.Texture(
`https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/${state.meta.id}.jpg`,
scene
);
mesh.material = handMaterial;
}
function setupHandAction(
mesh: BABYLON.Mesh,
state: Card,
_handIdx: number,
scene: BABYLON.Scene
) {
const dispatch = store.dispatch;
mesh.actionManager = new BABYLON.ActionManager(scene);
mesh.actionManager.isRecursive = true;
// 监听点击事件
mesh.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(
BABYLON.ActionManager.OnPickTrigger,
(_event) => {
dispatch(
setCardModalText([state.meta.text.name, state.meta.text.desc])
);
dispatch(
setCardModalImgUrl(
`https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/${state.meta.id}.jpg`
)
);
dispatch(
setCardModalInteractivies(
state.interactivities.map((interactive) => {
return {
desc: interactTypeToString(interactive.interactType),
response: interactive.response,
};
})
)
);
dispatch(setCardModalIsOpen(true));
}
)
);
// 监听`Hover`事件
mesh.actionManager.registerAction(
new BABYLON.CombineAction(
{ trigger: BABYLON.ActionManager.OnPointerOverTrigger },
[
new BABYLON.SetValueAction(
{
trigger: BABYLON.ActionManager.OnPointerOverTrigger,
},
mesh,
"scaling",
CONFIG.HandHoverScaling()
),
]
)
);
// 监听`Hover`离开事件
mesh.actionManager.registerAction(
new BABYLON.CombineAction(
{ trigger: BABYLON.ActionManager.OnPointerOutTrigger },
[
new BABYLON.SetValueAction(
{
trigger: BABYLON.ActionManager.OnPointerOutTrigger,
},
mesh,
"scaling",
CONFIG.HandHoverOutScaling()
),
]
)
);
}
function interactTypeToString(t: InteractType): string {
switch (t) {
case InteractType.SUMMON: {
return "普通召唤";
}
case InteractType.SP_SUMMON: {
return "特殊召唤";
}
case InteractType.POS_CHANGE: {
return "改变表示形式";
}
case InteractType.MSET: {
return "前场放置";
}
case InteractType.SSET: {
return "后场放置";
}
case InteractType.ACTIVATE: {
return "发动效果";
}
default: {
return "未知选项";
}
}
}
import * as BABYLON from "@babylonjs/core";
import { useAppSelector } from "../../hook";
import { selectMeHands } from "../../reducers/duel/handsSlice";
import * as CONFIG from "../../config/ui";
import { Card, InteractType } from "../../reducers/duel/util";
import {
setCardModalImgUrl,
setCardModalIsOpen,
setCardModalText,
setCardModalInteractivies,
} from "../../reducers/duel/mod";
import { store } from "../../store";
import { useHover } from "react-babylonjs";
import { useClick } from "./hook";
import { useState, useRef } from "react";
const Hands = () => {
const hands = useAppSelector(selectMeHands).cards;
return (
<>
{hands.map((hand, idx) => {
return <Hand state={hand} idx={idx} key={idx} />;
})}
</>
);
};
const Hand = (props: { state: Card; idx: number }) => {
const handShape = CONFIG.HandShape();
const hoverScale = CONFIG.HandHoverScaling();
const defaultScale = new BABYLON.Vector3(1, 1, 1);
const planeRef = useRef(null);
const [state, idx] = [props.state, props.idx];
const [hovered, setHovered] = useState(false);
const dispatch = store.dispatch;
useHover(
() => setHovered(true),
() => setHovered(false),
planeRef
);
useClick(
() => {
dispatch(setCardModalText([state.meta.text.name, state.meta.text.desc]));
dispatch(
setCardModalImgUrl(
`https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/${state.meta.id}.jpg`
)
);
dispatch(
setCardModalInteractivies(
state.interactivities.map((interactive) => {
return {
desc: interactTypeToString(interactive.interactType),
response: interactive.response,
};
})
)
);
dispatch(setCardModalIsOpen(true));
},
planeRef,
[state]
);
return (
<>
<plane
name={`hand-${idx}`}
ref={planeRef}
width={handShape.width}
height={handShape.height}
scaling={hovered ? hoverScale : defaultScale}
position={
new BABYLON.Vector3(
state.transform.position?.x,
state.transform.position?.y,
state.transform.position?.z
)
}
rotation={
new BABYLON.Vector3(
state.transform.rotation?.x,
state.transform.rotation?.y,
state.transform.rotation?.z
)
}
>
<standardMaterial
name={`hand-mat-${idx}`}
diffuseTexture={
new BABYLON.Texture(
`https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/${state.meta.id}.jpg`
)
}
/>
</plane>
</>
);
};
function interactTypeToString(t: InteractType): string {
switch (t) {
case InteractType.SUMMON: {
return "普通召唤";
}
case InteractType.SP_SUMMON: {
return "特殊召唤";
}
case InteractType.POS_CHANGE: {
return "改变表示形式";
}
case InteractType.MSET: {
return "前场放置";
}
case InteractType.SSET: {
return "后场放置";
}
case InteractType.ACTIVATE: {
return "发动效果";
}
default: {
return "未知选项";
}
}
}
export default Hands;
// 一些自定义`Hook`
import { ActionEvent } from "@babylonjs/core";
import { MutableRefObject, useEffect, useRef } from "react";
import { Nullable } from "@babylonjs/core/types.js";
import { Mesh } from "@babylonjs/core/Meshes/mesh.js";
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh.js";
import { ActionManager } from "@babylonjs/core/Actions/actionManager.js";
import { IAction } from "@babylonjs/core/Actions/action.js";
import { ExecuteCodeAction } from "@babylonjs/core/Actions/directActions.js";
export interface MeshEventType {
(env: ActionEvent): void;
}
type DependencyList = ReadonlyArray<unknown>;
/**
* rewritten useClick hook
*
* origion ref: https://github.com/brianzinn/react-babylonjs/blob/master/packages/react-babylonjs/src/hooks/utilityHooks.ts#L132
*
* Why i rewritten this?: https://github.com/brianzinn/react-babylonjs/issues/209
*
* @param onClick What would be passed in the OnPickTrigger from ActionManager
* @param ownRef to re-use a Ref you already have, otherwise one is created for you and returned.
* @param deps observation object
*/
export function useClick(
onClick: MeshEventType,
ownRef?: MutableRefObject<Nullable<Mesh>>,
deps?: DependencyList
): [MutableRefObject<Nullable<Mesh>>] {
const createdRef = useRef<Nullable<Mesh>>(null);
const ref = ownRef ?? createdRef;
useEffect(() => {
if (ref.current) {
if (ref.current instanceof AbstractMesh) {
const mesh = ref.current as Mesh;
if (!mesh.actionManager) {
mesh.actionManager = new ActionManager(mesh.getScene());
}
const action: Nullable<IAction> = mesh.actionManager.registerAction(
new ExecuteCodeAction(ActionManager.OnPickTrigger, function (
ev: any
) {
onClick(ev);
})
);
return () => {
// unregister on teardown
mesh.actionManager?.unregisterAction(action!);
};
} else {
console.warn("onClick hook only supports referencing Meshes");
}
}
}, [...(deps || []), ref]);
// todo: if use ref.current as dep, duplicate register action.
return [ref];
}
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
export default (scene: BABYLON.Scene) => {
const left = -2.15;
const gap = 1.05;
const shape = CONFIG.CardSlotShape();
for (let i = 0; i < 5; i++) {
const slot = BABYLON.MeshBuilder.CreatePlane(`magic${i}`, shape, scene);
// 位置
slot.position = new BABYLON.Vector3(
left + gap * i,
shape.depth / 2 + CONFIG.Floating,
-2.6
);
// 旋转
slot.rotation = CONFIG.CardSlotRotation();
// 材质
const magicMaterial = new BABYLON.StandardMaterial("magicMaterial", scene);
magicMaterial.diffuseTexture = new BABYLON.Texture(
`http://localhost:3030/images/card_slot.png`
);
magicMaterial.diffuseTexture.hasAlpha = true;
slot.material = magicMaterial;
}
};
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
// TODO: use config
const left = -2.15;
const gap = 1.05;
const Magics = () => {
return (
<>
{[0, 1, 2, 3, 4].map((idx) => {
return <Magic idx={idx} />;
})}
</>
);
};
const Magic = (props: { idx: number }) => {
const shape = CONFIG.CardSlotShape();
const position = new BABYLON.Vector3(
left + gap * props.idx,
shape.depth / 2 + CONFIG.Floating,
-2.6
);
const rotation = CONFIG.CardSlotRotation();
return (
<plane
name={`magic-${props.idx}`}
width={shape.width}
height={shape.height}
position={position}
rotation={rotation}
>
<standardMaterial
name={`magic-mat-${props.idx}`}
diffuseTexture={
new BABYLON.Texture(`http://localhost:3030/images/card_slot.png`)
}
alpha={0.2}
></standardMaterial>
</plane>
);
};
export default Magics;
import React from "react";
import { Engine, Scene } from "react-babylonjs";
import { ReactReduxContext, Provider } from "react-redux";
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
import Hands from "./hands";
import Monsters from "./monsters";
import CardModal from "./cardModal";
import HintNotification from "./hintNotification";
import Magics from "./magics";
import Field from "./field";
import Deck from "./deck";
import Exclusion from "./exclusion";
const NeosDuel = () => (
<>
<ReactReduxContext.Consumer>
{({ store }) => (
<Engine antialias adaptToDeviceRatio canvasId="babylonJS">
<Scene>
<Provider store={store}>
<Camera />
<Light />
<Hands />
<Monsters />
<Magics />
<Field />
<Deck />
<Exclusion />
<Ground />
</Provider>
</Scene>
</Engine>
)}
</ReactReduxContext.Consumer>
<CardModal />
<HintNotification />
</>
);
const Camera = () => (
<freeCamera
name="duel-camera"
position={new BABYLON.Vector3(0, 8, -10)}
target={BABYLON.Vector3.Zero()}
></freeCamera>
);
const Light = () => (
<hemisphericLight
name="duel-light"
direction={new BABYLON.Vector3(1, 2.5, 1)}
intensity={0.7}
></hemisphericLight>
);
const Ground = () => {
const shape = CONFIG.GroundShape();
const texture = new BABYLON.Texture(
`http://localhost:3030/images/newfield.png`
);
texture.hasAlpha = true;
return (
<ground name="duel-ground" width={shape.width} height={shape.height}>
<standardMaterial
name="duel-ground-mat"
diffuseTexture={texture}
></standardMaterial>
</ground>
);
};
export default NeosDuel;
/*
* 一个简洁的决斗界面实现
*
* */
import { useAppSelector } from "../../hook";
import React, { useEffect, useRef } from "react";
import { observeStore } from "../../store";
import * as BABYLON from "@babylonjs/core";
import renderHands from "./hands";
import renderMonsters from "./monsters";
import renderExtraMonsters from "./extraMonsters";
import renderMagics from "./magics";
import renderDeck from "./deck";
import renderCemetery from "./cemetery";
import renderExclusion from "./exclusion";
import renderField from "./field";
import * as CONFIG from "../../config/ui";
import { Card } from "../../reducers/duel/util";
import { selectCurrentPlayer } from "../../reducers/duel/turnSlice";
import CardModal from "./cardModal";
import HintNotification from "./hintNotification";
import { selectMeHands } from "../../reducers/duel/handsSlice";
import { selectMeMonsters } from "../../reducers/duel/monstersSlice";
// CONFIG
const Duel = () => {
// ----- 数据获取 -----
const hands = useAppSelector(selectMeHands).cards;
const monsters = useAppSelector(selectMeMonsters).monsters;
const currentPlayer = useAppSelector(selectCurrentPlayer);
// ----- WebGL渲染 -----
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
// 初始化Scene
const canvasCurrent = canvasRef.current;
const engine = new BABYLON.Engine(canvasCurrent, true);
const scene = new BABYLON.Scene(engine);
// 创建Camera
const camera = new BABYLON.FreeCamera(
"camera1",
new BABYLON.Vector3(0, 8, -10), // 俯视方向
scene
);
camera.setTarget(BABYLON.Vector3.Zero()); // 俯视向前
camera.attachControl(canvasCurrent, true);
// 创建光源
const light = new BABYLON.HemisphericLight(
"light",
new BABYLON.Vector3(1, 2.5, 1),
scene
);
light.intensity = 0.7;
// 魔法陷阱区
renderMagics(scene);
// 怪兽区
renderMonsters(monsters, scene);
// 创建额外怪兽区
renderExtraMonsters(scene);
// 创建手牌
renderHands(hands, scene);
// 创建卡组
renderDeck(scene);
// 创建墓地
renderCemetery(scene);
// 创建除外区
renderExclusion(scene);
// 创建场地
renderField(scene);
// 创建地板
const ground = BABYLON.MeshBuilder.CreateGround(
"ground",
CONFIG.GroundShape(),
scene
);
const groundMaterial = new BABYLON.StandardMaterial(
"groundMaterial",
scene
);
groundMaterial.diffuseTexture = new BABYLON.Texture(
`http://localhost:3030/images/newfield.png`
);
groundMaterial.diffuseTexture.hasAlpha = true;
ground.material = groundMaterial;
/* 一些未处理的逻辑,在这里用日志打印出来 */
// 当前操作玩家
console.log(`currentPlayer:` + currentPlayer);
// 渲染循环
engine.runRenderLoop(() => {
scene.render();
});
}, [canvasRef, hands, monsters, currentPlayer]); // FIXME: 这里需要优化,应该分组件按需渲染
useEffect(() => {
// 监听状态变化,并实现动画
const onHandsChange = (prev_hands: Card[] | null, cur_hands: Card[]) => {
console.log(prev_hands, "change to", cur_hands);
};
const unsubscribe = observeStore(
(root) => selectMeHands(root).cards,
onHandsChange
);
return () => {
// 取消监听
unsubscribe();
};
}, []);
return (
<>
<canvas
width={window.innerWidth}
height={window.innerHeight}
ref={canvasRef}
/>
<CardModal />
<HintNotification />
</>
);
};
export default Duel;
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
import { Monster } from "../../reducers/duel/util";
import { clearMonsterSelectInfo } from "../../reducers/duel/mod";
import { store } from "../../store";
import { sendSelectPlaceResponse } from "../../api/ocgcore/ocgHelper";
export default (monsters: Monster[], scene: BABYLON.Scene) => {
const left = -2.15;
const gap = 1.05;
const shape = CONFIG.CardSlotShape();
for (const monster of monsters) {
const slot = BABYLON.MeshBuilder.CreatePlane(
`monster${monster.sequence}`,
shape,
scene
);
// 位置
setupMonsterTransform(slot, monster, left, gap, shape);
// 旋转
slot.rotation = CONFIG.CardSlotRotation();
// 材质
setupMonsterMaterial(slot, monster, scene);
// 高亮
setupHintEdge(slot, monster);
// 事件管理
setupMonsterAction(slot, monster, scene);
}
};
function setupMonsterTransform(
mesh: BABYLON.Mesh,
state: Monster,
left: number,
gap: number,
shape: { width: number; height: number; depth: number }
) {
mesh.position = new BABYLON.Vector3(
left + gap * state.sequence,
shape.depth / 2 + CONFIG.Floating,
-1.35
);
}
function setupMonsterMaterial(
mesh: BABYLON.Mesh,
state: Monster,
scene: BABYLON.Scene
) {
const monsterMaterial = new BABYLON.StandardMaterial(
"monsterMaterial",
scene
);
monsterMaterial.diffuseTexture = state.occupant
? new BABYLON.Texture(
`https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/${state.occupant.id}.jpg`
)
: new BABYLON.Texture(`http://localhost:3030/images/card_slot.png`);
monsterMaterial.diffuseTexture.hasAlpha = true;
mesh.material = monsterMaterial;
}
function setupHintEdge(mesh: BABYLON.Mesh, state: Monster) {
if (state.selectInfo) {
mesh.enableEdgesRendering();
mesh.edgesWidth = 2.0;
mesh.edgesColor = BABYLON.Color4.FromColor3(BABYLON.Color3.Yellow());
} else {
mesh.disableEdgesRendering();
}
}
function setupMonsterAction(
mesh: BABYLON.Mesh,
state: Monster,
scene: BABYLON.Scene
) {
const dispatch = store.dispatch;
mesh.actionManager = new BABYLON.ActionManager(scene);
// 监听点击事件
mesh.actionManager.registerAction(
new BABYLON.ExecuteCodeAction(
BABYLON.ActionManager.OnPickTrigger,
(_event) => {
if (state.selectInfo) {
sendSelectPlaceResponse(state.selectInfo.response);
dispatch(clearMonsterSelectInfo(0));
dispatch(clearMonsterSelectInfo(1));
}
}
)
);
}
import * as BABYLON from "@babylonjs/core";
import * as CONFIG from "../../config/ui";
import { useClick } from "./hook";
import { store } from "../../store";
import { Monster } from "../../reducers/duel/util";
import "react-babylonjs";
import { useRef } from "react";
import { sendSelectPlaceResponse } from "../../api/ocgcore/ocgHelper";
import { clearMonsterSelectInfo } from "../../reducers/duel/mod";
import { useAppSelector } from "../../hook";
import { selectMeMonsters } from "../../reducers/duel/monstersSlice";
const left = -2.15; // TODO: config
const gap = 1.05;
const Monsters = () => {
const monsters = useAppSelector(selectMeMonsters).monsters;
return (
<>
{monsters.map((monster, idx) => {
return <CommonMonster state={monster} key={idx} />;
})}
<ExtraMonsters />
</>
);
};
const CommonMonster = (props: { state: Monster }) => {
const planeRef = useRef(null);
const shape = CONFIG.CardSlotShape();
const position = new BABYLON.Vector3(
left + gap * props.state.sequence,
shape.depth / 2 + CONFIG.Floating,
-1.35
);
const rotation = CONFIG.CardSlotRotation();
const edgesWidth = 2.0;
const edgesColor = BABYLON.Color4.FromColor3(BABYLON.Color3.Yellow());
const dispatch = store.dispatch;
useClick(
(_event) => {
if (props.state.selectInfo) {
sendSelectPlaceResponse(props.state.selectInfo?.response);
dispatch(clearMonsterSelectInfo(0));
dispatch(clearMonsterSelectInfo(1));
}
},
planeRef,
[props.state]
);
return (
<plane
name={`monster-${props.state.selectInfo}`}
ref={planeRef}
width={shape.width}
height={shape.height}
position={position}
rotation={rotation}
enableEdgesRendering
edgesWidth={props.state.selectInfo ? edgesWidth : 0}
edgesColor={edgesColor}
>
<standardMaterial
name={`monster-mat-${props.state.sequence}`}
diffuseTexture={
props.state.occupant
? new BABYLON.Texture(
`https://cdn02.moecube.com:444/images/ygopro-images-zh-CN/${props.state.occupant.id}.jpg`
)
: new BABYLON.Texture(`http://localhost:3030/images/card_slot.png`)
}
alpha={0.2}
></standardMaterial>
</plane>
);
};
// TODO: use props and redux
const ExtraMonsters = () => {
const xs = [-1.1, 1];
const shape = CONFIG.CardSlotShape();
const position = (x: number) =>
new BABYLON.Vector3(x, shape.depth / 2 + CONFIG.Floating, 0);
const rotation = CONFIG.CardSlotRotation();
return (
<>
{xs.map((x, idx) => (
<plane
name={`extra-monster-${idx}`}
position={position(x)}
rotation={rotation}
>
<standardMaterial
name={`extra-monster-mat-${idx}`}
diffuseTexture={
new BABYLON.Texture(`http://localhost:3030/images/card_slot.png`)
}
alpha={0.2}
></standardMaterial>
</plane>
))}
</>
);
};
export default Monsters;
...@@ -3,7 +3,7 @@ import JoinRoom from "./JoinRoom"; ...@@ -3,7 +3,7 @@ import JoinRoom from "./JoinRoom";
import WaitRoom from "./WaitRoom"; import WaitRoom from "./WaitRoom";
import { Routes, Route } from "react-router-dom"; import { Routes, Route } from "react-router-dom";
import Mora from "./Mora"; import Mora from "./Mora";
import Duel from "./Duel/mod"; import NeosDuel from "./Duel/main";
export default function () { export default function () {
// FIXME: 这里Mora/Duel路由应该由每个房间指定一个路径 // FIXME: 这里Mora/Duel路由应该由每个房间指定一个路径
...@@ -12,7 +12,7 @@ export default function () { ...@@ -12,7 +12,7 @@ export default function () {
<Route path="/" element={<JoinRoom />} /> <Route path="/" element={<JoinRoom />} />
<Route path="/:player/:passWd/:ip" element={<WaitRoom />} /> <Route path="/:player/:passWd/:ip" element={<WaitRoom />} />
<Route path="/mora" element={<Mora />} /> <Route path="/mora" element={<Mora />} />
<Route path="/duel" element={<Duel />} /> <Route path="/duel" element={<NeosDuel />} />
</Routes> </Routes>
); );
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment