Ceci est une ancienne révision du document !
Daphné Chamot-Rooke (contact : daphne.chamot-rooke@etu.upmc.fr)
Himany Seri (contact : himany.seri@etu.upmc.fr)
Début : Février 2019
Fin : mai 2019
Objectif : créer un dispositif qui modifie la perception d'autrui
Matériel :
De nos jours l’étude des mouvements de foule permet de simuler la mécanique des foule : ses mouvements semblables à la physique des fluides et sa psychologie sociale. Dans de nombreuses situations nous avons affaire à des mouvements de foule, que ce soit dans des concerts, des conventions, des manifestations, et même sur les réseaux sociaux.
Comment l’individu influence-t-il la foule et comment celle-ci influence telle l’individu ? Comment initie-t-on un mouvement de foule ?
Nous avons décidé d'orienter notre projet sur comment percevoir la foule et comment *se* percevoir dans la foule. Les mécanismes de l'individu à la foule sont en effet des allers-retours, comme lors d'un croisement de regard.
Nous avions d'abord pensé à travailler sur les émotions, les émotions dans la foule. Nous avions eu l'idée d'un bracelet retranscrivant l'émotion globale de la foule. Puis nous nous sommes orientées vers des applications, car cela nous semblait plus réalisable pour travailler avec de nombreuses personnes. Nous avons choisi de créer un programme pour ordinateur, un jeu multijoueur qui permettrait de retranscrire des mouvements de foule.
Notre jeu se compose d'un avatar représentant soi-même à la 3e personne, vu du dessus. Un “monstre” invisible “mange” les avatars lorsqu'il croise leur chemin. Il faut donc faire attention à son entourage, suivre les autres.
Nous voulons faire plusieurs “salles” avec plusieurs consignes, peut-être en faire une où l'on se voit à la première personne, et une autre avec des consignes différentes pour chaque individu afin de créer un tout avec l'action de chaque individu.
Code JavaScript pour le serveur :
//this code is based on https://github.com/CodingTrain/website/tree/master/CodingChallenges/CC_032.2_agar.io_sockets/Node
var blobs = []; //liste pour stocker les blobs
function Blob(id, x, y) { //définit l'objet blob
this.id = id;
this.x = x;
this.y = y;
}
var monstres = []; //liste pour stocker les monstres
function Monster (xm, ym) { //définit l'objet monstre
this.xm = xm;
this.ym = ym;
}
// Using express: http://expressjs.com/
var express = require('express');
// Create the app
var app = express();
// Set up the server
// process.env.PORT is related to deploying on heroku
var server = app.listen(process.env.PORT || 3000, listen);
// This call back just tells us that the server has started
function listen() {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://' + host + ':' + port);
}
app.use(express.static('public'));
// WebSocket Portion
// WebSockets work with the HTTP server
var io = require('socket.io')(server);
setInterval(heartbeat, 33);
function heartbeat() {
io.sockets.emit('heartbeat_blobs', blobs);
io.sockets.emit('heartbeat_monstre', monstres);
}
// Register a callback function to run when we have an individual connection
// This is run for each individual user that connects
io.sockets.on('connection',
// We are given a websocket object in our function
function(socket) {
console.log("We have a new client: " + socket.id);
socket.on('start_blobs',
function(data) {
console.log(socket.id + " " + data.x + " " + data.y);
socket.broadcast.emit('start_blobs', data);
var blob = new Blob(socket.id, data.x, data.y);
blobs.push(blob);
}
);
socket.on('start_monstres',
function(datam) {
console.log(datam.xm + " " + datam.ym);
socket.broadcast.emit('start_monstres', datam);
var monstre = new Monster(datam.xm, datam.ym);
monstres.push(monstre);
}
);
socket.on('update',
function(data) {
//console.log(blobs);
for (var i = 0; i < blobs.length; i++) {
if (socket.id == blobs[i].id) {
//console.log(blobs[i].id);
blobs[i].x = data.x;
blobs[i].y = data.y;
}
}
}
);
socket.on('move',
function(datam) {
//console.log(blobs);
for (var i = 0; i < monstres.length; i++) {
monstres[i].xm = datam.xm;
monstres[i].ym = datam.ym;
}
}
);
socket.on('eaten', function (data) {
// remove the eaten blob
// set array of blobs excluding the eaten blob
var eaten = {
id: ''
};
var newblob = blobs.filter(function( blobs ) {
if (blobs.id === data.eatenId) {
eaten.id = blobs.id;
}
return blobs.id !== data.eatenId;
});
blobs = newblob;
io.sockets.emit('', blobs);
});
socket.on('disconnect', function() {
console.log("Client has disconnected" + " " + socket.id);
blobs = blobs.filter(function( blobs ) {
return blobs.id !== socket.id;
});
});
}
);
Code JavaScript pour le client :
//déclaration de variables
var socket;
var blob; //personnage
var blobs = []; //liste de blobs
var monstres = [];
var monstre; //monstre
var timer; //bool pour le timer
var jeu = false; //bool pour le jeu
var intro = true; //bool pour l'intro
const radius = 30; //rayon du monstre
var alive = true;
function setup() {
createCanvas(900, 600);
if (alive) {
socket = io.connect('http://localhost:3000');
}
//nouveaux blob, monstre et timer
timer = new Timer();
blob = new Blob(random(width), random(height));
monstre = new Monster(random(width), random(height));
//data pour la position du blob
var data = {
x: blob.pos.x,
y: blob.pos.y,
};
//data pour la position du monstre
var datam = {
xm: monstre.pos.x,
ym: monstre.pos.y,
};
socket.emit('start_blobs', data); //partage les data
socket.emit('start_monstres', datam);
alive = true;
socket.on('heartbeat_blobs',
function(data) {
var stillAlive = false;
for (var i = 0; i < data.length; i++) {
if (socket.id == data[i].id) {
stillAlive = true;
//console.log('heart beat');
}
}
//console.log(data);
alive = stillAlive;
blobs = data;
});
socket.on('heartbeat_monstre',
function(datam) {
monstres = datam;
});
}
function draw() {
background(0); //fond noir, taille du texte 30 et écrit en blanc
textSize(30);
fill(255);
a=timer.minute();
b=timer.second();
temps = a + ":" + b; //on stocke les secondes et les minutes dans un string
if (socket.connected && alive && jeu) {
timer.stop(); //démarre le timer
// if (isNaN(blob.pos.x) || isNaN(blob.pos.y)) {
// alive = false;
// }
blob.show(); //dessine le blob
//if(b%5==0) {
//monstre.show(); //dessine le monstre de temps en temps
//}
blob.update();
monstre.deflect(); //empêche que le monstre sorte des bords
monstre.move(); //le monstre bouge
for (var i = blobs.length - 1; i >=0; i--) {
if (socket.id !== blobs[i].id) {
fill(255);
circle(blobs[i].x, blobs[i].y, 20);
} else {
var newblob = new Blob(blobs[i].x, blobs[i].y, 20);
if (mange(newblob)) {
var data = {
eatenId: blobs[i].id
};
socket.emit('eaten', data);
}
}
}
for (var i = monstres.length - 1; i >=0; i--) {
fill(blob.c);
//noFill();
circle(monstres[i].xm, monstres[i].ym, radius*2)
}
//jeu = mange(blob); //mange le blob si ils se rencontrent
score = temps;
text(temps, width/2 - 40, 50); //affiche le temps en haut de l'écran
var data = {
x: blob.pos.x,
y: blob.pos.y,
};
var datam = {
xm: monstre.pos.x,
ym: monstre.pos.y,
};
socket.emit('update', data);
socket.emit('move', datam);
} if (!jeu) {
timer.start();
fill(255);
stroke(255);
textSize(50);
if (intro) {
//si c'est l'intro : titre du jeu et démarrer
text("Foule", width/2 - 70, height/2 - 100);
text("Cliquer pour jouer", width/2 - 200, height/2);
} else {
//si c'est la fin du jeu : Game Over et le temps final s'affichent
text("Game Over", width/2 - 140, height/2 - 100);
text("Score : " + score, width/2 - 150, height/2);
text("Appuyer sur F5 pour redémarrer", width/20, height/2 + 100);
}
}
}
function reset() { //quand c'est la fin du jeu, permet de redémarrer une partie juste en cliquant
console.log('allez');
alive=true;
stillAlive=true;
jeu = true;
//blob = new Blob(random(width), random(height));
//monstre = new Monster(random(radius,width-radius),random(radius,height-radius));
timer.stop(); //redémarre le timer à 0
}
function mousePressed() { //on passe l'intro et la fin du jeu en cliquant
intro=false;
alive = true;
if (jeu==false) {
reset();
}
}
// Classe Blob
class Blob {
constructor(x,y) {
this.pos = createVector(x, y);
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.topspeed = 2;
this.r = 20;
this.c = color(int (50 + Math.random() * (220 - 50)), int (50 + Math.random() * (220 - 50)), int (50 + Math.random() * (220 - 50)));
//couleur ni trop sombre ni trop claire
}
update () {
this.mouse = createVector(mouseX, mouseY); //position du curseur
this.acc.add(this.mouse);
this.acc.sub(this.pos);
this.acc.setMag(0.2); //on définit le vecteur accélération
this.vel.add(this.acc); //on ajoute l'accélération au vecteur vitesse
this.mouse.sub(this.pos); //
this.mouse.setMag(7); //magnitude force de rappel du blob vers le curseur
this.pos.add(this.mouse); //on ajoute à la position courante
this.vel.limit(this.topspeed); //valeur limite à la vitesse
this.pos.add(this.vel); //on ajoute la vitesse au vecteur position
this.pos.x=Math.min(this.pos.x,width-10); //gestion des coins de la boite
this.pos.x=Math.max(this.pos.x,10);
this.pos.y=Math.min(this.pos.y,height-10);
this.pos.y=Math.max(this.pos.y,10);
}
show () { //dessine le blob
fill(this.c);
stroke(this.c);
circle(this.pos.x, this.pos.y, this.r);
}
}
// Classe Monster
class Monster {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = createVector(random(2,4),random(2,4));
if (int(random(0, 2)) == 1) {
this.vel.x *= -1;
}
if (int(random(0, 2)) == 1) {
this.vel.y *= -1;
}
}
move () { //permet au monstre de bouger (à vitesse constante si epsi=0,0)
this.epsi = createVector(random(-1,1), random(-1,1)); // Exciter la vitesse pour mettre un peu de piment ...
this.vel.add(this.epsi);
this.pos.add(this.vel);
if(b%5==0) {
this.vel = createVector(random(2, 4), random(2, 4));
if (int(random(0, 2)) == 1) {
this.vel.x *= -1;
}
if (int(random(0, 2)) == 1) {
this.vel.y *= -1;
}
}
}
/*show () { //dessine le monstre
strokeWeight(1);
noFill();
circle(this.pos.x, this.pos.y, radius*2);
}*/
deflect () { //permet au monstre de ne pas sortir des bords, il "rebondit"
if (this.pos.x + radius > width || this.pos.x - radius < 0) {
this.vel.x *= -1;
}
if (this.pos.y + radius > height || this.pos.y - radius < 0) {
this.vel.y *= -1;
}
}
}
mange = function(blob) { //le monstre "mange" le blob s'ils se rencontrent et le jeu est fini
if (this.blob.pos.x <= monstre.pos.x + radius && this.blob.pos.x >= monstre.pos.x - radius && this.blob.pos.y <= monstre.pos.y + radius && this.blob.pos.y >= monstre.pos.y - radius) {
jeu = false;
return true;
}
return false;
}
// Classe Timer
class Timer {
constructor() {
this.startTime = 0;
this.stopTime = 0;
this.running = false;
}
start () {
this.startTime = new Date();
this.running = true;
}
stop () {
this.stopTime = new Date();
this.running = false;
}
getElapsedTime () {
if (this.running) {
this.elapsed = (new Date() - this.startTime);
} else {
this.elapsed = (this.stopTime - this.startTime);
}
return this.elapsed;
}
second () {
this.sec = int ((this.getElapsedTime() / 1000) % 60);
if (this.sec<10) {
this.sec="0" + this.sec;
} else {
this.sec="" + this.sec;
}
return this.sec;
}
minute () {
this.min = int ((this.getElapsedTime() / (1000*60)) % 60);
if (this.min<10) {
this.min="0" + this.min;
} else {
this.min="" + this.min;
}
return this.min;
}
}
Code html pour l'index :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript" src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js"></script>
<script language="javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
<script type="text/javascript" src="crowd.js"></script>
</head>
<body>
</body>
</html>