טיול באתר תלת ממדי בעזרת HTML5 ו CSS3 בלבד.

לאחר שהתודעתי ליכולות 3D שטמונות ב CSS3. לא יכולתי להתאפק ולנסות ליצור אתר בו ניתן להסתובב בגוף ראשון (FPS) ללא WebGL. ההבדל הגדול בשימוש בטכנולוגיה הזאת, בניגוד ל WebGL, הוא בכך שהאלמנטים בעולם ( הקירות בדוגמה שלי) הם למעשה DIV פשוט של HTML.
כך כל מי שיודע קצת HTML יכול לבנות אתר שהוא בעצם עולם שבו המבקר מסתובב בתלת מימד.
אכן הטכנולוגיה הזאת עדיין בחיתולים וישימה במלואה רק ב Chrome ו safari. אבל סביר להניח שבמהרה כל השאר יתמכו גם הם.
היתרון הגדול על פני WebGL. שהוא נתמך בספארי על IOS ( איפון ואיפד ).

לחץ על התמונה בכדי לעבור לדמו

3D HTML5 CSS3 FPS Movements

טיול באתר תלת ממדי בעזרת HTML5 ו CSS3 בלבד


להורדת הקוד לחץ פה
להתעללות בקוד דרך FIDDLE לחץ פה

בואו נביט תחילה על ה HTML:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>3D CSS First Person look by Ronen Tsamir</title>
    <meta name="author" content="Ronen Tsamir" />
    <link rel="stylesheet" href="world.css" />
    <link rel="stylesheet" href="style.css" />
    <script type="text/javascript" src="src/RtUtils.js"></script>
    <script type="text/javascript" src="src/math/gl-matrix.js"></script>
    <script>
       if(!RTUtils.isBrowser(["chrome","safari"]))
           window.location = "fallback.html"
    </script>
</head>
<body>
<div id="wrapper">
<div class="viewport" contentEditable="false">
    <section class="cube" id="world">
        <div class="top"></div>
        <"div class="front>
            <h1>
                <p>This 3D Room, FPS Style, build using HTML5 and CSS3</p>
                <p> perspective, 3D transform and Rotation via transition.</p>
                <p>No webGL</p>
            </h1>
        </div>
        <div class="back">
            <h2>Salvador Dali Wall</h2>
            <img src="assets/Dali2.jpg"  width="450" height="280"/>
            <img src="assets/Dali4.jpg"  width="450" height="280"/>
            <img src="assets/Dali8.jpg"  width="450" height="280"/>
            <img src="assets/Dali10.jpg"  width="450" height="280"/>
         </div>
        <div class="right">
            <img src="assets/me.jpg"  width="300" height="300"/>
            <h2><p>By Ronen Tsamir</p>
            <time>01th March 2012</time></h2>
            <a href="http://ronen.tsamir.net/?page_id=1282" target="_top"> To The Post</a> or
            <a href="http://jsfiddle.net/Wp5LW/" target="_blank">   Fiddle me </a>

        </div>
        <div class="left">
            <h2> Some others posts</h2>
            <a href="http://ronen.tsamir.net/?page_id=255" target="_blank"> <img src="assets/flash10api1.jpg"  width="300" height="200"/></a>

            <a href="http://ronen.tsamir.net/?page_id=1143" target="_blank"><img src="assets/bowGame.jpg"  width="300" height="200"/> </a>
             <a href="http://ronen.tsamir.net/?page_id=290" target="_blank"> <img src="assets/walls.jpg"  width="300" height="200"/> </a>
        </div>
        <div class="bottom"></div>
    </section>
</div>
</div>
    <script src="world.js"></script>
</body>
</html>
ניתן ליראות שמדובר ב HTML רגיל שמחובר אליו CSS רגיל style.css

ההבדל היחיד הוא שיש אלמנטים שמוצמד אליהם Class של כיוון ( top, front, back , right, left, bottom )
ה Classes האלה מוגדרים ב world.css
ה css הזה למעשה מאתחל את הפרמטרים של ההטלה הפרספקטיבית ומציב כל DIV במקום ובזוית שלו במרחב.

נסתכל לדוגמה בקיר הימני:

.left{
    height: 500px;
    width: 2000px;
    -webkit-transform:  rotateY(  90deg ) translateZ(-1000px );
    -moz-transform:  rotateY(  90deg ) translateZ(-1000px );
    -o-transform:  rotateY(  90deg ) translateZ(-1000px );
    transform:  rotateY(  90deg ) translateZ(-1000px );
}

