Skip to content

Commit

Permalink
Merge branch 'feat-mvp' of github.com:Mississippi-Labs/mississippi in…
Browse files Browse the repository at this point in the history
…to feat-mvp
  • Loading branch information
TiyoSheng committed Nov 29, 2023
2 parents d9a6ab9 + 7c6f5cd commit 6661406
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 4 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Home from '@/pages/home';
import HeroEdit from '@/pages/heroEdit';
import PIXIAPP from '@/components/PIXIAPP';
import { useEffect } from 'react';
import TestBattle from '@/pages/testBattle';

export const App = () => {

Expand All @@ -27,6 +28,7 @@ export const App = () => {
<Route path="test" element={<Test />} />
<Route path="heroEdit" element={<HeroEdit />} />
<Route path="app" element={<PIXIAPP />} />
<Route path="testBattle" element={<TestBattle />} />
</Route>
</Routes>
</BrowserRouter>
Expand Down
156 changes: 156 additions & 0 deletions packages/client/src/components/DuelField/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React, { useEffect, useImperativeHandle, useRef, useState } from 'react';
import { AnimatedSprite, Stage } from '@pixi/react';
import Player, { IPlayer } from '@/components/PIXIPlayers/Player';
import { Actions, ActionType, EffectCfg, EffectFrameCount, FrameOffsetY, FrameSize } from '@/config/hero';
import { loadAssets } from '@/utils';
import * as PIXI from 'pixi.js';


export type AttackType = 'sprint' | 'sneak' | 'magic';
export type RoleType = 'left' | 'right';

interface IProps {
leftPlayer: IPlayer;
rightPlayer: IPlayer;
afterAttack?: (role: RoleType) => void;
}

export interface IDuelFieldMethod {
leftAttack: (type: AttackType) => void;
rightAttack: (type: AttackType) => void;
kill: (role: RoleType) => void;
}


const DuelField = React.forwardRef<IDuelFieldMethod>((props: IProps, ref) => {

const { leftPlayer, rightPlayer, afterAttack } = props;
const [leftAction, setLeftAction] = useState<ActionType>('idle');
const [rightAction, setRightAction] = useState<ActionType>('idle');
const textureMap = useRef({
magic: null,
sprint: null,
sneak: null
});
const [frameIndex, setFrameIndex] = useState(0);
const frameInterval = useRef<NodeJS.Timeout>();
const [attackType, setAttackType] = useState<AttackType>(null);
const [effectPositionX, setEffectPositionX] = useState(200);

useImperativeHandle(ref, () => ({
leftAttack: (type: AttackType) => {
setLeftAction('slash');
setRightAction('blink');
setEffectPositionX(680);
console.log(leftPlayer.name, 'attack', type);
if (textureMap.current[type]) {
setAttackType(type);
}
},
rightAttack: (type: AttackType) => {
setRightAction('slash');
setLeftAction('blink');
setEffectPositionX(200);
if (textureMap.current[type]) {
setAttackType(type);
}
console.log(rightPlayer.name, 'attack', type);
},
kill: (role: RoleType) => {
if (role === 'left') {
setLeftAction('die')
} else if (role === 'right') {
setRightAction('die');
}
},
}));

useEffect(() => {
if (!attackType) {
return
}
clearInterval(frameInterval.current);
frameInterval.current = setInterval(() => {
setFrameIndex((prevState => {
if (prevState + 1 >= EffectFrameCount) {
setAttackType(null);
clearInterval(frameInterval.current)
}
return (prevState + 1) % EffectFrameCount;
}));
}, 60);
}, [attackType]);

const loadTexture = (type: AttackType) => {
loadAssets(`/assets/img/effect/${type}.png`, (assets) => {
const sheet = PIXI.Texture.from(assets);
const textures = [];
for (let i = 0; i < EffectFrameCount; i++) {
const offsetX = (i % EffectCfg[type].col) * EffectCfg[type].width;
const offsetY = ~~(i / EffectCfg[type].col) * EffectCfg[type].height;
const frame = new PIXI.Rectangle(offsetX, offsetY, EffectCfg[type].width, EffectCfg[type].height);
textures.push(new PIXI.Texture(sheet, frame));
}
textureMap.current[type] = textures;
})
}

useEffect(() => {
loadTexture('sneak');
loadTexture('sprint');
loadTexture('magic');
}, []);


return (
<Stage width={860} height={380} options={{ resolution: 1, backgroundAlpha: 0 }}>
<Player
{...leftPlayer}
name={''}
size={128}
y={1.5}
x={0.5}
action={leftAction}
onActionEnd={(action) => {
if (action === 'slash') {
setLeftAction('idle');
setRightAction('idle');
afterAttack?.('left');
}
}}
/>
<Player
{...rightPlayer}
name={''}
size={128}
y={1.5}
x={5.1}
action={rightAction}
onActionEnd={(action) => {
if (action === 'slash') {
setLeftAction('idle');
setRightAction('idle');
afterAttack?.('right');
}
}}
/>
{
attackType && textureMap.current[attackType] && (
<AnimatedSprite
anchor={0.5}
x={effectPositionX}
y={240}
scale={attackType === 'sprint' ? 1.5 : 1}
isPlaying
animationSpeed={0}
currentFrame={frameIndex}
textures={textureMap.current[attackType]}
/>
)
}

</Stage>
);
});

export default DuelField;
8 changes: 6 additions & 2 deletions packages/client/src/components/PIXIPlayers/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@ export interface IPlayer {
isPlaying?: boolean;
hpVisible?: boolean;
hunted?: boolean;
onActionEnd?: (type: ActionType) => void;
}



const Player = (props: IPlayer) => {

const { action = 'idle', size = cellSize, toward = 'Right', x = 0, y = 0, equip = {}, name, isPlaying = true, waiting = false,
moving, hpVisible = false, hp, maxHp, hunted = false } = props;
moving, hpVisible = false, hp, maxHp, hunted = false, onActionEnd } = props;
const { clothes, handheld, head: cap } = equip;
const [textureMap, setTextureMap] = useState({
body: null,
Expand Down Expand Up @@ -127,7 +130,8 @@ const Player = (props: IPlayer) => {
} else {
setFrameIndex(prevIndex => {
if (prevIndex === Actions[action].step - 1) {
clearInterval(frameInterval.current)
clearInterval(frameInterval.current);
onActionEnd?.(action as ActionType);
return prevIndex;
}
return (prevIndex + 1) % Actions[action].step;
Expand Down
23 changes: 21 additions & 2 deletions packages/client/src/config/hero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export const Actions: ActionsType = {
slash: {
step: 4,
row: 8,
loop: true
loop: false
},
blink: {
step: 2,
Expand All @@ -262,4 +262,23 @@ export const Actions: ActionsType = {

export const FrameSize = 64;
export const FrameOffsetY = FrameSize / 2;
export const HeroRegions = ['body', 'head', 'hair', 'eyes', 'arms', 'armor', 'helmet', 'weapon'];
export const HeroRegions = ['body', 'head', 'hair', 'eyes', 'arms', 'armor', 'helmet', 'weapon'];

export const EffectFrameCount = 30;
export const EffectCfg = {
sprint: {
col: 7,
width: 120,
height: 109
},
sneak: {
col: 6,
width: 291,
height: 301,
},
magic: {
col: 6,
width: 265,
height: 265
}
}
119 changes: 119 additions & 0 deletions packages/client/src/pages/testBattle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, { useRef, useState } from 'react';
import { Stage } from '@pixi/react';
import Player from '@/components/PIXIPlayers/Player';
import DuelField, { AttackType, IDuelFieldMethod } from '@/components/DuelField';

import './styles.scss';

const playerA = {
"x": 0.5,
"y": 1.5,
"hp": 115,
"attack": 46,
"attackRange": 4,
"speed": 4,
"strength": 35,
"space": 3,
"oreBalance": 0,
"treasureBalance": 0,
"state": 2,
"lastBattleTime": 0,
"maxHp": 115,
"name": "V2",
"url": "",
"addr": "0xb53c83ef2467da36c687c81cb23140d92e3d10ba",
"username": "V2",
"equip": {
"clothes": "Chain Mail",
"handheld": "Bone Wand",
"head": "Great Helm"
},
"waiting": false,
"action": "idle",
"moving": false,
"toward": "Right"
}

const playerB = {
"x": 5.1,
"y": 1.5,
"hp": 135,
"attack": 40,
"attackRange": 3,
"speed": 4,
"strength": 32,
"space": 3,
"oreBalance": 0,
"treasureBalance": 0,
"state": 2,
"lastBattleTime": 0,
"maxHp": 135,
"name": "V",
"url": "",
"addr": "0xb58fd9cb0c9100bb6694a4d18627fb238d3bb893",
"username": "V",
"equip": {
"clothes": "Chain Mail",
"handheld": "Falchion",
"head": "Helm"
},
"waiting": false,
"action": "idle",
"moving": false,
"toward": "Left"
}

const TestBattle = () => {

const duel = useRef<IDuelFieldMethod>();
const [attackType, setAttackType] = useState<AttackType>('sprint');
const [role, setRole] = useState('left');

return (
<div >
<div className={'opt'}>

<button onClick={() => {
duel.current.leftAttack(attackType);
}}>left attack</button>

<button onClick={() => {
duel.current.rightAttack(attackType);
}}>right attack</button>

<select
value={attackType}
onChange={(e) => {
setAttackType(e.target.value as AttackType)
}}
>
<option value="sprint">Sprint</option>
<option value="sneak">Sneak</option>
<option value="magic">Magic</option>
</select>

<button onClick={() => {
duel.current.kill(role);
}}>kill</button>

<select
value={role}
onChange={(e) => {
setRole(e.target.value)
}}
>
<option value="left">left</option>
<option value="right">right</option>
</select>
</div>

<DuelField
ref={duel}
leftPlayer={playerA}
rightPlayer={playerB}
/>
</div>
);
};

export default TestBattle;
12 changes: 12 additions & 0 deletions packages/client/src/pages/testBattle/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.opt {
width: 800px;
padding: 20px;
display: flex;
justify-content: space-around;

button, select {
width: 60px;
height: 32px;
}

}

0 comments on commit 6661406

Please sign in to comment.