Nov 04 2010

La détection de collision

Tag: android,flash,Jeux indépendants,jeux vidéo,openglTurbo Connard @ 19:58

A mes débuts de “flasher” j’avais était super impressionné par “N” : un vrai jeu avec un moteur de collisions + physique poussé. Ce jeu fonctionne vraiment bien et on ne tombe jamais sur aucun glitch de collision très courant à l’époque des jeux flashs : Passage au travers d’un mur, décalage plus qu’approximatif dans le calcul de la trajectoire etc. Je me suis replongé dans la détection de collision l’autre jour et j’ai tout de suite pensé à ce jeu comme base de réflexion.

Je sais qu’il existe des moteurs très puissant de physique tel que box 2D (largement exploité en minigame iphone, android flash…) mais malheureusement je trouve que trop de physique peut nuire grandement au gameplay et j’ai pas envie de m’embêter avec des poids, vélocité etc… pour un jeu de plateforme. Sur le plutôt bon Angrybirds la physique m’énerve un peu trop des fois.

Dans “new super mario bros wii” par exemple, il y a une super bonne détection de collisions qui est loin d’être réaliste (heureusement) et pourtant on s’en passe et le jeu arrache tout. Mario est soumis à la gravité, il peut marcher sur des plateformes rondes (qui tournent), en pentes, qui bougent, des rectangulaires qui tournent etc… C’est ce compromis que je recherche et aujourd’hui, à ma connaissance, il n’y a pas de moteur de collisions / faussement physique.

Exemple de belles plateformes rectangulaires qui tournent dans le niveau 9-1 :

En cherchant un peu je suis tombé sur les tutoriels de N : http://www.metanetsoftware.com/technique/tutorialA.html
Ils expliquent assez bien et sont très utiles pour comprendre les concepts de collisions entre différents objets. Et bien sûr la fameuse technique de l’axe séparateur qui semble faire l’unanimité chez les développeurs.

Récap :

  • on peut dire que deux objets ne se touchent pas tant qu’on peut tirer un trait entre les deux.
  • un de ces traits est forcément parallèle à un coté d’un des objets.

Partant de là, il est assez facile par “projection” de trouver (ou pas) les collisions. Encore faut il comprendre la projection, sur quoi projeter et pourquoi.

En informatique 2D on teste souvent la collision entre “hitbox” (une boîte rectangulaire qui entoure la totalité ou une partie seulement d’un sprite.) Ça facilite grandement les calculs, on gagne en performance mais des fois c’est pas suffisant. On verra néanmoins que ce calcul rapide est à effectué dans certains cas. Le principe est simple : si les distances respectives en x et y entre les centres des deux objets sont inférieures à la moitié de la somme de leurs longueurs et hauteurs, il y a collision.

La soustraction de ces distances et des sommes des longueurs et hauteurs donnent des valeurs de superpositions des objets : des vecteurs de pénétration.

Ça marche très bien, mais seulement pour les AABB : Axis Aligned Bounding Box. Comprendre “Boites rectangulaires sans rotation”

Ce principe de détection de collisions marche super bien dans beaucoup de cas (Shmup, jeux de plateforme etc…) mais il est limite quand on veut pousser l’expérience de l’utilisateur un peu plus loin. (voir exemple de mario ci dessus)

Si on commence à faire tourner les boites ca devient compliqué car la petite astuce précédente ne marche plus :

Pourtant, on voit bien que l’on peut tracer ce fameux trait entre les deux boites ! et on voit bien qu’il devrait être parallèle au côté de la boite rouge comme précédemment énoncé.

Avec la précédente technique ça ne marche pas car on ne vérifie que le recouvrement sur les axes x et y. Sauf qu’ici, ce n’est pas sur ces axes qu’il faut chercher mais bien sur tous les axes décrits par les arêtes des boites. Pour “voir” que les boîtes ne se touchent pas, il faut se positionner au niveau de la boite rouge et de regarder le long de son côté à droite sur le dessin.

en supprimant les doublons on trouve 4 axes…

Pour le cas des AABB il n’y avait que 2 axes, ici on en a 4 mais le principe reste le même. Il faut calculer les superpositions des deux boites sur les 4 axes. Je ne vais pas vous expliquer comment faire parce que pour le coup, les tutoriels de N expliquent très bien les vecteurs et tout le bordel mais le principe est pas forcément simple à comprendre.

En écartant les axes ca devient un peu plus lisible (c’est pas nécessaire pour le code, en général on teste les axes en 0,0).

Première projection, je trouve une collision.

Deuxième projection, pas de collision…

En faisant les mêmes calculs sur tous les axes séparateurs, on obtient donc les collisions ou pas. En prenant le vecteur de pénétration le plus court de toutes les collisions des projections on trouve la position “idéale” où l’objet devrait être.

