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)