freeball
Version:
Soccer with balls that can fly
367 lines (323 loc) • 13.3 kB
JavaScript
var g; // for debugging
if (typeof define !== 'function') { var define = require('amdefine')(module) }
define(function(require) {
var CANNON = require("cannon");
var types = require("js/types");
var bodyTypes = types.bodyTypes;
var ballRadius = 6;
var endGameScore = 2;
var packetMembers = {
position: "p",
velocities: "v",
bodyTypes: "bodyTypes",
packetMembers: "packetMembers"
};
function generate_id() {
var symbols = 'abcdefghijklmnopqrstuvwxyz1234567890';
var chars = [];
for(var i = 0; i < 10; i++) {
chars.push(symbols[Math.floor(Math.random() * symbols.length)]);
}
return chars.join('');
}
function cannonToThreeVec(vec) {
return [vec.x, vec.y, vec.z];
}
var startGame = function(eventCallback, playersArray) {
var ticksPerSecond = 50;
var msPerFrame = 1000.0 / ticksPerSecond;
var sPerFrame = 1.0 / ticksPerSecond;
var maxSubSteps = 3;
var commands = {};
var world = new CANNON.World();
var scores = {};
var isGameOver = false;
var shouldResetPositions = true;
var playerRadius = 1.5; // m
var playerStartPos = [
new CANNON.Vec3(0, -50, 25),
new CANNON.Vec3(0, 50, 25)
];
var ballStartPos = new CANNON.Vec3(0, 0, 25);
var ballBody;
// TODO: Why does normal `g` not work here? It looks sluggish...
world.gravity.set(0, 0, -7.82); // m/s²
var worldMaterial = new CANNON.Material("worldMaterial");
var playerMaterial = new CANNON.Material("worldMaterial");
var contactMaterial = new CANNON.ContactMaterial(worldMaterial, worldMaterial, { friction: 0.1, restitution: 0.6 });
world.addContactMaterial(contactMaterial);
var pveMaterial = new CANNON.ContactMaterial(playerMaterial, worldMaterial, { friction: 0.1, restitution: 0.3 });
world.addContactMaterial(pveMaterial);
var pvpMaterial = new CANNON.ContactMaterial(playerMaterial, playerMaterial, { friction: 0.1, restitution: 0.4 });
world.addContactMaterial(pvpMaterial);
var game = {};
game.world = world;
game.id = generate_id();
var newBodiesThisTick = [];
var newCommands = [];
game.command = function(command) {
newCommands.push(command);
};
// Setup our world
function newBody(body, typ) {
// notify the client something is added to the world
newBodiesThisTick.push(typ);
body.gameType = typ;
world.addBody(body);
};
function createBall() {
var radius = ballRadius; // m
var sphereBody = new CANNON.Body({
mass: 1, // kg
position: ballStartPos, // m
shape: new CANNON.Sphere(radius),
material: worldMaterial
});
newBody(sphereBody, {
type: bodyTypes.ball,
radius: radius
});
ballBody = sphereBody;
}
// TODO: delete playerBodies
var playerBodies = {};
var playerSlotBodies = [];
function createPlayer(player) {
var x = 0;
var y = player.team * 100 - 50;
var sphereBody = new CANNON.Body({
mass: 5, // kg
position: playerStartPos[player.team], // m
shape: new CANNON.Sphere(playerRadius),
material: playerMaterial
});
// linearDamping is air friction. This makes the player feel more in control
// because when they stop holding forward the ball stops moving.
// Also it limits the maximum speed to a terminal velocity.
sphereBody.linearDamping = 0.3;
//var playerId = '' + teamI + '-' + playerI;
var typeInfo = {
type: bodyTypes.player,
radius: playerRadius,
teamI: player.team,
//playerI: playerI,
id: player.id
};
newBody(sphereBody, typeInfo);
playerBodies[player.id] = sphereBody;
playerSlotBodies.push(sphereBody);
scores[player.team] = 0;
}
function createAllPlayers() {
playersArray.map(function(playa) {
createPlayer(playa);
});
};
function createGround() {
// From: https://github.com/schteppe/cannon.js/blob/4cc2eb1e883839f823b30e1f22654e340467aea8/src/shapes/Plane.js#L7
// A plane, facing in the Z direction. The plane has its surface at z=0
// and everything below z=0 is assumed to be solid plane.
// To make the plane face in some other direction than z, you must put it inside
// a Body and rotate that body. See the demos.
var x = new CANNON.Vec3(1,0,0);
var y = new CANNON.Vec3(0,1,0);
var z = new CANNON.Vec3(0,0,1);
var planes = [
// floor
{
pos: [0, 0, 0],
quat: [z, 0]
},
// end of field walls
{
pos: [0, 200, 0],
quat: [x, Math.PI / 2]
},
{
pos: [0, -200, 0],
quat: [x, -Math.PI / 2]
},
// angled roof
{
pos: [-30, 0, 50],
quat: [y, -Math.PI * 5 / 4]
},
{
pos: [30, 0, 50],
quat: [y, Math.PI * 5 / 4]
},
// angled bottom floor
{
pos: [40, 0, 20],
quat: [y, -Math.PI / 4]
},
{
pos: [-40, 0, 20],
quat: [y, Math.PI / 4]
}
];
var groundShape = new CANNON.Plane();
planes.forEach(function(planeDef) {
var groundBody = new CANNON.Body({
mass: 0, // mass == 0 makes the body static
material: worldMaterial
});
var pos = planeDef.pos;
var quat = planeDef.quat;
groundBody.position.set(pos[0], pos[1], pos[2]);
//console.log(groundBody.position)
groundBody.quaternion.setFromAxisAngle(quat[0], quat[1]);
groundBody.addShape(groundShape);
newBody(groundBody, {
type: bodyTypes.ground,
quaternion: groundBody.quaternion
});
})
};
function createPoppers() {
var popperData = [
{y:-150, team: 0},
{y: 150, team: 1}
];
var popperRadius = 7;
popperData.forEach(function(popper) {
var popperBody = new CANNON.Body({
mass: 0, // static
position: new CANNON.Vec3(0, popper.y, 8),
shape: new CANNON.Sphere(popperRadius),
material: worldMaterial
});
newBody(popperBody, {
type: bodyTypes.popper,
team: popper.team,
radius: popperRadius
});
popperBody.addEventListener("collide", function(e) {
var targetType = e.body.gameType.type;
if(targetType === bodyTypes.ball) {
score(popper.team);
}
});
});
}
function resetPositions() {
playerSlotBodies.forEach(function (body, index) {
body.position.copy(playerStartPos[index]);
body.velocity.set(0, 0, 0);
});
ballBody.position.copy(ballStartPos);
ballBody.velocity.set(0, 0, 0);
}
function score(team) {
scores[team] += 1;
if(scores[team] >= endGameScore) {
eventCallback(types.eventTypes.gameOver, {
teamWon: team,
newScore: scores[team]
});
isGameOver = true;
} else {
eventCallback(types.eventTypes.score, {
teamScored: team,
newScore: scores[team]
});
// This code would get called mid-world-step so modifying
// positions would cause objects to fly around explosively.
// Instead we wait for the simulation step to end.
shouldResetPositions = true;
}
//console.log("Score");
}
function handleCommands() {
for(var i = 0; i < newCommands.length; i++) {
var com = newCommands[i];
try {
//console.log(body);
// TODO: better error handling
//console.log(vec);
//console.log(body.velocity);
//body.velocity.copy(com.moveVec);
//body.velocity.normalize();
//body.velocity = body.velocity.scale(100);
var body = playerBodies[com.playerId];
//var modify = body.force;
var modify = body.velocity;
//body.velocity.copy(com.moveVec);
var v = com.moveVec;
body.force.set(v.x, v.y, v.z);
body.force.normalize();
body.force = body.force.scale(500);
/*
var v = com.moveVec;
body.velocity.set(v.x, v.y, v.z);
body.velocity.normalize();
body.velocity = body.velocity.scale(15);
*/
//body.velocity = body.force.scale(100);
//console.log(body.velocity);
} catch(e) {
console.warn('Bad command: ' + e);
}
}
newCommands = [];
}
function reportState() {
var positions = [];
var velocities = [];
for(var i = 0; i < world.bodies.length; i++) {
positions.push(cannonToThreeVec(world.bodies[i].position));
velocities.push(cannonToThreeVec(world.bodies[i].velocity));
}
var state = {
p: positions,
v: velocities,
t: tick
}
if(newBodiesThisTick.length > 0) {
state.n = newBodiesThisTick;
newBodiesThisTick = [];
}
eventCallback(types.eventTypes.tick, state);
};
// Start the simulation loop
var lastTimeMs = new Date().getTime();
var tick = -1;
function simloop() {
tick++;
var timeMs = new Date().getTime();
var dtSeconds = (timeMs - lastTimeMs) / 1000;
handleCommands();
//world.step(sPerFrame, dtSeconds, maxSubSteps);
//world.step(sPerFrame);
world.step(dtSeconds);
//console.log("Sphere z position: " + sphereBody.position.z);
lastTimeMs = timeMs;
reportState();
if(shouldResetPositions) {
resetPositions();
shouldResetPositions = false;
}
if(!isGameOver) {
setTimeout(simloop, msPerFrame);
}
};
function notifyPlayersGameStarted() {
var playerIds = playersArray.map(function(value) { return value.id });
eventCallback(types.eventTypes.serverStartGame, playerIds);
}
function main() {
console.log('start sim');
createGround();
createBall();
createAllPlayers();
createPoppers();
notifyPlayersGameStarted();
setTimeout(simloop, msPerFrame);
g = game;
}
main();
return game;
};
startGame.generate_id = generate_id;
return startGame;
});