Tink is an easy-to-use set of interactivity tools for the Pixi rendering engine. You can use Tink to easily create:
- Drag and drop objects.
- Click-able, touch-able buttons with customizable actions.
- A universal pointer object that works for both touch and and the mouse.
- Interactive sprites that behave like buttons.
(Important! This library targets Pixi v3.0.11, which is the most stable version of Pixi, and is the only version I can recommend using. This library will eventually be upgraded for Pixi v4 when the v4 branch matures.)
Setting up
A universal pointer
Pointer interaction with sprites
Drag and drop sprites
Buttons
Making buttons
Making an interactive sprite
Keyboard control
Setting Tink's optional scale
Let's find out how to use Tink.
First, link to the tink.js
script in your HTML file.
<script src="tink.js"></script>
Then create a new instance of Tink at the beginning of your JavaScript program.
Supply it with a reference to your running PIXI instance and the renderer.view
object (the HTML5 canvas).
let t = new Tink(PIXI, renderer.view);
The variable t
now represents your running Tink instance. Generally you should
create a new Tink instance after all the resources have loaded.
Next call Tink's update
method inside your game loop to update all of Tink's
interactive objects each frame. Here's a simple game loop that will do
the trick:
function gameLoop(){
//Start the loop
requestAnimationFrame(gameLoop);
//Update Tink
t.update();
//Optionally, you probably also want to render your root Pixi
//container, the `stage` object, in this loop:
//renderer.render(stage);
}
This is what you need to do to get started with the examples ahead.
Tink lets you make a pointer object that automatically figures
out whether the user is interacting with a mouse or with touch.
Use Tink's makePointer
method to create a pointer.
pointer = t.makePointer();
Usually one pointer will be enough for most games or applications, but you can make as many as you need. (Does your game or application require complex multi-touch interaction with gestures? Then consider using an excellent HTML5 library called hammer.js.
The pointer
object has three user-definable methods that you can
program: press
, release
, and tap
. press
is triggered when the
left mouse button is pressed down, or the user presses his or her finger
to the device screen. release
is triggered when the mouse button is
released, or the user lifts his or her from the screen. tap
is triggered
if the left mouse button is clicked, or the user taps the screen.
Here's an example of how you can define these methods on the pointer
:
pointer.press = () => console.log("The pointer was pressed");
pointer.release = () => console.log("The pointer was released");
pointer.tap = () => console.log("The pointer was tapped");
Also use the tap
method to capture mouse clicks.
The pointer
also has x
and y
properties that tell you its position
on the canvas (Pixi's renderer.view
.)
pointer.x
pointer.y
It also has three Boolean properties that tell you pointer's current
state: isUp
, isDown
and tapped
.
pointer.isUp
pointer.isDown
pointer.tapped
If you need to hide the pointer for some reason, use the Boolean visible
property.
//Hide the pointer
pointer.visible = false;
//Make the pointer visible
pointer.visible = true;
The pointer
has a hitTestSprite
method that you can use to find out
if the pointer is touching a sprite.
pointer.hitTestSprite(anySprite);
If the pointer is within the rectangular area of a sprite,
hitTestSprite
will return true
.
hitTestSprite
will also work with circular sprites. Just add a property
to a sprite called circular
and set it to true
.
anyCircularSprite.circular = true;
This flags hitTestSprite
to use a circular collision detection algorithm
instead of the default rectangular one. If you want to display a hand icon
while the pointer is over sprite the you can set the pointer's
cursor
property to "pointer"
. Setting it "auto"
when the pointer
leaves the sprite's area will display the default arrow icon. Here's some
sample code you could use inside your game loop to enable this
feature.
if (pointer.hitTestSprite(anySprite)) {
//Display a hand icon while the pointer is over the sprite
pointer.cursor = "pointer";
}
else {
//Display the default arrow icon when the
//pointer moves outside the sprite's area
pointer.cursor = "auto";
}
pointer.cursor
just references the HTML5 canvas's element's
style.cursor
property to achieve this, using two standard values
from the HTML5 spec: "pointer"
and "auto"
. You can assign any cursor style
value that you like. (A web search for "HTML style.cursor" will turn up a complete
list of possible values.) You can also set this manually if you want
to through Pixi's renderer.view
object. Here's how:
renderer.view.style.cursor = "cursorStyle";
These cursor styles will only be visible on a mouse-based interface; on a touch interface they're ignored.
You can add drag-and-drop functionality to a sprite with Tink's
makeDraggable
method. Just supply it with a single sprite, or a list of
sprites, that you want to make draggable.
t.makeDraggable(cat, tiger, hedgehog);
You can then use the mouse or touch to drag the sprites around the canvas.
When you select a draggable sprite, its stacking order changes so that it appears above the other sprites. The mouse's arrow icon also changes to a hand when its over a draggable sprite.
Draggable sprites have a Boolean property called draggable
that is
set to true
. To disable dragging, set draggable
to false
.
anySprite.draggable = false;
Setting it back to true
will enable dragging again.
To completely remove a sprite (or list of sprites) from the drag-and-drop
system, use the makeUndraggable
method, like this:
t.makeUndraggable(cat, tiger, hedgehog);
Drag-and-drop is a fundamental interactive feature that can be used as the basis for making puzzles games, matching games, or sophisticated user interfaces.
Buttons are an important UI component that you'll definitely want to use in your games and applications. Tink has a useful button method that lets you quickly create them. Before I show you how to make buttons, lets first find out what buttons actually are, can how you can use them.
You can think of buttons as "clickable/touchable sprites". The most important thing you need to know about buttons is that they have states and actions. States define what the button looks like, and actions define what it does. Most buttons have three states:
- Up: When the pointer is not touching the button.
- Over: When the pointer is over the button.
- Down: When the pointer is pressing down on the button.
Touch-based interfaces need only two states: up and down.
With the button object that you'll learn to make in the next section,
you be able access these states through the button's state
property, like this:
playButton.state
The state
property could have the string value "up"
, "over"
,
or "down"
, which you could use in your game logic.
Buttons also have actions:
- Press: When the pointer presses the button.
- Release: When the pointer is released from the button.
- Over: When the pointer moves into the button's area.
- Out: When the pointer moves out of the button's area.
- Tap: When the button has been tapped (or clicked.)
You can define these actions as user-definable methods, like this:
playButton.press = () => console.log("pressed");
playButton.release = () => console.log("released");
playButton.over = () => console.log("over");
playButton.out = () => console.log("out");
playButton.tap = () => console.log("tapped");
In the button object that we'll make ahead, you'll able to access the button's "pressed" and "released" actions in a string property, like this:
playButton.action
Got it? Good! So how do we actually make buttons?
First, start with three images that define the three button states. You might call them "up.png", "over.png", and "down.png". Then add those three images to a tileset, or as frames in a texture atlas.
Although having three image states is standard, sometimes buttons have only two image states. This is particularly true of touch-only buttons, which don't have an "over" state. The button object that we're going to make ahead will use three images if they're available, but if it only has two, Tink will assign these to the "up" and "down" states.
Next, publish the texture atlas and load it into your program:
PIXI.loader
.add("images/button.json")
.load(setup);
Then in the setup
function where you initialize your sprites, create an array that
references each of the three button frames in this order: up, over and down.
function setup() {
//Create an alias for the texture atlas frame ids
let id = PIXI.loader.resources["images/button.json"].textures;
let buttonFrames = [
id["up.png"],
id["over.png"],
id["down.png"]
];
}
These don't have to be frame ids: you can use an array of any Pixi textures, like single image textures if you want to.
Finally, use Tink's button
method to create the button. Supply the
buttonFrames
array as the first argument.
let playButton = t.button(buttonFrames, 32, 96);
The second and third optional arguments are the button's x
and y
position.
And don't forget to add the button to the stage
(Pixi's root
container object)!
stage.addChild(playButton);
You now have a useful button object that you can use in any game or application.
At its heart, a button is just an ordinary Pixi MovieClip
with extra properties
and methods, so you can treat it like any other MovieClip
object.
Tink has another useful method called makeInteractive
that lets you add button
properties and methods to any ordinary sprite.
t.makeInteractive(anySprite);
This lets you turn any sprite into a button-like object. You can now
assign press
or release
methods to the sprite, and access its
state
and action
properties, like this:
anySprite.press = () => {
//Do something when the pointer presses the sprite
};
anySprite.release = () => {
//Do something when the pointer is released after pressing the sprite
};
If you want any sprite to behave like a button, use makeInteractive
!
keyboard
is a method that listens for and captures keyboard events. It's really
just a convenient wrapper function for HTML keyup
and keydown
events
so that you can keep your application code clutter-free and easier to write and read.
Here's how to use the keyboard
method. Create a new keyboard object like this:
let keyObject = t.keyboard(asciiKeyCodeNumber);
Its one argument is the ASCII key code number of the keyboard key
that you want to listen for. Here's a list of ASCII key codes you can
use.
Then assign press
and release
methods to the keyboard object like this:
keyObject.press = () => {
//key object pressed
};
keyObject.release = () => {
//key object released
};
Keyboard objects also have isDown
and isUp
Boolean properties that you can use to
check the state of each key.
Tink has another convenience method called arrowControl
that lets you
quickly create a 4 direction controller for sprites using the
keyboard arrow keys. It's useful for quickly prototyping game ideas.
Supply the arrowControl
method with the sprite you want to control
and the pixels per frame that you want it to move:
t.arrowControl(anySprite, 5);
Then just update your sprite's velocity inside your game loop, like this:
function gameLoop() {
requestAnimationFrame(gameLoop);
anySprite.x += vx;
anySprite.y += vy
}
You'll then be able to move the sprite in all four directions using
the arrow keys.
(For the arrowControl
method to work, your sprite needs to have vx
and vy
properties
that refer to the sprite's velocity.)
You now know everything you need to know about using Tink, so go ahead and start using it! But, if you want to re-size or re-scale Pixi's renderer inside the browser window, there's one more thing you need to know.
You can use an optional helper function called scaleToWindow
that
will automatically scale your Pixi renderer to the maximum size of
the browser window. Visit scaleToWindow
's source code repository to
find out how to use it. Tink's constructor has an optional second argument: scale
. The
default scale
value is 1, but if you've re-scaled the canvas using
the scaleToWindow
function, supply scaleToWindow
's return value.
Here's an example of what this might look like:
First, run scaleToWindow
and capture the returned scale
value at the beginning of your program.
let scale = scaleToWindow(renderer.view);
Next, create a new instance of Tink and supply the scale
value as the second argument in the constructor.
let t = new Tink(PIXI, renderer.view, scale);
This will ensure that the coordinates that Tink uses will match the canvas's scaled pixel coordinates.
Finally, make sure to update the Tink scale if you also opted for rescaling the canvas element every time the size of the browser window has changed:
window.addEventListener("resize", function(event){
let scale = scaleToWindow(anyCanvasElement);
t.scale(scale)
});