-
Notifications
You must be signed in to change notification settings - Fork 555
Crafty FAQ (draft)
This is a draft FAQ that could eventually be put at craftyjs.com
Sure! Doing a project is a great way to learn a new skill. However, you should also work through a javascript course or tutorial to learn how javascript works in general (syntax, variables, functions). A good course will also show you how to debug javascript. Finally, keep your project as simple as possible when you're first learning.
There are two types of event bindings:
- Bindings "owned" by a certain entity (the usual case). These are created by
some_entity.bind("PlayerDeath", some_function)
- Global bindings (less common). These are created by
Crafty.bind("PlayerDeath", some_function)
Then there are two types of event triggers:
- An entity-specific event trigger (the usual case). You would run
some_entity.trigger("PlayerDeath")
. It runs the functions bound to PlayerDeath, but ONLY those owned by the same entity, i.e.some_entity
. - A global event trigger (less common). You would run
Crafty.trigger("PlayerDeath")
. It runs EVERYTHING bound to PlayerDeath, no matter what entity owns it, and also including the global events.
For debugging, starting in version 0.6.0, you can run the command console.log(Crafty.debug('handlers'));
to see the internal object that stores all the information about which functions are bound to which events.
If you are always drawing the same image, it is usually easier to just load the image file and use the Image
component or Crafty.sprite
. But if the image changes in too many ways to pre-load, you can do custom canvas drawing. You should make your own component with the custom drawing routine bound to the "Draw"
event. Here is a simple example to use as a template. Let's call it "DiagonalLine":
Crafty.c("DiagonalLine", {
init: function () {
this.requires("2D, Canvas");
this.bind("Draw", this._draw_me);
this.ready = true;
},
_draw_me: function (e) {
var ctx = e.ctx;
ctx.lineWidth = 2;
ctx.strokeStyle = "green";
ctx.beginPath();
ctx.moveTo(e.pos._x, e.pos._y);
ctx.lineTo(e.pos._x + 5, e.pos._y + 7);
ctx.stroke();
}
});
Crafty.e("DiagonalLine").attr({x: 10, y: 20, w: 5, h: 7});
(The last line is just an example: It draws a diagonal line from (10,20) to (15, 27).)
Keep in mind:
- Whenever the appearance (shape, color, position, etc.) of the drawing changes, be sure to run
this.trigger("Change")
. (Or "Invalidate" from version 0.6.2 on). Otherwise Crafty does not realize that it needs to redraw. Changingthis.x
orthis.y
triggers Change automatically, but other kinds of changes may not. - An entity with this component needs to have the
2D
andCanvas
components too, and it needs to have x, y, w, h properties that define an invisible rectangular box which is as big or bigger than the drawing. This is important - it communicates to Crafty where the entity is, so that Crafty can correctly redraw only the parts of the canvas that need to be updated. (If you want, you can make the invisible rectangular box the size of the whole canvas; the only disadvantage of doing this is that it will force Crafty to redraw more entities more often, so the game may run slower.) - Nothing will draw until you set
this.ready = true;
- You should think of
e.pos._x
as a synonym ofthis._x
, ande.pos._y
as a synonym ofthis._y
, but with the special advantage that if you rotate or flip the entity (using the normal Crafty commands, e.g.this.rotation = 90;
,this.flip('X');
,this.origin('center');
, etc.), your drawing will rotate or flip in the correct way.
I have an idea to improve Crafty -- either changing the code or improving a description on the documentation pages -- What is the next step?
The easiest way to proceed is to write an email to the mailing list, or open a "New Issue" on the github repository. Someone else can change the code for you.
Alternatively, if you already know how to use git, you can change the code yourself and then submit a pull request to the "develop" branch of the github repository. Before you submit the pull request, you should make sure that the change actually works, by re-building and testing the crafty.js
file and/or the API documentation pages. See instructions here.
Please search the mailing list to see if anyone has already asked the same question that you have. You can join and post to the mailing list. If your question is about code that behaves wrong or in a funny way, it is helpful if you can capture the issue in a small jsfiddle demonstration. If you include the link in your email, everyone will be able to understand your question very easily and you will get a better answer.
<html>
<head><meta charset="UTF-8"> </head>
<body>
<script src="https://rawgithub.com/craftyjs/Crafty/release/dist/crafty-min.js"></script>
<script>
// Init Crafty:
Crafty.init();
WebFontConfig = {
google: { families: [ 'Scheherazade::arabic,latin', 'Indie+Flower::latin' ] },
/* code to execute once all font families are loaded */
active: function() {
Crafty.scene("main");
}
};
(function() {
var wf = document.createElement('script');
wf.type = 'text/javascript';
wf.async = 'true';
wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
'://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(wf, s);
})();
Crafty.scene("main", function() {
Crafty.e("2D, Canvas, Text")
.attr({x: 100,y: 60})
.text("Hello, العربية!").textFont("size", '72px').textFont("family", 'Indie Flower');
});
</script>
</body>
</html>
This phenomen is called "tunneling", which happens when fast moving, small entities pass through other thin entities.
In short, the problem is that the entity moves so fast that it never touches another entity and just jumps over it between two consecutive frames, as if no collision occurred.
This is usually circumvented by performing continuous collision detection, as described in more detail here.
However, you can approximate the CCD by considering the area the entity has skipped between frames (referred to as ccdbr
). Using this .ccdbr()
you can perform a collision search. Since it's an approximation you have to choose the appropriate entity hit among multiple entities returned from such a search - in this case you would probably choose the nearest entity to the bullet's position from the previous frame. Take a look at the Supportable
component implementation for example.
All Crafty's visible entities are placed inside Crafty's stage, more specifically they are placed as children of Crafty's stage DOM element which defaults to <div id='cr-stage' />
.
You can add your own custom DOM elements alongside it to the <body />
:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My CraftyJs Game</title>
<script src="crafty.js"></script>
</head>
<body>
<div id="header">My custom header</div>
<div id="content">
<div><p>My game description</p></div>
<div id="cr-stage"></div>
</div>
<div id="footer">My custom footer</div>
<script>
window.addEventListener('load', function() {
// will init the game with specified dimensions in the #cr-stage div
Crafty.init(600, 300, document.getElementById('cr-stage'));
// code your game
...
}, false);
</script>
</body>
</html>
Note: If you change the DOM structure dynamically after Crafty is initialized (by adding / removing, resizing or moving DOM elements), you need to tell Crafty that the DOM has changed. Call Crafty.viewport.reload()
every time the DOM changes in that case.
There are several alternatives on how to add GUI elements that work on top of a Crafty game. Some of them are:
- Need to inform user about something very important? Use
window.alert("Important message!");
- Need to ask the user for permission? Use
var ok = window.confirm("Proceed with action?");
- Need to get user input? Use
var username = window.prompt("Enter your username:", "Player1");
A dedicated Crafty entity can contain your custom HTML code. This entity behaves just like a normal entity and can be drag & dropped, moved by input controls or shifted out-of-view by the viewport.
In many cases you want to have static GUI elements, so you would probably add the entity to a static UI layer.
You can hook up native DOM events to trigger Crafty specific code or vice versa.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My CraftyJs Game</title>
<script src="crafty.js"></script>
</head>
<body>
<div id="cr-stage"></div>
<script>
window.addEventListener('load', function() {
// will init the game with specified dimensions in the #cr-stage div
Crafty.init(600, 300, document.getElementById('cr-stage'));
// Define a UI layer that is completely static and sits above the other layers
Crafty.createLayer("UI", "DOM", {
xResponse: 0, yResponse:0, scaleResponse:0, z: 50
});
// Some text that displays current button clicked, sits in the UI layer
Crafty.e("2D, UI, Text")
.textColor("black")
.textFont({size: '20px', family:'Arial'})
.attr({x: 20, y: 50, w: 200})
.text("No button clicked.")
// Listen to Crafty event 'ButtonClick' and change text accordingly
.bind("ButtonClick", function(buttonName) {
this.text("Clicked " + buttonName + ".");
});
// Custom GUI elements that sit in the UI layer, in this case clickable buttons
Crafty.e("2D, UI, HTML")
.attr({x: 20, y: 20, w: 200, h: 20})
.append(
"<button class='button' name='Button 1'>Button 1</button>" +
"<button class='button' name='Button 2'>Button 2</button>"
);
// Listen to DOM event 'click' on buttons, then trigger Crafty event 'ButtonClick'
var buttons = document.getElementsByClassName('button');
for (var i = 0, l = buttons.length; i < l; ++i) {
buttons[i].onclick = function(evt) {
Crafty.trigger("ButtonClick", evt.target.name);
};
}
}, false);
</script>
</body>
</html>
If you need more flexibility than in the previously described approaches, you can inject arbitrary HTML/CSS/JS into custom children of Crafty's stage DOM element.
Crafty code can modify the custom GUI elements or vice versa.
Note: If you change the DOM structure dynamically after Crafty is initialized (by adding / removing, resizing or moving DOM elements), you need to tell Crafty that the DOM has changed. Call Crafty.viewport.reload()
every time the DOM changes in that case.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My CraftyJs Game</title>
<script src="crafty.js"></script>
<!-- CSS for GUI: static main container and contained text component -->
<style>
#gui {
position: relative;
text-align: center;
z-index: 6000000;
}
#scoreText {
float: left;
color: blue;
font-size: x-large;
}
</style>
</head>
<body>
<div id="cr-stage"></div>
<script>
window.addEventListener('load', function() {
// will init the game with specified dimensions in the #cr-stage div
Crafty.init(600, 300, document.getElementById('cr-stage'));
// green box which will go out-of-view with elapsed time
Crafty.e("2D, Canvas, Color, green_box")
.attr({x: 0, y: 0, w: 50, h: 50})
.color("rgb(0,255,0)");
// main gui component, put all your static gui stuff in here
var gui = document.createElement("div");
gui.setAttribute("id", "gui"); // set id!
// one of the gui components: score text
var scoreText = document.createElement("div");
scoreText.setAttribute("id", "scoreText"); // set id!
scoreText.appendChild(document.createTextNode("0"));
Crafty.bind("Score", function(newValue) {
// modify external gui component in response to Crafty event
scoreText.innerHTML = newValue;
});
// append external gui components - requires viewport reload!
gui.appendChild(scoreText);
Crafty.stage.elem.appendChild(gui);
Crafty.viewport.reload();
Crafty.bind("EnterFrame", function() {
Crafty.viewport.x += 1; // all entites will move but gui won't
Crafty.trigger("Score", Crafty.viewport.x);
});
}, false);
</script>
</body>
</html>