Dans ce test, on ne parle pas de physique, de rebonds ou de gravité… C’est un autre sujet, cependant un petit test flash donne déjà des résultats surprenants :

Dans cet exemple, on peut déplacer les formes à la souris et le rectangle gris à l’aide des touches. Le moteur physique se résume à “vitY += 1; ” autrement dit seulement une gravité basique. C’est pour ça que les plateformes ne transmettent pas leur vitesse de rotation au sprite rectangulaire. Mais ce n’est pas ce que l’on teste ici.


Oct 27 2010

TCGMLPlayer

Tag: flash,gmlTurbo Connard @ 20:10

bon ben voila, je crois que j’ai assez creusé la piste flash pour cette appli…

les sources par la.


Oct 06 2010

Graffiti Analysis Flash Test 2

Tag: flash,gmlTurbo Connard @ 21:20

Ok, j’avais laissé en plan le “graffiti analysis test” mais il fallait quand même que je règle ce problème de vecteur “UP”. J’ai donc trouvé la formule magique pour orienter les graffitis dans le “bon” sens.

En gros les applications qui enregistrent les tags doivent préciser un vecteur “UP” qui détermine l’orientation du graffiti. Certaines applications (ex : Coding With Attitude GML Drawer : MFF2010  ) ne le font pas donc on prendra l’axe Y par défaut, c’est l’orientation qui est la plus “normale”.

Mais d’autres applications (presque toutes d’ailleurs puisqu’il s’agit de portages) ont par défaut un vecteur “UP” sur l’axe des x  : (1,0,0);

Si on opère pas de transformation, certains graffitis vont s’écrire de haut en bas et non pas de gauche à droite. La première solution est de choisir arbitrairement : “bon ben si c’est Y, on touche à rien et si c’est X ben on fait une rotation de -90° et c’est marre…” Mais on vaut quand même mieux que ça !

Donc la technique c’est de trouver l’axe et l’angle de rotation puis d’appliquer la transformation.
Le code est dans le “onload” du xml.

