WoofJS is a JavaScript framework for making games developed with ❤️ by The Coding Space.
We reccomend you clone this JSBin to get started: https://jsbin.com/lekovu/edit?js,output
Alternatively, you can put Woof between the <head>
tags.
<script src="https://cdn.rawgit.com/stevekrouse/WoofJS/21ec67e3592012375ebc4d11094937d11b461067/woof.js"></script>
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();
Every sprite can be created using the following options:
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 Rectangle({x: maxX, y: maxY, angle: 0, rotationStyle: "ROTATE", showing: false});
Each sprite has its own specific options:
var IMAGE_NAME = new Image({url: "https://i.imgur.com/SMJjVCL.png", imageWidth: 30, imageHeight: 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({rectangleHeight: 10, rectangleWidth: 20, 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.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”)
IMAGE_NAME.imageHeight = ...; IMAGE_NAME.imageWidth = ...;
RECTANGLE_NAME.rectangleHeight = ...; RECTANGLE_NAME.rectangleWidth = ...;
CIRCLE_NAME.radius = ...;
LINE_NAME.x1 = ...;
LINE_NAME.y1 = ...;
LINE_NAME.lineWidth = ...;
TEXT_NAME.size = 20;
Change the color:
RECTANGLE_NAME.color = "purple";
TEXT_NAME.color = "rgb(10, 150, 30)";
LINE_NAME.color = "#ff20ff";
CIRCLE_NAME.color = "green";
Set the text value to an unchanging value: TEXT_NAME.text = "Sample Text";
Set the text value to an changing functional expression: TEXT_NAME.text = () => "Variable Name: " + variableName;
Set the text font family: TEXT_NAME.fontFamily = "arial";
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)”;
sampleVariable += ...;
sampleVariable -= ...;
sampleArray.splice(startIndex, endIndex);
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))) {
doSomething();
}
Check if a condition holds for everything in an array =
if(sampleArray.every(thing => thing.touching(...))) {
doSomething();
}
onClick(() => {
...
});
NAME.onClick(() => {
...
});
forever (() > {
...
});
if (...) {
...
}
if (...) {
...
} else {
...
}
Reverse freeze or stop all: defrost();
Delete an object: NAME.delete();
// create a list to store all of the clones
var clones = [];
every(4, "seconds", () => {
// create a clone every 4 seconds
var clone = addCircle ({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();
}
})
});
Only allow something to happen once every X miliseconds:
onClick(throttle(() => score++, 1000)) // after a click, you won't be able to click for 1 second
NAME.x > maxX
NAME.x < minX
NAME.y > maxY
NAME.y < minY
NAME.touching(OTHER_NAME)) {...};
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);
Previous mouse X: pMouseX
Previous mouse Y: pMouseY
Mouse X speed: mouseXSpeed
Mouse Y speed: mouseYSpeed
List of keys currently pressed: keysDown
Is 'A' pressed?: keysDown.includes('A')
Is the space key pressed?: keysDown.includes(' ')
Is the up key pressed?: keysDown.includes('UP')
Right edge of the screen: maxX
Left edge of the screen: minX
Top edge of the screen: maxY
Bottom edge of the screen: minY
Random X value on the screen between minX
and maxX
: randomX()
Random Y value on the screen between minY
and maxY
: randomY()
Width of the screen: width
Height of the screen: height
Distance of thing to a point: NAME.distanceTo(X, Y);
Hour in military time: hourMilitary();
Less Than or Equal To: ... <= ...
Greater Than or Equal To: ... >= ...
Not Equals: ... != ...
Random color: randomColor()
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)
But you can also create a function without a name, which is called an anonymous function:
forever(() => {
sprite.x++;
})
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 surery 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 resturant 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. Finally once this first order is taken care of, the waiter would ask the second person at the table for their order. Of course this would be ridiculous. It makes much more sense for a resturant 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. This allows the resturant to do multiple things at once, which is ultimately faster for some types of tasks, particularly those that take a lot of time to process.
Most commands 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, it would never move on to the next line.
repeat
, repeatUntil
, every
, after
are also asynchronous.
Asynchronous commands become quite confusing when you something 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 repeat
. Why? Because the line below repeat
happens immediately after the asynchronously command starts, not after it finishes.
So how do we tell the computer to do something after an asynchronous command is finished? This is different for each language, but for Woof's repeat
and repeatUntil
, add a function as an extra parameter to those commands that specifies 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 done.
Also, be sure not to nest async 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. Instead you have to use recursion and tell your repeat code to start-over only after the 4th repeat is finished finished. (If you put a repeat in a forever, it'll keep starting new repeats for forever really quickly. It's like repeatedly asking a waiter for seconds before your first course has arrived.)
- Repeat 10 times:
repeat(10, () => {
NAME.x++;
});
- Do something after done repeating:
repeat(10, () => {
NAME.x++;
}, () => {
NAME.color = "blue";
});
- Chaining repeats:
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);
});
});
});
});
- Repeat constantly until (you have to put the condition in a function):
repeatUntil(() => IMAGE_NAME.y < minY, () => {
IMAGE_NAME.y -= SPEED;
});
- Do seomthing after a
repeatUntil
:
repeatUntil(() => IMAGE_NAME.y < minY, () => {
IMAGE_NAME.y -= SPEED;
}, () => {
freeze();
});
- Do this constantly forever:
forever(() => {
CIRCLE_NAME.radius = CIRCLE_NAME.distanceTo(mouseX, mouseY);
});
- Do this every second:
var timer = 20; // make the timer start at 20
var timerText = new Text({x: 0, y: maxY - 20, size: 20, color: "white", text: () => `Time Left: ${timer}`}); // add text that diplays the timer, updating automatically
every(1, "second", () => {
if (timer === 0){ freeze(); } // freeze the screen when the timer reaches 0
timer--; // make the timer go down every second
});
- Do this when a condition (in a function) is true. This is a short-hand for a forever-if statement.
when(() => keysDown.includes("LEFT"), () => {
rectangle.x -= 5;
});
- Do this after one second:
after(1, "second", () => {...});
By default, when you include the Woof script in your code, we default to making 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 beginnger programmers.
However, if you'd like to turn off this mode, simple add global="false"
in your script tag.
Despite its many noted flaws, Processing still remains the dominent graphics programming framework for beginners. Woof strives to solve the same problems as Processing, and be a beginner-friendly graphics frameworks for art, animation and game development, but it also hopes to improve upon Processing's main flaws:
- opaque parameters setting - In Woof, all object properties can be set explicitly with dot notation or as named-parameters in the constructor.
- side effects in render - In Woof, you can cause state to change in response to events or intervals. You don't have to hook your effectful code into your render method to make things move.
- render - In Woof, (like ReactJS) we take care of rendering your objects onto the canvas for you. This means you don't even have to think about how your view layer works. It just does.
- hidden state - In Woof, there's no hidden view-layer state. If you make one circle red, only that circle is red.
- non-modular - In Woof, because all view-layer state is encapulated into objects, it allows you to easily build modular and functional code without worrying about side effects.
- poor decomposition - In Woof, you can have infinitely many objects listen to infinitely many events. You don't have to funnel all of your code through the same top-level events that forces you to build tangled code.
- lack of identity / metaphor - Woof steals Scratch's and Smalltalk's everything-is-an-object-that-you-can-see-on-the-screen-immediately metaphor. It also steals LOGO's pen, angles, turning and moving athropomorphic metaphors.
- Making a page of draggable elements takes Pixi.js 80 lines, while using WoofJS it only takes 20 lines.
- Making a bunch of crazy bouncing images takes Pixi.js 254 lines, but only takes WoofJS 32 lines.
- Making pong using JQuery and Underscore.js takes 146 lines, while it takes WoofJS under 60 lines (and our version has more elements PLUS it works on mobile!).
- Making this analog clock takes 125 lines of JavaScript, but only 40 lines using WoofJS.
- Recreating the viral game Flappy Bird takes under 70 lines using WoofJS.