PV3Dで某ゲームを3D化する試作

PV3D でスリーディースリディーしたのを作るのも楽しいけど、2D 的なものを 3D に持ってきて表現するのも面白いかなーと思って作ってみた。


後悔はしていない。操作は特にできなくてクリックしたら跳ねるだけ。○天堂さんに怒られたら消す。

ネタとしては未完成な感じだが、PV3D でのカメラ+Tweener の扱いが少し分かったので実りはあった。

PV3D のバージョンは 2.0.883

ソースはこれ。wonderfl にも post してる

// PV3D で某ゲームを3D化してみた
// 
// [遊び方]
// ・クリックでジャンプする
// ・それだけ
package {
import flash.events.Event;
import flash.text.TextField;
import org.papervision3d.view.*;
import caurina.transitions.Tweener;

[SWF(backgroundColor="#000000", width="475", height="475")]
public class MaOio3dTest extends BasicView {
    // character
    private var character:Character;

    // ジャンプ中の状態
    private var jump:Boolean;
    private var prevJump:Boolean;

    // アニメーションの設定
    private var animateIndex:int;
    private var animateParams:Array = [
        { x: -700, y: 120, z: -300, focus: 50, time: 6 },
        { x: -70, y: 220, z: -100, focus: 8, time: 4 },
        { x: 300, y: 40, z: -80, focus: 50, time: 5, transition: 'easeInOutSine' },
        { x: 0, y: 0, z: -300, focus: 10, time: 4, transition: 'easeInOutSine' }
    ];

    public function MaOio3dTest(){
        super(475, 475, false);
        stage.scaleMode = "noScale";
        stage.align = "TL";

        // 画像を変換する
        Map.base64ToBitmapData(init);
    }

    private function init():void{
        // マップとキャラクタを準備する
        Map.createMap(scene);
        scene.addChild(character = new Character());

        // カメラの初期位置を設定
        camera.focus = 1000;
        camera.z = -20000;

        var tf:TextField = new TextField();
        tf.textColor = 0xffffff;
        tf.text = "CLICK TO START";
        tf.x = tf.y = 50;
        addChild(tf);

        // アニメーション
        var initialized:Boolean = false;
        stage.addEventListener("keyDown", function(event:Event):void{jump = true;});
        stage.addEventListener("click", function(event:Event):void{
            if (!initialized){
                // 初期化
                removeChild(tf);
                startRendering();
                animate();
                initialized = true;
            }

            jump = true;
        });
    }

    // Tweener を利用したアニメーションを行う
    private function animate():void{
        var param:Object = animateParams[animateIndex];
        param.onComplete = animate;
        param.delay = 2; // 必ず2秒停止する
        Tweener.addTween(camera, param);
        animateIndex = (animateIndex + 1) % animateParams.length;
    }

    // BasicView の描画処理
    protected override function onRenderTick(e:Event = null):void{
        super.onRenderTick(e);

        // キャラクターの状態を更新する
        if (prevJump && jump){
            jump = false;
        }
        prevJump = jump;
        character.update(jump);
    }
}
}


import flash.display.*;
import flash.events.Event;
import flash.geom.*;
import flash.utils.ByteArray;
import org.papervision3d.objects.primitives.*;
import org.papervision3d.materials.*;
import org.papervision3d.core.proto.*;
import org.papervision3d.objects.*;
import org.papervision3d.objects.primitives.*;
import org.papervision3d.materials.utils.MaterialsList;
import mx.utils.Base64Decoder;

// キャラクタ クラス
class Character extends Plane{
    // 画像一覧
    private var characterImages:Array;

    // キャラクタ用の Sprite
    private var character:Sprite;

    // 表示情報
    private var jumping:Boolean;
    private var vy:int;

    // 主人公の実装
    public function Character(){
        super(null, 17, 16);

        // 画像を準備
        var bmd1:BitmapData = new BitmapData(16, 16, true);
        var bmd2:BitmapData = new BitmapData(17, 16, true);
        bmd1.copyPixels(Map.bmdIcons, new Rectangle(240, 0, 16, 16), new Point());
        bmd2.copyPixels(Map.bmdIcons, new Rectangle(256, 0, 17, 16), new Point());
        bmd1.threshold(bmd1, bmd1.rect, new Point(), "==", 0xffffffff, 0, 0xffffffff); // 背景透過
        bmd2.threshold(bmd2, bmd2.rect, new Point(), "==", 0xffffffff, 0, 0xffffffff);
        characterImages = [new Bitmap(bmd1), new Bitmap(bmd2)];

        // character に追加
        character = new Sprite();
        for each (var img:Bitmap in characterImages){
            character.addChild(img);
            img.visible = false;
        }
        setImage(0);

        // Plane の設定
        material = new MovieMaterial(character, true, true);
        x = -64;
        y = -40;
        z = -8;
    }

    // 状態を更新
    public function update(jump:Boolean):void{
        if (jump && !jumping){
            vy = 9;
            jumping = true;
        }

        if (jumping){
            y += vy;
            if (vy < -8){
                vy = 0;
                jumping = false;
            }
            vy--;
        }
        setImage(jumping ? 1 : 0);
    }

    // 表示する画像(静止・ジャンプ中)を更新する
    private function setImage(num:int):void{
        for (var i:int = 0; i < characterImages.length; i++){
            characterImages[i].visible = (num == i);
        }
    }
}