package {
        import flash.display.DisplayObject;
        import flash.display.MovieClip;
        import flash.display.Sprite;
        import flash.display.StageAlign;
        import flash.display.StageScaleMode;
        import flash.events.Event;
        import flash.events.MouseEvent;
        import flash.geom.Matrix3D;
        import flash.geom.Rectangle;
        import flash.geom.Vector3D;
        import flash.net.URLLoader;
        import flash.net.URLRequest;
        /**
         * @author Turboconnard
         */

        public class Test3D extends MovieClip {
                private var yaxis : Sprite;
                private var zaxis : Sprite;
                private var xaxis : DisplayObject;
                private var axe : Sprite;
                private var container : Sprite;
                private var graf : Sprite;
       
                public function Test3D() {
                        //waiting for stage to be instanciate
                        addEventListener(Event.ADDED_TO_STAGE, _add);
                }       
                private function _add(event : Event) : void {
                        /* remember 3D objects may have a z if you want transform thme */
                       
                       
                        stage.scaleMode = StageScaleMode.NO_SCALE;
                        stage.align = StageAlign.TOP_LEFT;
                        stage.addEventListener(Event.RESIZE,resize);
               
                        //creating global container
                        container = new Sprite();
                        container.z = 0;
                        graf = new Sprite();           
                        graf.z = 0;
                        addChild(container);
                        container.addChild(graf);       
                       
                        //building 3D axis viewer
                        xaxis = getLine(0x0000ff);
                        yaxis = getLine(0x00ff00);
                        zaxis = getLine(0xff0000);
                       
                        xaxis.z = 0;
                        yaxis.z = 0;
                        zaxis.z = 0;
                        yaxis.transform.matrix3D.appendRotation(90, Vector3D.Z_AXIS);
                        zaxis.transform.matrix3D.appendRotation(90, Vector3D.Y_AXIS);

                        axe = new Sprite();
                        axe.z = 0;
                        axe.addChild(xaxis);
                        axe.addChild(yaxis);
                        axe.addChild(zaxis);
                        addChild(axe);
                        //load a graffiti
                        load(null);
                        //resize
                        resize(null);
                }
                /**
                 * load a random graffiti
                 */

                public function load(e:MouseEvent) : void {
                        stage.removeEventListener(MouseEvent.CLICK,load);
                        alpha = 0.2;
                        var urlLoader : URLLoader = new URLLoader();
                        urlLoader.addEventListener(Event.COMPLETE, _onLoad);                   
                        urlLoader.load(new URLRequest("http://000000book.com/data/" +"random"+ ".gml"));
                }
                /**
                 *
                 */

                private function _onLoad(event : Event) : void {
                        stage.addEventListener(MouseEvent.CLICK,load);
                        alpha = 1;
                        var gml:XML = XML(URLLoader(event.target).data);
                        var env:XMLList = gml.tag.environment;
                       
                        //Some graffitis aren’t drawn with Y as up axis.
                        //we have to rotate our world to mach our Y up axis.
                        //my up vector in flash                
                        var up:Vector3D =  new Vector3D(0,1,0);
                        //graffiti’s up vector
                        var modelup:Vector3D;
                        if(int(env.up.x) + int(env.up.y) + int(env.up.z) == 0 ) modelup = new Vector3D(0,1,0);
                        else modelup = new Vector3D(env.up.x, env.up.y, env.up.z);
                        //looking for axis to rotate
                        var rotationaxis:Vector3D = up.crossProduct(modelup);
                        //looking for rotation angle
                        var angle:Number = Math.acos( up.dotProduct(modelup) );
                        //rotate objects
                       
                        axe.transform.matrix3D = new Matrix3D();
                        graf.transform.matrix3D = new Matrix3D();
                        axe.transform.matrix3D.appendRotation(angle/Math.PI*180, rotationaxis);
                        graf.transform.matrix3D.appendRotation(angle/Math.PI*180, rotationaxis);
               
                        //draw graffiti
                        graf.graphics.clear();
                        graf.graphics.lineStyle(8,0);
                        for(var i:int=0;i<XMLList(gml.tag.drawing.stroke).length();i++){
                                var pts:XMLList = XMLList(gml.tag.drawing.stroke)[i].pt;
                                for(var k:int=0;k<pts.length();k++){   
                                        if(k==0) graf.graphics.moveTo(pts[k].x*env.screenBounds.x,pts[k].y*env.screenBounds.y);
                                        else graf.graphics.lineTo(pts[k].x*env.screenBounds.x,pts[k].y*env.screenBounds.y);
                                }                              
                        }
                       
                        resize(null);
                }
                /**
                 * resize
                 * @param e Event
                 */

                private function resize(e:Event) : void {
                        axe.x = stage.stageWidth/2;
                        axe.y = stage.stageHeight/2;
                       
                        //moving objects to center
                        var box:Rectangle = container.getRect(container);
                        container.x = – box.x + stage.stageWidth/2 – box.width/2;
                        container.y = – box.y + stage.stageHeight/2 – box.height/2;
                }

                /**
                 * draw an arrow
                 * @param pColor arraow color
                 */

                public  function getLine(pColor : uint) : Sprite {
                        var s : Sprite = new Sprite();
                        s.graphics.lineStyle(1, pColor,1,false,"none");
                        s.graphics.lineTo(20, 0);
                        s.graphics.lineTo(15, -2);
                        s.graphics.moveTo(20, 0);
                        s.graphics.lineTo(15, 2);                      
                        return s;
                }
        }
}
 


Sep 28 2010

Jeux vidéo et accessibilité

Tag: Jeux indépendants,jeux vidéoTurbo Connard @ 18:43

J’ai lu un article traitant de l’accessibilité des jeux vidéo dans le sensass magazine Amusement. Cet article ne traite pas du tout la partie technique liée au développement d’un jeu et je me demandais bien ce que ça implique en terme de dev. J’ai donc fait un tour sur le site mentionné :  www.ablegamers.com

Le site portail teste les jeux récents et note l’accessibilité de ces derniers. On y trouve en outre pas mal de news, tests de matériels, interviews etc… et bien sur le lien que j’espérais trouver “Developper ressources”.

Le lien mène à un autre site (un peu confus) qui tente de recenser des demandes récurrentes des joueurs handicapés et des informations sur les handicaps.
Après avoir parcouru le site je me rends compte qu‘il est extrêmement facile de développer un jeu accessible. Je ne prétends pas pouvoir créer des jeux accessibles à tous types de handicap mais certaines features faciliteraient bien les choses à certains gamers et sont hyper simple à mettre en place.

La customisation des touches/commandes.

Pas mal de joueurs ont du hardware spécifique : pad hacké, clavier mappé sur divers boutons, système de pointage différent, etc…
Laissez les joueurs définir leurs boutons/touches et paramétrer la vélocité de la souris. C’est peanuts en code et en plus ça fait classe un écran de configuration de plus.

Difficulté

Une feature qui devrait être présente sur TOUS les jeux. J’avais déjà lu ça dans un précédent Amusement.Un mec se plaignait de Gears of War. Il avait fait passer le monstre aveugle 3 fois à travers le mur  avant la serre là. Puis il s’est dit “ben merde, c’est pas ça qu’il faut faire” donc il a tenté plein de trucs, est mort à chaque fois et à abandonner le jeu. Normal. (J’ai eu la même sur certains boss de RE5)

