animations en 2d

Transcription

animations en 2d
CHAPITRE2
ANIMATIONSEN2D
AlexandreBlondinMassé
Départementd'informatique
UniversitéduQuébecàMontréal
23septembre2015
INF5071-Infographie
EXERCICE(COURS
PRÉCÉDENT)
1. ÉcrivezdesfonctionsenJavaScriptquitransformentunpointd'un
systèmedecoordonnéesàunautre:
function cartesian2screen(x, y) {
// Compléter
}
function polar2cartesian(r, a) {
// Compléter
}
2. ÉcrivezunefonctionJavaScriptquitraceunpolygonerégulierden
côtéscentréen(x, y),inscritdansuncerclederayonretd'angle
initiala(enradians)
Graphics.prototype.drawRegularPolygon = function (x, y, n, r, a) {
// Compléter
}
SOLUTION
1. Unesolutionpossibleestlasuivante:
function cartesian2screen(x, y) {
return [x, -y];
}
function polar2cartesian(r, a) {
return [r * Math.cos(a), r * Math.sin(a)];
}
Onpeutaussiutiliserdesobjets:
function cartesian2screen(x, y) {
return {x: x, y: -y};
}
function polar2cartesian(r, a) {
return {x: r * Math.cos(a), y: r * Math.sin(a)};
}
SOLUTION(SUITE)
2. Voiciunesolutionpossible:
Phaser.Graphics.prototype.drawRegularPolygon = function (x, y, n, r, a) {
this.moveTo(x + r * Math.cos(a), y + r * Math.sin(a));
for (i = 1; i <= n; i++) {
var a2 = i * 2 * Math.PI / n;
var x2 = x + r * Math.cos(a + a2);
var y2 = y + r * Math.sin(a + a2);
this.lineTo(x2, y2);
}
}
ANIMATIONSDEBASE
En2D,lestransformationsgéométriquesprincipalessont:
lestranslations;
lesrotations;
leshomothéties(changementd'échelle)et
lesréflexions.
Toutescestransformationssontdestransformationslinéaires,à
l'exceptiondelatranslation,quiestunetransformationaffine;
Autrementdit,ellespeuventêtrereprésentéespardesmatrices.
TRANSLATIONS(1/2)
Soitv = (a, b)unvecteur;
LafonctionTv:ℝ2 → ℝ2définiepar
T(x, y) = (x + a, y + b)
estappeléetranslationdevecteur`v`.
L'inversedeTvestlatranslationdevecteur − v.
DansPhaser,appliquerunetranslationesttrèsfacile:ilsuffitde
mettreàjourlesattributsxetydel'objetGraphicscorrespondant.
TRANSLATIONS(2/2)
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
var square;
function preload() {
}
function create() {
square = game.add.graphics();
square.clear();
square.lineStyle(2, 0xFF0000, 1);
square.drawRect(0, 0, 30, 30);
}
function update() {
var xSpeed = 300;
var ySpeed = 200;
var elapsed = game.time.elapsed / 1000; // Temps écoulé en secondes
square.x += elapsed * xSpeed;
square.x %= game.width;
square.y += elapsed * ySpeed;
square.y %= game.height;
}
Voirlerésultat
ROTATIONS(1/2)
Soitθunnombreréel.
LafonctionRθ:ℝ2 → ℝ2définiepar
Rθ(x, y) = ⎡⎜⎣cosθ − sinθ sinθcosθ⎤⎜⎦⎡⎜⎣x y
⎤⎜⎦
estappeléerotationd'angleθ(autourdel'origine).
L'inversedeRθestlarotationd'angle − θ.
DansPhaser,appliquerunerotationestaussitrèsfacile:
Lecentrederotationestdonnéparlesattributsxety;
Ilsuffitdemodifierl'attributangle(endegrés)ourotation(en
radians).Apparemment,rotationestlégèrementplusefficace,car
iln'yapasdeconversionàl'internequiesteffectuée.
ROTATIONS(2/2)
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
var square;
var ellipse;
function preload() {
}
function create() {
square = game.add.graphics(200, 200);
square.lineStyle(2, 0xFF0000, 1);
square.drawRect(0, 0, 50, 50);
ellipse = game.add.graphics(400, 300);
ellipse.lineStyle(2, 0x00FFFF, 1);
ellipse.drawEllipse(-15, -25, 30, 50);
}
function update() {
var squareSpeed = Math.PI / 2;
var ellipseSpeed = -2 * Math.PI;
var elapsed = game.time.elapsed / 1000; // Temps écoulé en secondes
square.rotation += elapsed * squareSpeed;
ellipse.rotation += elapsed * ellipseSpeed;
}
Voirlerésultat
HOMOTHÉTIES(1/2)
Soitkunnombreréelpositif.
LafonctionHk:ℝ2 → ℝ2définiepar
Hk(x, y) = ⎡⎜⎣k0 0k⎤⎜⎦⎡⎜⎣x y⎤⎜⎦
estappeléehomothétiedefacteurk(centréeàl'origine).
L'inversedeHkestl'homothétiedefacteur1 ⁄ k.
HOMOTHÉTIES(2/2)
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
var circle;
var startTime;
function preload() {
}
function create() {
circle = game.add.graphics(game.width / 2, game.height / 2);
startTime = game.time.time;
}
function update() {
var amplitude = 200;
var period = 3;
var t = (game.time.time - startTime) / 1000; // Temps écoulé depuis le début
circle.clear();
circle.lineStyle(2, 0xFF0000, 1);
circle.drawCircle(0, 0, 200 + amplitude * Math.sin(2 * Math.PI * t / period));
}
Voirlerésultat
RÉFLEXION
Soitv = (a, b)unvecteur.
LafonctionSv:ℝ2 → ℝ2définiepar
Sv(x, y) = ⎡⎜⎣a2 − b22ab 2abb2 − a2⎤⎜⎦⎡⎜⎣x y
⎤⎜⎦
estappeléeréflexionparrapportauvecteurv(baséàl'origine).
L'inversedeSvestSv(onappellecetypedefonctionune
involution).
AUTRESOPÉRATIONSGÉOMÉTRIQUES
D'autrestransformationslinéairessonttrèspratiquesen
infographie:
Lesprojections;
Lestransvections.
LESCOULEURS
Sionmanipulecorrectementlescoordonnéesdecouleur,onpeut
obtenirdesanimationsintéressantes:
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
var circle;
var startTime;
function preload() {
}
function create() {
circle = game.add.graphics(game.width / 2, game.height / 2);
startTime = game.time.time;
}
function update() {
var amplitude = 100;
var period = 5;
var t = (game.time.time - startTime) / 1000; // Temps écoulé depuis le début
var red = 255 - amplitude * (1 + Math.sin(2 * Math.PI * t / period));
var blue = 255 - amplitude * (1 + Math.cos(2 * Math.PI * t / period));
var color = red << 16 | blue;
circle.clear();
circle.lineStyle(10, color, 1);
circle.drawCircle(0, 0, 200);
}
Voirlerésultat
LATRANSPARENCE(1/3)
Latransparence(alphablending)estégalementunenotiontrès
utiliséeeninfographie;
Typiquement,ils'agitd'unnombreentre0et1,maisonvoitaussi
entre0et255,enparticulierlorsqu'ilestintégréausystèmeRGB
OnparlealorsdesystèmedecouleursRGBA
R:red
G:green
B:blue
A:alpha
Lorsqueα = 0,alorsl'objetesttransparent;
Sionaplutôtα = 1(ouα = 255),alorsl'objetestopaque;
Ellepermetdecréerfacilementdifférentseffets.
LATRANSPARENCE(2/3)
Onpeutsuperposerdesimages:
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
var circle1, circle2;
function preload() {
}
function create() {
var color1 = 0xFF0000;
var color2 = 0x00FF00;
circle1 = game.add.graphics();
circle1.lineStyle(0, color1);
circle1.beginFill(color1);
circle1.drawCircle(300, 300, 300);
circle1.endFill();
circle2 = game.add.graphics();
circle2.lineStyle(0, color2);
circle2.beginFill(color2, 0.5); // Transparence de 0.5
circle2.drawCircle(500, 300, 300);
circle2.endFill();
}
function update() {
}
Voirlerésultat
LATRANSPARENCE(3/3)
Onpeutfaireapparaître/disparaître(fadein/fadeout)desobjets:
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
var circle;
var startTime;
function preload() {
}
function create() {
circle = game.add.graphics(game.width / 2, game.height / 2);
circle.lineStyle(0, 0x00FFFF);
circle.beginFill(0x00FFFF);
circle.drawCircle(0, 0, 300);
circle.endFill();
startTime = game.time.time;
}
function update() {
var period = 3;
var t = (game.time.time - startTime) / 1000;
circle.alpha = 0.5 * (1 + Math.sin(2 * Math.PI * t / period));
}
Voirlerésultat
LEJEUSUPERHEXAGON
SCÈNEDEBASE(1/2)
Dansunpremiertemps,ondessineunhexagone;
OnpeututiliserlafonctiondrawRegularPolygondel'exerciceprésenté
plustôt;
Nousallonségalementtracerleszonesinduitesparlessixcôtésde
l'hexagone;
Ceszonespeuventêtredessinéescommedestrapèzes(c'est-à-dire
destrianglestronqués)quidépassentlecanvas;
Afinderendrelecodeunpeupluspropre,nousallonscréerun
objetScenequireprésentetouslesélémentsdelascène.
SCÈNEDEBASE(2/2)
// ---------------- //
// Global variables //
// ---------------- //
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
game.cx = game.width / 2;
game.cy = game.height / 2;
var scene;
// ----- //
// Utils //
// ----- //
Phaser.Graphics.prototype.drawRegularPolygon = function (x, y, n, r, a) {
this.moveTo(x + r * Math.cos(a), y + r * Math.sin(a));
for (i = 1; i <= n; i++) {
var a2 = i * 2 * Math.PI / n;
var x2 = x + r * Math.cos(a + a2);
var y2 = y + r * Math.sin(a + a2);
this.lineTo(x2, y2);
}
}
// ------------ //
// Scene object //
// ------------ //
function Scene (graphics) {
this.graphics = graphics;
}
Voirlerésultat
Scene.prototype.drawZone = function (angle, color) {
this.graphics.lineStyle(0, color, 1);
this.graphics.moveTo(50 * Math.cos(angle),
50 * Math.sin(angle));
PARAMÉTRERLERAYON(1/2)
Notrescènen'estévidemmentpasgénériqueactuellement;
Nousallonsmaintenantdonnerlapossibilitédeparamétrerlerayon
del'hexagonepourqu'ilpuisse"vibrer";
Ilestpossibled'utiliserunefonctionpériodique(parexemplela
fonctionsin)aussicomplexequel'onveut;
Nousallonscependantnouscontenterd'unefonctionaléatoire
uniforme.
PARAMÉTRERLERAYON(2/2)
// ---------------- //
// Global variables //
// ---------------- //
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
game.cx = game.width / 2;
game.cy = game.height / 2;
var scene;
// ----- //
// Utils //
// ----- //
Phaser.Graphics.prototype.drawRegularPolygon = function (x, y, n, r, a) {
this.moveTo(x + r * Math.cos(a), y + r * Math.sin(a));
for (i = 1; i <= n; i++) {
var a2 = i * 2 * Math.PI / n;
var x2 = x + r * Math.cos(a + a2);
var y2 = y + r * Math.sin(a + a2);
this.lineTo(x2, y2);
}
}
// ------------ //
// Scene object //
// ------------ //
function Scene (graphics, radius) { // NOUVEAU
this.graphics = graphics;
this.radius = radius; // NOUVEAU
}
Voirlerésultat
Scene.prototype.drawZone = function (angle, color) {
this.graphics.lineStyle(0, color, 1);
this.graphics.moveTo(this.radius * Math.cos(angle), // NOUVEAU
PARAMÉTRERL'ANGLE
Àpartirducode,ilestfaciledepermettreàl'angledelascènede
varierenfonctiondutemps:
// ---------------- //
// Global variables //
// ---------------- //
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
game.cx = game.width / 2;
game.cy = game.height / 2;
var scene;
// ----- //
// Utils //
// ----- //
Phaser.Graphics.prototype.drawRegularPolygon = function (x, y, n, r, a) {
this.moveTo(x + r * Math.cos(a), y + r * Math.sin(a));
for (i = 1; i <= n; i++) {
var a2 = i * 2 * Math.PI / n;
var x2 = x + r * Math.cos(a + a2);
var y2 = y + r * Math.sin(a + a2);
this.lineTo(x2, y2);
}
}
// ------------ //
// Scene object //
// ------------ //
function Scene (graphics, angle, radius) { // NOUVEAU
this.graphics = graphics;
this.angle = angle; // NOUVEAU
Voirlerésultat
PARAMÉTRERLESCOULEURS(1/2)
Laparamétrisationdescouleursestrelativementsimpleaussi;
OnpeututiliserlesystèmeHSVpourobtenirdestonsplusfoncés
pourunecouleurdonnée:
Phaser.Color.updateValue = function(color, valueChange) {
var rgb = Phaser.Color.getRGB(color);
var hsv = Phaser.Color.RGBtoHSV(rgb.r, rgb.g, rgb.b);
hsv.v += valueChange;
rgb = Phaser.Color.HSVtoRGB(hsv.h, hsv.s, hsv.v);
console.log(Phaser.Color.toRGBA(rgb.r, rgb.g, rgb.b));
return rgb.r << 16 | rgb.g << 8 | rgb.b;
}
PARAMÉTRERLESCOULEURS(2/2)
// ---------------- //
// Global variables //
// ---------------- //
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
game.cx = game.width / 2;
game.cy = game.height / 2;
var scene;
var colorChangeTime;
// ----- //
// Utils //
// ----- //
Phaser.Graphics.prototype.drawRegularPolygon = function (x, y, n, r, a) {
this.moveTo(x + r * Math.cos(a), y + r * Math.sin(a));
for (i = 1; i <= n; i++) {
var a2 = i * 2 * Math.PI / n;
var x2 = x + r * Math.cos(a + a2);
var y2 = y + r * Math.sin(a + a2);
this.lineTo(x2, y2);
}
}
Voirlerésultat
Phaser.Color.updateValue = function(color, valueChange) {
var rgb = Phaser.Color.getRGB(color);
var hsv = Phaser.Color.RGBtoHSV(rgb.r, rgb.g, rgb.b);
hsv.v += valueChange;
rgb = Phaser.Color.HSVtoRGB(hsv.h, hsv.s, hsv.v);
console.log(Phaser.Color.toRGBA(rgb.r, rgb.g, rgb.b));
return rgb.r << 16 | rgb.g << 8 | rgb.b;
}
// ------------ //
SPRITES
Laressourcedebasegraphiqueeninfographie2Destappelée
sprite;
Ilconvientdedistinguerlesdifférentstypesdesprites:
Cellesquin'influencentpaslaphysiquedujeu(imagedefond);
Cellesquiinfluencentlaphysique,maissonimmobiles(mur,
plafond,obstacle,etc.);
Cellesquisontmobiles(personnage,animal,etc.).
IMAGESDEFOND(1/3)
EnPhaser,lechargementd'uneimagedefonds'effectueendeux
étapes:
Onprécharge(preload)l'image;
Onajoutel'imageentantquesprite;
Remarque:ilestpossible(etmêmefréquent)queplusieurssprites
soientcrééesàpartird'unemêmeimage.
IMAGESDEFOND(2/3)
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
function preload() {
game.load.image('background', 'assets/background.png');
}
function create() {
game.add.sprite(0, 0, 'background');
}
function update() {
}
Voirlerésultat
IMAGESDEFOND(3/3)
Noteimportante:
Pourdesraisonsdesécurité,lechargementd'imagesne
fonctionnerapassilefichierindex.htmlestludefaçonlocale;
Ceciestdûaufaitqu'onnepeutpaspermettreàJavaScript
d'accéderàn'importequelfichiersurnotremachine;
Danscecas,ilfautcréerunserveurlocal;
Lapagesuivantecontientplusd'informationàcesujet.
SPRITESIMMOBILES(1/2)
Souvent,ilestimportantquecertainsobjetsd'unescènesoient
considérésdefaçondistincte;
Danslecasd'objetsimmobiles,lesdeuxraisonssuivantessontles
plusfréquentes:
Onsouhaitequecetobjetapparaisseenplusieursexemplaires;
Onsouhaitequ'ilsoitconsidérécommeunobstacle;
SPRITESIMMOBILES(2/2)
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
function preload() {
game.load.image('background', 'assets/background.png');
game.load.image('rock', 'assets/rock.png');
}
function create() {
game.add.sprite(0, 0, 'background');
game.add.sprite(400,500, 'rock');
game.add.sprite(400,450, 'rock');
game.add.sprite(400,400, 'rock');
}
function update() {
}
Voirlerésultat
SPRITESMOBILES
Enplusd'avoirdesobjetsprovenantd'uneimage,ilestévidemment
intéressantqueceux-cipuissentsedéplacer:
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
function preload() {
game.load.image('background', 'assets/background.png');
game.load.image('rock', 'assets/rock.png');
game.load.image('cloud', 'assets/cloud.png');
}
function create() {
game.add.sprite(0, 0, 'background');
game.add.sprite(400,500, 'rock');
game.add.sprite(400,450, 'rock');
game.add.sprite(400,400, 'rock');
var cloud = game.add.sprite(0,200, 'cloud');
game.physics.startSystem(Phaser.Physics.ARCADE);
game.physics.arcade.enable(cloud);
// On démarre le système physique
cloud.enableBody = true;
// Le nuage est géré dans le système
cloud.body.velocity.x = 200;
// La vitesse horizontale du nuage
cloud.body.bounce.x = 1.0;
// La collision est parfaitement élastique
cloud.body.collideWorldBounds = true; // Collision avec les bords du canvas
}
function update() {
}
Voirlerésultat
SPRITESANIMÉES(1/3)
Lorsqu'unobjetévoluedansunescène,onobtientdeseffetsplus
réalistessisonapparenceévolueégalement;
Onpeutbienentenduutiliserdestransformationsgéométriqueset
décomposerl'imageenplusieursimagesmobiles;
Cependant,cetteapprochen'estpaspratique,carilfautdécrireen
détailstouteslestransformations,cequidevientrapidementtrès
lourd;
Unefaçonsimpleetpopulaireconsisteàutiliserdessprites
animées;
Autrementdit,ondonneunelisted'imagesplutôtqu'uneseule
image.
SPRITESANIMÉES(2/3)
Unelisted'imageestappeléenanglaisspritesheet:
Unespritesheetd'unoiseau(1200 × 1256)
Ilestpréférablequechaquepetiteimagesoitdemêmetaille
Dansl'exemple,chaquespriteestdedimension240 × 314.
SPRITESANIMÉES(3/3)
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'game',
{preload: preload, create: create, update: update});
var bird;
function preload() {
game.load.image('background', 'assets/background.png');
game.load.image('rock', 'assets/rock.png');
game.load.image('cloud', 'assets/cloud.png');
game.load.spritesheet('bird', 'assets/bird.png', 240, 314);
}
function create() {
game.add.sprite(0, 0, 'background');
game.add.sprite(400,500, 'rock');
game.add.sprite(400,450, 'rock');
game.add.sprite(400,400, 'rock');
var cloud = game.add.sprite(0,200, 'cloud');
bird = game.add.sprite(400, 200, 'bird');
bird.scale.setTo(0.4, 0.4);
game.physics.startSystem(Phaser.Physics.ARCADE);
game.physics.arcade.enable(cloud);
game.physics.arcade.enable(bird);
cloud.enableBody = true;
cloud.body.velocity.x = 200;
cloud.body.bounce.x = 1.0;
cloud.body.collideWorldBounds = true;
bird.animations.add('fly');
bird.animations.play('fly', 30, true); // 50 IPS, boucle infinie = vrai
bird.enableBody = true;
bird.body.velocity.x = -100;
}
Voirlerésultat
function update() {
PHYSIQUE
Danslaplupartdesapplicationsgraphiquesavecanimations,on
utiliselesprincipesdebasedelaphysiquemécanique;
Ceux-cisontgérésparcequ'onappelleunmoteurphysique;
Lesmoteursphysiquesgèrentlestroisvariablesdebased'un
systèmemécanique:
Laposition;
Lavitesse;
L'accélération.
Ilspeuventégalementprendreencharged'autresphénomènes:
lescollisions;
lesrebonds;
lagravité;
lafriction;
etc.
LAPHYSIQUEDANSPHASER
Phaserproposetroismoteursphysiquespardéfaut:
ArcadePhysics:pourgérerlescollisionsavoirboîteenglobantes
rectangulaires(sansrotation);
NinjaPhysics:pourgérerlestuilespluscomplexes,lespentes,les
rotations,etc.
P2:pourmodéliserdesphénomènesphysiquespluscomplexes,
aveccontraintes,ressorts,polygones,etc.
Ilexisteégalementuneinterface(malheureusementnilibreni
gratuite)avecBox2D,quioffrebeaucoupplusdefonctionnalités
(malgréqueBox2Dsoitopen-source).
LEMOUVEMENT
Lessixquantitésfondamentalesdumouvementd'uncorpssont:
Laposition;
Lavitesse;
L'accélération.
L'angle;
Lavitesseangulaire;
L'accélérationangulaire.
DansilssontprisenchargeparlesystèmephysiqueArcade.
LAGRAVITÉETLESCOLLISIONS
DansPhaser,lagestiondelagravitéesttrèssimple;
Ilsuffitdemodifierlesattributs.body.gravity.xet.body.gravity.yde
l'objet;
Enparticulier,lagravitépeutêtredifférentesurchacundesobjets;
Aussi,enplusdedétecterlescollisions,onpeutfacilementindiquer
letypedecollision(élastique,partiellementélastiqueounon);
Ilsuffitdemodifierlesattributs.body.bounce.xet.body.bounce.y(où1
indiqueunecollisionélastiqueet0unecollisionnonélastique);
Voirl'exemplesurPhaser
AUTRESPHÉNOMÈNESPLUS
COMPLEXES
Phaseroffreplusieursexemplessurdesphénomènespluscomplexes:
Accélérationverslejoueur
Avecdesressorts
Mouvementd'unechaîne

Documents pareils