ActionScript 3
Les bases du développement de jeux
Recherche…
Introduction
personnage isométrique animant + mouvement
① les concepts utilisés dans cet exemple:
Classe | Usage |
---|---|
URLRequest + Loader + Event | Chargement de la carte de l'atlas (sprite) à partir du chemin externe. |
BitmapData + Sprite + beginBitmapFill + Matrix + stageWidth & stageHeight | dessiner des ressources chargées sur bitmapdata, utiliser des bitmaps en mosaïque, dessiner avec transformation. |
MovieClip + scrollRect + Bitmap + Rectangle | création et cliping MovieClip en utilisant Bitmap comme scénario. |
KeyboardEvent | détecter les entrées utilisateur |
Event.EXIT_FRAME | implémenter la fonction de boucle de jeu |
② Ressources: (pas de permission pour utiliser ces ressources à des fins commerciales)
③ Code et commentaires:
Remarque: FPS 15 Utilisé pour ce didacticiel, il est recommandé, mais si nécessaire, vous devez modifier une partie du code par vous-même.
dans un premier temps, nous devons télécharger nos ressources à partir des URL externes.
const src_grass_tile_url:String = "https://i.stack.imgur.com/sjJFS.png";
const src_character_atlas_url:String = "https://i.stack.imgur.com/B7ztZ.png";
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, setGround);
loader.load(new URLRequest(src_grass_tile_url));
setGround sera appelé une fois que src_grass_tile_url
sera chargé et prêt à être utilisé. en mettant en œuvre setGround pour obtenir des ressources et dessiner comme arrière-plan du jeu
function setGround(e:Event):void {
/* drawing ground */
/* loader is a displayObject, so we can simply draw it into the bitmap data*/
/* create an instance of Bitmapdata with same width and height as our window*/
/* (also set transparent to false because grass image, does not contains any transparent pixel) */
var grass_bmd:BitmapData = new BitmapData(loader.width, loader.height, false, 0x0);
/* time to draw */
grass_bmd.draw(loader); // drawing loader into the bitmapData
/* now we have to draw a tiled version of grass_bmd inside a displayObject Sprite to displaying
BitmapData on stage */
var grass_sprite:Sprite = new Sprite();
// for drawing a bitmap inside sprite, we must use <beginBitmapFill> with graphic property of the sprite
// then draw a full size rectangle with that Fill-Data
// there is a repeat mode argument with true default value so we dont set it true again.
// use a matrix for scalling grass Image during draw to be more cute!
var mx:Matrix = new Matrix();
mx.scale(2, 2);
grass_sprite.graphics.beginBitmapFill(grass_bmd, mx);
grass_sprite.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
// now add sprite to displayobjectcontainer to be displayed
stage.addChild(grass_sprite);
// well done, ground is ready, now we must initialize our character
// first, load its data, i just re-use my loader for loading new image, but with another complete handler (setCharacter)
// so remove existing handler, then add new one
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, setGround);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, setCharacter);
loader.load(new URLRequest(src_character_atlas_url));
}
le code est très bien commenté, après avoir fait avec le sol, son temps pour mettre en œuvre le caractère. character contient également une ressource qui doit être chargée de la même manière. Donc, à la fin de setGround
nous nous dirigeons vers setCharacter
qui est un autre rappel complet.
function setCharacter(e:Event):void {
// let assuming that what is really character!
// a set of images inside a single image!
// that images are frames of our character (also provides movement for different directions)
// first load this
var character_bmd:BitmapData = new BitmapData(loader.width, loader.height, true, 0x0); // note character is transparent
character_bmd.draw(loader);
// take a look at sprite sheet, how many frames you see?
// 42 frames, so we can get width of a single frame
const frame_width:uint = character_bmd.width / 42; // 41 pixels
// as i show you above, to displaying a BitmapData, we have to draw it using a DisplayObject,
// another way is creating a Bitmap and setting its bitmapdata
var character_bmp:Bitmap = new Bitmap(character_bmd);
// but its not enough yet, a movieClip is necessary to cliping and animating this bitmap (as a child of itself)
var character_mc:MovieClip = new MovieClip();
character_mc.addChild(character_bmp);
character_bmp.name = "sprite_sheet"; // setting a name to character_bmp, for future accessing
character_mc.scrollRect = new Rectangle(0, 0, frame_width, character_bmd.height); // cliping movieclip, to dusplaying only one frame
character_mc.name = "character"; // setting a name to character_mc, for future accessing
stage.addChild(character_mc); // adding it to stage.
// well done, we have a character, but its static yet! 2 steps remaining. 1 controlling 2 animating
// at first setting a control handler for moving character in 8 directions.
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, keyUp);
}
maintenant le personnage est prêt à contrôler. son affiché bien et prêt à contrôler. Donc, les événements clavier attachés et l'écoute des touches fléchées comme suit:
// we storing key stats inside <keys> Object
var keys:Object = {u:false, d:false, l:false, r:false};
function keyDown(e:KeyboardEvent):void {
switch (e.keyCode) {
case 38: //up
keys.u = true;
break;
case 40: //down
keys.d = true;
break;
case 37: //left
keys.l = true;
break;
case 39: //right
keys.r = true;
break;
}
}
function keyUp(e:KeyboardEvent):void {
switch (e.keyCode) {
case 38: //up
keys.u = false;
break;
case 40: //down
keys.d = false;
break;
case 37: //left
keys.l = false;
break;
case 39: //right
keys.r = false;
break;
}
}
keys:Object
stocke la variable booléenne de 4 pour chaque touche fléchée, le processus de déplacement doit être effectué à l'intérieur de la fonction update (Loop) du jeu, nous devons donc lui transmettre des statistiques de clavier. permet d'implémenter la fonction de boucle .
// initialize game Loop function for updating game objects
addEventListener(Event.EXIT_FRAME, loop);
// speed of character movement
const speed:Number = 5;
// this function will be called on each frame, with same rate as your project fps
function loop(e:Event):void {
if (keys.u) stage.getChildByName("character").y -= speed;
else if (keys.d) stage.getChildByName("character").y += speed;
if (keys.l) stage.getChildByName("character").x -= speed;
else if (keys.r) stage.getChildByName("character").x += speed;
}
la vitesse est une constante d'assistance, définit la vitesse du caractère. Le code ci-dessus présente un mouvement simple à 8 directions avec cette priorité basse: Up > Down
Left > Right
. ainsi, si vous appuyez simultanément sur les flèches haut et bas, le caractère ne se déplace que vers le haut (pas de gel).
bien joué!!! il ne reste qu'une étape, l'animation, la partie la plus importante de ce tutoriel
Qu'est-ce que l'animation? un ensemble d'images clés contenant au moins une image
permet de créer notre objet keyframs, qui contient le nom des images clés
et aussi quelques données sur le début et la fin de l'image de cette image clé
Notez que dans les jeux isométriques, chaque image clé contient 8 directions (peut être réduite à 5 avec l'utilisation du retournement)
var keyframs:Object = {
idle: {up:[0,0], up_right:[1,1], right:[2,2], down_right:[3,3], down:[4,4]}, // [2,2] means start frame is 2 and end frame is 2
run: {up:[5,10], up_right:[11,16], right:[17,22], down_right:[23,28], down:[29,34]}
};
nous devrions ignorer les images restantes, cet exemple ne fournit que l'animation inactive et exécutée
Par exemple, la première image de l'animation inactive avec la direction droite est: <keyframs.idle.right [0]>
permet maintenant la mise en œuvre de la fonction Animator
var current_frame:uint;
function animate(keyframe:Array):void {
// how it works
// just called with a keyframe with direction (each frame),
// if keyframe is what is already playing, its just moved to next frame and got updated (or begning frame for loop)
// other wise, just moved to begining frame of new keyframe
if (current_frame >= keyframe[0] && current_frame <= keyframe[1]) { // check if in bound
current_frame++;
if (current_frame > keyframe[1]) // play back if reached
current_frame = keyframe[0];
} else {
current_frame = keyframe[0]; // start new keyframe from begining
}
// moving Bitmap inside character MovieClip
var character:MovieClip = stage.getChildByName("character") as MovieClip;
var sprite_sheet:Bitmap = character.getChildByName("sprite_sheet") as Bitmap;
sprite_sheet.x = -1 * current_frame * character.width;
}
lire les commentaires de la fonction ci-dessus, cependant le travail principal de cette fonction déplace le Bitmap
sprite_sheet à l' intérieur du caractère MovieClip
.
Nous savons que chaque mise à jour doit être effectuée à l'intérieur de la fonction Loop. Nous allons donc appeler cette fonction à partir de Loop avec les images clés associées. c'est la fonction de boucle mise à jour:
// speed of character movement
const speed:Number = 8;
var last_keyStat:Object = {u:false, d:false, l:false, r:false}; // used to getting a backup of previous keyboard stat for detecting correct idle direction
// this function will be called on each frame, with same rate as your project fps
function loop(e:Event):void {
if (keys.u) stage.getChildByName("character").y -= speed;
else if (keys.d) stage.getChildByName("character").y += speed;
if (keys.l) stage.getChildByName("character").x -= speed;
else if (keys.r) stage.getChildByName("character").x += speed;
// animation detection
if (keys.u && keys.l) { animate(keyframs.run.up_right); flip(true); }
else if (keys.u && keys.r) { animate(keyframs.run.up_right); flip(false); }
else if (keys.d && keys.l) { animate(keyframs.run.down_right); flip(true); }
else if (keys.d && keys.r) { animate(keyframs.run.down_right); flip(false); }
else if (keys.u) { animate(keyframs.run.up); flip(false); }
else if (keys.d) { animate(keyframs.run.down); flip(false); }
else if (keys.l) { animate(keyframs.run.right); flip(true); }
else if (keys.r) { animate(keyframs.run.right); flip(false); }
else {
// if character dont move, so play idle animation
// what is the best practice to detecting idle direction?
// my suggestion is to sotring previous keyboard stats to determining which idle direction is correct
// do any better thing if possible (absolutely is possible)
// i just simply copy it from above, and replaced (keys) with (last_keyStat) and (run) with (idle)
if (last_keyStat.u && last_keyStat.l) { animate(keyframs.idle.up_right); flip(true); }
else if (last_keyStat.u && last_keyStat.r) { animate(keyframs.idle.up_right); flip(false); }
else if (last_keyStat.d && last_keyStat.l) { animate(keyframs.idle.down_right); flip(true); }
else if (last_keyStat.d && last_keyStat.r) { animate(keyframs.idle.down_right); flip(false); }
else if (last_keyStat.u) { animate(keyframs.idle.up); flip(false); }
else if (last_keyStat.d) { animate(keyframs.idle.down); flip(false); }
else if (last_keyStat.l) { animate(keyframs.idle.right); flip(true); }
else if (last_keyStat.r) { animate(keyframs.idle.right); flip(false); }
}
// update last_keyStat backup
last_keyStat.u = keys.u;
last_keyStat.d = keys.d;
last_keyStat.l = keys.l;
last_keyStat.r = keys.r;
}
Lisez les commentaires, nous détectons simplement une véritable image-clé grâce aux statistiques du clavier. alors aussi faire la même chose pour détecter une animation inactive. pour les animations inactives, nous n'avons aucune entrée clé à utiliser pour détecter quel caractère de direction est activé, de sorte qu'une variable d'assistance simple pourrait être utile pour stocker l'état précédent du clavier (last_keyStat).
il y a aussi une nouvelle fonction flip
qui est une autre fonction d'aide utilisée pour simuler les animations manquantes (à gauche + up_left + down_left) également cette fonction fait quelques corrections qui sont commentées ci-dessous:
// usage of flip function is because of Movieclip registration point, its a fix
// as the registration point of MovieClip is not placed in center, when flipping animation (for non existing directions inside spritesheet)
// character location changes with an unwanted value equal its width, so we have to prevent this and push it back or forward during flip
function flip(left:Boolean):void {
var character:MovieClip = stage.getChildByName("character") as MovieClip;
if (left) {
if (character.scaleX != -1) {
character.scaleX = -1;
character.x += character.width; // comment this line to see what happen without this fix
}
} else {
if (character.scaleX != 1) {
character.scaleX = 1;
character.x -= character.width; // comment this line to see what happen without this fix
}
}
}
notre travail se termine ici. Un remerciement spécial pour l'éditeur qui rend ce tutoriel plus facile à contrôler. Aussi, voici une démonstration en direct de ce tutoriel et un lien externe de code complet.