C’est hyper frustrant d’acheter un jeu à 60€ et de n’en voir que 10% parce que le boss du deuxième niveau est trop balèze. Du coup on se décourage et on passe à un autre jeu. Pourquoi ne pas ajouter une option, “passer à la suite” ?

Non mais sans dèc, ça change quoi ? Tu les as déjà lâché tes 60€ j’veux dire, t’es pas en train de voler l’éditeur du jeu en lui carottant quoi que ce soit… Qu’est ce que ça peux lui foutre à l’éditeur que t’aille au niveau final sans avoir réellement buter le boss du deuxième niveau ?
Franchement, j’aurais aimé que cette option existe sur lostplanet parceque j’ai jamais vu la fin de ce putain de jeu à cause du boss de fin imprenable. J’ai du faire 5000 tentatives et je l’ai jamais eu. Ça me reste encore en travers de la gorge.

Bref, facile à mettre en place et hyper bien pour quand on est bloqué.

Autre exemple, t’achètes Street Figther  4 pour te friter avec tes potes et en fait, t’es obligé de terminer 50 fois le jeux pour avoir tous les persos… Honnêtement, j’ai mis le jeux en mode ultra easy avec un round gagnant et j’ai passé plusieurs heures comme un con à latter du Tchounli et autre Dalmish en regardant la télé. Il pourrait pas y avoir un bouton “Afficher tous les persos” et c’est marre ! En économisant la xbox et la télé HD de 102cm qui tournent pendant 12 heures pour débloquer ce nazbrok de Seth chez chaque acheteur du jeu, tu refroidis la planète de 2 degrés.

Le son

Facile à mettre en place aussi, des notifications visuelles pour accompagner le son.
J’imagine un exemple à la con qui me frustrerait bien, un grand classic du jeu vidéo :

“L’interrupteur Timer qui ouvre une porte pendant un certain temps.”

C’est toujours pareil, t’actionne l’interrupteur et t’entends un petit (clic………clic……..clic……clic…clic..clic.cliccliccclclclclc, BOUM !). Bref, sans ce son t’es perdu, niqué. Ça me rendrait dingue.
Certains jeux montrent l’ouverture de la porte, d’autres montrent l’interrupteur se remettre en place (ex:cordes qui remontent, manivelles qui tournent, etc…)
Ya forcément moyen de trouver des astuces visuelles pour toutes ces notifications sonores.

Bon et sans parler des sous-titres paramétrables qui devraient être obligatoires pour tous les dialogues.

Graphisme

Je me rappelle jouer à “addidas power soccer” contre un pote légèrement daltonien (ou peut être tout simplement nul aux jeux vidéo). Il choisissait obligatoirement une équipe avec des maillots différents des miens, et moi forcément je le faisais chier en prenant une équipe avec des maillots sensiblement identiques aux siens…

Bref sans pour autant penser le graphisme avec toutes les contraintes des différents handicaps il est plutôt bon d’avoir un fort contraste sur les éléments interactifs et ou textes.

Récemment j’ai joué à l’excellent “limbo”. Ce jeu est super et non, il n’est pas court (comprenne qui pourra). Ce jeu plutôt facile propose une aventure aux interactions limitées mais sympas. Ça ne m’a pas empêché de rester bloqué pendant pas mal de temps sur un passage :

La glissade sur le toit après la tyrolienne.

J’ai dû chercher sur le net la solution : En fait ya un mini panneau avec une flèche qu’on voit presque pas, faut sauter et appuyer dessus pour actionner un changement de gravité. Ok. Appuyer sur un panneau, pourquoi pas. Je suis passé 50 fois devant et je l’ai pas vu…

Avec un bon contraste, des objets clairement reconnaissables on doit pouvoir s’en sortir.

Conclusion :

Voilà une première réflexion sur l’accessibilité dans le jeu vidéo. Rien de bien extraordinaire mais tellement logique quand on y pense. Pour finir, une demande que je trouvais super pertinente mais qui n’a rien à voir avec le jeu en lui même c’est d’avoir une documentation online pour chaque jeu. Pourquoi ? ben déjà tout simplement pour savoir si le jeu est accessible ou pas ! et ensuite pour pouvoir appréhender le jeu avant de l’acheter ou tout simplement d’en connaitre les règles…



Aug 11 2010

Graffiti Analysis Flash Test

Tag: android,flashTurbo Connard @ 12:50

Faisant du flash depuis pas mal d’annése, c’était plus facile pour moi de faire un prototype en flash avant d’attaquer la version Java / android de l’application.
La première partie consiste à charger les tags et les afficher. J’ai fait une classe simple pour tenter de lisser un peu mieux mes “bords de courbe”. Je suis pas mal satisfait du résultat que voila :

