본문 바로가기

인지심리이론/실험 설계

[HTML5로 실험 자극 만들기] 무선적으로 움직이는 점 만들기

(완성 예제 : 크롬, 파이어폭스, IE9에서 볼 수 있습니다)


글 : 인지심리 매니아


지각심리학은 자극의 움직임이 어떻게 뉴런의 발화로 이어지는지 연구해왔다. 이런 실험은 보통 두 가지 유형의 자극을 사용한다. 하나는 기울어진 막대처럼 생긴 자극이고, 다른 하나는 무선적인 방향으로 움직이는 점 자극이다.

 

오늘은 무선적으로 움직이는 점들을 구현한 예제를 소개하고자 한다이 예제는 Codeflow라는 블로그에서 소개되어 있으며필자가 목적에 맞게 약간의 수정을 가했다.


 


구조도 


이 코드는 네 개의 object를 갖고 있다. 먼저 main system을 불러온다. System은 여러 개의 점(particle)을 불러내서 해당 좌표에 그린다. Particle은 각각 고유한 좌표와 속도(velocity)를 갖고 있다. 각각의 좌표는 Vector로 표현된다. Vectorxy좌표를 변수로 가지고 있으며, 좌표를 계산할 때 사용할 사칙연산 메소드를 가지고 있다.

 


소스 코드


index.html

<!DOCTYPE HTML>

<!-- http://codeflow.org/entries/2010/aug/22/html5-canvas-and-the-flying-dots/ -->

<html lang="en"><head>

<style type="text/css">

html,body{width:200px;height:200px;margin:0;padding:0;border:0;outline:0}

