ActionScript 3
ゲーム開発の基礎
サーチ…
前書き
アイソメトリックキャラクターのアニメーション+動き
①この例で使用している概念:
クラス | 使用法 |
---|---|
URLRequest + Loader + Event | 外部パスから地図(スプライト)をロードしています。 |
BitmapData + Sprite + beginBitmapFill + Matrix + stageWidth & stageHeight | ロードされたリソースをビットマップデータに描画し、 タイル化されたビットマップを使用して、変換で描画する。 |
MovieClip + scrollRect + Bitmap + Rectangle | キャラクタームービークリップの作成とクリップ Bitmapをタイムラインとして使用します。 |
KeyboardEvent | ユーザ入力を検出する |
Event.EXIT_FRAME | ゲームループ機能を実装する |
②リソース:( このリソースを商用目的で使用することは許可されていません)
③コードとコメント:
注: FPS 15このチュートリアルでは推奨されていますが、もっと必要な場合は、コードの一部を自分で修正する必要があります。
まず外部URLからリソースをダウンロードする必要があります。
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はcaledされますsrc_grass_tile_url
ロードされ、使用する準備ができています。 setGroundを実装してリソースを取得し、それをゲームの背景として描画する
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));
}
私たちが地面を尽くした後、そのコードは文字を実装する時間です。文字には同じ方法で読み込まなければならないリソースも含まれています。 setGround
の終わりにsetGround
完全なコールバックであるsetCharacter
向かいます。
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);
}
今すぐキャラクターはコントロールする準備ができています。表示されたウェルと制御の準備ができています。キーボードイベントが添付され、次のコードのように矢印キーを聞く:
// 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
は4つのブール変数を各矢印キーごとに格納しkeys:Object
移動はゲームの更新(ループ)関数内で行う必要があります。そのためキーボードの統計情報を渡す必要があります。 ループ機能を実装できます。
// 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;
}
speedはヘルパー定数で、文字の速度を定義します。上のコードは、この優先度が低い単純な8方向の動きを示します: Up > Down
Left > Right
。上下矢印が同じ時間に押されている場合ので、文字だけで(凍結しない) まで移動します。
よくやった!!! 1つのステップしか残っていない、アニメーション、このチュートリアルの最も重要な部分
本当にアニメーションは何ですか?少なくとも1つのフレームを含むキーフレームのセット
キーフレームの名前を含むkeyframsオブジェクトを作成できます
このキーフレームの開始フレームと終了フレームに関するいくつかのデータ
アイソメトリックゲームでは、各キーフレームに8つの方向が含まれていることに注意してください (フリップを使用して5に減らすことができます)
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]}
};
残りのフレームを無視する必要があります。この例では、アイドル状態とアニメーションのみを提供しています
たとえば、右向きのアイドルアニメーションの開始フレームは次のようになります。<keyframs.idle.right [0]>
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;
}
上記の関数のコメントを読んでください。しかし、この関数の主な仕事は文字の MovieClip
中のスプライトシートの Bitmap
を動かしています。
すべての更新はLoop関数内で行う必要があることがわかっているので、この関数を関連するキーフレームを持つLoopから呼び出します。これは更新されたループ関数です:
// 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;
}
コメントを読んだだけで、キーボードの統計情報から真のキーフレームを検出するだけです。アイドルアニメーションを検出するためにも同じことを行います。アイドルアニメーションでは、どの方向文字がオンであるかを検出するためのキー入力がないため、simleヘルパー変数はキーボードの以前の状態(last_keyStat)を保存するのに便利です。
欠けているアニメーションをシミュレートするために使用される別のヘルパー関数である新しい関数flip
もあります(left + up_left + down_left)。また、この関数は以下にコメントされているいくつかの修正を行います:
// 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
}
}
}
私たちの仕事はここで終わります。このチュートリアルをより分かりやすくするエディタのおかげで特別な感謝です。また、このチュートリアルのライブデモと完全なコードの外部リンクがあります。