Il reste un truc à faire mais j’ai la flemme… dans un fichier gml il y a un vecteur “up” qui définit la verticale du monde 3D. Il faudrait appliquer une rotation au container pour que les tags soient toujours bien orientés. Il arrive de tomber sur des tags qui sont dans le mauvais sens.

Cliquer pour charger un tag aléatoire.

sources :

package
{
        import flash.display.StageAlign;
        import flash.display.StageScaleMode;
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.events.MouseEvent;
        import flash.geom.Point;
        import flash.net.URLLoader;
        import flash.net.URLRequest;
        import flash.utils.getTimer;

        /**
         * @author turboconnard
         * http://www.turboconnard.com
         */

        public class Main extends Sprite
        {
                private static var HEIGHT:Number = 400;
               
                private static var LINESTYLE:Number = 2;
                private static var LINEALPHA:Number = 1  ;
                private static var ALPHA:Number = 0.8;
                private static var FADEIN:Number = 0;
                private static var FADEOUT:Number = 8;
               
                private static var STROKEBASE:Number = 30;
                private static var STROKE:Number;
                private static var MAXSTROKE:Number = 20;
                private static var EASE:Number = 5;

                private var tag:String = "19126";

                private var gml:XML;
                private var _count:int;
                private var _container:Sprite;
                private var _startTime:Number;
                private var _strokes:Vector.<XMLList > ;
                private var _stroke:int;

                private var oldPoint:Point;
                private var oldP1:Point;
                private var oldP2:Point;
                private var oldStroke:Number;
                private var angle:Number;
               
                public function Main()
                {
                        load();
                        stage.scaleMode = StageScaleMode.NO_SCALE;
                        stage.align = StageAlign.TOP_LEFT;
                        stage.addEventListener(MouseEvent.CLICK, _clic);
                }

                private function _clic(event : MouseEvent):void
                {
                        load();
                }

                public function load():void
                {
                        var urlLoader : URLLoader = new URLLoader();
                        urlLoader.addEventListener(Event.COMPLETE, _onLoad);
                        urlLoader.load(new URLRequest("http://000000book.com/data/"+tag+".gml"));
                        tag = "random";
                }

                private function _onLoad(event : Event):void
                {
                        HEIGHT = stage.stageHeight;
                        while (numChildren)     removeChildAt(0);
                       
                        _strokes = new Vector.<XMLList>();
                        _stroke = 0;
                        gml = XML(URLLoader(event.target).data);
                        for (var i : int = 0; i < gml.tag.drawing.stroke.length(); i++)
                        {
                                _strokes.push(gml.tag.drawing.stroke[i].pt);
                        }
                        createStroke();
                        _startTime = getTimer();
                        addEventListener(Event.ENTER_FRAME, _draw);
                }
                private function end()
                {
                        removeEventListener(Event.ENTER_FRAME, _draw);
                }
                private function _draw(event : Event):void
                {
                        if ((getTimer() – _startTime) > (_strokes[_stroke][_count].time * 1000)){
                                _count++;
                                if (_count >= _strokes[_stroke].length())
                                {
                                        _stroke++;
                                        if (_stroke >= _strokes.length)
                                        {
                                                end();
                                                return;
                                        }
                                        else
                                        {
                                                createStroke();
                                        }
                                }
                                draw();
                        }
                }
                public function createStroke():void
                {
                        _count = 0;
                        oldPoint = new Point(_strokes[_stroke][0].x * HEIGHT,_strokes[_stroke][0].y * HEIGHT);
                        oldP1 = oldPoint.clone();
                        oldP2 = oldPoint.clone();
                        oldStroke = STROKE = MAXSTROKE/2;
                       
                        _container = new Sprite();
                        _container.y = HEIGHT;
                        _container.rotation = -90;
                        addChild(_container);
                }
                public function drawOne(point:Point):void
                {
                        var a:Number
                        if(_count ==0){
                                a =  Math.PI / 2Math.atan2(_strokes[_stroke][1].x * HEIGHT – oldPoint.x, _strokes[_stroke][1].y * HEIGHT – oldPoint.y);
                        }else{
                                a =  Math.atan2(point.y – oldPoint.y,point.x – oldPoint.x);
                        }
                       
                        var dist:Number = Math.sqrt(Math.pow(point.x – oldPoint.x,2) + Math.pow(point.y – oldPoint.y,2));
                        STROKE += ((STROKEBASE / dist > MAXSTROKE ? MAXSTROKE : STROKEBASE / dist) – STROKE)/EASE;

                        var p0:Point = oldPoint.add(new Point(Math.cos(a + Math.PI / 2) * oldStroke,Math.sin(a + Math.PI / 2) * oldStroke));
                        var p1:Point = oldPoint.add(new Point(Math.cos(a – Math.PI / 2) * oldStroke,Math.sin(a – Math.PI / 2) * oldStroke));
                        var p2:Point = point.add(new Point(Math.cos(a + Math.PI / 2) * STROKE,Math.sin(a + Math.PI / 2) * STROKE));
                        var p3:Point = point.add(new Point(Math.cos(a – Math.PI / 2) * STROKE,Math.sin(a – Math.PI / 2) * STROKE));

                        var c : Sprite = new Sprite();
                        _container.addChild(c);
                        var leng = _strokes[_stroke].length();
                        if(_count<FADEIN)c.graphics.beginFill(0xffffff, _count/FADEIN * ALPHA);
                        else if(_count >leng-FADEOUT) c.graphics.beginFill(0xffffff,(leng-_count)/FADEOUT * ALPHA);
                        else c.graphics.beginFill(0xffffff, ALPHA);
                        //c.graphics.beginFill(0xffffff, ALPHA);
                        c.graphics.lineStyle(undefined);
                        if(LINESTYLE>0) c.graphics.lineStyle(LINESTYLE, 0, LINEALPHA);
                        c.graphics.moveTo(oldP1.x, oldP1.y);
                        c.graphics.lineTo(p0.x, p0.y);
                        c.graphics.lineTo(p2.x, p2.y);
                        c.graphics.lineStyle(undefined);
                        c.graphics.lineTo(p3.x, p3.y);
                        if(LINESTYLE>0) c.graphics.lineStyle(LINESTYLE, 0, LINEALPHA);
                        c.graphics.lineTo(p1.x, p1.y);
                        c.graphics.lineTo(oldP2.x, oldP2.y);
                        c.graphics.lineStyle(undefined);
                        oldStroke = STROKE;
                        oldP1 = point.add(new Point(Math.cos(a + Math.PI / 2) * oldStroke,Math.sin(a + Math.PI / 2) * oldStroke));
                        oldP2 = point.add(new Point(Math.cos(a – Math.PI / 2) * oldStroke,Math.sin(a – Math.PI / 2) * oldStroke));
                        oldPoint = point;
                }
                public function draw():void
                {
                        var curPoint:Point = new Point(_strokes[_stroke][_count].x * HEIGHT,_strokes[_stroke][_count].y * HEIGHT);
                        drawOne(curPoint);
                }
        }
}
 