body{background-color:#fff}

span{position:absolute;left:5px;top:2px}

a{font-family:sans-serif;color:#88ca0c;text-decoration:none;font-weight:bold;cursor:pointer}</style>

<script type="text/javascript" src="gravity.js"></script>

</head>


<body onload="main()">

<canvas id="particles" width="200" height="200"></canvas></a>

</body>

</html>


 

gravity.js

var Vector = function(x, y){

    this.x = x;

    this.y = y;


    this.sub = function(other){

        return new Vector(

            this.x - other.x,

            this.y - other.y

        );

    }

    this.isub = function(other){

        this.x -= other.x;

        this.y -= other.y;

    }

    this.iadd = function(other){

        this.x += other.x;

        this.y += other.y;

    }

    this.length = function(){

        return Math.sqrt(this.x*this.x + this.y*this.y);

    }

    this.idiv = function(scalar){

        this.x /= scalar;

        this.y /= scalar;

    }

    this.zero = function(){

        this.x = 0;

        this.y = 0;

    }

    this.validate = function(){

        if(isNaN(this.x+this.y)){

            this.x = 0;

            this.y = 0;

        }

    }

}

            

var Particle = function(canvas){

    var x_speed = Math.random()*2;

    var y_speed = Math.sqrt(16-x_speed^2); // y 값을 계산해서 속력이 4가 되도록 한다.


if(Math.random() <0.5){

x_speed=-x_speed;

}

if(Math.random() <0.5){

y_speed=-y_speed;

}

    var bounce_damping = 1;

    this.velocity = new Vector(x_speed, y_speed)

    this.position = new Vector(

        Math.random() * canvas.width,

        Math.random() * canvas.height

        

    )


    this.step = function(){

        this.position.iadd(this.velocity);


        // border bounce

        if(this.position.x < 0){

            this.position.x = 0;

            this.velocity.x *= -bounce_damping;

        }

        else if(this.position.x > canvas.width){

            this.position.x = canvas.width;

            this.velocity.x *= -bounce_damping;

        }


        if(this.position.y < 0){

            this.position.y = 0;

            this.velocity.y *= -bounce_damping;

        }

        else if(this.position.y > canvas.height){

            this.position.y = canvas.height;

            this.velocity.y *= -bounce_damping;

        }


    }

    this.draw = function(context){

        context.beginPath();

        context.arc(this.position.x, this.position.y, 2.5, 0, Math.PI*2, false);

        context.fill();

    }

}


var System = function(amount, milliseconds){

    var canvas = document.getElementById('particles');

    var context = canvas.getContext('2d');

        

    var particles = [];

    for(var i=0; i<amount; i++){

        particles.push(new Particle(canvas));

    }

       

    setInterval(function(){

       

        // dot drawing style

context.clearRect(0, 0, canvas.width, canvas.height);

        context.globalCompositeOperation = 'lighter'; 

        context.fillStyle = 'rgba(0,0,0,0.9)';


        // nbody code acceleration accumulation

        for(var i=0, il=amount; i<il; i++){

            var a = particles[i];

            a.step();

            a.draw(context);

        }

    }, milliseconds);

}


var main = function(){

    var system = new System(15, 40);

};





설명

 

참고: 이 자바스크립트 코드는 객체를 활용하고 있다. 자바스크립트에서 객체를 활용하는 방법은 인터넷에 많이 소개되어 있으므로 이를 참고하기 바란다.

 

Vector

var Vector = function(x, y){

    this.x = x;

    this.y = y;

 

    this.sub = function(other){

        return new Vector(

            this.x - other.x,

            this.y - other.y

        );

    }

    this.isub = function(other){

        this.x -= other.x;

        this.y -= other.y;

    }

    this.iadd = function(other){

        this.x += other.x;

        this.y += other.y;

    }

    this.length = function(){

        return Math.sqrt(this.x*this.x + this.y*this.y);

    }

    this.idiv = function(scalar){

        this.x /= scalar;

        this.y /= scalar;

    }

    this.zero = function(){

        this.x = 0;

        this.y = 0;

    }

    this.validate = function(){

        if(isNaN(this.x+this.y)){

            this.x = 0;

            this.y = 0;

        }

    }

}

 

Vector xy좌표라는 두 개의 변수를 가지고 있으며, 좌표를 계산할 때 사용할 사칙연산 메소드를 갖고 있다. 예를 들어, 아래와 같은 경우 vec3=vec1-vec2 (-2,-2)가 된다.

var vec1 = new Vector(1, 2);

var vec2 = new Vector(3, 4);

var vec3 = vec1.sub(vec2);

 


Particle

var Particle = function(canvas){

    var x_speed = Math.random()*2;

    var y_speed = Math.sqrt(16-x_speed^2);          // y 값을 계산해서 속력이 4가 되도록 한다.

 

           if(Math.random() <0.5){

                     x_speed=-x_speed;

                                }

           if(Math.random() <0.5){

                     y_speed=-y_speed;

                                }

          

    var bounce_damping = 1;

    this.velocity = new Vector(x_speed, y_speed)

    this.position = new Vector(

        Math.random() * canvas.width,

        Math.random() * canvas.height

       

    )

 

    this.step = function(){

        this.position.iadd(this.velocity);

 

        // border bounce

        if(this.position.x < 0){

            this.position.x = 0;

            this.velocity.x *= -bounce_damping;

        }

        else if(this.position.x > canvas.width){

            this.position.x = canvas.width;

            this.velocity.x *= -bounce_damping;

        }

 

        if(this.position.y < 0){

            this.position.y = 0;

            this.velocity.y *= -bounce_damping;

        }

        else if(this.position.y > canvas.height){

            this.position.y = canvas.height;

            this.velocity.y *= -bounce_damping;

        }

 

    }

    this.draw = function(context){

        context.beginPath();

        context.arc(this.position.x, this.position.y, 2.5, 0, Math.PI*2, false);

        context.fill();

    }

}

 

Particle() x_speed y_speed라는 변수를 가진다. 변수들의 값은 무선적으로 결정되지만, sqrt(x_speed^2+y_speed^2)=4. , 속도(velocity) 4가 되도록 만들었다. , 점의 최초 좌표를 생성한다.

 

Step 메소드는 좌표에 velocity를 더해줘서 점이 한 프레임당 4만큼 이동하도록 만든다. 점이 화면 바깥으로 나가는 것을 방지하기 위해 경계선에 부딪힐 때 velocitybounce_damping(=-1 or +1)변수를 곱해줘서 방향이 반대로 향하도록 만들었다.

 

Draw 메소드는 해당 좌표에 작은 원을 그린다.

 


System

var System = function(amount, milliseconds){

    var canvas = document.getElementById('particles');

    var context = canvas.getContext('2d');

        

    var particles = [];

    for(var i=0; i<amount; i++){

        particles.push(new Particle(canvas));

    }

       

    setInterval(function(){

        // fading

/*

        context.globalCompositeOperation = 'source-out'; //source-out으로 바꿔서 점들의 꼬리를 제거했다

        context.fillStyle = 'rgba(100,100,100)';

        context.fillRect(0, 0, canvas.width, canvas.height);

*/

        // dot drawing style

context.clearRect(0, 0, canvas.width, canvas.height);

        context.globalCompositeOperation = 'lighter'; 

        context.fillStyle = 'rgba(0,0,0,0.9)';


        // nbody code acceleration accumulation

        for(var i=0, il=amount; i<il; i++){

            var a = particles[i];

            a.step();

            a.draw(context);

        }

    }, milliseconds);

}


System은 캔버스에 particle을 그린다. 먼저 particle이라는 배열을 만들고 particle instance들을 모두 저장한다. 그 다음 setinterval 명령어로 각 점들의 step draw 메소드를 실행시켜서 점들을 화면에 그린다.

 

 

Main 

var main = function(){

    var system = new System(15, 40);

};


Main system의 인스턴스를 만든다. 15는 점의 개수, 40 setinterval 명령어가 실행되는 시간 간격을 말한다. 예제의 경우 각 점들은 40 밀리세컨드 단위로 움직이게 될 것이다.