// マップデータ
class Map{
    // マップの種類を定義
    private static var mapTypeIndex:int = 0;
    private static const SKY:int = 0;   // Sky
    private static const GRD:int = 1;   // Ground
    private static const BLQ:int = 2;   // Block question
    private static const MT0:int = 3;   // Mountain left
    private static const MT1:int = 4;   // Mountain top
    private static const MT2:int = 5;   // Mountain right
    private static const MT3:int = 6;   // Mountain with tree
    private static const MT4:int = 7;   // Mountain background
    private static const BU0:int = 8;   // Bush left
    private static const BU1:int = 9;   // Bush mid
    private static const BU2:int = 10;  // Bush right
    private static const CL0:int = 11;  // Cloud left top
    private static const CL1:int = 12;  // Cloud mid top
    private static const CL2:int = 13;  // Cloud right top
    private static const CL3:int = 14;  // Cloud left bottom

    // 画像を BASE64 化したもの
    private static const Icons:String = "R0lGODlhEQEQALMLAP/////Mmf+ZM/9mAP8zAMz/AJlmAGaZ/zPM/wCZAAAAAP///wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAARARAAAAT/8MhJ6xkh66ywHtcgjuRonZOCoupaJXAsx8ps325+KHyr/0BXD0AsGo+Alm+1aDoJ0KhzSqUKroLgIVAaKbjeAXZMvi6FCW3ivLrNeu64tqIo2AvsuZ6F7CNVCkk5VFGFUlWGBAtjWmBdXyQdAgplY5R5FjVpQTCYL3GdPKA4ejx3d557e4F+rUWsqQdUBrQGV7ZVTYmFV41dIpBhYyVksTsxap06o2uizDKlp6eAqtUUrthG1ChTtAK2t1hVteC25L6/Xx3AYlfAPCKWOZowycoucaJwz8hzddKo8FhTxSObwSSCTnQzB+6bOCfeGpajhe7RgB7rsABzdxFLKnr1/zjJSJVPE0h+0aQlkGZsIJ2DMBNaWNjwW61ZDLFIrFjiywZJHCkJ2GhmxclNQGx4KsmPVJB/BWDcWYnKpRZWMbNhqlJJwDhvOskt4BnJ0cV2Zih58cjC6Y8bmJg2nXF1KsBpVq9mxeYJpwECN3M16WqgCVkvZoEOu8iDbSY39t6cmDv3R4+7mAXuKNVDL4AD2BDwdUHzLy3AtCCSM326sOEgZstGQquxozw6ckTCtUC5qQ6ome8C0gwEq0zLREADEH2E+WhuTVYXEru6Fuq/Cyg+XSd7dqXGt1OAiqz0U29m84Kr/9dyh3Ei7SWwOoCgfpH69u/72Tr4FgEBUABYk/hN5BiSnQGNBAAPYj3RxlEZSxxFl25uLHHeMzmop6Ed2xi132bIGYEffsuN2AoT/V2RSCVR/PVfgKj5wkEPsZ1VBlGOHTMKebtJcCF6LAC3YWZ1fNRKIO0NcZ9zJTKpTV8p7vKfV7e4KKV2QDiiQTDeCeWRl5fIhyEFGPFgHigt/LjjCUIOuZ4QSDgZ3w5/7AdfLIRIUUhpUsYI2yOxwdMVhDoCuc9IZh7Ajwpqjsemm256glV+y2njD1aAGGdmooOollp0gRWYCBVaMIYRBmV2kOqqGDXlDJoSVthobo9C+iYfRDh5xJwpcOqer0B4+umBN6VmYC4RAAA7";
    public static var bmdIcons:BitmapData;

    // マップ配置
    private static const map:Array = [
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, MT0, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, MT0, MT3, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, MT1, MT3, MT4, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, MT2, MT3, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, MT2, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, GRD, GRD],
        [SKY, SKY, SKY, CL1, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, GRD, GRD],
        [SKY, SKY, CL0, CL2, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, GRD, GRD],
        [SKY, SKY, SKY, CL3, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, BU0, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, BU1, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, BU1, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, BU2, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, BLQ, SKY, SKY, MT0, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, MT1, MT3, GRD, GRD],
        [SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, SKY, MT2, GRD, GRD]
    ];

    private static var materialCache:Object = {};

    // 指定したタイプのマテリアルを取得する。
    public static function getMaterial(mapType:int):MaterialObject3D{
        if (materialCache[mapType]) return materialCache[mapType];

        var bmd:BitmapData = new BitmapData(16, 16);
        bmd.copyPixels(bmdIcons, new Rectangle(mapType * 16, 0, 16, 16), new Point());
        materialCache[mapType] = new BitmapMaterial(bmd);
        return materialCache[mapType];
    }

    // scene にマップを追加する
    public static function createMap(scene:DisplayObjectContainer3D):void{
        for (var xx:int = 0; xx < map.length; xx++){
            for (var yy:int = 0; yy < map[yy].length; yy++){
                var material:MaterialObject3D = getMaterial(map[xx][yy]);

                var obj:DisplayObject3D;
                if (map[xx][yy] == GRD || map[xx][yy] == BLQ){
                    obj = new Cube(new MaterialsList({all: material}), 16, 16, 16);
                    obj.z = -8;
                } else {
                    obj = new Plane(material, 16, 16);
                    obj.z = 0;
                }
                obj.x = xx * 16 - 8 - 128;
                obj.y = -yy * 16 + 8 + 128;
                scene.addChild(obj);
            }
        }
    }

    // BASE64 化解除
    public static function base64ToBitmapData(callback:Function):void{
        var decoder:Base64Decoder = new Base64Decoder();
        decoder.decode(Icons);

        var bytes:ByteArray = decoder.toByteArray();
        bytes.position = 0;
        var loader:Loader = new Loader();
        loader.loadBytes(bytes);
        loader.contentLoaderInfo.addEventListener("complete", function(event:Event):void{
            bmdIcons = new BitmapData(loader.width, loader.height);
            bmdIcons.draw(loader);
            callback();
        });
    }
}