Aug 05 2010

Grafitti Analysis : WIP

J’espère que vous connaissez déjà graffiti Analysis (un super projet d’Evan Roth) qui tente de mémoriser et d’archiver des données de “tag”. La base de données est ouverte et on peut donc charger les tags au format “GML” (graffiti mark up language). Je vais essayer de terminer une appli de lecture de tags et écriture de tag. J’ai déjà réussi à lire de tags, la j’en crée. Il ne me reste plus qu’a tout mettre ensemble pour faire une appli finie. Je balancerai bien sûr toutes les sources.


Jul 21 2010

Turboconnard AGD : Exemple 2 Connard jump

Tag: android,Eclipse,Jeux indépendants,jeux vidéoTurbo Connard @ 14:16

Je continue peinard les tests du framework. Hier j’ai essayé de faire “Connard Jump”. Un peu plus de 100 lignes de code pour un gameplay bien connu des détenteurs de webphones (comme ils disent) :

J’ai ajouté un schtroumpfeur de textures qui charge tout les ressources de R.drawable en texture opengl. Du coup on a plus à se soucier de charger les textures on peut directement faire :

Bitmap b = new Bitmap(R.drawable.machin);
 stage.addChild(b);

Voila donc la source de Connard Jump :

 

 

package com.hello;

import java.util.Vector;

import com.turboconnard.core.Agd;
import com.turboconnard.display.Bitmap;
import com.turboconnard.display.Sprite;
import com.turboconnard.events.AccelerometerEvent;
import com.turboconnard.events.Event;
import com.turboconnard.geom.Point3D;
import com.turboconnard.hardware.Accelerometer;

public class HelloWorld2D extends Agd {
        private static final float MINISPACE = 40;
        private static final double SPACE = 50;
        private Bitmap hero;
        private float vitY;
        private float vitX = 0;
        private float cibleX = 0;
        private Sprite decors;
        private Vector<Sprite> pfCheck;
        private float _y;
        private float _lastY;

        public HelloWorld2D() {
                super();
                Bitmap b = new Bitmap(R.drawable.background);
                b.x = stage.width / 2;
                b.y = stage.height / 2;
                vitY = -10;
               
                hero = new Bitmap(R.drawable.test);
                hero.x = stage.width / 2;
                hero.y = stage.height5;
                decors = new Sprite();
                decors.z = 0;//1;

                hero.z =  0;//-1;
                stage.addChild(b);
                stage.addChild(decors);
                stage.addChild(hero);
               
                Accelerometer.getInstance().addEventListener(AccelerometerEvent.ON_CHANGE, this);
                generateLevel();
        }
        /**
         * create FirstPlatforms
         */

