Sphinx-SpriteButton

Sphinx-SpriteButton

Comme tous mes plugins, ce script est publié sous licence CC BY 4.0.

Bonjour à toutes et tous. Aujourd’hui, ce n’est pas vraiment un plugin que je souhaite présenter. A mes yeux, c’est plutôt la correction d’un bug assez dérangeant. En effet, avez-vous déjà essayé de créer un Sprite_Button ? Et avez-vous déjà tenté de lui spécifier une ancre autre que (0 ; 0) ou de lui appliquer une rotation ? Si oui, n’avez-vous jamais remarqué que votre sprite n’est pas correctement cliquable ? Voici la raison :

Dans la zone n°1, vous retrouvez un bouton classique. Ou presque. Pour faciliter son positionnement dans votre interface, vous avez décidé de placer une ancre à (0.6 ; 0.6). Une ancre permet de placer votre bouton non pas à partir de son coin haut gauche, mais d’un autre point. Bon, je reconnais que je n’ai jamais placé d’ancre à (0.6 ; 0.6), mais à (0.5 ; 0.5), autrement dit depuis le centre, si. Et ce, soit pour faciliter le positionnement d’un bouton dans mon interface, ou alors pour le faire pivoter autour de son centre au lieu de son coin haut gauche (la rotation a lieu autour de l’ancre, qui, par défaut, est aux coordonnées 0 ; 0 du sprite).

Mais à partir de ce moment, votre bouton n’est plus cliquable partout, mais seulement dans son coin bas droite. Et peut-être plus dérangeant encore, votre bouton est cliquable en dehors de sa surface, et pour être très précis dans l’ensemble de la zone n°2.

Eh oui, et tout ca, ca tient seulement au fait que RMMV, lorsque le joueur clique, teste si ce clic est sur le bouton, en partant de la position (sprite.x ; sprite.y) jusqu’à la position (sprite.x + largeur ; sprite.y + hauteur), sans tenir compte d’éventuels facteurs qui remettent en cause ce calcul, tel que l’utilisation d’une ancre (qui décale les coordonnées x et y du Sprite_Button) ou d’une rotation.

Ceci est pour moi un bug que j’ai relevé depuis un moment (pour le décalage lié à l’ancre, et hier le décalage lié à la rotation autour du coin haut gauche). Et c’est ce bug que je vous propose de corriger ici.

Ma correction

Ma correction tient en un script. Rien de foufou, c’est une bête correction des coordonnées en fonction de l’ancre (pour le décalage que j’ai illustré) et une correction plus délicate du test pour la rotation. Pour ce dernier point, j’ai dû ressortir mes vieux bouquins de maths. Et ma correction a elle aussi une limite que j’illustre ci-dessous :

Dans la zone n°1, on retrouve notre bouton, qui a subit ici une rotation autour de son centre. Comme expliqué un peu plus tôt, avec le comportement actuel de RMMV, le bouton ne serait cliquable dans la zone n°2. Autrement dit, dans une zone de la taille du bouton (avant rotation) décalé à cause de l’ancre.

Ma correction rendra le bouton cliquable sur sa surface (zone n°1) mais aussi sur la totalité de la zone n°3 (en bleu). Ainsi, il sera aussi cliquable en dehors du bouton, de la valeur minimale de x et y des 4 coins du sprite, jusqu’à leur valeur maximale.

Voici le plugin de correction que je vous propose :

//=============================================================================
// Sphinx-SpriteButton.js
//=============================================================================

/*:
 * @plugindesc Correction d'un bug des boutons
 * @author Sphinx
 *
 * @help
 * Correction des coordonnées de clic sur un bouton lorsque les coordonnées X
 * et Y ne correspondent pas au coin haut-gauche dans le cas de l'utilisation
 * de sprite.anchor.x et de sprite.anchor.y, et/ou de sprite.rotation.
 * Dans ce dernier cas, et si la rotation change la forme du Sprite_Button,
 * le clic est détecté sur un cadre allant des valeurs minimales des
 * coordonnées x et y aux valeurs maximales de ces coordonnées. Ainsi, le clic
 * peut être détecté en dehors du Sprite_Button.
 */
Sprite_Button.prototype.sphinxInitialize = Sprite_Button.prototype.initialize;
Sprite_Button.prototype.initialize = function() {
    Sprite_Button.prototype.sphinxInitialize.call(this);
	this.anchor = { x: 0, y: 0 };
	this.rotation = 0;
};

Sprite_Button.prototype.sphinxCanvasToLocalX = Sprite_Button.prototype.canvasToLocalX;
Sprite_Button.prototype.canvasToLocalX = function(x) {
    var x = Sprite_Button.prototype.sphinxCanvasToLocalX.call(this, x);
    if(this.anchor.x > 0) {
        x += this.anchor.x * this.width;
    }
    return x;
};

Sprite_Button.prototype.sphinxCanvasToLocalY = Sprite_Button.prototype.canvasToLocalY;
Sprite_Button.prototype.canvasToLocalY = function(y) {
    var y = Sprite_Button.prototype.sphinxCanvasToLocalY.call(this, y);
    if(this.anchor.y > 0) {
        y += this.anchor.y * this.height;
    }
    return y;
};

Sprite_Button.prototype.sphinxIsButtonTouched = Sprite_Button.prototype.isButtonTouched;
Sprite_Button.prototype.isButtonTouched = function() {
    if(this.rotation == 0) return Sprite_Button.prototype.sphinxIsButtonTouched.call(this);
	var points = [
		{ x: -this.anchor.x, y: -this.anchor.y },
		{ x: 1 - this.anchor.x, y: -this.anchor.y },
		{ x: 1 - this.anchor.x, y: 1 - this.anchor.y },
		{ x: -this.anchor.x, y: 1 - this.anchor.y }
	];
	for(var i = 0; i < points.length; ++i) {
		var x = Math.round(points[i].x * Math.cos(this.rotation) - points[i].y * Math.sin(this.rotation));
		var y = Math.round(points[i].x * Math.sin(this.rotation) + points[i].y * Math.cos(this.rotation));
		points[i].x = x;
		points[i].y = y;
	}
	var minX = Math.min(points[0].x, points[1].x, points[2].x, points[3].x) * this.width;
	var maxX = Math.max(points[0].x, points[1].x, points[2].x, points[3].x) * this.width;
	var minY = Math.min(points[0].y, points[1].y, points[2].y, points[3].y) * this.height;
	var maxY = Math.max(points[0].y, points[1].y, points[2].y, points[3].y) * this.height;
    var x = this.canvasToLocalX(TouchInput.x);
    var y = this.canvasToLocalY(TouchInput.y);
	return x >= minX && y >= minY && x < maxX && y < maxY;
};

Ce n’est évidemment pas parfait pour des rotations modifiant la taille d’origine du bouton (notamment, pour les boutons qui ne sont pas carrés ou pour des rotations différentes de % 90° pour ces derniers), mais ca améliore quand même l’existant. C’est pourquoi je le partage aujourd’hui.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.