Cart
スリックカート (Box2D版)
[↑] アクセル
[←] [→] ステアリング
[↓] ブレーキ
[SPC] リセット
/**
* Copyright flashrod ( http://wonderfl.net/user/flashrod )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/8xrd
*/
// スリックカート (Box2D版)
// [↑] アクセル
// [←] [→] ステアリング
// [↓] ブレーキ
// [SPC] リセット
package {
import Box2D.Collision.Shapes.b2CircleDef;
import Box2D.Collision.Shapes.b2PolygonDef;
import Box2D.Collision.b2AABB;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2World;
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.display.Shape;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.getTimer;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.display.Loader;
public class Cart2D extends Sprite {
// ビューポート
private static const VW:Number = 465;
private static const VH:Number = 465;
// ワールド
private static const W:Number = 465;
private static const H:Number = 3000;
private static const CX:Number = W/2;
private static const CY:Number = H/2;
// スタート・ゴールライン(ワールド座標系)
private static const START_Y:Number = H-50;
private static const GOAL_Y:Number = 50;
// パラメータ類
private static const A_FORCE:Number = 20000;
private static const S_FORCE:Number = A_FORCE * 0.7;
private static const B_DAMP:Number = 1.0;
private static const NB_DAMP:Number = 0.1;
// インタラクション
private static const VK_LEFT:int = 37;
private static const VK_UP:int = 38;
private static const VK_RIGHT:int = 39;
private static const VK_DOWN:int = 40;
private static const VK_SPC:int = 32;
/** アクセルを踏んでいるときは真 */
private var acl:Boolean = false;
/** ブレーキを踏んでいるときは真 */
private var brk:Boolean = false;
/** ステアリング(左:-90°,右:90°)(radian)*/
private var steering:Number = 0;
/** ワールド */
private var world:b2World;
/** 車モデル */
private var car:b2Body;
/** 壁モデル */
private var fixed:Array; // of b2Body
/** 障害物モデル */
private var float:Array; // of b2Body
/** ワールドビュー */
private var worldView:Sprite;
/** スタートからの経過時間(ミリ秒) */
private var time:int;
/** カート2D構築 */
public function Cart2D() {
var keyTableDOWN:Array = [];
keyTableDOWN[VK_UP] = function():void { acl = true; };
keyTableDOWN[VK_DOWN] = function():void { brk = true; };
keyTableDOWN[VK_LEFT] = function():void { steering =-Math.PI/2; };
keyTableDOWN[VK_RIGHT] = function():void { steering = Math.PI/2; };
keyTableDOWN[VK_SPC] = reset;
stage.addEventListener(KeyboardEvent.KEY_DOWN, function(e:KeyboardEvent):void {
var f:Function = keyTableDOWN[e.keyCode];
if (f != null) {
f();
}
});
var keyTableUP:Array = [];
keyTableUP[VK_UP] = function():void { acl = false; };
keyTableUP[VK_DOWN] = function():void { brk = false; };
keyTableUP[VK_LEFT] = function():void { steering = 0; };
keyTableUP[VK_RIGHT] = function():void { steering = 0; };
stage.addEventListener(KeyboardEvent.KEY_UP, function(e:KeyboardEvent):void {
var f:Function = keyTableUP[e.keyCode];
if (f != null) {
f();
}
});
// モデルを作る
this.world = createWorld(); // ワールドを作る
this.car = createCarBody(); // 車モデルを作る
this.fixed = createFixed(); // 壁を作る
this.float = createFloat(); // 障害物を作る
// ビューを作る
this.worldView = new Sprite();
addChild(worldView);
createBackground();
createFixedView();
createFloatView();
// 時間表示
var tf:TextField = new TextField();
var fmt:TextFormat = new TextFormat();
fmt.color = 0xFFFFFF;
fmt.font = 'Verdana';
tf.defaultTextFormat = fmt;
addChild(tf);
// 背景を塗る
graphics.beginFill(0x888888);
graphics.drawRect(0, 0, VW, VH);
graphics.endFill();
// 車ビューを作る
var carView:DisplayObject = createCarView();
worldView.addChild(carView);
car.SetUserData(carView); // 車モデルに車ビューを関連付け
addEventListener(Event.ENTER_FRAME, function(e:Event):void {
step();
if (countup) {
var t:int = getTimer() - time;
var s:int = int(t / 1000);
var ms:int = (t - s * 1000) / 10;
tf.text = s+'"'+ms;
}
});
}
/** リセット
*/
private function reset():void {
car.SetXForm(new b2Vec2(CX, START_Y+16), 0);
car.SetLinearVelocity(new b2Vec2());
car.SetAngularVelocity(0);
}
/** ワールドを作る
* @return ワールド
*/
private function createWorld():b2World {
var aabb:b2AABB = new b2AABB();
aabb.lowerBound.Set(0, 0);
aabb.upperBound.Set(W, H);
var gravity:b2Vec2 = new b2Vec2();
return new b2World(aabb, gravity, true);
}
/** 車モデルを作る
* @return 車モデル
*/
private function createCarBody():b2Body {
var def:b2BodyDef = new b2BodyDef();
def.position.Set(CX, START_Y+16);
def.angularDamping = 0.75; // 回転の減衰
var body:b2Body = world.CreateBody(def);
var rect:b2PolygonDef = new b2PolygonDef();
rect.SetAsBox(16, 16);
rect.density = 1; // 0:固定 kg/m^2
rect.restitution = 0.4; // 反発係数[0,1]
rect.friction = 0.1; // 摩擦[0,1]
body.CreateShape(rect);
body.SetMassFromShapes();
return body;
}
/** 車ビューを作る
* @return 車表示オブジェクト
*/
private function createCarView():DisplayObject {
var sprite:Sprite = new Sprite();
var image:Loader = new Loader();
image.load(new URLRequest("http://img.f.hatena.ne.jp/images/fotolife/f/flashrod/20100121/20100121222408.png?1264080327"));
image.x = -32/2;
image.y = -32/2;
sprite.addChild(image);
return sprite;
}
/** 動かない壁をつくる
*/
private function createFixed():Array {
var a:Array = [
{x:388, y:120, w:100, h:8, a:0.7853981633974483},
{x:73, y:120, w:100, h:8, a:-0.7853981633974483},
{x:124, y:764, w:116.25, h:8, a:0},
{x:343, y:1226, w:116.25, h:8, a:0},
{x:119, y:1612, w:116.25, h:8, a:0},
{x:348, y:2000, w:140, h:8, a:0.7853981633974483},
{x:116, y:2500, w:140, h:8, a:-0.7853981633974483},
{x:0, y:1500, w:8, h:1500, a:0},
{x:465, y:1500, w:8, h:1500, a:0},
{x:232, y:3000, w:232, h:8, a:0},
];
var list:Array = [];
for each (var o:* in a) {
var def:b2BodyDef = new b2BodyDef();
def.position.Set(o.x, o.y);
def.angle = o.a;
var body:b2Body = world.CreateBody(def);
var rect:b2PolygonDef = new b2PolygonDef();
rect.SetAsBox(o.w, o.h);
rect.restitution = 0.4;
rect.friction = 0.1;
body.CreateShape(rect);
body.SetUserData({w:o.w, h:o.h});
list.push(body);
}
return list;
}
/** [B2D]動く障害物をつくる
* @return 障害物リスト
*/
private function createFloat():Array {
var a:Array = [
{x:283, y:341, r:8},
{x:112, y:178, r:8},
{x:355, y:654, r:8},
{x:170, y:473, r:8},
];
var list:Array = [];
for each (var o:* in a) {
var def:b2BodyDef = new b2BodyDef();
def.position.Set(o.x, o.y);
def.linearDamping = 1.0;
def.angularDamping = 1.0;
var body:b2Body = world.CreateBody(def);
var circle:b2CircleDef = new b2CircleDef();
circle.density = 0.01;
circle.radius = o.r;
circle.restitution = 0.6;
circle.friction = 0.2;
body.CreateShape(circle);
body.SetMassFromShapes();
body.SetUserData({r:o.r});
list.push(body);
}
return list;
}
/** 壁を表示
*/
private function createFixedView():void {
for each (var b:b2Body in fixed) {
var o:* = b.GetUserData();
var shape:Shape = new Shape();
shape.graphics.lineStyle(1, 0x000000);
shape.graphics.beginFill(0xCCCCCC);
shape.graphics.drawRect(-o.w, -o.h, 2*o.w, 2*o.h);
shape.graphics.endFill();
var p:b2Vec2 = b.GetPosition();
shape.x = p.x;
shape.y = p.y;
shape.rotation = b.GetAngle() * (180 / Math.PI);
o.s = shape;
worldView.addChild(shape);
}
}
/** 障害物を作成
*/
private function createFloatView():void {
for each (var b:b2Body in float) {
var o:* = b.GetUserData();
var shape:Shape = new Shape();
shape.graphics.lineStyle(1, 0x000000);
shape.graphics.beginFill(0x444444);
shape.graphics.drawCircle(0, 0, o.r);
shape.graphics.endFill();
var p:b2Vec2 = b.GetPosition();
shape.x = p.x;
shape.y = p.y;
o.s = shape;
worldView.addChild(shape);
}
}
/** 障害物の位置を更新
*/
private function updateFloatView():void {
for each (var b:b2Body in float) {
var o:* = b.GetUserData();
var shape:Shape = o.s;
var p:b2Vec2 = b.GetPosition();
shape.x = p.x;
shape.y = p.y;
shape.rotation = b.GetAngle() * (180 / Math.PI);
}
}
/** 背景を作成
*/
private function createBackground():void {
for each (var j:int in [START_Y, GOAL_Y]) {
var shape:Shape = new Shape();
var k:int = 0;
for (var i:int = 0; i < W; i += 8) {
shape.graphics.beginFill(0xFFFFFF);
shape.graphics.drawRect(i, k, 8, 8);
shape.graphics.endFill();
k = k == 0 ? 8 : 0;
}
shape.x = 0;
shape.y = j;
worldView.addChild(shape);
}
}
/** 物理エンジンの1ステップを実行します
*/
private function step():void {
world.Step(1 / 9, 10);
// 車の位置
var p:b2Vec2 = car.GetPosition(); // 車の中心
var t:Number = car.GetAngle(); // 0が上向き
var vy:Number =-Math.cos(t); // (vx,vy)進行方向ベクトル
var vx:Number = Math.sin(t);
// アクセル
if (acl) {
// 力を働かせる点は車の後方
var rp:b2Vec2 = new b2Vec2(p.x - 16 *vx, p.y - 16 *vy);
car.ApplyForce(new b2Vec2(A_FORCE *vx, A_FORCE *vy), rp);
}
// ブレーキ
car.m_linearDamping = brk ? B_DAMP : NB_DAMP;
// ステアリング
if (steering != 0) {
// 力を働かせる点は車の前方
var sy:Number = S_FORCE *-Math.cos(t + steering);
var sx:Number = S_FORCE * Math.sin(t + steering);
var fp:b2Vec2 = new b2Vec2(p.x + 16 *vx, p.y + 16 *vy);
car.ApplyForce(new b2Vec2(sx, sy), fp);
}
// モデルをビューに反映する
var d:DisplayObject = DisplayObject(car.GetUserData());
d.x = p.x;
d.y = p.y;
d.rotation = car.GetAngle() * (180 / Math.PI);
updateFloatView();
// スクロール
//worldView.x = -(p.x - VW/2);
worldView.y = -(p.y - VH/2);
// 時間計測
countup = (GOAL_Y <= p.y && p.y < START_Y);
}
private var _countup:Boolean = false;
internal function get countup():Boolean { return _countup; }
internal function set countup(newValue:Boolean):void {
var oldValue:Boolean = this._countup;
this._countup = newValue;
if (!oldValue && newValue) { // false→true
time = getTimer();
}
}
}
}