        private void generateLevel() {
                _y = stage.height;
                while (_y > 0) {
                        _y = _y – (float) (Math.random() * SPACE) – MINISPACE;
                        addPlateform(_y);
                }
        }

        private void addPlateform(float pY) {
                _lastY = pY;
                Bitmap pf = new Bitmap(R.drawable.plate);
                pf.y = pY;
                pf.x = (float) Math.random() * stage.width;
                decors.addChild(pf);
        }

        public void update() {
                //gravity
                vitY += 0.3;
                //acceleration x
                cibleX += (vitX – cibleX) / 5;
                //if we go up
                if (vitY < 0) {
                        checkAddPF();          
                } else {
                        //if we go down
                        checkPF();
                        testColision();
                }
                //move x
                hero.x += cibleX;
                //bounce against walls
                if (hero.x > stage.width) {
                        hero.x = stage.width;
                        cibleX *= -1;
                }else if (hero.x < 0) {
                        hero.x = 0;
                        cibleX *= -1;
                }
                //loose !
                if (hero.y > stage.height) {
                        vitY *= -0.5;
                        /*
                        hero.y = stage.height;
                        */

                }
        }
       
        private void checkAddPF() {
                //if hero.y <250 move level
                if (hero.y < 250){
                        _y += vitY;
                        //addPlateform if offset is > MINISPACE
                        if( (_lastY – _y )> MINISPACE ) addPlateform(_y –  (float) (Math.random() * SPACE) );
                        decors.y -= vitY;              
                }else{
                        //if hero.y >250 move hero
                        hero.y += vitY;
                }
        }

        private void checkPF() {
                pfCheck = new Vector<Sprite>();
                for (Sprite b : decors.getDisplayList()) {
                        if ((b.y + decors.y) > hero.y)
                                pfCheck.add(b);
                        if (b.y + decors.y > stage.height)
                                decors.removeChild(b);
                }
                hero.y += vitY;
        }

        private void testColision() {
                for (Sprite b : pfCheck) {
                        if ((b.x50) < hero.x && (b.x + 50) > hero.x && (hero.y+hero.height/2) > (b.y + decors.y) ) {
                                hero.y = (b.y + decors.y -hero.height/2);
                                vitY = -10;
                                return;                        
                        }
                }
        }
        public void event(Event e) {
                if (e.name == AccelerometerEvent.ON_CHANGE) {
                        Point3D p = (Point3D) e.param;
                        vitX = p.x;
                }
        }
}
 

hop la :


Jul 15 2010

Turboconnard AGD : Exemple

Tag: android,jeux vidéo,openglTurbo Connard @ 19:11

Youpi, ça commence à ressembler à quelque chose.
Quand on touche l’écran un cube est placé dans l’espace à des positions aléatoires, le téléphone vibre quand vous le touchez (hmmm, hot, sex…).
Pencher le téléphone influe sur la rotation des cubes. Faut que je sorte les textures (actuellement en dur dans le Cube) et je file le sources.

Exemple de code simple :

package com.helloworldagd;

import android.view.MotionEvent;

import com.turboconnard.core.Agd;
import com.turboconnard.display.Cube;
import com.turboconnard.display.Sprite;
import com.turboconnard.events.AccelerometerEvent;
import com.turboconnard.events.Event;
import com.turboconnard.geom.Point3D;
import com.turboconnard.hardware.Accelerometer;
import com.turboconnard.hardware.Vibrato;

public class HelloWorldAGD extends Agd {

        private Sprite s;
        private float tx;
        private float ty;
        public HelloWorldAGD(){
                super();
                Accelerometer.getInstance().addEventListener(AccelerometerEvent.ON_CHANGE, this);
                tx = ty = 0;
                s = new Sprite();
                s.x = stage.width / 2;
                s.y = stage.height / 2;
                stage.addChild(s);

        }
        public void addCube(){
                Cube c = new Cube((float) (Math.random()*80 + 20));
                c.x = (float) Math.random()*stage.width(stage.width/2);
                c.y = (float) Math.random()*stage.height-(stage.height/2);
                c.z = (float) Math.random()*stage.height-(stage.height/2);
                s.addChild(c);         

                Vibrato.getInstance().vibrate(30);
        }
        public boolean onTouchEvent(MotionEvent e){
                if(e.getAction() == MotionEvent.ACTION_DOWN){
                        addCube();
                }
                return true;
        }
        public void update(){

        }
        public void event(Event e){
                if(e.name == AccelerometerEvent.ON_CHANGE){
                        Point3D p = (Point3D) e.param;
                        tx += (p.x – tx)/5;
                        ty += (p.y – ty)/5;

                        s.rotationX += tx;
                        s.rotationY += ty;
                }
        }
}
 

 