במקטע הזה הקיר השמאלי מסתובב 90 מעלות על ציר ה Y ואז מוזז 1000 פיקסלים על ציר ה Z וכך הוא מגיע למקומו בחדר.
מאחר שההזזה על ציר ה Z נעשית לאחר הסיבוב, למעשה הקיר מוזז על ציר ה X של העולם, כלומר וקטור המיקום של הקיר בעולם הוא [1000,0,0]

כך ממקמים את כל הקירות של החדר במקום ( אפשר כמובן למקם עוד הרבה אלמנטים חוץ מהקירות, אני בחרתי במבנה של חדר סגור בשביל להשאיר את הדוגמה פשוטה)
מתקבל חדר שמידותיו הם 2000 על 2000 פיקסלים בגובה של 500 פיקסלים.

למעשה עד כאן יצרנו את מבנה העולם שלנו ומה שנשאר זה להוסיף את אפשרות ההסתובבות בעולם.

כאן מצטרף למשחק הקובץ world.js

/**
 * Created with JetBrains WebStorm.
 * User: Ronen
 * Date: 13/11/12
 * Time: 23:20
 * To change this template use File | Settings | File Templates.
 */


(function () {
   
/*  The View (Camera Matrix) */
var mWorldMatrix = mat4.identity();

var mCameraRotation = vec3.create();
/* The Camera Rotation around the Y axis set to 0 */
mCameraRotation[1] =0;

/* The Camera Location Vector */
var mCameraLoc = vec3.create();

/* The Camera Location along the x axis set to 700px; */
mCameraLoc[0] = 700;
/* The Camera Location along the z axis set to 500px; */
mCameraLoc[2]= 500;



/* get the world container div. */
var mView = window.document.getElementById("world");

/* Constant - rotation sensitivity */
var ROTATION_SENSITIVITY = 300;

/* Constant - movement sensitivity */
var MOVEMENT_SENSITIVITY = 2;

/* use for the mouse control */
var mIsMouseDown = false;
/* use for the moving control */
var touchX;
var touchY;
/* init events. */
RTUtils.addEvent('touchstart', touchStart, window);
RTUtils.addEvent('touchmove', touchMove, window);
RTUtils.addEvent('touchend', touchEnd, window);
RTUtils.addEvent('mousemove', mouseMove, window);
RTUtils.addEvent('mouseup',   mouseUp, window);
RTUtils.addEvent('mousedown',   mouseDown, window);


//-----------------------------------------------------------------
/* get the Transform Property for the current browser */
var el = document.createElement('div'),
    transformProps = 'transform WebkitTransform MozTransform OTransform msTransform'.split(' '),
    transformProp = support(transformProps);


    /**
     * get the Transform Property for the current browser
     *
     * @param {array} props Array of transform types
     *
     * @returns {string} The current browser transform property
     */


function support(props) {
    for(var i = 0, l = props.length; i < l; i++) {
        if(typeof el.style[props[i]] !== "undefined") {
            return props[i];
        }
    }
}

 //-----------------------------------------------------------------
    /**
     * Get the user mouse or touch location.
     * Determine the movement delta and call the move() method with the movement delta.
     *
     * @param {number} x X screen value
     * @param {number} y Y screen value
     *
     */

 function userMovement(x,y)
 {
     console.log(x + "," + y)
     var dX = x - touchX ;
     var dY = touchY - y;
     if(Math.abs(dX) <= 1)
         dX = 0;
     if(Math.abs(dY) <= 1)
         dY = 0;
     move(dY, dX);
     touchX = x;
     touchY = y;
 }

 //-----------------------------------------------------------------
    /**
     * Move the camera
     *
     * @param {number} speedMoveX  - the forword movment speed.
     * @param {number} speedRotationY - the rotation speed around the Y axis.
     *
     */

 function move(speedMoveX,speedRotationY)
 {
    /* Add the rotation speed on the Y axis to the camera rotation vector. */
    mCameraRotation[1] += speedRotationY / ROTATION_SENSITIVITY;
     /* Compute the speed vector and add it to the camera location. */
    mCameraLoc[2] += speedMoveX * MOVEMENT_SENSITIVITY * Math.cos(-mCameraRotation[1]);
    mCameraLoc[0] += speedMoveX * MOVEMENT_SENSITIVITY * Math.sin(-mCameraRotation[1]);
     /* Check collision with the room wall. */
    setRoomCollision(mCameraLoc);

     /* prepare the camera 3D matrix. */
    var aCameraMatrix3D = mat4.identity();
    mat4.rotateY(aCameraMatrix3D, mCameraRotation[1]);
    mat4.translate(aCameraMatrix3D, [mCameraLoc[0],0, mCameraLoc[2]]);
    var aRotationOnly = mat4.identity();
    mat4.rotateY(aRotationOnly, mCameraRotation[1]);

    var aViewMatrix = mat4.identity();
    mat4.rotateY(aViewMatrix, mCameraRotation[1]);
    aViewMatrix[12] = -aCameraMatrix3D[12];
    aViewMatrix[13] = -aCameraMatrix3D[13];
    aViewMatrix[14] = -aCameraMatrix3D[14]+735;

    /* create a String from the matrix; */
    var aMatrixString = mat4.strForCSS3(aViewMatrix);
     /* apply the Matrix on the World view. */
    mView.style[transformProp] = aMatrixString;
 }
 //-----------------------------------------------------------------

    /**
     * Collide with the room walls
     *
     * @param {vec3} pCameraLoc  - the location vector of the camera.
     *
     */


 setRoomCollision = function (pCameraLoc) {
     if(pCameraLoc[2] > 950)
         pCameraLoc[2] = 950;
     if(pCameraLoc[2] < -950)
         pCameraLoc[2] = -950;
     if(pCameraLoc[0] > 1650)
         pCameraLoc[0] = 1650;
     if(pCameraLoc[0] < -250)
         pCameraLoc[0] = -250;
 };

    /**
     * All the next 6 method are regular method for touch & mouse control
     */


//-----------------------------------------------------------------
function touchStart(e)
{
    var touch = event.touches[0];
    touchX = touch.pageX;
    touchY = touch.pageY;
}
//-----------------------------------------------------------------
function touchMove(e)
{
    console.log("touchMove")
    e.preventDefault();
    var touch = event.touches[0];
    //console.log("Touch x:" + touch.pageX + ", y:" + touch.pageY);
    userMovement(touch.pageX,touch.pageY)

}
//-----------------------------------------------------------------
function touchEnd(e){}
//-----------------------------------------------------------------
function mouseMove(ev)
{
    if(!mIsMouseDown)
        return;
    ev.preventDefault();
    userMovement(ev.clientX, ev.clientY);
}
//-----------------------------------------------------------------

function mouseUp(e)
{
    e.preventDefault();
    mIsMouseDown = false;
}
//-----------------------------------------------------------------

function mouseDown(ev)
{
    console.log("mouseDown");
    ev.preventDefault();
    touchX = ev.clientX;
    touchY = ev.clientY;
    mIsMouseDown = true;

}
move(0,0);
})();

