我正在React上制作多人游戏,并使用Colyseus.js作为我的多人API。问题是我需要在大厅中显示所有连接的玩家:
export default function Lobby() {
const classes = useStyles();
const gameState = useSelector(state => state.roomState);
const history = useHistory();
// Creating array to display all connected players
let playersArray = [];
if(Object.entries(gameState).length !== 0) {
playersArray = Object.values(gameState.players.toJSON());
}
// Check if game has started
useEffect(()=>{
console.log('gamestarted:', gameState.gameStarted);
if(gameState.gameStarted){
history.push('/game')
}
}, [gameState.gameStarted]);
return (
<PageFrame>
<Grid
container
direction="column"
alignItems="center"
className={classes.root}
spacing={2}
>
<Grid item xs={12}>
<Typography className={classes.gameTitle}>
{(gameState != null) ? gameState.gameName : <CircularProgress />}
</Typography>
</Grid>
<Grid item xs={12}>
Waiting for all players to join...
</Grid>
<Grid item xs={12}>
Connected Players: {playersArray.length}
</Grid>
<Grid item xs={12}>
{playersArray.map((data, index) => {
return (
<Chip
key={index}
label={data.party}
className={classes.chip}
style={{"--color": green[500]}}
/>
);
})}
</Grid>
<Grid item xs={12}>
</Grid>
</Grid>
</PageFrame>
);
}
我使用useSelector
来获取我从Colyseus.js接收到的状态,并以此方式进行调度(在Lobby
中以<Route>
作为子级的另一个组件中):
const joinRoom = (id, partyName, actions) => {
client.joinById(id, {partyName, id}).then(room => {
dispatch({type: GAME_ROOM, payload: room });
dispatch({type: RECONNECT_PARAMS, payload: {roomId: room.id, sessionId: room.sessionId} });
// Redirect to lobby
history.push('/lobby')
{...}
room.onStateChange((state) => {
console.log("the room state has been updated:", state);
// HERE DISPATCHING RECEIVED STATE
dispatch({type: GAME_STATE, payload: state });
});
room.onLeave((code) => {
{...}
});
}
服务器上的 state
具有对象的属性players
,该对象的密钥带有我需要显示的播放器名称。
我的减速器:
const initialState = {
joinDetails:{
gameCode: '',
partyName: ''
},
sessionParams: {
roomId: "",
sessionId: "",
},
room: {},
roomState: {},
scoreChange: null,
totalChange: null,
playerState:{
showResults: false,
questions: [],
totalBudget: 5000,
totalGlobalBudget: 5000,
budget: [],
activeStep: 0,
stepCompleted: [false,false,false,false,false],
decisions: [-1,-1,-1,-1],
}
};
function rootReducer(state = initialState, action) {
switch (action.type) {
case JOIN_FORM:
return {...state, joinDetails: action.payload};
case GAME_ROOM:
return {...state, room: action.payload};
case GAME_STATE:
return {...state, roomState: action.payload};
case RECONNECT_PARAMS:
return {...state, sessionParams: action.payload};
case CHANGE_STEP:
if(action.payload === 'add'){
return {...state, playerState: {...state.playerState, activeStep: state.playerState.activeStep + 1}}}
else{
return {...state, playerState: {...state.playerState, activeStep: state.playerState.activeStep - 1}}}
case COMPLETE_STEP:
return {...state, playerState: {...state.playerState, stepCompleted: action.payload}};
case SET_BUDGET:
return {...state, playerState: {...state.playerState, budget: action.payload}};
case SET_QUESTIONS:
return {...state, playerState: {...state.playerState, questions: action.payload}};
case SET_TOTAL_BUDGET:
if(action.payload.add !== undefined){
return {...state, playerState: {...state.playerState, totalBudget: state.playerState.totalBudget + action.payload.add}}
}
else{
return {...state, playerState: {...state.playerState, totalBudget: action.payload}};
}
case SET_GLOBAL_BUDGET:
if(action.payload.add !== undefined){
return {...state, playerState: {...state.playerState, totalGlobalBudget: state.playerState.totalGlobalBudget + action.payload.add}}
}
else{
return {...state, playerState: {...state.playerState, totalGlobalBudget: action.payload}}
}
case SET_DECISIONS:
return {...state, playerState: {...state.playerState, decisions: action.payload}};
case SET_CHANGES:
return {...state, scoreChange: action.payload.change, totalChange: action.payload.total};
case SHOW_RESULTS:
return {...state, playerState: {...state.playerState, showResults: action.payload}};
case CLEAR_STATE:
return {...state, playerState: {}};
default:
return state;
}
}
export default rootReducer;
问题是新玩家加入后,玩家列表不会更新。我可以确认:
useSelector
起作用了,因为每个玩家都会看到在他之前加入的玩家列表。我尝试寻找突变状态,但是在我看来,那里一切都很好。这是我的第一个redux项目,所以也许我错过了一些东西。 我在redux devtools中注意到了一个可疑的事情,有时(!)最新状态会通过devtools视图中的所有先前状态传播,例如“差异”标签显示所有相同的操作均未引起任何变化,除了第一个已将状态更改为最终形式,例如:
实际更改:
显示的更改(但仅在第三次之后才变成这样):
不确定这是正常现象还是导致我的问题。
如果您需要附加代码,请告诉我。
谢谢。
编辑:根据评论中的建议,更改了一些名称,问题仍然存在。
编辑2:问题似乎与useSelector
直接相关:目前我有
gameState = useSelector(state => state.roomState)
但是,如果我选择整个状态,则一切正常,组件会重新显示。
gameState = useSelector(state => state)
这远非理想:现在我需要调用gameState.roomState.something
而不是各处的gameState.something
。 useSelector
不能识别状态的特定部分的变化而仅识别整个状态的变化怎么可能?
答案 0 :(得分:1)
所以我的问题在这里:
room.onStateChange((state) => {
console.log("the room state has been updated:", state);
// HERE DISPATCHING RECEIVED STATE
dispatch({type: GAME_STATE, payload: state });
我将state
放入有效负载,而没有将其解构/复制到新对象,因此实际上仅传递了一个引用。当然,当进行更改时,它也会更改所有其他参考,并且没有可比较的内容。正确的方法是在将收到的state
传递给操作时对其进行破坏:
room.onStateChange((state) => {
console.log("the room state has been updated:", state);
// HERE DISPATCHING RECEIVED STATE
dispatch({type: GAME_STATE, payload: {...state} })
答案 1 :(得分:0)
第二次编辑后,我更好地了解了您的操作,以找出为什么state.roomState并未更改以触发useSelector,并且不敢相信,这与我两周前回答的问题完全相同-{{ 3}}
您可以附加多个有效载荷来进行调度。将joinRoom操作更改为一个调度,同时保留“ GAME_ROOM”和“ RECONNECT_PARAMS”
client.joinById(id, {partyName, id}).then(room => {
dispatch({
type: 'GAME_ROOM',
room,
params: {roomId: room.id, sessionId: room.sessionId}
});
..........
然后在化简器中将它们组合为一个案例侦听器
case 'GAME_ROOM':
return {...state, room: action.room, sessionParams: action.params }