Et voilà ça fait ça :


Jul 09 2010

AGD – En avant pour opengl

Tag: android,Jeux indépendants,jeux vidéo,openglTurbo Connard @ 19:31

Après avoir fait mumuse  avec mon framework turboconnard agd, j’ai quand même remarqué que ça ramait… le développement “Canvas mode” pour le jeu c’est vraiment trop lent.

Je suis donc passé en mode opengl pour dessiner mes jolis sprites. Le problème avec opengl c’est que c’est hyper complexe. Il faut environ douze jours pour comprendre comment tracer un triangle puis deux fois plus de temps pour y appliquer une texture. Je cherche à faire des choses simple… mais c’est déjà l’enfer pour trouver un tutoriel sur “comment appliquer une texture sur un carré” alors lorsqu’il s’agit de trouver “comment faire apparaitre une texture à 100% dans un univers en 3D” c’est la mort…

Le but recherché :

Dessiner des trucs en deux dimensions dans un univers en trois dimensions. Typiquement, ça permet de développer peinard en 2D sans se prendre la tête et de temps en temps claquer un effet 3D pour le “Wow effect”.

Une solution :

En opengl il y a un “mode” qui permet d’aplatir toutes les perspectives et d’ainsi travailler en deux dimensions (glOrthof pour android) et c’est pratique. Seulement si vous voulez une couche de 3D en plus ben vous êtes niqué.

On utilise alors le mode glFrustumf! (à tes souhaits)

glFrustumf c’est la méthode qui permet de définir la perspective de votre scène 3D. Bien comprendre cette méthode vous facilitera la vie pour vous représenter les éléments en 3D calqués sur votre écran 2d.

Cette méthode attend six paramètres : left, right,  bottom,top, near, far. Les quatre premiers définissent le rapport de la fenêtre de vue de la scène. Cette fonction permet de définir le fenêtre de projection de l’univers 3D sur votre écran.

explications :

votre écran à un rapport largeur / hauteur (16/9, 4/3 etc…)  avec un glFrustumf(-1,1,-1,1,near,far); vous aurez une projection de ce type :

Il faut donc “calculer” une projection correcte de type :  glFrustumf(-(width/height),(width/height),-1,1,near,far);
Pour une unité de hauteur il ya (width/height) de largeur :  Pour un 16/9 ça fait bien, 1 de hauteur pour 1.7778 de largeur.

Ok pour le rapport, fastoche. Après ça, ce que je voulais c’était d’avoir une texture à 100% lorsqu’elle est en z = 0.
Il faut d’abord comprendre que par défaut le “point de vue” de la scène est en 0,0,0 dans le repère. Si vous arrivez à afficher un quelconque objet, pour le voir vous devez soit reculer la “caméra”, soit éloigner l’objet. Mais de combien dois-je reculer la caméra pour que mon objet occupe bien sa “taille réelle” ?

La précédente fenêtre qu’on a déterminée se positionne  en fait au paramètre “near” sur l’axe z :

exemple ici : glFrustumf(-(width/height),(width/height),-1,1,7,20);

Tout objet en dehors de la zone “couleur jambon” sur l’axe Z n’apparaitra pas. Maintenant, il faut réussir à placer l’objet bleu à la bonne position sur l’axe Z pour qu’il s’imprime à 100% sur l’écran. Un peu de trigonométrie suffit :

L’objet est un plan d’une unité (1×1), une texture de 64×64 est mappée dessus.

mon écran fait “height” en hauteur pour “1″ unité (rappelez vous le glFrustumf à 1 pour la hauteur).
le rapport de la texture est (64 / height ) pour une unité.

On calcule l’angle de l’origine vers l’objet  :
ang = Math.atan2( ( 64 / height)) , near);
puis avec l’angle on calcul le z.

z =  1/Math.tan(ang);

Du coup si on recule la camera de -z, la texture fera pile poil 64×64 sur votre écran.


Jul 04 2010

Flying Saucer Android release

Tag: android,Eclipse,Jeux indépendants,jeux vidéoTurbo Connard @ 19:39

Je viens de poster dans les downloads de agd une première version de “flying saucer” : le super mini jeu qui se termine en 20 secondes.

J’ai développé ce prototype pour être confronté à de vrais problèmes lors de sa réalisation et ça a porté ces fruits, j’ai dû ajouter plein de trucs relous. Je pense que ce jeu en restera la. Certes, douze ennemis à tuer c’est pas forcément le jeu le plus fun de la terre mais il permet d’appréhender des logiques simple de développement. C’est pourquoi bien sûr, je fournis les sources.

Voilà vous pouvez essayer de compiler ce jeu et de vous amuser pendant au moins une minute !

 

 

 

 


Next Page »