אין באפשרותי להסביר פה כל שורה ( יש תיעוד סביר בגוף הקוד ) אבל אתמקד בכמה עקרונות חשובים.

למעשה לכל אפליקציית טיול בגוף ראשון בעולם כלשהו יש שני מרכיבים עיקריים העולם ו…..אני. (כן כמו בחיים)
העולם מיוצג על ידי מבנה כלשהו, במקרה שלנו אוסף של DIVS
והמצלמה תמיד מיוצגת על ידי מטריצה 4X4. השומרת את מצב המצלמה בעולם.
המשתמש בדרך כלל משנה את המטריצה של המצלמה ( במקרה שלנו על ידי תנועת אצבע בניידים או עכבר במחשב )
וכל פעם שהמטריצה משתנה אנו מטילים אותה על העולם.

קודם כל יש צורך לקבל מצביע לעולם שלנו. וזאת אני עושה בשורה הבאה:

var mView = window.document.getElementById("world");
המתודה הבאה למעשה מחשבת את המטריצה של המצלמה ומטילה אותה על העולם:
כאשר speedMoveX ו speedRotationY מתקבלים מתוך התנועה של המשתמש.

 function move(speedMoveX,speedRotationY)
 {
    /* Add the rotation speed on the Y axis to the camera rotation vector. */
    mCameraRotation[1] += speedRotationY / ROTATION_SENSITIVITY;
     /* Compute the speed vector and add it to the camera location. */
    mCameraLoc[2] += speedMoveX * MOVEMENT_SENSITIVITY * Math.cos(-mCameraRotation[1]);
    mCameraLoc[0] += speedMoveX * MOVEMENT_SENSITIVITY * Math.sin(-mCameraRotation[1]);
     /* Check collision with the room wall. */
    setRoomCollision(mCameraLoc);

     /* prepare the camera 3D matrix. */
    var aCameraMatrix3D = mat4.identity();
    mat4.rotateY(aCameraMatrix3D, mCameraRotation[1]);
    mat4.translate(aCameraMatrix3D, [mCameraLoc[0],0, mCameraLoc[2]]);
    var aRotationOnly = mat4.identity();
    mat4.rotateY(aRotationOnly, mCameraRotation[1]);

    var aViewMatrix = mat4.identity();
    mat4.rotateY(aViewMatrix, mCameraRotation[1]);
    aViewMatrix[12] = -aCameraMatrix3D[12];
    aViewMatrix[13] = -aCameraMatrix3D[13];
    aViewMatrix[14] = -aCameraMatrix3D[14]+735;

    /* create a String from the matrix; */
    var aMatrixString = mat4.strForCSS3(aViewMatrix);
     /* apply the Matrix on the World view. */
    mView.style[transformProp] = aMatrixString;
 }

