I'm learning how to use the WaitForSeconds function in Unityscript, and have had success with it before. But now I'm trying it in a script that is supposed to, when the object's health reaches 0, move the object (a box in this case) off-screen, then after a set amount of seconds have it reappear in a random position on-screen, and set the health back to its default value. What I have thus far is:
function Update ()
{
if(health <= 0)
{
RespawnWaitTime ();
var position = Vector3(Random.Range(-6,6),Random.Range(-4.5,4.5),0); //this is the onscreen range
transform.position = position;
health = 2;
}
}
function RespawnWaitTime ()
{
var offScreen = Vector3(10,10,0);
transform.position = offScreen;
yield WaitForSeconds(2);
}
However, it doesn't wait the 2 seconds at all. The box just goes straight to its new position as if the function wasn't there. I believe that it does go to its off-screen position, but just jumps straight back without waiting. I've tested to see if it's applying the wait at all by changing some of the code to:
function RespawnWaitTime ()
{
var offScreen = Vector3(10,10,0);
transform.position = offScreen;
print("I'm over here!");
yield WaitForSeconds(2);
print("I'm coming back!");
}
The first bit of text ends up printing right away, then after two seconds, the second bit of text appears as well, just as expected. So why doesn't the wait apply to the box also? Thanks for your help.
I suspect WaitForSeconds is asynchronously so when update calls RespawnWaitTime then RespawnWaitTime returns immediately. Could you try the following code to see how WaitForSeconds behaves?
function Update () {
print("1 in update before calling respandwaittime");
RespawnWaitTime ();
print("3 in update after calling respandwaittime");
}
function RespawnWaitTime (){
print("2 in in respainwaittime before calling waitforseconds");
yield WaitForSeconds(2);
print("4 in in respainwaittime after calling waitforseconds");
}
Since the output was 1,2,3,4 (as expected) you could reprogram like so:
function Update () {
if(health <= 0){
recover();
return;
}
}
function recover(){
var offScreen = Vector3(10,10,0);
transform.position = offScreen;
yield WaitForSeconds(2);
//this is the onscreen range
var position = Vector3(Random.Range(-6,6),Random.Range(-4.5,4.5),0);
transform.position = position;
health = 2;
}
You need to use "yield RespawnWaitTime();" - otherwise the function which called it will continue to execute at the same time as the coroutine is running.
Yielding the coroutine pauses execution of the code which called it, until the coroutine is finished, at which point the original code resumes from the line following the yield statement.
HOWEVER - as you can't put yield statements inside Update() - you would need to call an intermediate function, something like the following:
function Update ()
{
if(health <= 0)
{
Respawn ();
}
}
function Respawn ()
{
yield RespawnWaitTime ();
var position = Vector3(Random.Range(-6,6),Random.Range(-4.5,4.5),0);
transform.position = position;
health = 2;
}
function RespawnWaitTime () : IEnumerator
{
var offScreen = Vector3(10,10,0);
transform.position = offScreen;
yield WaitForSeconds(2);
}
Note I have also added ":IEnumerator" to RespawnWaitTime() - if you don't, the console will print an error for the yield statement that calls it. As I understand, the code will probably work despite the compile error being shown, but I prefer not to take chances ;-)
Related
What I am doing
I am in the middle of building a turtle graphics app using Blockly. The user can build a code from blocks, then the Blockly engine generates JS code, which draws to a canvas.
What my problem is
The Blockly engine generates the JS code, but returns it as a string, which I have to eval() to draw to the canvas.
I can change the code of the blocks to generate different output, but it's important to keep it as simple as possible, because the users can read the actual code behind the block input. So I would like not to mess it up.
What I would like to do
I have full control over the atomic operations (go, turn, etc.), so I would like to insert a small piece of code to the beginning of the functions, which delays the execution of the rest of the bodies of the functions. Something like:
function go(dir, dist) {
// wait here a little
// do the drawing
}
I think it should be something synchronous, which keeps the delay in the flow of the execution. I've tried to use setTimeout (async, fail), a promise (fail), timestamp checks in a loop (fail).
Is it even possible in JS?
You must not make the code wait synchronously. The only thing you will get is a frozen browser window.
What you need is to use the js interpreter instead of eval. This way you can pause the execution, play animations, highlight currently executing blocks, etc... The tutorial has many examples that will help you get started. Here is a working code, based on the JS interpreter example:
var workspace = Blockly.inject("editor-div", {
toolbox: document.getElementById('toolbox')
});
Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
Blockly.JavaScript.addReservedWords('highlightBlock');
Blockly.JavaScript['text_print'] = function(block) {
var argument0 = Blockly.JavaScript.valueToCode(
block, 'TEXT',
Blockly.JavaScript.ORDER_FUNCTION_CALL
) || '\'\'';
return "print(" + argument0 + ');\n';
};
function run() {
var code = Blockly.JavaScript.workspaceToCode(workspace);
var running = false;
workspace.traceOn(true);
workspace.highlightBlock(null);
var lastBlockToHighlight = null;
var myInterpreter = new Interpreter(code, (interpreter, scope) => {
interpreter.setProperty(
scope, 'highlightBlock',
interpreter.createNativeFunction(id => {
id = id ? id.toString() : '';
running = false;
workspace.highlightBlock(lastBlockToHighlight);
lastBlockToHighlight = id;
})
);
interpreter.setProperty(
scope, 'print',
interpreter.createNativeFunction(val => {
val = val ? val.toString() : '';
console.log(val);
})
);
});
var intervalId = setInterval(() => {
running = true;
while (running) {
if (!myInterpreter.step()) {
workspace.highlightBlock(lastBlockToHighlight);
clearInterval(intervalId);
return;
}
}
}, 500);
}
#editor-div {
width: 500px;
height: 150px;
}
<script src="https://rawgit.com/google/blockly/master/blockly_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/blocks_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/javascript_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/msg/js/en.js"></script>
<script src="https://rawgit.com/NeilFraser/JS-Interpreter/master/acorn_interpreter.js"></script>
<xml id="toolbox" style="display: none">
<block type="text"></block>
<block type="text_print"></block>
<block type="controls_repeat_ext"></block>
<block type="math_number"></block>
</xml>
<div>
<button id="run-code" onclick="run()">run</button>
</div>
<div id="editor-div"></div>
EDIT
Added variable running to control the interpreter. Now it steps over until the running variable is set to false, so the running = false statement inside the highlightBlock function essentially works as a breakpoint.
EDIT
Introduced lastBlockToHighlight variable to delay the highlighting, so the latest run statement is highlighted, not the next one. Unfortunately the JavaScript code generator doesn't have a STATEMENT_SUFFIX config similar to STATEMENT_PREFIX.
Recently I published a library that allows you to interact asynchronously with blockly, I designed this library for games like that.
In fact in the documentation you can find a game demo that is a remake of the maze game.
The library is called blockly-gamepad 🎮, I hope it's what you were looking for.
blockly-gamepad 🎮
live demo
Here is a gif of the demo.
How it works
This is a different and simplified approach compared to the normal use of blockly.
At first you have to define the blocks (see how to define them in the documentation). You don't have to define any code generator, all that concerns the generation of code is carried out by the library.
Each block generate a request.
// the request
{ method: 'TURN', args: ['RIGHT'] }
When a block is executed the corresponding request is passed to your game.
class Game{
manageRequests(request){
// requests are passed here
if(request.method == 'TURN')
// animate your sprite
turn(request.args)
}
}
You can use promises to manage asynchronous animations, as in your case.
class Game{
async manageRequests(request){
if(request.method == 'TURN')
await turn(request.args)
}
}
The link between the blocks and your game is managed by the gamepad.
let gamepad = new Blockly.Gamepad(),
game = new Game()
// requests will be passed here
gamepad.setGame(game, game.manageRequest)
The gamepad provides some methods to manage the blocks execution and consequently the requests generation.
// load the code from the blocks in the workspace
gamepad.load()
// reset the code loaded previously
gamepad.reset()
// the blocks are executed one after the other
gamepad.play()
// play in reverse
gamepad.play(true)
// the blocks execution is paused
gamepad.pause()
// toggle play
gamepad.togglePlay()
// load the next request
gamepad.forward()
// load the prior request
gamepad.backward()
// use a block as a breakpoint and play until it is reached
gamepad.debug(id)
You can read the full documentation here.
EDIT: I updated the name of the library, now it is called blockly-gamepad.
If i understood you!
You can build a new class to handle the executing of go(dir, dist) functions, and override the go function to create new go in the executor.
function GoExecutor(){
var executeArray = []; // Store go methods that waiting for execute
var isRunning = false; // Handle looper function
// start runner function
var run = function(){
if(isRunning)
return;
isRunning = true;
runner();
}
// looper for executeArray
var runner = function(){
if(executeArray.length == 0){
isRunning = false;
return;
}
// pop the first inserted params
var currentExec = executeArray.shift(0);
// wait delay miliseconds
setTimeout(function(){
// execute the original go function
originalGoFunction(currentExec.dir, currentExec.dist);
// after finish drawing loop on the next execute method
runner();
}, currentExec.delay);
}
this.push = function(dir, dist){
executeArray.push([dir,dist]);
run();
}
}
// GoExecutor instance
var goExec = new GoExecutor();
// Override go function
var originalGoFunction = go;
var go = function (dir, dist, delay){
goExec.push({"dir":dir, "dist":dist, "delay":delay});
}
Edit 1:
Now you have to call callWithDelay with your function and params,
the executor will handle this call by applying the params to the specified function.
function GoExecutor(){
var executeArray = []; // Store go methods that waiting for execute
var isRunning = false; // Handle looper function
// start runner function
var run = function(){
if(isRunning)
return;
isRunning = true;
runner();
}
// looper for executeArray
var runner = function(){
if(executeArray.length == 0){
isRunning = false;
return;
}
// pop the first inserted params
var currentExec = executeArray.shift(0);
// wait delay miliseconds
setTimeout(function(){
// execute the original go function
currentExec.funcNam.apply(currentExec.funcNam, currentExec.arrayParams);
// after finish drawing loop on the next execute method
runner();
}, currentExec.delay);
}
this.push = function(dir, dist){
executeArray.push([dir,dist]);
run();
}
}
// GoExecutor instance
var goExec = new GoExecutor();
var callWithDelay = function (func, arrayParams, delay){
goExec.push({"func": func, "arrayParams":arrayParams, "delay":delay});
}
Requirement is pretty simple, for some reasons I am unable to sort this out. Outer function calls inner function. Inner function is asynchronous, calls inner function on success if condition is matched. All fine - now user can run outer function while inner function is in progress. I want inner function to stop working immediately, and start afresh.
document.getElementById("demo").addEventListener("click", function() {
demo_click();
});
//
function demo_click() {
var idnum = 0;
var length = 25;
innerFunc();
//
function innerFunc() {
if (idnum < length) {
console.log(idnum);
setTimeout(function() {
idnum++;
innerFunc();
}, 500);
}
}
}
It logs from 0 to 24 as expected. Here is fiddle - https://jsfiddle.net/h7ab8u69/2/
Solution might be trivial, but please suggest me what / where to put the condition.
EDIT
From start afresh I meant that previous calls of innerFunc would stop and it will start logging (console) from zero. It still starts from zero, but if you click twice, thrice and so on - logs (console) will appear from previous calls of innerFunc as well. Please visit above fiddle link and open console and click on button - problem will be evident.
PS
SetTimeout is just to display asynchronous call. In my code, which is Image.onLoad event. So, clearing the timeout is not an option. Basically, I am loading image one after another on button click. I want to stop previous loading and start new loading on another click. To make it simple, I used SetTimeout.
//var imgArray = [‘0.jpg’, ‘1.jpg’, ‘2.jpg’ and so on]
function innerFunc() {
var img = new Image;
img.onload = function() {
//draw this image on canvas
//code to draw
// draw next only if some condition is correct
if (idnum < length) {
idnum++;
innerFunc();
}
};
img.src = imgArray[idnum];
}
What i understood is you want to run some code and stop it when run the outer function again. If it is not timeout then you need to use a global variable.
var clickCount=0;
function demo_click() {
var idnum = 0;
var length = 25;
clickCount++;
var thisCount = clickCount;
// stop the loop before starting again
innerFunc();
function innerFunc() {
if (idnum < length) {
console.log(idnum);
for(var i =0 ; i < imgarraylength; i++){
if(thisCount < clickCount){
break;
}
idnum++;
innerFunc();
}
}
}
}
Hope it helps.
I have been working on my first major programming project and so far it has been going pretty well. My goal is to have an animation run until either an event is triggered or 15 seconds have passed. After the animation times out I want to animation to repeat. My current solution to the plan is shown below.
var animationSpeed = 40;
var animationTimout = 375;
var testTime = 1;
//some more set up variables
function onClick() {
startDrive();
setTimeout(startDrive, 15000); //****
}
function startDrive() {
var Driving = setInterval(carDriving, aniSpeed);
}
function carDriving() {
testLocation();
drawCar();
angleCalc();
newLocation();
getInfo();
}
function testLocation() {
//this code gets information on whether or not the animation should be stopped
testTime = testTime + 1
if(a === 1 || testTime > animationTimeout) {
//a test to cancel the animation, the other variables test to
clearInterval(Driving);
}
}
function drawCar() {
//draws the car
}
function angleCalc() {
//gets some info on how to move the car
}
function newLocation() {
//decides on new coords for the car based on angleCalc();
}
function getInfo() {
//gets some info I plan to use later in the project
}
When I run the code without the starred line, the whole thing works. The car animates as I want it to and stops if the conditions for stopping are met. The car freezes where it was on the canvas, and it seems as if the interval was cleared. When I add the starred line of code, the animation seems to work, but it runs twice as fast as before. I am utterly lost and nothing I try works. Please help.
The problem is likely due to the local variable defined here:
function startDrive() {
var Driving = setInterval(carDriving, aniSpeed);
}
The variable Driving is only defined in the function startDrive, it's a local variable because you are using var to define it inside the function. So when you attempt to access it inside testLocation() you are not accessing the same variable. In fact when you do clearInterval(Driving) the variable Driving isn't defined. A simple solution would be to make Driving global by removing the var:
function startDrive() {
Driving = setInterval(carDriving, aniSpeed);
}
Or you can pass the timer as a parameter inside the testLocation function. This way you will be clearing the interval properly.
I'm sure this must be a common problem, but after much searching I can't find an answer.
I have a twitter-bootstrap loading bar that I would like to update after each stage of a calculation is completed.
Here is the function for updating the loading bar:
var lb = $('#loading-bar');
var lbc = 0;
function increment_loading_bar(pc) {
setTimeout(function(){
lbc = lbc + pc;
lb.width(lbc+"%");;
}, 1);
}
And the calls to update the bar are within a .each() loop
var inc = 100/array.length();
$.each(array,function(index,element){
increment_loading_bar(inc/2);
//
//Gnarly processing ....
//
increment_loading_bar(inc/2);
}
However, this only updates after all the processing has finished. How can the redraw of the bar be forced as the code is executed?
Many thanks!
As I said in my question, the /redraw/ needs to be forced as the code is executed
To my knowledge, you can only indirectly force the Redraw by pausing your Process once in a while.
For example like this:
var inc = 100/array.length();
var processQueue = array;
var currentIndex;
setTimeout(runProcess, 5);
function runProcess() {
var element = processQueue[currentIndex];
// Process the element here
// ....
increment_loading_bar(inc);
currentIndex++;
if (currentIndex < processQueue.length) {
setTimeout(runProcess, 5);
} else {
// processing has finished
}
}
This way you give the browser some time (5ms in this example) between each step to redraw the loading bar.
I have a setInterval calling a loop which displays an animation.
When I clearInterval in response to a user input, there are possibly one or more loop callbacks in queue. If I put a function call directly after the clearInterval statement, the function call finishes first (printing something to screen), then a queued loop callback executes, erasing what I wanted to print.
See the code below.
function loop() {
// print something to screen
}
var timer = setInterval(loop(), 30);
canvas.onkeypress = function (event) {
clearInterval(timer);
// print something else to screen
}
What's the best way to handle this? Put a delay on the // print something else to screen? Doing the new printing within the loop?
Edit: Thanks for the answers. For future reference, my problem was that the event that triggered the extra printing was buried within the loop, so once this executed, control was handed back to the unfinished loop, which then overwrote it. Cheers.
You could also use a flag so as to ignore any queued functions:
var should;
function loop() {
if(!should) return; // ignore this loop iteration if said so
// print something to screen
}
should = true;
var timer = setInterval(loop, 30); // I guess you meant 'loop' without '()'
canvas.onkeypress = function (event) {
should = false; // announce that loop really should stop
clearInterval(timer);
// print something else to screen
}
First of all, you probably meant:
var timer = setInterval(loop, 30);
Secondly, are you sure calling clearInterval does not clean the queue of pending loop() calls? If this is the case, you can easily disable these calls by using some sort of guard:
var done = false;
function loop() {
if(!done) {
// print something to screen
}
}
var timer = setInterval(loop(), 30);
canvas.onkeypress = function (event) {
clearInterval(timer);
done = true;
// print something else to screen
}