WoofJS is a JavaScript framework for creating games by The Coding Space.
If you're new to JavaScript, you may want to get acquainted with its basic syntax and paradigm.
We reccomend you File>Clone this JSBin to get started: https://jsbin.com/lekovu/edit?js,output
Alternatively, you can put Woof between your HTML <head>
tags.
<script src="https://cdn.rawgit.com/stevekrouse/WoofJS/67c5becefae34077a01c25bf6e27d24a639f74af/woof.js"></script>
var circle = new Circle({})
forever(() => {
circle.radius = circle.distanceTo(mouseX, mouseY)
})
circle.onMouseDown(() => {
circle.color = randomColor()
})
To create a new sprite, start by typing one of the following lines:
var IMAGE_NAME = new Image({});
var TEXT_NAME = new Text({});
var CIRCLE_NAME = new Circle({});
var RECTANGLE_NAME = new Rectangle({});
var LINE_NAME = new Line({});
Pro tip: Be sure to change the SPRITE_NAME
to a name of your choice!
You may add any of the following options to any of your sprites:
var IMAGE_NAME = new Image({x: 100, y: 20, angle: UP, rotationStyle: "ROTATE", showing: true});
var TEXT_NAME = new Text({x: 50, y: -100, angle: DOWN, rotationStyle: "NO ROTATE", showing: true});
var CIRCLE_NAME = new Circle({x: 0, y: 0, angle: 90, rotationStyle: "ROTATE LEFT RIGHT", showing: false});
var RECTANGLE_NAME = new Rectangle({x: 62, y: 12, angle: 0, rotationStyle: "ROTATE", showing: false});
var LINE_NAME = new Line({x: maxX, y: maxY, angle: 0, rotationStyle: "ROTATE", showing: false});
Each sprite has its own specific options. For example, Image
has url
, Circle
has radius
, and text has fontFamily
:
var IMAGE_NAME = new Image({url: "https://i.imgur.com/SMJjVCL.png/?1",width: 30, height: 30});
var TEXT_NAME = new Text({text: "Hello world!", size: 12, color: "rgb(100, 50, 240)", fontFamily: "arial", textAlign: "left"});
var CIRCLE_NAME = new Circle({radius: 10, color: "#ffffff"});
var RECTANGLE_NAME = new Rectangle({width: 20, height: 55, color: "pink"});
var LINE_NAME = new Line({x: -100, y: 100, x1: 10, y1: 20, color: "pink", lineWidth: 10});
NAME.angle = LEFT;
NAME.angle = RIGHT;
NAME.angle = UP;
NAME.angle = DOWN;
NAME.angle = 47.7;
NAME.pointTowards(mouseX, mouseY);
NAME.pointTowards(NAME.x, NAME.Y);
NAME.x = mouseX;
NAME.y = mouseY;
NAME.x = otherNAME.x;
NAME.y = otherNAME.y;
NAME.setRotationStyle(“ROTATE LEFT RIGHT”)
NAME.setRotationStyle(“ROTATE”)
NAME.setRotationStyle(“NO ROTATE”)
You can only setImageURL()
for Images.
Change the color for rectangles, text, lines and circles:
RECTANGLE_NAME.color = "purple";
TEXT_NAME.color = "rgb(10, 150, 30)";
LINE_NAME.color = "#ff20ff";
CIRCLE_NAME.color = "green";
You set the size in different ways for each type of sprite:
IMAGE_NAME.height = ...; IMAGE_NAME.width = ...;
RECTANGLE_NAME.height = ...; RECTANGLE_NAME.width = ...;
CIRCLE_NAME.radius = ...;
LINE_NAME.x1 = ...;
LINE_NAME.y1 = ...;
LINE_NAME.lineWidth = ...;
TEXT_NAME.size = 20;
Set the backdrop to an image URL:
setBackdropURL("http://example.com/img.jpg");
Set the backdrop to a color:
setBackdropColor("blue");
Set the backdrop size:
fullScreen = false;
var width = 300;
var height = 400;
setBackdropSize(width, height);
NAME.penColor = “rgb(10, 100, 20)”;
You can combine creating/naming a variable with setting it:
var sampleVariable = ...;
new Text({text: () => "variableName: " + variableName});
("Showing a variable" works by giving a Text Sprite a function instead of a "string in quotes" as its text
attribute. The Text Sprite constantly reevaluates the function which keeps the value on the screen in sync with the value of the variable.)
(You can add anything to an array in JavaScript, including numbers, strings, but even Sprites, functions, and other arrays.)
new Text({text: () => "listName: " + listName});
Do something for each thing in an array:
sampleArray.forEach(thing => {
console.log(thing)
});
Check if a condition holds for at least one thing in an array:
if (sampleArray.some(thing => thing.over(mouseX, mouseY))) {
...
}
Check if a condition holds for everything in an array:
if(sampleArray.every(thing => thing.touching(...))) {
...
}
Find something in an array:
var needle = sampleArray.find(thing => thing.touching(...));
if(needle) {
console.log(needle);
}
Warning: The shape of events in Scratch prevent you from putting an event inside other blocks. Although JavaScript doesn't prevent you from putting events inside other blocks, you should avoid it. For example, don't place an onMouseDown
event inside a forever
block.
ready(() => {
...
});
Note: Unlike in Scratch, using ready()
is reccomended but not always required.
onMouseDown(() => {
...
});
On mouse up:
onMouseUp(() => {
...
});
NAME.onMouseDown(() => {
...
});
On mouse up:
NAME.onMouseUp(() => {
...
});
onKeyDown(() => {
...
});
onKeyDown(key => {
if (key == 'A') {
...
}
});
onKeyDown(key => {
if (key == 'UP') {
...
}
});
If you want to see if the center of your sprite is outside of a boundary, here are some expressions that could be helpful:
NAME.x > maxX
NAME.x < minX
NAME.y > maxY
NAME.y < minY
NOTE: touching detects the rectangular boundary of a sprite, so if you have an image with a large transparent border, you will need to trim your image to make touching accurate.
NAME.distanceTo(mouseX, mouseY);
List of keys currently pressed: keysDown
Previous mouse X: pMouseX
Previous mouse Y: pMouseY
Mouse X speed: mouseXSpeed
Mouse Y speed: mouseYSpeed
Right edge of the screen: maxX
Left edge of the screen: minX
Top edge of the screen: maxY
Bottom edge of the screen: minY
Width of the screen: width
Height of the screen: height
Hour in military time: hourMilitary();
Random X value on the screen between minX
and maxX
: randomX()
Random Y value on the screen between minY
and maxY
: randomY()
Random color: randomColor()
Less Than or Equal To: ... <= ...
Greater Than or Equal To: ... >= ...
Not Equals: ... != ...
Between Two Numbers : NAME.x.between(minX, maxX)
You can create a function with a name:
var namedFunction = (input1, input2) => {
// do stuff here with input1 and input2
}
You can run a function by putting parentheses next to its name:
namedFunction(1, 2)
You need to do this even if the function takes no parameters:
namedFunctionWithoutParameters()
But you can also create a function without a name, which is called an anonymous function:
forever(() => {
sprite.x++;
})
There is no wait block in JavaScript. Instead, you can use after()
:
after(..., "seconds", () => {...});
However, after()
is a poor substitute for Scratch's wait block and you may find some programs very difficult to write without wait. Please accept my apologies on behalf of the designers of JavaScript.
If you want to wait at regular intervals, use every()
:
every(..., "seconds", () => {
...
});
repeat(10, () => {
...
});
forever (() => {
...
});
if (...) {
...
}
if (...) {
...
} else {
...
}
repeatUntil(() => ..., () => {
...
});
There is no wait-until in JavaScript. You can simulate a wait-until block by specifying a third function to a repeatUntil
. Refer to the "Control Flow" section below for more details.
repeatUntil(() => ..., () => {}, () => {
...
});
when()
is a short-hand for a forever
-if
statement.
when(() => ..., () => {
...
});
Reverse freeze or stop all: defrost();
// create a list to store all of the clones
var clones = [];
every(4, "seconds", () => {
// create a clone every 4 seconds
var clone = new Circle({radius: 10, color: "pink", x:
randomX(), y: randomY()});
// add each clone to the list
clones.push(clone);
});
forever(() => {
// forever, for each clone in clones
clones.forEach(clone => {
// move it to the right
clone.x++;
// delete it if it goes off the screen
if (clone.x > maxX) {
clone.delete();
}
})
});
Delete an object: NAME.delete();
Only allow something to happen once every X miliseconds:
onMouseDown(throttle(() => score++, 1000)) // after a mousedown, you won't be able to trigger this event again for 1000 milliseconds
There are two types of commands in JavaScript:
- Synchronous: "Do this immediately and move on when it's done."
Synchronous example in real life: Open heart surgery. When a doctor begins open heart surgery on a patient, she stays in the operating room with the patient until the surgery is done and the patient is stitched back up. She doesn't start open surgery with one patient and then move on to another operating room where she begins operating on a second patient before the first operation is done. She starts and finishes one operation before starting a second.
- Asynchronous: "Start this immediately, but don't wait till it's done. Move on to the next command immediately after you start this command."
Asynchronous example in real life: Ordering food or drinks. First, let's imagine what a synchronous restaurant would look like. A waiter would come to your table, take 1 person's order, rush it back to the kitchen, wait for the kitchen to finish with that person's order, and then bring it back to that person. Only once this first order is taken care of would the waiter then ask the second person at the table for their order. Of course this would be ridiculous. It makes much more sense for a restaurant to process its customers' orders “asynchronously.” That is, after a waiter takes one person's order, he is free to take another person's order before finishing the first order to completion. This allows the restaurant to do multiple things at once, which is ultimately faster because some tasks, like chopping vegetables while you wait for a pot of water to heat, make more sense in parallel than sequence.
Most commands in programming are synchronous. For example, if
-statements, setting or changing variables, and calling most Woof methods like rectangle.move(10)
are all synchronous commands.
forever
is an example of an asynchronous command. Think about it: if forever
told the computer to wait until it was done before moving on, the computer would never move on to the next line.
repeat
, repeatUntil
, every
, after
are also asynchronous commands.
Asynchronous commands become quite confusing when you want something to happen after an asynchronous command is finished. If you want something after the 10th time you repeat
it, you can't just put it on the line below the close of the repeat
block. Why? Because the line below repeat
happens immediately after the asynchronously command starts, not after it finishes. If at the end of ordering a meal, you ask the waiter to refill your water, you expect him to refill it immediately after sending your order to the kitchen, not after you've received all your food, despite this order coming after the food orders.
But what if, for some perverse reason, you wanted the waiter to wait until after you received your food to refill your water? That is, how do we tell the computer to do something after an asynchronous command is finished? This is different for each programming language, but for WoofJS, repeat
and repeatUntil
, optionally accept a function as an extra parameter to specify what should happen after the asynchonous command is finished. This is called a "callback" because that function is "called back" and run after the main part of the command is finished.
Also, be careful not to wantonly nest asynchronous commands within each other's main body. For example if you want to make an image move in a square for forever, you can't just put four nested repeats inside a forever. If you put a repeat in a forever, that will cause the computer continuously spawning new repeats really quickly. It's like repeatedly asking a waiter for more and more food before your first course has arrived. The waiter and kitchen will spend so much time processing these thousands of orders, it'll likely cause the restaurant to crash! Instead, in Woof, you have to use recursion and tell your repeat code to start-over only after the 4th repeat is finished finished.
Change color to blue after moving to the right 10 times:
repeat(10, () => {
NAME.x++;
}, () => {
NAME.color = "blue";
});
Chaining repeats to slowly draw a square:
repeat(100, () => {
NAME.angle = RIGHT;
NAME.move(1);
}, () => {
repeat(100, () => {
NAME.angle = UP;
NAME.move(1);
}, () => {
repeat(100, () => {
NAME.angle = LEFT;
NAME.move(1);
}, () => {
repeat(100, () => {
NAME.angle = DOWN;
NAME.move(1);
});
});
});
});
When you include the Woof script in your code, it defaults to creating a full-screen project and polluting your global namespace with Woof's methods. We find not having to type "Woof." over and over again makes a huge difference for beginner programmers, especially those new to typing.
However, if you'd like to turn off this mode, simply add global="false"
in your script tag and create your project manually:
var project = new Woof({global: false, width: 300, height: 400});
var IMAGE_NAME = new Woof.Image({project: project, url: "https://i.imgur.com/SMJjVCL.png?1"})
WoofJS was created to be the next step after block-based coding in Scratch. For more details, you can read our announcement post.