Téma: Flash alapú biliárd-program tervezése és fejlesztése ActionScript 3.0 környezetben
Eredetileg szakdolgozatként készült egy nem-programozói diplomához, ami célra meg is felelt. A szakdolgozat egésze letölthető dokumentummal együtt.
Készítette: Vojsánszky Szabolcs (2005)
A készítés során főbb szempontok a következők voltak
Felhasznált eszközök
A végleges megoldás különösebb tervmintákat nélkülöző, erős jóindulattal XP módszereket követ. Ennek talán részben köszönhető a párhuzamosság hiánya AS3-ban, ami előnyben részesíti a szuperosztályok használatát.
Példák - a leginkább újrahasznosítható részek
KeyObject.as:
import flash.display.MovieClip; import flash.display.Stage; import flash.events.Event; import flash.events.KeyboardEvent; import flash.ui.Keyboard; import flash.utils.Proxy; public class keyObject extends MovieClip { private var keysDown:Array = new Array(); public function keyObject() { addEventListener(Event.ADDED_TO_STAGE, onStage, false, 0, true); } private function onStage(event:Event) { stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyPress, false, 0, true); stage.addEventListener(KeyboardEvent.KEY_UP, onKeyRelease, false, 0, true); } private function onKeyPress(key:KeyboardEvent) { keysDown[key.keyCode] = true; } private function onKeyRelease(key:KeyboardEvent) { keysDown[key.keyCode] = null; clearKeys(); } private function clearKeys() { var last:Number = keysDown.length; while (keysDown[last] != true && last >= 0) last --; keysDown.splice(last + 1, keysDown.length - last - 1); } public function isDown(keyCode):Boolean { return keysDown[keyCode]; } }
perPixelHitTest.as:
import flash.display.BitmapData; import flash.display.BitmapDataChannel; import flash.display.BlendMode; import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; public class perPixelHitTest { public static function hitTestPixel(object1, object2, parent):Boolean { var collision:Rectangle; collision = getRectangle(object1, object2, parent); if (collision != null && collision.size.length > 0) return true; else return false; } public static function hitPointPixel(object1, object2, parent):Point { var collision:Rectangle; collision = getRectangle(object1, object2, parent); if (collision != null && collision.size.length > 0) { var returnPoint:Point; returnPoint = getCenter(collision); return returnPoint; } else return null; } public static function getRectangle(object1, object2, parent):Rectangle { var rectangle1 = object1.getBounds(parent); var rectangle2 = object2.getBounds(parent); var intersection = rectangle1.intersection(rectangle2); if (intersection.size.length > 0) { intersection.width = Math.ceil(intersection.width); intersection.height = Math.ceil(intersection.height); var alpha1 = getAlpha(object1, intersection, BitmapDataChannel.RED, parent); var alpha2 = getAlpha(object2, intersection, BitmapDataChannel.GREEN, parent); alpha1.draw(alpha2, null, null, BlendMode.LIGHTEN); var collision = alpha1.getColorBoundsRect(0xffff00, 0xffff00); collision.x += intersection.x; collision.y += intersection.y; return collision; } else return null; } private static function getAlpha(object, rectangle, channel, parent):BitmapData { var parentXformInvert:Matrix = parent.transform.concatenatedMatrix.clone(); parentXformInvert.invert(); var objectXform:Matrix = object.transform.concatenatedMatrix.clone(); objectXform.concat(parentXformInvert); objectXform.translate( -rectangle.x, -rectangle.y); var bitmapData:BitmapData = new BitmapData(rectangle.width, rectangle.height, true, 0); bitmapData.draw(object, objectXform); var alphaChannel:BitmapData = new BitmapData(rectangle.width, rectangle.height, false, 0); alphaChannel.copyChannel(bitmapData, bitmapData.rect, new Point(0, 0), BitmapDataChannel.ALPHA, channel); return alphaChannel; } public static function getCenter(inputRectangle):Point { var returnPoint:Point = new Point(); returnPoint.x = inputRectangle.x + inputRectangle.width / 2; returnPoint.y = inputRectangle.y + inputRectangle.height / 2; return returnPoint; } }
Feladat:
Valósítsunk meg egy egyszerű „ping-pong” játékot Flash környezetben AS3 segítségével.
A ping-pong játék lényege:
Adott egy játéktér
Adott a játéktér két végében egy-egy „ütő” (téglalap)
Adott egy labda (kör), amely a két ütő között pattog
A labda a két térfél között pattog a falon.
A labda az ütőkről az érintési pozíciótól függően visszapattan, a közepéről egyenletesen, a széleiről túlzottan.
Az egyik ütő legyen a játékos által irányítható:
jobb kurzorbillentyű hatására jobbra, bal kurzorbillentyű hatására balra mozogjon.
A másik ütő legyen gépi irányítású:
minél okosabban próbálja megítélni, hova pattan majd a labda és oda tartson.
Számoljuk a játékosok által szerzett pontokat!
Egy játékos pontot szerez, ha az általa ütött labdát a másik nem tudja elkapni
(azaz a játéktérnek a másik játékos felőli részén túljut az ütőn)
Megoldás:
Hozzunk létre egy-egy egyéni osztályt a Sprite-ból leszármaztatva mind az ütőknek, mind a labdának. Segédosztályként vezessük be a Direction (irány) osztályt.
Főprogram:
import Ball; import Pad; import Direction; // Hozzuk létre, majd helyezzük el az ütőket és labdát a pályán var pad1:Pad = new Pad(stage, true, true); var pad2:Pad = new Pad(stage, false, false); var ball:Ball = new Ball(stage); addChild(pad1); addChild(pad2); addChild(ball); // Állítsuk be a játék gyorsaságát const startfrequency:Number = 10; var frequency:Number = startfrequency; // Segédváltozók visszaszámláláshoz // (mikor valaki épp pontot veszített, kicsit álljon le a labda) var counting:Boolean = true; var counter:Number = 0; var countdown:Number = 3; // Játsszunk le kis csipogást, mikor egy ütő eltalálja a labdát function playbeep() { var beep:Beep = new Beep(); beep.play(); } // Hozzunk létre egy eseményfigyelőt, ami ütemezi a játékot var moverFunction:Function = function() { // Ha épp nem visszaszámlálunk, akkor mozgassuk a labdát if(!counting) { ball.move(); // Amúgy egy szövegdobozba írjuk ki a hátralévő időt } else { countdownText.text = String(Math.ceil(countdown-counter/1000)); counter+=frequency; // Ha végeztünk, állítsuk le a számlálót. if(counter/1000>=countdown) { counter = 0; counting = false; countdownText.text = ""; ball.setTarget(); } } // Mozgassuk az ütőket pad1.move(ball); pad2.move(ball); // Ellenőrizzük, ütésben van-e a labda valamelyik ütővel // ha igen, csipogjunk és kezeljük le if(pad1.hitTestObject(ball)) { ball.hit(pad1); playbeep(); } if(pad2.hitTestObject(ball)) { ball.hit(pad2); playbeep(); } // Ellenőrizzük, hogy leesett-e a labda a pályáról. // ha igen, növeljük a megfelelő számlálót, és állítsuk vissza if(ball.over(pad1)) { ball.reset(); counting = true; gate1counter.text=String(Number(gate1counter.text)+1); } if(ball.over(pad2)) { ball.reset(); counting = true; gate2counter.text=String(Number(gate2counter.text)+1); } } //Regisztráljuk az eseményfigyelőnket egy Timerhez és indítsuk el azt var mover:Timer = new Timer(frequency, 0); mover.addEventListener("timer", moverFunction); mover.start();
Direction.as:
package {
// Egy 2D vektor, amely megadja, hogy a labda melyik irányba halad
public class Direction {
public var x:Number = 0;
public var y:Number = 0;
public function Direction(p_x:int, p_y:int) {
x = p_x;
y = p_y;
}
// Visszaadja az 1-re normált x komponenst.
public function normX():Number {
return x/Math.sqrt(x*x+y*y);
}
// Visszaadja az 1-re normált y komponenst.
public function normY():Number {
return y/Math.sqrt(x*x+y*y);
}
// Lekezeli az ütő és labda összeütközését
public function hit(pad:Pad, ball:Ball) {
if(y<0&&pad.onTop || y>0&&!pad.onTop) y*=-1;
x=(x+(ball.x-pad.x)*10)/pad.width;
}
}
}
Pad.as:
package {
import flash.display.Sprite;
import flash.events.KeyboardEvent;
import Direction;
public class Pad extends Sprite {
// Az ütő mozgása időközönként
var rate:Number = 2;
// Le van-e nyomva a bal kurzorbillentyű
var leftDown:Boolean;
// Le van-e nyomva a jobb kurzorbillentyű
var rightDown:Boolean;
// Mesterséges intelligencia irányítja-e az ütőt
var AI:Boolean = true;
// Felül van-e az ütő.
var onTop:Boolean = false;
public function Pad(stage, p_onTop = true, p_AI = true) {
AI = p_AI;
onTop = p_onTop;
// Az ellenfél ütőjét lassítsuk kicsit le.
if(AI) rate*=0.8;
// Rajzoljuk ki az ütőt
graphics.beginFill(0xFFFFFF);
graphics.drawRect(-40, -3, 80, 6);
graphics.endFill();
// Helyezzük el az ütőt a pályán
this.x = stage.stageWidth/2;
this.y = onTop?3:stage.stageHeight-3;
// Figyeljük a billentyűket
stage.addEventListener(KeyboardEvent.KEY_UP,key_Up);
stage.addEventListener(KeyboardEvent.KEY_DOWN,key_Down);
}
// A mozgást kezelő függvény
public function move(ball:Ball) {
// Mi van, ha nem MI a játékos
if(!AI) {
// ha a bal le van nyomva, és nincs túl balra
if(leftDown && this.x>=this.width/2) {
// vigyük balra az ütőt rate pixellel
this.x-=rate;
// ha a jobb le van nyomva, és nincs túl jobbra
} else if(rightDown
&& this.x+this.width/2<=stage.stageWidth) {
// vigyük jobbra az ütőt rate pixellel
this.x+=rate;
}
// Mi van, ha MI a játékos
} else {
// vigyük a labda beütközési helye felé az ütőt
if(ball.getTarget()-rate>x
&& x+width/2<=stage.stageWidth) {
x+=rate;
} else if(ball.getTarget()+rate<x && x>=width/2) {
x-=rate;
}
}
}
// Keyboard eseményfigyelők, amik a bal-jobb gombokat kezelik
private function key_Up(event:KeyboardEvent) {
switch (event.keyCode) {
case 37 :
leftDown = false;
break;
case 39 :
rightDown = false;
break;
}
}
private function key_Down(event:KeyboardEvent) {
switch (event.keyCode) {
case 37 :
leftDown = true;
break;
case 39 :
rightDown = true;
break;
}
}
}
}
Ball.as:
package {
import flash.display.Sprite;
import Direction;
import Pad;
public class Ball extends Sprite {
// Az labda mozgása időközönként pixelekben
const rate:Number = 3;
// Az labda iránya
public var dir:Direction = new Direction(Math.random()*2-1, -4);
// A labda következő beütközési helye
private var targetPosition:Number;
public function Ball(stage) {
// Rajzoljuk meg a labdát
graphics.beginFill(0xFFFFFF);
graphics.drawEllipse(-3, -3, 6, 6);
graphics.endFill();
// Pozicionáljuk
this.x = stage.stageWidth/2;
this.y = stage.stageHeight/2;
}
// A labda mozgását kezelő függvény
public function move() {
// Ha kimenne a pályáról, fordítsuk meg az irányát
if((y+height/2>=stage.stageHeight && dir.y>0)
|| (y-height/2<=0 && dir.y<0)) {
dir.y*=-1;
}
if((x+width/2>=stage.stageWidth && dir.x>0)
|| (x-width/2<=0 && dir.x<0)) {
dir.x*=-1;
}
// Amúgy növeljük mindként koordinátáját az adott
// iránykomponens 1-re normáltjának a rate-szeresével
x+=rate*dir.normX();
y+=rate*dir.normY();
}
// Kezeljük le, ha az ütővel ütközik
public function hit(pad:Pad) {
dir.hit(pad, this);
// Változni fog a becsapódás helye, számoljuk ki újra
setTarget();
}
// Számoljuk ki a becsapódás helyét
public function setTarget() {
// Számoljuk ki a becsapódás abszolút helyét
if(dir.y<0) {
targetPosition = x-y*(dir.x/dir.y);
} else {
targetPosition = x+(stage.stageHeight-y)*(dir.x/dir.y);
}
// Vegyük hozzá a falakról való visszapattanást
if(targetPosition<0) {
targetPosition *= -1;
} else if(targetPosition>stage.stageWidth) {
targetPosition = stage.stageWidth*2-targetPosition;
}
}
// Visszaadja a becsapódás helyét
public function getTarget():Number {
return targetPosition;
}
// Visszaadja, hogy elkerülte-e a labda az ütőt.
public function over(pad:Pad):Boolean {
return pad.onTop?y-height/2<=pad.y:y+height/2>=pad.y;
}
// Berakja a labdát a pálya közepére (mikor valaki pontot veszít)
public function reset() {
x = stage.stageWidth/2;
y = stage.stageHeight/2;
dir.x = Math.random()*2-1;
setTarget();
}
}
}
A program a flex újdonságainak bemutatására szolgál. Segítségével online kipróbálhatjuk a Flex újdonságait. A fent linken meg lehet tekinteni a programot, letöltéshez pedig a program jobb egérgombos menüjében válasszuk a "Show source" opciót! Ezen a képernyőn belül már megjelenik a download source link.
Használati utasítás-képpen írok egy-egy sort minden oldalról:
A feladat egy repülőgépes lövöldözős játék elkészítése. A játékos oldalnézetből látja a pályát és a repülőgépét a megfelelő módon navigálva minél több ellenséges egységet kell kiiktatnia.
A játékosnak kezdetben 3 életegysége van, amely a játék ideje alatt változik. Életegységet veszít a játékos, ha az aktuális páncélja teljesen megrongálódik. Plusz egy életet kap a játékos minden, a pályán véletlenszerűen generált életegységet növelő pont megszerzése után.
A páncél egy életegység elhasználásakor 100%-os állapotba kerül és az ellenség által bevitt találatok folyamatosan rongálják azt. Ha a páncél megsemmisül, a játékos elveszít egy életegységet és a páncél újra megjavul. A pályán véletlenszerűen generált páncéljavító pontok felvételével a játékos páncélja javítható.
2 féle fegyvertípus létezik. A gépfegyver és a rakéta.
A rakéta nagyobb sebzést okoz, viszont egyszerre csak 1 db rakétát lehet a repülőgépre szerelni. A játéktéren véletlenszerűen generált plusz rakéta pont felvételével a játékos repülőgépére plusz egy rakéta kerül abban az esetben, ha még nincs felszerelve rakéta.
A játékos vadászgépére kezdetben egy gépfegyver kerül. Ennek száma növelhető eggyel, ha a plusz gépfegyver pontot megszerzi a játékos. A gépfegyverek tüzelési sebessége is növelhető a megfelelő tüzelési sebesség pont felvételével. A gépfegyver töltény nincs végtelenítve, így ezzel is ügyesen kell gazdálkodnia a játékosnak. Természetesen a plusz töltény pont megnöveli a muníciót.
A játék során ellenséges repülőgépeket kell levadászni. Az ellenség megpróbál a játékos repülőgépével egy vonalba helyezkedni, így növelve a találati arányt. Egyelőre kizárólag légi egységek vannak betervezve a játékba.
A játék indulásánál a főmenü fogadja a játékost. Itt olvashat a játékról, irányításról és innen tudja elindítani a játékot.
A játék során a repülőgépet a kurzormozgató nyilakkal kell irányítani a játékosnak. A gépfegyverekkel való tüzelés a Space billentyűvel történik, míg a rakéták kilövését a CTRL gomb lenyomásával teheti meg a játékos.
A program architektúráját tekintve követi az MVC tervezési mintát. A nézet maga a Flash-ben megrajzolt objektumokból áll össze. A modell tartalmazza az összes egyedre vonatkozó információkat, például a repülőre, gépfegyverre vagy akár magára a gépfegyver töltényre. A kontroller pedig a nézeten kiváltott interakciókra reagál és közvetíti azt a modell felé.
Főbb animációk, melyek nagyobb gondolkodást igényeltek:
A Flash-t kizárólag a rajzolásra használtam. A kódolást már az Eclipse alapú Flash Builder eszközzel végeztem, mivel kezelését tekintve felhasználóbarátabb és okosabb, mint a Flash beépített Action Script Window-ja.
A rajzoláshoz ezen kívül még Photoshop-ot is használtam.
Szerző neve: Veress Vivien
Készítés éve: 2014
A feladat egy népszerű képkonvertáló programot valósít meg. Különböző típusú képekből ASCII karakter sorozatot állít elő a pixelek alapján, és megjeleníti azokat, így kirajzolva az eredeti képet.
A program egy Flash tutorial-ra épül (http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7eee.html) és az Adobe Flash CS4 nevű fejlesztői környezetében készítettem el. A megvalósításnál sok hasznos beépített könyvtárat felhasználunk.
Először is egy felhasználói felületet hozunk létre (AsciiArtApp.fla). Ezt nagyon egyszerűen, az elemek felületre húzogatásával megtehetjük. Ezután úgynevezett action-öket rendelhetünk a különböző elemekhez, és így megírhatjuk a logikát a felülethez. Eseménykezelőkön keresztül hívjuk meg a szükséget műveleteket, amiket az actionscript fájlokban írunk meg. (AsciiArtBuilder.as, BitmapToAsciiConverter.as, Image.as, ImageInfo.as)