L'application sur laquelle je travaille est une application multi-jeux (un jeu d'oiseaux, un jeu de taupes et un jeu de mines). L'écran de démarrage est le menu. Il me donne un avertissement de cycle sur cet écran. Lorsque je tape pour lancer le jeu de la taupe, il joue naturellement, mais dès que j'appuie sur le bouton retour pour revenir au menu, je reçois des erreurs : " Impossible d'effectuer une mise à jour de l'état de React sur un composant non monté" erreur suivie par continue " TYPE ERORR : null n'est pas un objet ".
Captures d'écran :
App.js du jeu de la taupe
import {
View,
StyleSheet,
Image,
SafeAreaView,
Text,
TouchableWithoutFeedback
} from 'react-native';
import Images from './assets/Images';
import Constants from './Constants';
import Mole from './Mole';
import GameOver from './GameOver';
import Clear from './Clear';
import Pause from './Pause';
const DEFAULT_TIME = 20;
const DEFAULT_STATE = {
level: 1,
score: 0,
time: DEFAULT_TIME,
cleared: false,
paused: false,
gameover: false,
health: 100
}
export default class MoleGame extends Component {
constructor(props) {
super(props);
this.moles = [];
this.state = DEFAULT_STATE;
this.molesPopping = 0;
this.interval = null;
this.timeInterval = null;
}
componentDidMount = () => {
this.setupTicks(DEFAULT_STATE, this.pause);
}
setupTicks = () => {
let speed = 750 - (this.state.level * 50);
if (speed < 350) {
speed = 350;
}
this.interval = setInterval(this.popRandomMole, speed);
this.timeInterval = setInterval(this.timerTick, 1000);
}
reset = () => {
this.molesPopping = 0;
this.setState(DEFAULT_STATE, this.setupTicks)
}
pause = () => {
if (this.interval) clearInterval(this.interval);
if (this.timeInterval) clearInterval(this.timeInterval);
this.setState({
paused: true
});
}
resume = () => {
this.molesPopping = 0;
this.setState({
paused: false
}, this.setupTicks);
}
nextLevel = () => {
this.molesPopping = 0;
this.setState({
level: this.state.level + 1,
cleared: false,
gameover: false,
time: DEFAULT_TIME
}, this.setupTicks)
}
timerTick = () => {
if (this.state.time === 0) {
clearInterval(this.interval);
clearInterval(this.timeInterval);
this.setState({
cleared: true
})
} else {
this.setState({
time: this.state.time - 1
})
}
}
gameOver = () => {
clearInterval(this.interval);
clearInterval(this.timerInterval);
this.setState({
gameover: true
})
}
onDamage = () => {
if (this.state.cleared || this.state.gameOver || this.state.paused) {
return;
}
let targetHealth = this.state.health - 10 < 0 ? 0 : this.state.health - 20;
this.setState({
health: targetHealth
});
if (targetHealth <= 0) {
this.gameOver();
}
}
onHeal = () => {
let targetHealth = this.state.health + 10 > 100 ? 100 : this.state.health + 10
this.setState({
health: targetHealth
})
}
onScore = () => {
this.setState({
score: this.state.score + 1
})
}
randomBetween = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}
onFinishPopping = (index) => {
this.molesPopping -= 1;
}
popRandomMole = () => {
if (this.moles.length != 12) {
return;
}
let randomIndex = this.randomBetween(0, 11);
if (!this.moles[randomIndex].isPopping && this.molesPopping < 3) {
this.molesPopping += 1;
this.moles[randomIndex].pop();
}
}
render() {
let healthBarWidth = (Constants.MAX_WIDTH - Constants.XR * 100 - Constants.XR * 60 - Constants.XR * 6) * this.state.health / 100;
return (
<View style={styles.container}>
<Image style={styles.backgroundImage} resizeMode="stretch" source={Images.background} />
<View style={styles.topPanel}>
<SafeAreaView>
<View style={styles.statsContainer}>
<View style={styles.stats}>
<View style={styles.levelContainer}>
<Text style={styles.levelTitle}>Level</Text>
<Text style={styles.levelNumber}>{this.state.level}</Text>
</View>
</View>
<View style={styles.stats}>
<View style={styles.timeBar}>
<Text style={styles.timeNumber}>{this.state.time}</Text>
</View>
<Image style={styles.timeIcon} resizeMode="stretch" source={Images.timeIcon} />
</View>
<View style={styles.stats}>
<View style={styles.scoreBar}>
<Text style={styles.scoreNumber}>{this.state.score}</Text>
</View>
<Image style={styles.scoreIcon} resizeMode="stretch" source={Images.scoreIcon} />
</View>
<View style={styles.stats}>
<TouchableWithoutFeedback onPress={this.pause}>
<View style={styles.pauseButton}>
<Image style={styles.pauseButtonIcon} resizeMode="stretch" source={Images.pauseIcon} />
</View>
</TouchableWithoutFeedback>
</View>
</View>
<View style={styles.healthBarContainer}>
<View style={styles.healthBar}>
<View style={[styles.healthBarInner, { width: healthBarWidth }]} />
</View>
<Image style={styles.healthIcon} resizeMode="stretch" source={Images.healthIcon} />
</View>
</SafeAreaView>
</View>
<View style={styles.playArea}>
{Array.apply(null, Array(4)).map((el, rowIdx) => {
return (
<View style={styles.playRow} key={rowIdx}>
{Array.apply(null, Array(3)).map((el, colIdx) => {
let moleIdx = (rowIdx * 3) + colIdx;
return (
<View style={styles.playCell} key={colIdx}>
<Mole
index={moleIdx}
onDamage={this.onDamage}
onHeal={this.onHeal}
onFinishPopping={this.onFinishPopping}
onScore={this.onScore}
ref={(ref) => { this.moles[moleIdx] = ref }}
/>
</View>
)
})}
</View>
)
})}
</View>
{this.state.cleared && <Clear onReset={this.reset} onNextLevel={this.nextLevel} level={this.state.level} score={this.state.score} />}
{this.state.gameover && <GameOver onReset={this.reset} level={this.state.level} score={this.state.score} />}
{this.state.paused && <Pause onReset={this.reset} onResume={this.resume} />}
</View>
)
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column'
},
backgroundImage: {
width: Constants.MAX_WIDTH,
height: Constants.MAX_HEIGHT,
position: 'absolute'
},
topPanel: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: Constants.YR * 250,
flexDirection: 'column'
},
statsContainer: {
width: Constants.MAX_WIDTH,
height: Constants.YR * 120,
flexDirection: 'row'
},
stats: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
pauseButton: {
width: Constants.YR * 50,
height: Constants.YR * 50,
backgroundColor: 'black',
borderColor: 'white',
borderWidth: 3,
borderRadius: 10,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
pauseButtonIcon: {
width: Constants.YR * 25,
height: Constants.YR * 25,
},
levelContainer: {
width: Constants.YR * 80,
height: Constants.YR * 80,
backgroundColor: '#ff1a1a',
borderColor: 'white',
borderWidth: 3,
borderRadius: 10,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
levelTitle: {
fontSize: 21,
color: 'white',
fontFamily: 'LilitaOne'
},
levelNumber: {
fontSize: 17,
color: 'white',
fontFamily: 'LilitaOne'
},
scoreIcon: {
position: 'absolute',
left: 0,
width: Constants.YR * 40,
height: Constants.YR * 40,
},
scoreBar: {
height: Constants.YR * 25,
position: 'absolute',
left: 20,
right: 5,
backgroundColor: 'white',
borderRadius: 13,
justifyContent: 'center',
alignItems: 'center'
},
scoreNumber: {
fontSize: 17,
color: 'black',
fontFamily: 'LilitaOne',
},
timeIcon: {
position: 'absolute',
left: 0,
width: Constants.YR * 40,
height: Constants.YR * 40,
},
timeBar: {
height: Constants.YR * 25,
position: 'absolute',
left: 20,
right: 5,
backgroundColor: 'white',
borderRadius: 13,
justifyContent: 'center',
alignItems: 'center'
},
timeNumber: {
fontSize: 17,
color: 'black',
fontFamily: 'LilitaOne',
},
healthBarContainer: {
height: Constants.YR * 40,
width: Constants.MAX_WIDTH - Constants.XR * 120,
marginLeft: Constants.XR * 60
},
healthIcon: {
position: 'absolute',
top: 0,
left: 0,
width: Constants.YR * 46,
height: Constants.YR * 40,
},
healthBar: {
height: Constants.YR * 20,
width: Constants.MAX_WIDTH - Constants.XR * 100 - Constants.XR * 60,
marginLeft: Constants.XR * 40,
marginTop: Constants.YR * 10,
backgroundColor: 'white',
borderRadius: Constants.YR * 10
},
healthBarInner: {
position: 'absolute',
backgroundColor: '#ff1a1a',
left: Constants.XR * 3,
top: Constants.YR * 3,
bottom: Constants.YR * 3,
borderRadius: Constants.YR * 8
},
playArea: {
width: Constants.MAX_WIDTH,
marginTop: Constants.YR * 250,
height: Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112,
flexDirection: 'column',
},
playRow: {
height: (Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112) / 4,
width: Constants.MAX_WIDTH,
flexDirection: 'row',
},
playCell: {
width: Constants.MAX_WIDTH / 3,
height: (Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112) / 4,
alignItems: 'center'
}
});
Mole.js
import { View, StyleSheet, Button, Image, TouchableWithoutFeedback } from 'react-native';
import Images from './assets/Images';
import SpriteSheet from 'rn-sprite-sheet';
//import Constants from './Constants';
export default class Mole extends Component {
constructor (props) {
super(props);
this.mole = null;
this.actionTimeout = null;
this.isPopping = false;
this.isWacked = false;
this.isHealing = false;
this.isAttacking = false;
this.isFeisty = false;
}
pop =()=>{
this.isWacked = false;
this.isPopping = true;
this.isAttacking = false;
this.isFeisty = Math.random() < 0.4;
if(!this.isFeisty){
this.isHealing = Math.random() < 0.12;
}
if(this.isHealing){
this.mole.play({
type: "heal",
onFinish : ()=>{
this.actionTimeout = setTimeout(()=>{
this.mole.play({
type : "hide",
fps: 24,
onFinish: ()=>{
this.isPopping = false;
this.props.onFinishPopping(this.props.index);
}
})
}, 1000);
}
})
}
else{
this.mole.play({
type : "appear",
fps: 24,
onFinish: ()=>{
if (this.isFeisty){
this.actionTimeout = setTimeout(() => {
this.isAttacking = true;
this.props.onDamage();
this.mole.play({
type: "attack",
fps: 12,
onFinish: () => {
this.mole.play({
type: "hide",
fps: 24,
onFinish: () => {
this.isPopping = false;
this.props.onFinishPopping(this.props.index);
}
})
}
})
}, 1000)
}
else{
this.actionTimeout = setTimeout(()=>{
this.mole.play({
type : "hide",
fps: 24,
onFinish: ()=>{
this.isPopping = false;
this.props.onFinishPopping(this.props.index);
}
})
}, 1000);
}
}
})
}
}
whack = ()=>{
if(!this.isPopping || this.isWacked || this.isAttacking){
return;
}
if (this.actionTimeout){
clearTimeout(this.actionTimeout);
}
this.isWacked = true;
this.props.onScore ();
if( this.isHealing){
this.props.onHeal();
}
this.mole.play({
type: "dizzy",
fps: 24,
onFinish: () => {
this.mole.play({
type: "faint",
fps: 24,
onFinish: () => {
this.isPopping = false;
this.props.onFinishPopping(this.props.index);
}
})
}
})
}
render() {
return (
<View style= {styles.container}>
<SpriteSheet
ref= {ref=> {this.mole = ref}}
source = {Images.sprites}
columns = {6}
rows = {8}
width = {100}
animations = {{
idle: [0],
appear: [1,2,3,4],
hide: [4,3,2,1,0],
dizzy : [36,37,38],
faint: [42,43,44,0],
attack: [11,12,13,14,15,16],
heal: [24,25,26,27,28,29,30,31,32,33]
}} />
<TouchableWithoutFeedback onPress= {this.whack} style= {{position: 'absolute', top: 0 , bottom:0, left:0, right:0 }}>
<View style= {{position: 'absolute', top: 0 , bottom:0, left:0, right:0 }} />
</TouchableWithoutFeedback>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
})
index.js (Navigation)
import React from "react";
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import MoleGame from './Game1/App.js'
import BirdGame from './Game2/App.js'
import HomeScreen from './Game3/screens/HomeScreen.js'
import GameScreen from './Game3/screens/GameScreen.js'
import Home from './Home.js'
const Stack = createStackNavigator();
export default App = () => {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false, gestureEnabled: false, }} >
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="MoleGame" component={MoleGame} />
<Stack.Screen name="BirdGame" component={BirdGame} />
<Stack.Screen name="MineSweeperHome" component={HomeScreen} />
<Stack.Screen name="MineSweeperGame" component={GameScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
Home.js
import { View, Text, TouchableOpacity, Dimensions, StyleSheet } from 'react-native';
const GameButton = ({ label, onPress, style }) => {
return (
<TouchableOpacity
style={style}
onPress={onPress}
>
<Text style={styles.text}>{label}</Text>
</TouchableOpacity>
);
};
export default Home = ({ navigation }) => {
return (
<View style={styles.container}>
<View style={styles.welcome}>
<Text style={styles.text}> Welcome! </Text>
</View>
<View style={styles.buttonContainer}>
<GameButton
label={'Play Bird Game'}
onPress={() => navigation.navigate('BirdGame')}
style={styles.gameButton}
/>
<GameButton
label={'Play Mole Game'}
onPress={() => navigation.navigate('MoleGame')}
style={{ ...styles.gameButton, backgroundColor: 'green' }}
/>
<GameButton
label={'Play MineSweeper Game'}
onPress={() => navigation.navigate('MineSweeperHome')}
style={{ ...styles.gameButton, backgroundColor: 'grey' }}
/>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFCB43'
},
welcome: {
alignItems: 'center',
justifyContent: 'center',
padding: 10,
marginTop: 80,
},
buttonContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-evenly'
},
gameButton: {
height: 70,
width: Dimensions.get("screen").width / 1.4,
backgroundColor: 'red',
borderColor: 'red',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 20,
borderWidth: 2,
borderColor: 'black',
},
text: {
fontSize: 22,
}
})
Quelques contextes et avis
- Le projet a été lancé sur la machine Mac de mon ami. Selon lui, il ne reçoit pas ces erreurs. Et il a également été en mesure de construire l'application dans un .apk pour Android.
- Nous utilisons tous deux la même version de node.js (15.11.0).
- Il y a des moments où mon métro ne fonctionne pas, et quand cela arrive, l'application fonctionne sans aucune erreur.
- J'ai essayé de le construire dans un .apk, mais la construction a échoué.
- Avant que l'application ne soit combinée en 3 jeux. Il s'agissait de jeux individuels. Le jeu de la taupe a fonctionné et a été intégré avec succès dans un .apk.
- Le jeu de la taupe a été créé à partir de ici Le code est à peu près le même.
UPDATE :
Après avoir ajouté
componentWillUnmount(){
clearInterval(this.interval);
clearInterval(this.timerInterval);
}
à l'App.js. Je n'obtiens plus en permanence "TypeError : null is not an object (evaluating '_this.moles[randomIndex].isPopping')". Malheureusement, j'obtiens toujours l'erreur TypeError : null is not an object (evaluating '_this.moles.play). Cela se produit lorsque je retourne au menu principal. J'ai essayé d'ajouter clearTimeout(this.actionTimeout)
sur mole.js, mais cela n'a donné aucun effet.
Capture d'écran :
TypeError : null n'est pas un objet (évaluation de '_this.moles.play)