ActionScript 3
Fundamentos de desarrollo de juegos
Buscar..
Introducción
Carácter isométrico animando + movimiento.
① los conceptos utilizados en este ejemplo:
Clase | Uso |
---|---|
URLRequest + Loader + Event | Cargando mapa atlas (sprite) desde la ruta externa. |
BitmapData + Sprite + beginBitmapFill + Matrix + stageWidth & stageHeight | dibujo de recursos cargados a bitmapdata, Utilizando bitmaps en mosaico, dibujando con transformación. |
MovieClip + scrollRect + Bitmap + Rectangle | Creando y recortando movieclip personaje utilizando Bitmap como línea de tiempo. |
KeyboardEvent | detectar entradas de usuario |
Event.EXIT_FRAME | implementando la función de bucle del juego |
② Recursos: (no hay permiso para utilizar estos recursos con fines comerciales)
③ Código y Comentarios:
Nota: FPS 15 Se utiliza para este tutorial, se recomienda, pero si necesita más, debe modificar parte del código por su cuenta.
Al principio debemos descargar nuestros recursos desde urls externos.
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 se llamará una vez que src_grass_tile_url
esté cargado y listo para su uso. en la aplicación de seguimiento de setGround para obtener recursos y dibujarlos como fondo del juego
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));
}
El código está muy bien comentado, después de que hayamos terminado con el terreno, es hora de implementar el carácter. El carácter también contiene un recurso que debe cargarse de la misma manera. así que al final de setGround
nos dirigimos al setCharacter
que es otra llamada completa.
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);
}
Ahora el personaje está listo para controlar. Se muestra bien y listo para controlar. así se adjuntan los eventos del teclado y se escuchan las teclas de flecha como el siguiente código:
// 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
almacena la variable booleana de 4 por cada tecla de flecha, el proceso de movimiento debe realizarse dentro de la función de actualización (Loop) del juego, por lo que debemos pasarle las estadísticas del teclado. Permite implementar la función Loop .
// 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 velocidad es una constante auxiliar, define la velocidad del carácter. el código anterior presenta un movimiento simple de 8 direcciones con esta prioridad baja: Up > Down
Left > Right
. por lo tanto, si se presionan las flechas hacia arriba y hacia abajo al mismo tiempo, el personaje solo se mueve hacia arriba (no se congela).
¡¡¡bien hecho!!! Solo queda un paso, animación, la parte más importante de este tutorial.
¿Qué es realmente la animación? un conjunto de fotogramas clave que contiene al menos un fotograma
permite crear nuestro objeto de fotogramas clave, que contiene el nombre de los fotogramas clave
y también algunos datos sobre el inicio y el final del cuadro de este fotograma clave
Tenga en cuenta que en los juegos isométricos, cada fotograma clave contiene 8 direcciones (se puede reducir a 5 con el uso de voltear)
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]}
};
debemos ignorar los cuadros restantes, este ejemplo solo proporciona animación inactiva y de ejecución
por ejemplo, el cuadro de inicio de la animación inactiva con dirección a la derecha, es: <keyframs.idle.right [0]>
ahora permite implementar la función animadora
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;
}
lea los comentarios de la función anterior, sin embargo, el trabajo principal de esta función es mover sprite_sheet Bitmap
dentro del carácter MovieClip
.
Sabemos que cada actualización debe realizarse dentro de la función Loop, por lo que invocaremos esta función desde Loop con los fotogramas clave relacionados. Esta es la función Loop actualizada:
// 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;
}
Lea los comentarios, simplemente detectamos un verdadero fotograma clave a través de las estadísticas del teclado. Luego, haga lo mismo para detectar la animación inactiva. para las animaciones inactivas no tenemos ninguna entrada clave que podamos usar para detectar en qué dirección está el carácter, por lo que una variable de ayuda sencilla podría ser útil para almacenar el estado anterior del teclado (last_keyStat).
también hay una nueva función flip
que es otra función auxiliar que se usa para simular animaciones faltantes (left + up_left + down_left) también esta función hace algunas correcciones que se comenta a continuación:
// 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
}
}
}
Nuestro trabajo está terminando aquí. Un agradecimiento especial para los editores que hacen que este tutorial sea más difícil de guardar. También aquí hay una demostración en vivo de este tutorial más un enlace externo de código completo.