问题
I'm working on a simple Vue game app with buttons to decrease player or "demon" health by a random number within a range when a button is clicked. In my Vue instance is a method attack
that calculates the damage and checks if the game is over (if it results in the player or demon's health going at/below zero). Currently if it's at or below zero, the JS built-in confirm function is called to announce the player won or lost and asking them if they want to start a new game. However, I'd like to first check if player or demon health <= 0 and if so, set health to 0 so it will be reflected in a "health bar" because right now it displays what it was before it went to at/below 0.
checkWin: function() {
if (this.demonHealth <= 0) {
// this.demonHealth = 0; <-- applied after confirm();
if (confirm('You won! New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
return true;
} else {
// this.playerHealth = 0; <-- applied after confirm();
if (this.playerHealth <= 0) {
if (confirm('You lost. New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
return true;
}
}
}
Full code: https://codepen.io/nataliecardot/pen/XWrMedm
Here's the full Vue instance:
new Vue({
el: '#app',
data: {
playerHealth: 100,
demonHealth: 100,
gameIsRunning: false
},
methods: {
startGame: function() {
this.gameIsRunning = true;
this.playerHealth = 100,
this.demonHealth = 100
},
attack: function() {
this.demonHealth -= this.calculateDamage(3, 10);
if (this.checkWin()) {
return;
}
this.playerHealth -= this.calculateDamage(5, 12);
this.checkWin();
},
calculateDamage: function(min, max) {
return Math.max(Math.floor(Math.random() * max) + 1, min);
},
checkWin: function() {
if (this.demonHealth <= 0) {
// this.demonHealth = 0; <-- applied after confirm();
if (confirm('You won! New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
return true;
} else {
// this.playerHealth = 0; <-- applied after confirm();
if (this.playerHealth <= 0) {
if (confirm('You lost. New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
return true;
}
}
}
}
});
I also tried removing the checkWin() function, adding a new method that checks for the score being <= 0 and if so setting it to zero, and having the code that was in checkWin() in a callback. This resulted in zero being applied, but confirm() wasn't called, and the game kept going:
methods: {
startGame: function() {
this.gameIsRunning = true;
this.playerHealth = 100,
this.demonHealth = 100
},
scoreCheck: function() {
if (this.demonHealth < 0) {
this.demonHealth = 0;
}
if (this.playerHealth < 0) {
this.playerHealth = 0;
}
},
attack: function() {
this.demonHealth -= this.calculateDamage(3, 10);
this.scoreCheck(() => {
if (this.demonHealth === 0) {
if (confirm('You won! New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
return;
}
});
this.playerHealth -= this.calculateDamage(5, 12);
this.scoreCheck(() => {
if (this.playerHealth === 0) {
if (confirm('You lost. New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
}
});
},
calculateDamage: function(min, max) {
return Math.max(Math.floor(Math.random() * max) + 1, min);
},
}
Edit: I also tried $nextTick() on the original code, but it worked the same as before:
checkWin: function() {
if (this.demonHealth <= 0) {
this.demonHealth = 0;
this.$nextTick(function() {
if (confirm('You won! New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
return true;
});
} else {
if (this.playerHealth <= 0) {
this.playerHealth = 0;
this.$nextTick(function() {
if (confirm('You lost. New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
return true;
});
}
}
}
回答1:
Actually I wanted to mark this as duplicate - but then i realized that most solutions suggest the usage of setTimout
- wich is creating an unneeded racecondition between javascript and browser rendering.
When you mutatate object properties in the scope of vue - meaning they are reactive - and you want to wait for a dom update and a dom render there is following you can do:
First await vm.$nextTick()
which will calculate the dom
and then give the browser time to breath with double requestAnimationFrame
.
And as an implementation example:
Vue.skipFrame = function(){
return new Promise(resolve => {
requestAnimationFrame(() =>
requestAnimationFrame(resolve)
)
})
}
let app = new Vue({
el: '#app',
data: {
monster: 10,
hero: 100
},
methods: {
attack() {
this.monster = Math.max(
this.monster - (Math.random() * 10)|0,
0
)
this.checkWin()
},
async checkWin(){
await this.$nextTick()
await Vue.skipFrame()
if(this.monster == 0){
confirm('You won! Again?') ?
this.start():
this.quit()
}
},
start() {
this.hero = 100;
this.monster = 100;
},
quit(){
this.monster = 1000;
}
}
});
.as-console-wrapper{
height: 0px !important
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>Monster HP: {{monster}}</h1>
<h1>Hero HP: {{hero}}</h1>
<button @click="attack">Attack</button>
</div>
回答2:
The following answer doesn't work on chrome
and for that i submitted an issue ticket at github
Like @Phil suggests using $nextTick
will solve your problem ..it is basically a method that contains a callback that gets executed after the DOM is updated in case of synchronous Or returns a promise on the asynchronous one (im using the async here) and the reason why your confirm
is executed before the DOM update even though it is inside a condition loop of the updated state (dh || ph <=0)
is because confirm
will essentially pause the rendering of HTML and run though the entire JavaScript file before they resume the HTML rendering...and concerning your bar dynamic width i added some condition so if the player / deamon health is below 0 the health bar will be empty .
checkWin: function() {
if (this.demonHealth <= 0) {
this.$nextTick().then(() => {
if (confirm('You won! New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
})
return true;
} else {
if (this.playerHealth <= 0) {
this.$nextTick().then(() => {
if (confirm('You lost. New game?')) {
this.startGame();
} else {
this.gameIsRunning = false;
}
})
return true;
}
}
}
<div :style="{ width: playerHealth >= 0 ? playerHealth + '%' : 0 }">
<div :style="{ width: demonHealth >= 0 ? demonHealth + '%' : 0 }">
here is a Demo : codepen
来源:https://stackoverflow.com/questions/57650735/execute-non-function-code-before-a-function-call