preface
Now, last time, PixiJS game development application practice (a) to share some of the basic knowledge of PixiJS, packaged a simple resource preloader, today to share some of the subsequent functions of the API.
Frame animation playback
Frame animation is a common form of animation realization, which is achieved by decomposing the animation action object into several consecutive key frames and then playing them continuously.
AnimatedSprite object
As an excellent rendering framework, Pixi also includes an API-AnimatedSprite object that supports frame animation. Its official type description documentation is as follows:
export declare class AnimatedSprite extends Sprite {
animationSpeed: number; // Frame animation playback speed
loop: boolean; // Whether to loop
updateAnchor: boolean; // Whether to update the resources of the animation textureonComplete? :() = > void; // Play end callback (loop = false)onFrameChange? :(currentFrame: number) = > void; // Animation Sprite texture callback when re-renderingonLoop? :() = > void; // callback triggered when loop = true
play(): void; // Play method
}
Copy the code
Frame the layout of the animation design
Frame animation, the width and height of each frame is exactly the same, in the case of a large number of frames, there may be multiple lines of distribution, below is a 2 *12 column fireworks frame animation resource map, and the second line only has two frames, after playing this frame, if you play it again, there will be a blank animation, so this frame is its last playing frame.
API package
First of all, let’s design the parameters that need to be passed into the frame animation playback class, listed as follows:
interface animateParams {
/** The texture name of the frame animation resource */
name: string
/ line number * * * /
rows: number
The column number / * * * /
columns: number
/** The number of blank frames in the last line */cutnum? :number
/** Animation playback speed */speed? :number
/** Whether the animation plays in a loop */loop? :boolean
/** Animation end callback (loop is false) */onComplete? :() = > void
}
Copy the code
The name is the name of the Texture object preloaded by the Preloader. The general idea is to read each frame, create a single frame size rectangle, and then load it with the Texture object.
export default class extends AnimatedSprite {
constructor(option: animateParams) {
const { name, columns, rows, cutnum = 0, speed = 1, loop = true, onComplete } = option
const texture = TextureCache[name]
// The width and height of a single frame
const width = Math.floor(texture.width / columns)
const height = Math.floor(texture.height / rows)
// Frame animation playback resource group
const framesList = []
// Create an animation texture array by traversing the frame animation source
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
// Blank areas are not drawn
if (cutnum && i === rows - 1 && j > cutnum) {
break
}
// Create a rectangle area for a frame
const recttangle = new Rectangle(j * width, i * height, width, height)
const frame = new Texture(texture.baseTexture, recttangle)
framesList.push(frame)
}
}
// Perform playback initialization for the parent AnimatedSprite
super(framesList)
this.animationSpeed = speed
this.loop = loop
this.onComplete = onComplete
}
}
const animateSprite = new CreateMovieClip({
name: 'fire'.rows: 2.columns: 12.cutnum: 10.speed: 0.5.loop: true.onComplete: () = > {
console.log('Animation ends')
}
})
animateSprite.play()
Copy the code
Call method is also relatively simple, create a play instance object, and then call the Play method.
Collision detection
Collision detection is also a feature API commonly used in game development. There are several common collision types in 2D:
- Axis-aligned Bounding boxes, which are non-rotating rectangles
- Circular collision
Axisymmetric bounding box (double rectangle)
- Concept: Determine if any two (non-rotating) rectangles have no space on either side to determine whether they collide
Core algorithm:
Math.abs(vx) < combinedHalfWidths && Math.abs(vy) < combinedHalfHeights
// The distance between the center points of the rectangles vx and vy
// combinedHalfWidths and combinedHalfHeights are the sum of half of the width and height
Copy the code
Disadvantages of the algorithm:
- Limitations: The two objects must be rectangular and cannot rotate, i.e. symmetrical horizontally and vertically
- Collision detection for some rectangles that contain patterns (that do not fill the entire rectangle) may be inaccurate
Circular collision
- Compare the sum of the distances and radii between the centers of two circles
Core algorithm:
Math.sqrt(Math.pow(circleA.x - circleB.x, 2) + Math.pow(circleA.y - circleB.y, 2)) < circleA.radius + circleB.radius
Copy the code
Algorithm disadvantage: the second axisymmetric bounding box is similar.
The event agent
Pixi has its own event handling system that allows you to listen for events directly on drawn elements. However, in the process of development, it was found that tap events were buggy on cross-ends, that is, tap events bound to Sprite objects would also be triggered when users swiped up and down on the real phone on the mobile end, resulting in poor user experience. Therefore, in this case, either the user touch duration to determine whether the event is click or Scroll, but the time is not controllable. Therefore, it is necessary to encapsulate the original base event types and add some federated event types that support cross-end.
The principle of
Eventemitter3 is a classic event sender library that uses the publish-subscriber design pattern to listen for events and trigger event callbacks.
export default class EventManager {
private static event = new EventEmitter() // Define an event listener
// Define some PC +mobile syndication event types
public static ALL_CLICK: string = 'tap+click'
public static ALL_START: string = 'touchstart+mousedown'
public static ALL_MOVE: string = 'touchmove+mousemove'
/** * event listener - static method that calls * directly from the class without instantiating the call@param {String} name- Event name *@param {Function} fn- Event triggers callback *@param {Object} context- Context object */
public static on(
name: string,
fn: Function, context? :any
) {
EventManager.event.on(name, <any>fn, context)
}
/** * Event trigger *@param {String} name- Event name */
public static emit(name: string. args:Array<any>){ EventManager.event.emit(name, ... args) } }Copy the code
After defining the event manager above, we know the inheritance relationship of pixi: DisplayObject > Container > Sprite, so we need to directly delegate the on and of methods on the DisplayObject- DisplayObject prototype by overwriting the methods on the prototype:
const on = PIXI.Container.prototype.on
const off = PIXI.Container.prototype.off
/** * proxy on listener */
function patchedOn<T extends PIXI.DisplayObject> (
event: string,
fn: Function
) :T {
// Handle the joint event separately
switch (event) {
case EventManager.ALL_CLICK:
// tap+click combines events
on.call(this, EventManager.TOUCH_CLICK, fn)
return on.call(this, EventManager.MOUSE_CLICK, fn)
case EventManager.ALL_START:
on.call(this, EventManager.TOUCH_START, fn)
return on.call(this, EventManager.MOUSE_DOWN, fn)
case EventManager.ALL_MOVE:
on.call(this, EventManager.TOUCH_MOVE, fn)
return on.call(this, EventManager.MOUSE_MOVE, fn)
}
return on.apply(this.arguments)}// Off listeners work the same way
// reverse assignment again
PIXI.Container.prototype.on = patchedOn
PIXI.Container.prototype.off = patchedOff
Copy the code
It is also simple to use by introducing the following proxy module and calling the methods of the EventManager module directly:
// Initialize the event listener
EventManager.on('circle'.(data: unknown) = > {
console.log(data)
})
circle.on(EventManager.ALL_CLICK, (e) = > {
// Trigger the event
EventManager.emit('circle', e.type)
})
// Listen for the 'ALL_CLICK' union event and there will be no sliding 'tap' event bug
Copy the code
Afterword.
This is the final chapter of this series, for PixiJs to do a more in-depth application practice summary, share with you, hope to help. Follow-up for game development this direction, will continue to share more practical content, we progress together, there are questions in the comment area code words, also welcome to point to the wrong errata!
reference
1. “Wait, I’ll touch!” — Common 2D collision detection
2. Pixijs official document