Cách sử dụng ReactJS, Phaser và Redux cùng nhau
Sử dụng ReactJS với Redux là một kịch bản khá phổ biến, nhưng ngay cả với điều đó, mọi người vẫn thường phải đối mặt với những câu hỏi như “làm thế nào để…”, “khi nào nên…” và “tại sao…”.
Nói về Phaser, điều này có thể xảy ra gần như mọi lúc. Đôi khi người ta còn hỏi tại sao ReactJS lại được sử dụng trên Phaser… Có một số lý do cho việc đó.
Một trong những ưu điểm chính là ReactJS có thể giúp bạn tiết kiệm rất nhiều thời gian trong việc phát triển giao diện người dùng cho trò chơi của bạn hoặc các ứng dụng canvas khác. Vì Phaser tập trung vào hiệu suất trò chơi nên nó không thể cung cấp cho bạn tất cả các chức năng cần thiết có thể xử lý các biểu mẫu, menu, nút và cập nhật danh sách, nhưng ReactJS có thể và nó giúp bạn giải quyết vấn đề đau đầu với điều đó. Chắc chắn, một số bạn có thể nói rằng Phaser có thể, nhưng tôi sẽ nói với bạn rằng những giải pháp này khá khó khăn, phức tạp và nặng nề đối với các kỹ sư.
Một lý do khác khiến nó hữu ích là loại khung này có thể được sử dụng với trạng thái tương hỗ toàn cầu như Redux, điều này sẽ cho phép bạn loại bỏ mã lặp đi lặp lại được sử dụng để cập nhật hoặc lấy trạng thái hiện tại của các cảnh/thành phần trong các phần khác nhau của ứng dụng của bạn. Với điều đó, bạn sẽ không cần nhớ nơi tôi đặt phương thức đó và khi nào nó thực hiện điều này hay điều kia…
Nhưng làm thế nào chúng ta có thể kết bạn với tất cả những người này trong một ứng dụng?
Đó là một câu hỏi rất hay, phần đầu tiên của câu trả lời đã được thảo luận ở đây trong bài viết trước của tôihttps:///@costarassco/how-to-create-applications-architecture-in-frontend-and-why-it-is-important-764273fce03f. Tôi đã viết về kiến trúc của một ứng dụng như vậy ở đó.
Với phần thứ hai tôi sẽ chia sẻ với bạn ngày hôm nay…
Chúng ta hãy xem trò chơi canvas MMO RPG từ quan điểm giao diện người dùng, giống như trong tất cả các ứng dụng khác, chúng tôi có các thành phần cha và con, thông minh và kết xuất.
Trong trò chơi của tôi, tôi đã từng có những vật phẩm giúp tôi hiện thực hóa ý tưởng này:
- một điểm vào;
- một thành phần chịu trách nhiệm tải dữ liệu ban đầu;
- một cảnh cha mẹ và hai cảnh con chính bao gồm nhiều cảnh khác. Chúng là một không gian mở và một hành tinh nơi người dùng hạ cánh bằng tàu vũ trụ;
- các cảnh con, có thể trực tiếp kích hoạt các cập nhật của trạng thái hoặc sử dụng các cửa sổ giao diện người dùng;
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import ReactDOM from "react-dom";
import store from './redux/store';
import Game from './components/game';
class App extends Component {
render() {
return (
<Provider store={store}>
<Game />
</Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
export default App;
Thành phần đảm nhiệm việc tải và cài đặt dữ liệu ban đầu:
import React, { Component } from 'react';
import Phaser from 'phaser';
import PhaserReact from "phaser3-react";
import { connect } from 'react-redux';
import BootScene from '../scenes/boot/boot';
import PlayScene from '../scenes/play/play';
import { getProfileData } from '../services/http-service';
import { setProfileData } from '../redux/actions';
class Game extends Component {
async _onLoadProfileData() {
try {
const { profile } = await getProfileData();
this.props.onSetProfileData(profile);
// other stuff
} catch (e) {
console.log('could not load profile data... = ', e);
}
}
_onLoadGame() {
window.game = new Phaser.Game({
type: Phaser.AUTO,
parent: 'game-container',
width: window.innerWidth,
height: window.innerHeight,
dom: {
createContainer: true
},
plugins: {
global: [
{
key: 'phaser-react',
plugin: PhaserReact,
start: true
}
]
},
scene: [BootScene, PlayScene]
});
}
async componentDidMount() {
await this._onLoadProfileData();
this._onLoadGame();
}
render() {
return <div id="game-container"/>;
}
};
const mapDispatchToProps = dispatch => ({
onSetProfileData: (profileData) => dispatch(setProfileData(profileData)),
// other callbacks
});
export default connect(undefined, mapDispatchToProps)(Game);
- Đang tải dữ liệu ban đầu như dữ liệu của hồ sơ, hành tinh, người dùng khác và đám đông trong hệ thống, v.v. sau khi một thành phần được gắn kết;
- Một cách khác là cài đặt plugin toàn cầu trong đối tượng trò chơi Phaser;
import PlanetScene from './planet/planet';
import SpaceSystemScene from './space-system/space-system';
import store from '../../redux/store';
import { toggleSatOnPlanetBoolean, toggleLeavePlanetBoolean } from '../../redux/actions';
class PlayScene extends Phaser.Scene {
constructor() {
super({ key: 'PlayScene' });
this.hasPlanetSceneBeenLoaded = false;
this.hasSpaceSceneBeenLoaded = false;
}
update() {
if (
!this.hasPlanetSceneBeenLoaded
&& store.getState().profileData
&& store.getState().profileData.hasJustSatOnPlanet
) {
this.hasPlanetBeenLoaded = true;
this.hasSpaceSceneBeenLoaded = false;
this.scene.remove('SpaceSystemScene');
this.planetScene = game.scene.add('PlanetScene', new PlanetScene(), true);
store.dispatch(toggleSatOnPlanetBoolean(false));
} else if (
!this.hasSpaceSceneBeenLoaded
&& store.getState().profileData
&& store.getState().profileData.hasJustLeftPlanet
) {
this.hasPlanetBeenLoaded = false;
this.hasSpaceSceneBeenLoaded = true;
this.scene.remove('PlanetScene');
this.spaceSystemScene = game.scene.add('SpaceSystemScene', new SpaceSystemScene(), true);
store.dispatch(toggleLeavePlanetBoolean(false));
}
}
create() {
const { isOnPlanet } = store.getState().profileData;
if (isOnPlanet) {
this.planetScene = game.scene.add('PlanetScene', new PlanetScene(), true);
this.hasPlanetBeenLoaded = true;
} else {
this.spaceSystemScene = game.scene.add('SpaceSystemScene', new SpaceSystemScene(), true);
this.hasSpaceSceneBeenLoaded = true;
}
}
}
export default PlayScene;
Chúng tôi nhận được biến trạng thái toàn cầu và các hành động chỉ cần thiết trong cảnh gốc này và được sử dụng để cập nhật một số phần của trạng thái toàn cầu. Tôi cũng sử dụng một vài biến trạng thái cục bộ sẽ quan tâm đến thời điểm tôi nên kích hoạt các hành động.
Trước tiên, hãy xem một phương thức tạo, nếu người dùng trên một hành tinh, chúng ta sẽ tạo một cảnh hành tinh, nếu họ không, thì chúng ta sẽ tạo một cảnh không gian.
Trong phương pháp cập nhật, tôi liên tục kiểm tra xem người dùng có bay khỏi hành tinh hiện tại hay không, nếu có, tôi xóa cảnh hành tinh và sau đó yêu cầu cửa hàng cập nhật vị trí của người dùng tại đây
store.dispatch(toggleLeavePlanetBoolean(false));
Hãy nghỉ ngơi một chút
Nhưng còn ReactJS và những thứ bạn sắp hỏi thì sao?
Tôi quên mất nó, hãy để tôi chia sẻ tất cả các chi tiết dưới đây.
Vì tôi đang nói về một trong những cảnh chính, nên nó đến đây:
import store from '../../../redux/store';
import GalaxyMapScene from '../modals/galaxy-map/galaxy-map';
// lots of other imports
class SpaceSystemScene extends Phaser.Scene {
constructor () {
super({ key: 'SpaceSystemScene' });
// lots of stuff
}
onGalaxyMapClose() {
this.scene.remove('GalaxyMapScene');
}
onGalaxyMapShow() {
this.closeAllOpenedScenes();
this.galaxyMapScene = game.scene.add('GalaxyMapScene', new GalaxyMapScene({ closeCallback: this.onGalaxyMapClose.bind(this) }), true);
}
// other methods
}
Đây là mã cảnh bản đồ thiên hà:
import GalaxyMapComponent from './components/galaxy-map';
class GalaxyMapScene extends Phaser.Scene {
constructor ({ closeCallback }) {
super({ key: 'GalaxyMapScene' });
this.onClose = closeCallback;
}
onDestroy() {
this.galaxyMap.destroy();
}
addEvents() {
this.events.on('destroy', () => this.onDestroy());
}
create() {
const background = this.add.image(game.config.width / 2, game.config.height / 2, 'galaxy-map-background').setDisplaySize(1070, 570);
this.galaxyMap = this.add.reactDom(GalaxyMapComponent, { scope: this, onClose: this.onClose });
this.addEvents();
}
}
export default GalaxyMapScene;
Cửa sổ chế độ bản đồ thiên hà:
import React, { useCallback, useMemo, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import store from '../../../../../redux/store';
import { getOpenedCellsForGalaxyMap } from '../../../../../services/data-parser';
import './galaxy-map.less';
const GalaxyMap = ({ scope, onClose }) => {
const openedCells = getOpenedCellsForGalaxyMap();
const [chosenSystem, setChosenSystem] = useState();
const [systems, setSystems] = useState([]);
const [radiusVisibility] = useState(100);
useEffect(() => {
setSystems(store.getState().profileData.galaxyMap);
}, [store]);
const getGalaxies = useMemo(() => {
const radarSize = 2 * radiusVisibility;
return (
<div className='system-points'>
{
systems.map((system, index) => {
// my cool system element
})
}
</div>
);
}, [radiusVisibility, systems, checkApproachability, chosenSystem, onSystemPointClick, myCurrentPlanet, getPathLine])
const onGoToGyper = useCallback(() => { }, [chosenSystem])
return (
<div className='galaxy-map-container' style={{ left: game.config.width / 2 - 542, top: game.config.height / 2 - 286 }}>
{getCellsTemplate}
{getGalaxies}
</div>
)
}
GalaxyMap.propTypes = {
scope: PropTypes.object,
onClose: PropTypes.func,
}
export default GalaxyMap;
Tôi đã cắt bỏ phần lớn mã, nhưng bạn có thể thấy mọi thứ minh bạch và đơn giản như thế nào:
bạn gửi yêu cầu để nhận hệ thống → nó được lưu ở trạng thái toàn cầu → bản đồ thiên hà đọc trạng thái toàn cầu và nhận danh sách hệ thống → cập nhật trạng thái cục bộ của nó → sử dụng một số công cụ và phân tích cú pháp dữ liệu.
Tôi hy vọng bạn thích nó hoặc thấy hữu ích cho ý tưởng mới, trò chơi hoặc bất kỳ ứng dụng nào khác của bạn.
Cảm ơn bạn đã quan tâm.
Chúc mừng.

![Dù sao thì một danh sách được liên kết là gì? [Phần 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































