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.
Il existe de nombreux programmes pour simuler l'émergence de comportements et de structures chez les animaux. Par exemple, “Boids”, qui simule les regroupements des oiseaux selon trois critères :
Dans le même genre, il y a par exemple la synchronisation des lucioles.
Humanity est un jeu imaginé par le collectif japonais Tha Ltd, il s’agit d’un jeu de réflexion à mi-chemin entre la simulation de foule et l’expérience SF qui met en scène une foule aveugle qui fonce droit devant sans s’arrêter. Chaque niveau comporte des obstacles, des pièges, et autres difficultés. Le but est de sauver le plus d’êtres humains possible à chaque niveau. Pour le moment, on sait simplement qu’“Humanity” est prévu pour 2018.
Liens pour voir le jeu : Humanity Humanity WIP
Crowd city
Le but du jeu est de vous déplacer pour recruter de nouveaux membres et former une foule. Lorsque vous croisez un autre joueur, si vous avez plus de bonhommes que lui, foncez-lui dessus. Sinon, fuyez.
Source : Crowd City
Nous avons installé Node.js pour permettre de se connecter à plusieurs sur le même serveur.
On suit ensuite les instructions de Socket.io. On doit créer un package.json, un serveur en javascript, et un code client en javascript et un index en html dans un dossier public.
Nous avons été confrontées à plusieurs problèmes. En effet, nous avions commencé à coder en Java sur Processing, mais cela ne permet pas de créer un code multijoueur en ligne. Nous devions donc réecrire ce code en javascript en utilisant P5.js. Il a été difficile de comprendre le fonctionnement des sockets et les échanges entre le client et le serveur. Ensuite se posait la question de l'hébergement car il faut un serveur qui prend en compte le node js, qui coûte plus cher qu'un site basique. Finalement, nous serons hébergées provisoirement par une âme charitable.
C'est par les limites de notre savoir informatique que nous avons simplifié au maximum notre jeu mais les bases sont posées pour complexifier le principe.
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, vx, vy) { //définit l'objet blob this.id = id; this.x = x; this.y = y; this.vx = vx; this.vy = vy; } 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, data.vx, data.vy); 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("collide", 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].vx = data.vx; blobs[i].vy = data.vy; } } }); 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 = []; //liste de 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; let spring = 0.05; 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, vx: blob.vel.x, vy: blob.vel.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; } } 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 blob.show(); //dessine le blob blob.update(); monstre.deflect(); //empêche que le monstre sorte des bords monstre.move(); //le monstre bouge score = temps; text(temps, width / 2 - 40, 50); //affiche le temps en haut de l'écran for (var i = blobs.length - 1; i >= 0; i--) { blob.collide(blobs[i]); if (socket.id !== blobs[i].id) { fill(255); stroke(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--) { noFill(); noStroke(); circle(monstres[i].xm, monstres[i].ym, radius * 2); } var data = { x: blob.pos.x, y: blob.pos.y, vx: blob.vel.x, vy: blob.vel.y }; var datam = { xm: monstre.pos.x, ym: monstre.pos.y }; socket.emit("update", data); socket.emit("collide", 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; 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 } collide(other) { let dx = other.x - this.pos.x; let dy = other.y - this.pos.y; let distance = sqrt(dx * dx + dy * dy); if (distance < this.r / 2) { let angle = atan2(dy, dx); let targetX = this.pos.x + (cos(angle) * this.r) / 2; let targetY = this.pos.y + (sin(angle) * this.r) / 2; let ax = (targetX - other.x) * spring; let ay = (targetY - other.y) * spring; this.vel.x -= ax; this.vel.y -= ay; other.vx += ax; other.vy += ay; } } 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 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; } } } //permet au monstre de ne pas sortir des bords, il "rebondit" deflect() { 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; } } } //le monstre "mange" le blob s'ils se rencontrent et le jeu est fini mange = function(blob) { 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>