群集シミュレーション
群集シミュレーション
黄色い人は画面下から上へ、緑の人は左から右へ移動
マウスクリックで壁を設置
カーソルキーで速度ベクトル表示のON/OFF切り替え
解説など:http://game.g.hatena.ne.jp/Nao_u/20091223
/**
* Copyright Nao_u ( http://wonderfl.net/user/Nao_u )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/m29Y
*/
//
// 群集シミュレーション
//
// 黄色い人は画面下から上へ、緑の人は左から右へ移動
//
// マウスクリックで壁を設置
// カーソルキーで速度ベクトル表示のON/OFF切り替え
//
// 解説など:http://game.g.hatena.ne.jp/Nao_u/20091223
//
package {
import flash.display.Sprite;
import flash.events.*;
[SWF(width="465", height="465", backgroundColor="0xFFFFFF", frameRate="60")]
public class FlashTest extends Sprite {
public function FlashTest() {
Main = this;
initialize();
stage.addEventListener(Event.ENTER_FRAME,update);
stage.addEventListener(KeyboardEvent.KEY_UP, keyCheckUp);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyCheckDown);
stage.addEventListener(MouseEvent.MOUSE_UP, MouseCheckUp);
stage.addEventListener(MouseEvent.MOUSE_DOWN, MouseCheckDown);
}
}
}
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
import flash.text.TextField;
import flash.geom.*;
import flash.utils.getTimer;
import frocessing.color.ColorHSV;
var Main:Sprite;
var SCREEN_W:Number = 465;
var SCREEN_H:Number = 465;
var Text:TextField
var T:Number = 0.5;
var Mode:int = 0;
var AgentAry:Vector.<Agent> = new Vector.<Agent>;
var Grid:Vector.<Array>;
var GridSize:int = 16;
var GridLength:int = (SCREEN_W / GridSize)+2;
var Wall:Vector.<int>;
var GridVec:Vector.<Point>;
var GridVecDisp:Boolean = false;
function initialize():void{
Text = new TextField();
Text.text = "----";
Text.autoSize = "left";
Main.addChild(Text);
graphicClear();
initWall();
updateGrid();
var num:int = 380;
for( var i:int=0; i<num; i++ ){
var dir:Number;
var target:Point = new Point;
if( i < num/2 ) { dir =Math.PI; target.x = SCREEN_W/2-15; target.y = -SCREEN_H/4; }
else { dir =Math.PI/2; target.x = SCREEN_W*1.25; target.y = SCREEN_H/2+15;}
//dir =Math.PI*0.5;
AgentAry.push( new Agent( new Point(0+(i/25)*35, 0+((i%5)*55)), dir, target ) );
AgentAry[i].update();
}
}
function update(e :Event):void{
var time:int = getTimer();
graphicClear();
drawWall();
updateGrid();
for( var i:int=0; i<AgentAry.length; i++ ){
AgentAry[i].update();
}
updateClick();
if( KeyPrev == 0 && KeyData != 0 ){
if( GridVecDisp == true ) GridVecDisp = false;
else GridVecDisp = true;
}
KeyPrev = KeyData;
drawGridVec();
var endTime:int = getTimer() - time;
Text.text = "生成時間:" + endTime + "[ms]";
updateKey();
MouseUpdate();
}
function updateClick():void{
var mx:Number = Main.stage.mouseX;
var my:Number = Main.stage.mouseY;
if( MouseData & MOUSE_LEFT_TRG ){
var idx:int = int(mx/GridLength)+1+ (int(my/GridLength)+1)*GridSize;
if( Wall[idx] == 0 ) Wall[idx] = 1;
else Wall[idx] = 0;
}
}
function initWall():void{
GridVec = new Vector.<Point>
Wall = new Vector.<int>
var idx:int;
for( var x:int=0; x<GridSize+2; x++ ){
for( var y:int=0; y<GridSize+2; y++ ){
idx = x + y*GridSize;
Wall.push( 0 );
GridVec.push( new Point() );
}
}
// Wall[117] = 1;
Wall[118] = 1;
Wall[119] = 1;
Wall[120] = 1;
// Wall[121] = 1;
Wall[123] = 1;
Wall[123+16] = 1;
Wall[123+32] = 1;
}
function drawWall():void{
var idx:int;
for( var x:int=1; x<GridSize; x++ ){
for( var y:int=1; y<GridSize; y++ ){
idx = x + y*GridSize;
if( Wall[idx] == 1 ){
var sx:Number = (x-1)*GridLength;
var sy:Number = (y-1)*GridLength;
var ex:Number = GridLength;
var ey:Number = GridLength;
drawRect( sx, sy, ex, ey, 0x808080 );
}
}
}
}
function getWallCenter( pos:Point ):Point{
var ret:Point = new Point;
ret.x = (int(pos.x/GridLength))*GridLength + GridLength/2;
ret.y = (int(pos.y/GridLength))*GridLength + GridLength/2;
return ret;
}
function updateGrid():void{
Grid = new Vector.<Array>
var idx:int;
for( var x:int=0; x<GridSize+2; x++ ){
for( var y:int=0; y<GridSize+2; y++ ){
idx = x + y*GridSize;
Grid.push( new Array() );
GridVec[idx].x *= 0.5;
GridVec[idx].y *= 0.5;
}
}
var agt:Agent;
for( var i:int=0; i<AgentAry.length; i++ ){
agt = AgentAry[i];
idx = agt.getIndex();
Grid[idx].push( agt );
}
}
function addGridVec( agt:Agent ):void{
var idx:int = agt.getIndex();
GridVec[idx].x += agt.PrevVec.x;
GridVec[idx].y += agt.PrevVec.y;
}
var target:Point = new Point;
function appryGridVec( agt:Agent ):void{
var idx:int = agt.getIndex();
target.x = GridVec[idx].x + agt.Pos.x;
target.y = GridVec[idx].y + agt.Pos.y;
if( Math.abs(GridVec[idx].x) > 0.01 || Math.abs(GridVec[idx].y) > 0.01 ){
agt.rotToTarget( target, 0.6 );
}
}
function drawGridVec():void{
if( GridVecDisp == false ) return;
var idx:int;
for( var x:int=1; x<GridSize; x++ ){
for( var y:int=1; y<GridSize; y++ ){
idx = x + y*GridSize;
var sx:Number = (x-1)*GridLength+GridLength*0.5;
var sy:Number = (y-1)*GridLength+GridLength*0.5;
var ex:Number = sx+GridVec[idx].x * 2.5;
var ey:Number = sy+GridVec[idx].y * 2.5;
drawLine( sx, sy, ex, ey, 1.4, 0xff0000 );
}
}
}
class Agent{
public var Sp:Sprite;
public var isEnable:Boolean = false;
public var Pos:Point = new Point;
public var PrevPos:Point = new Point;
public var PrevVec:Point = new Point;
public var SpdVec:Point = new Point;
public var MoveVec:Point = new Point;
public var NearVel:Point = new Point;
public var TargetPos:Point = new Point;
public var WallTargetPos:Point = new Point;
public var DirVec:Point = new Point;
public var Spd:Number;
public var Rot:Number;
public var Color:int;
public var WallHitCnt:int;
public function Agent( p:Point, r:Number, t:Point ){
Sp=new Sprite();
Pos = p;
PrevPos.x = Pos.x;
PrevPos.y = Pos.y;
Sp.x = Pos.x;
Sp.y = Pos.y;
Main.stage.addChild(Sp);
Rot = r;
Spd = 1;
TargetPos = t;
while( Rot > Math.PI*2 ) Rot -= Math.PI*2;
//if( Rot > Math.PI/2 && Rot < Math.PI/2*3 )
if( Rot > Math.PI/2 )
Color = 0xf0e800;//new ColorHSV( (Rot % (2*Math.PI))*36, 0.5, 1).value32;
else Color = 0xb0e800;
}
public function update( ):void{
var reset:Boolean = false;
if( Spd < 1.5 ) Spd += 0.004;
SpdVec.x = Spd * Math.sin( Rot );
SpdVec.y = Spd * Math.cos( Rot );
if( Pos.x < -15 ) { Pos.x += SCREEN_W+30; reset = true; }
if( Pos.y < -15 ) { Pos.y += SCREEN_H+30; Pos.x = Pos.x * 0.25 + 160; reset = true; }
if( Pos.x > SCREEN_W+15 ) { Pos.x -= SCREEN_W+30; Pos.y = Pos.y * 0.25 + 200; reset = true; }
if( Pos.y > SCREEN_H+15 ) { Pos.y -= SCREEN_H+30; reset = true; }
Sp.x = Pos.x;
Sp.y = Pos.y;
checkWall();
checkNear();
var dirVec:Point = new Point;
if( !(NearVel.x == 0.0 && NearVel.y == 0.0 ) ) normalize( NearVel );
dirVec.x = SpdVec.x;
dirVec.y = SpdVec.y;
normalize( dirVec );
var c:Number = cross( dirVec, NearVel );
Rot -= c*0.6;
Pos.x += SpdVec.x + MoveVec.x*3;
Pos.y += SpdVec.y + MoveVec.y*3;
drawCircle( Pos.x, Pos.y, 5, Color );
var vec:Point = new Point;
vec.x = SpdVec.x + MoveVec.x*0.05;
vec.y = SpdVec.y + MoveVec.y*0.05;
normalize( vec );
DirVec.x += (vec.x - DirVec.x) * 0.10;
DirVec.y += (vec.y - DirVec.y) * 0.10;
drawCircle( Pos.x+DirVec.x*5, Pos.y+DirVec.y*5, 1.4, 0x0 );
if( WallHitCnt > 0 ) {
WallHitCnt--;
rotToTarget( WallTargetPos, 0.6 );
}else{
rotToTarget( TargetPos, 0.6 );
}
MoveVec.x = 0;
MoveVec.y = 0;
checkWall();
Pos.x += MoveVec.x*3;
Pos.y += MoveVec.y*3;
if( reset == false ){
PrevVec.x = (Pos.x - PrevPos.x) + MoveVec.x*3;
PrevVec.y = (Pos.y - PrevPos.y) + MoveVec.y*3;
addGridVec( this );
}
PrevPos.x = Pos.x;
PrevPos.y = Pos.y;
appryGridVec( this );
}
public function checkNear():void{
var idx:int;
var lp:int;
var ar:Array;
var ag:Agent;
NearVel.x = 0;
NearVel.y = 0;
for( var x:int=-1; x<2; x++ ){
for( var y:int=-1; y<2; y++ ){
idx = getIndexOfs(x,y);
ar = Grid[idx];
lp = ar.length;
for( var i:int=0; i<lp; i++ ){
ag = ar[i];
if( ag != this ){
var dist:Number = getDist( this.Pos, ag.Pos );
if( dist < 50 ){
if( dist < 25 ){
NearVel.x += ag.SpdVec.x;
NearVel.y += ag.SpdVec.y;
}
var dirVec:Point = new Point;
var dirVecAg:Point = new Point;
var targetVec:Point = new Point;
targetVec.x = ag.Pos.x - this.Pos.x;
targetVec.y = ag.Pos.y - this.Pos.y;
dirVec.x = SpdVec.x;
dirVec.y = SpdVec.y;
dirVecAg.x = ag.SpdVec.x;
dirVecAg.y = ag.SpdVec.y;
normalize( targetVec );
normalize( dirVec );
normalize( dirVecAg );
var spdDot:Number = dot( dirVec, dirVecAg );
var d:Number = dot( targetVec, dirVec );
if( d > 0 ){
var c:Number = cross( targetVec, dirVec );
Rot -= c*0.030;
if( dist < 18 ){
Rot -= c*0.005;
if( d > 0.40 ){
if( spdDot < 0.4 ) Spd *= 0.90;
Rot -= c*0.5+0.001;
}
}
}
if( dist < 9 ){
leave( ag, 0.25 );
if( spdDot < 0.95 )
{
if( d > 0.2 ) Spd *= 0.98;
else Spd *= 0.99;
}
}else if( dist < 13 ){
leave( ag, 0.12 );
if( spdDot < 0.5 )
{
if( d > 0.2 ) Spd *= 0.99;
else Spd *= 0.999;
}
}else if( dist < 20 ){
leave( ag, 0.035 );
}else if( dist < 30 ){
leave( ag, 0.01 );
}
}
}
}
}
}
}
public function checkWall():void{
checkWallPos( Pos.x, Pos.y );
checkWallPos( Pos.x, Pos.y-6 );
checkWallPos( Pos.x, Pos.y+6 );
checkWallPos( Pos.x-6, Pos.y );
checkWallPos( Pos.x+6, Pos.y );
checkWallPos( Pos.x-6, Pos.y-6 );
checkWallPos( Pos.x-6, Pos.y+6 );
checkWallPos( Pos.x+6, Pos.y-6 );
checkWallPos( Pos.x+6, Pos.y+6 );
}
public function checkWallPos( px:Number, py:Number ):void{
var idx:int = getIndexPos(px, py);
if( Wall[idx] != 0 ){
var wallPos:Point = getWallCenter( new Point(px,py) );
var vec:Point = new Point;
vec.x = wallPos.x - px;
vec.y = wallPos.y - py;
normalize( vec );
this.MoveVec.x -= 0.125 * vec.x;
this.MoveVec.y -= 0.125 * vec.y;
WallHitCnt = 50;
WallTargetPos.x = Pos.x - vec.x * 100;
WallTargetPos.y = Pos.y - vec.y * 100;
}
}
public function leave( ag:Agent, pow:Number ):void{
var vec:Point = new Point;
vec.x = ag.Pos.x - this.Pos.x;
vec.y = ag.Pos.y - this.Pos.y;
normalize( vec );
this.MoveVec.x -= pow * vec.x;
this.MoveVec.y -= pow * vec.y;
ag.MoveVec.x += pow * vec.x;
ag.MoveVec.y += pow * vec.y;
}
private var dirVec:Point = new Point;
private var targetVec:Point = new Point;
public function rotToTarget( target:Point, pow:Number ):void{
dirVec.x = SpdVec.x;
dirVec.y = SpdVec.y;
normalize( dirVec );
targetVec.x = target.x - Pos.x;
targetVec.y = target.y - Pos.y;
normalize( targetVec );
var c:Number = cross( dirVec, targetVec );
Rot -= c*pow;
}
public function getIndex():int{
var idx:int = int(Pos.x/GridLength)+1+ (int(Pos.y/GridLength)+1)*GridSize;
// if( idx < 0 || idx > (GridSize+1)*(GridSize+1) ) Text.text = "ERR " + idx +"x:"+Pos.x +" y:"+Pos.y ;
return idx;
}
public function getIndexOfs( ofsX:int, ofsY:int ):int{
var idx:int = int(Pos.x/GridLength)+1+ofsX + (int(Pos.y/GridLength)+1+ofsY)*GridSize;
// if( idx < 0 || idx > (GridSize+1)*(GridSize+1) ) Text.text = "ERR " + idx +"x:"+Pos.x +" y:"+Pos.y ;
return idx;
}
public function getIndexPos( px:Number, py:Number ):int{
var idx:int = int(px/GridLength)+1+ (int(py/GridLength)+1)*GridSize;
// if( idx < 0 || idx > (GridSize+1)*(GridSize+1) ) Text.text = "ERR " + idx +"x:"+Pos.x +" y:"+Pos.y ;
return idx;
}
public function setEnable( flg:Boolean ):void{
if( flg == true && isEnable == false ){
Sp.graphics.clear();
Sp.graphics.lineStyle(1.4,0x000000);
Sp.graphics.beginFill(0xe0d000,1);
Sp.graphics.drawCircle(0,0,8.0);
Sp.graphics.endFill();
}else if( flg == false && isEnable == true ){
Sp.graphics.clear();
}
isEnable = flg;
}
}
function graphicClear():void{
Main.graphics.clear();
Main.graphics.lineStyle(1.2,0xb0b040);
Main.graphics.moveTo( SCREEN_W/2, 0 );
Main.graphics.lineTo( SCREEN_W/2, SCREEN_H );
Main.graphics.moveTo( 0, SCREEN_H/2 );
Main.graphics.lineTo( SCREEN_W, SCREEN_H/2 );
}
function drawRect(sx:Number, sy:Number, ex:Number, ey:Number, col:int ):void{
Main.graphics.lineStyle( 0, col );
Main.graphics.beginFill( col );
Main.graphics.drawRect( sx, sy, ex, ey );
Main.graphics.endFill();
}
function drawLine( sx:Number, sy:Number, ex:Number, ey:Number, size:Number, col:int ):void{
Main.graphics.lineStyle(size,col);
Main.graphics.moveTo( sx, sy );
Main.graphics.lineTo( ex, ey );
}
function drawCircle( x:Number, y:Number, size:Number, col:int ):void{
Main.graphics.lineStyle(1.0,0x000000);
Main.graphics.beginFill( col, 1 );
Main.graphics.drawCircle( x, y, size );
Main.graphics.endFill();
}
function drawCircleLine( x:Number, y:Number, size:Number, col:int ):void{
Main.graphics.lineStyle(1.0,0x000000);
// Main.graphics.beginFill( col, 1 );
Main.graphics.drawCircle( x, y, size );
// Main.graphics.endFill();
}
var KEY_UP:int = 0x01;
var KEY_DOWN:int = 0x02;
var KEY_LEFT:int = 0x04;
var KEY_RIGHT:int = 0x08;
var KEY_UP_TRG:int = 0x10;
var KEY_DOWN_TRG:int = 0x20;
var KEY_LEFT_TRG:int = 0x40;
var KEY_RIGHT_TRG:int = 0x80;
var KeyData:int;
var KeyPrev:int;
function keyCheckDown(event:KeyboardEvent):void {
switch (event.keyCode){
case Keyboard.UP: KeyData |= KEY_UP|KEY_UP_TRG; break;
case Keyboard.DOWN: KeyData |= KEY_DOWN|KEY_DOWN_TRG; break;
case Keyboard.LEFT: KeyData |= KEY_LEFT|KEY_LEFT_TRG; break;
case Keyboard.RIGHT: KeyData |= KEY_RIGHT|KEY_RIGHT_TRG; break;
}
}
function keyCheckUp(event:KeyboardEvent):void {
switch (event.keyCode){
case Keyboard.UP: KeyData &= ~KEY_UP; break;
case Keyboard.DOWN: KeyData &= ~KEY_DOWN; break;
case Keyboard.LEFT: KeyData &= ~KEY_LEFT; break;
case Keyboard.RIGHT: KeyData &= ~KEY_RIGHT; break;
}
}
function updateKey():void{
KeyData &= ~(KEY_UP_TRG|KEY_DOWN_TRG|KEY_LEFT_TRG|KEY_RIGHT_TRG);
}
var MOUSE_LEFT:int = 0x01;
var MOUSE_LEFT_TRG:int = 0x02;
var MouseData:int;
function MouseCheckDown(event:MouseEvent):void{
MouseData |= MOUSE_LEFT;
MouseData |= MOUSE_LEFT_TRG;
}
function MouseCheckUp(event:MouseEvent):void{
MouseData &= ~MOUSE_LEFT;
}
function MouseUpdate():void{
MouseData &= ~MOUSE_LEFT_TRG;
}
function normalize( a:Point ):void {
var dist:Number = Math.sqrt( a.x * a.x + a.y*a.y );
a.x /= dist;
a.y /= dist;
}
// 2点間の距離を求める
function getDist( a:Point, b:Point ):Number {
var dx:Number = a.x-b.x;
var dy:Number = a.y-b.y;
return Math.sqrt(dx*dx + dy*dy);
}
// 内積 (dot product) : a⋅b = |a||b|cosθ
function dot( a:Point, b:Point ):Number {
return (a.x * b.x + a.y * b.y);
}
// 外積 (cross product) : a×b = |a||b|sinθ
function cross( a:Point, b:Point ):Number {
return (a.x * b.y - a.y * b.x);
}