אפשר די בקלות לעקוב שלב אחרי שלב ולראות איך אני יוצר את המטריצה.
השורה האחרונה היא החשובה ביותר בה למעשה מוטלת המטריצה על העולם…… וקסם התנועה מתרחש.

לסיכום אני רוצה להודות לינון פרק שללא הקורס הנהדר שלו שחשף אותי לנפלאות ה HTML5, במיוחד במובייל,. לא הייתי מחבר את כל הקצוות למדריך זה.

HTML5 למפתחי ActionScript3

HTML5 for AS3 Develpersבסדרת שיעורים זו אסביר על ספרייה גרפית קטנה שפיתחתי ל HTML5. הספרייה כתובה כמובן ב JavaScript ומוסיפה את האלמנטים הגרפים Shape, Sprite ו ImageLoader הקיימים ב AS3.
הספרייה נכתבה במטרה להקל על מעבר של מפתחי פלאש ל HTML5, ועל תרגום תוכן פלאשי ל HTML5.
את הספרייה ניתן להוריד מכאן.
קוד למשחקון דוגמא ב HTML5 שיצרתי תוך שימוש בספרייות הללו ניתן להוריד מכאן,
להרצת המשחק לחץ כאן

אז למה בכלל לעבור ל HTML5 כאשר יש כלי נפלא כפלאש על צורותיו השונות?
HTML5 קיבל משנה תוקף כדבר הבא לאחר ההכרזה של ADOBE על אי התמיכה בפלאש בדפדפני הטאבלטים והסמארטפונים. בנוסף ADOBE עצמה רואה ב HTML5 את עתיד ה RIA. בהודעה הזו אני רואה כתחילת הסוף של הפלאש כתוסף חובה בכל דפדפן וככלי פיתוח ל WEB.
סביר להניח שייקח זמן רב עד שהפלאש ב WEB ייעלם. בכל מקרה הפלאש ממשיך להיות איתנו ככלי פיתוח נהדר לפיתוח אפליקציות חוצה פלטפורמות.
בנוסף, לא יכולתי לעמוד בפני הקסם של עוד פלטפורמה גראפית, עוד סביבת פיתוח למישחקים ו RIA, שלא להזכיר את ה WebGL (עוד אעלה פה דוגמאות בהמשך) .

כל הסיבות הללו גרמו לי לצלול לתוך ה API של HTML5

אכן ה JavaScript רחוקה מאוד מ AS3 ואפילו ל AS2 אין היא מתקרבת. אבל מול היתרון של לרוץ בדפדפנים על כל הפלטפורמות קשה לעמוד. ולכן, מוצאים עצמם הרבה מפתחי פלאש עוברים ל HTML5.

הבעיה היא שהתפיסה הגרפית שעומדת בבסיס ה CANVAS שונה מאוד מהפלאש. אין אלמנטים גרפים, אין AddChild ואין MovieClip. למעשה אפילו אנימציית gif לא רצה על Canvas.
אז מה עושים? אם אין בונים!

כאשר בספרייה שבניתי מחלקת הבסיס היא DisplayObject.Shape וממנה יורשים DisplayObject.Sprite ו DisplayObject.ImageLoader למי שתוהה, אז כן יש ל JavaScript יכולות Object-Oriented לא רעות (לא טובות (-: אבל בהחלט אפשר לממש Object-Oriented Design Patterns גם ב JavaScrip ) אבל זה לא הנושא שלנו עכשיו).

את השימוש בספרייה שבניתי אסביר במדריכים הבאים.

שיעור ראשון – הכרת ה Stage ו ה Shape


שיעור שני – שימוש ב Sprite


שיעור שלישי – אנימציה ותמונות בעזרת ImageLoader