CircleIO Final Project


The Beginning


The project started off being somewhat simple, with creating the world, and the objects within it, then creating the player. Everything went smoothly in the beginning, and I had a working demo version of the project all done in Java in Processing.

int sqrR = 800;
int cellR = 25;
ArrayList entities = new ArrayList();
ArrayList players = new ArrayList();
void setup()
{
  size(800, 600);
  background(255);
  for(int x = sqrR; x >= -sqrR; x-=cellR)
  {
    for(int y = sqrR; y >= -sqrR; y-=cellR){
    stroke(100);
    strokeWeight(1);
    fill(255);
    rect(x, y, cellR, cellR);
    }
  }
}

void removeEntity(Entity e){
 for(int i = 0; i <= entities.size(); i++){
  if(entities.get(i) == e){
   entities.remove(i);
   return;
  }
 }
}

class Entity {
PVector loc;
boolean food;
boolean player;
color c;
float r;
public Entity(PVector location, boolean player, boolean food){
  if(player && food){
    println("Error: players are not food!");
   return; 
  }else{
   this.food = food;
   this.player = player;
   loc = location;
  }
  
}

void render(){
  fill(c);
  noStroke();
  ellipse(loc.x, loc.y, r, r);
  move();
}

float getSpeed(){
 return map(r, .5, 500, -10, -2)*-1; 
}

void move()
{
  if(player){
    if(mouseX > loc.x){
      loc.x+=getSpeed();
    }else if(mouseX < loc.x){ loc.x-=getSpeed(); } if(mouseY > loc.y){
      loc.x-=getSpeed();
    }else if(mouseY < loc.y){
      loc.x+=getSpeed();
    }
  }else{
   return; 
  }
}
}

class Player extends Entity {
  PVector loc;
  String name;
  Player(String name, PVector location){
    super(location, true, false);
    this.name = name;
  }
  
  ArrayList canEat(){
    ArrayList tmp = new ArrayList();
    for(Entity e : entities){
     if(PVector.dist(e.loc, loc) <= r/2){
       tmp.add(e);
       continue;
     }else{
      continue; 
     }
    }
    return tmp;
  }
  
  ArrayList canKill(){
    ArrayList tmp = new ArrayList();
     for(Player p : players){
      if(p != this){
     if(PVector.dist(p.loc, loc) <= r/2){
       tmp.add(p);
       continue;
     }else{
      continue; 
     }
    }else{
     continue; 
     }
    }
    return tmp;
  }
  
  void doStuff(){
   render();
   for(Player p : canKill()){
     
   }
  
}

When I got most of everything done with the game mechanics I moved onto doing networking, which is where I began to struggle. One of the biggest issues was when I tried sending data through a socket using serializeable objects but I ran into too many issues trying to do this in Processing, and couldn’t get it to work.


The Change


So after running into struggles with networking I decided to change things up and rewrite everything in JavaScript. The reason for this was because I had been looking into networking a lot at the time and found Node.js to be one of the most popular modules out there for networking. The only problem was it was for JavaScript. So I spent a weekend learning the basics of JavaScript then started working on converting things. At first it was difficult, and I kept getting many errors. Overtime, while looking at a few JavaScript tutorials I began to get the hang of things and was able to convert the whole project to JavaScript.


Node.js


Once I had finished changing my project to JavaScript and learning some of the basics I moved on to downloading, and implementing Node,js. Once I had gotten the library and all that was required setup I had to learn some basics with Node.js that create the server. It was hard at first because there is a lot to learn about using Node.js and Socket.io which is the other module I had used to help. I later added the Express framework, which was used for sending files to the users browser to run the client for the project.

The server running the project:

var express = require('express');
var app = express();
var server = app.listen(80, function(){
    listen();
});
var entities = [];
var tiles = [];
var sqrR = 1000;
var cellR = 20;
var eA = (sqrR * sqrR) * .01;
var removeE = [];
// app.listen(3000);

app.use(express.static('public'));

function listen() {
	var host = server.address().address;
	var port = server.address().port;
	console.log('App listening at http://localhost:' + port);
	initPop();
	genMap();
}

var io = require('socket.io')(server, {'pingInterval': 2000, 'pingTimeout': 10000});
//var io = io.listen(server);

io.sockets.on('connection', function(socket) {
	console.log("New user connected with id: " + socket.id);
	socket.emit('gen', tiles, sqrR, cellR);
    socket.emit('pop', entities);
    
    socket.on('disconnect', function () {
        console.log('Disconnected with id:' + socket.id);
        io.sockets.emit('disconnected', socket.id);
    });
    
    socket.on('getID', function() {socket.emit('id', socket.id)});
    
    socket.on('ate', function(e){
        removeEntity(e);
		socket.emit('eaten', e);
    });
	
	socket.on('kill', function(pl){
		console.log("Player: " + pl.name + " has been killed!");
		io.sockets.emit('killed', pl);
	});
    
    socket.on('update', function(player){
        io.sockets.emit('players', player);
    });
    
    socket.on('ping', function(){
        socket.emit('pong');
    });
    
});






io.sockets.on('update', function(player) {
	io.sockets.emit('data', player);
});



function initPop() {
	while (entities.length < eA) {
		var e = new E(random(-sqrR, sqrR),
				random(-sqrR, sqrR), false, 1);
		entities.push(e);
	}
}

function populate(){
    var es = [];
    for(i = entities.length; i <= eA; i++){
        var e = new E(random(-sqrR, sqrR),
				random(-sqrR, sqrR), false, 1);
		es.push(e);
    }
    return es;
}

function newEntity(){
	return e = new E(random(-sqrR, sqrR),
				random(-sqrR, sqrR), false, 1);
}

function genMap() {
    var i = 0;
	for (var x = -sqrR; x <= sqrR; x += cellR) {
		for (var y = -sqrR; y <= sqrR; y += cellR) { tiles[i] = new Vector(x, y); i++; } } } function random(min, max) { seeded = false; var rand; if (seeded) { rand = lcg.rand(); } else { rand = Math.random(); } if (typeof min === 'undefined') { return rand; } else if (typeof max === 'undefined') { if (min instanceof Array) { return min[Math.floor(rand * min.length)]; } else { return rand * min; } } else { if (min > max) {
      var tmp = min;
      min = max;
      max = tmp;
    }

    return rand * (max-min) + min;
  }
};

function randomNum(min, max) {
    return Math.floor(Math.random() * (max)) + min;
}

function E(x, y, player, score){
    this.x = x;
    this.y = y;
    this.player = player;
    this.score = score;
}
    
function Player(x, y, score, name, id){
    this.id = id;
    this.x = x;
    this.y = y;
    this.score = score;
    this.name = name;
}

function Vector(x, y){
    this.x = x;
    this.y = y;
}

function removeEntity(e){
    for(i in entities){
        if(entities[i].x === e.x && entities[i].y === e.y){
            entities.splice(i, 1);
			var e = newEntity();
        	io.sockets.emit('ne', e);
            continue;
        }else{
            continue;
        }
    }
}

 


Client/Server Side


Once everything was setup for Node.js, Socket.io, and express I began working on making the Client, but since I had already made the basic functionality I took what I had done and just added onto it as the client. But overtime I took out more info such as the world size, amount of tiles, entities, and removed all world generation. I then moved it all to the Server, which would send the data to the client for the client to use. The client was being used to use the data the server was sending such as player locations, entity locations, tile locations, etc. One issue with the Client/Server relationship was sending info, I originally had been trying to send objects but ran into the problem of it not being small enough to send through the sockets, so I ended up making data classes that just store the important info such as location, score, color, and ID.

The Client:

// Constants for the amount of cells and their sizes
var sqrR = 2000;
var cellR = 20;
var zoom = 1;
// Viewing distance constant
var vDist;

var rMode = true;
// Constant for radius on start
var R;
// Creates a constant for the amount of entities to be created on world creation based on world size and percentage of coverage in world (default currently 30%)
var eA = (sqrR * sqrR) * 0.01;
// Where all entities in the world are stored
var entities = [];
// Will be used for keeping track of players in a world
var player;
// Where "awake" (entities within viewing range) entities are stored so that there are less entities to check and so there isn't a waste in time spent checking entities with no chance of collision
var awakeE = [];
// Location for each tile in the world
var tiles = [];
// Location for each tile within viewing distance
var Atiles = [];

var socket;

function setup()
{
	socket = io.connect("http://10.2.4.92");
	var name = prompt("Enter Name:");
	if(name === null || name === ""){
		name = "Anon";
	}
  createCanvas((window.innerWidth), (window.innerHeight));
  background(255);
  frameRate(60);
  vDist = width/8;
  //createVector(random(-sqrR/2, sqrR/2), random(-sqrR/2, sqrR/2))
  this.player = new Player(name, createVector(width/2, height/2));
  this.R = player.getRadius();
}

socket.on('data', function(map, es){
	for(t in map){
		tiles[t] = createVector(map[t].x , map[t].y);
	}
	for(e in es){
		entities.push(new Entity(createVector(es[e].x, es[e].y), es[e].player, es[e].score));	
	}
});



var test = 100;
function drawGrid() {
  for (var i = 0; i < Atiles.length; i++) {
    colorMode(RGB);
    fill(255);
    stroke(130);
    strokeWeight(.5);
    rect(Atiles[i].x, Atiles[i].y, cellR, cellR);
  }
}
var inter = 0;
function draw() 
{
  background(255);
  var newzoom = 10;
  // Is used to slowly create a scale for the world and player so that things are adjusted for the players cell size
  // Also adjusts the world to the scale so things appear where they should be otherwise a cell appears to be right infront of you but its actual location is a few hundred pixels the otherway
  zoom = lerp(zoom, newzoom, .1);
  push();
  translate(width/2, height/2);
  scale(zoom);
  translate(-((player.loc2.x)), -((player.loc2.y)));
  drawGrid();
  for (var i = 0; i < awakeE.length; i++) 
  {
    awakeE[i].render();
  }
  pop();
  player.run();
  inter--;
  if (inter <= 0) {
    // updates the "awake" entities/tiles list will be set to update once every 10 seconds so that there isn't too much going on in the background
    updateATiles();
    updateAwake();
    populate();
    inter=30;
  }
  displayEntities();
}

function populate() 
{
  while (entities.length < eA)
  {
    var e = new Entity(createVector(random(-sqrR, sqrR), random(-sqrR, sqrR)), false, 1);
    entities.push(e);
  }
}

//Updates the visable tiles in the world
function updateATiles() {
  Atiles = [];
  for (var i = 0; i < tiles.length; i++) {
    var d = dist(this.player.loc2.x, this.player.loc2.y, tiles[i].x, tiles[i].y);
    if (d <= vDist) {
      Atiles.push(tiles[i]);
    } else {
      continue;
    }
  }
}

function displayEntities() {
  displayDimensions();
  fill(0);
  textSize(15);
  text("Total Entities: " + entities.length, 20, 20);
}

function displayDimensions() {
  fill(0);
  textSize(15);
  text("Dimensions: " + sqrR + "x" + sqrR, width-200, 20);
  fill(0);
  textSize(15);
  text("Location: X=" + Math.round(player.loc2.x) + " Y=" + Math.round(player.loc2.y), width-200, 40);
}

function updateAwake() 
{
  awakeE = [];
  for (var i = 0; i < entities.length; i++) {
    var d = dist(this.player.loc2.x, this.player.loc2.y, entities[i].loc.x, entities[i].loc.y);
    if (d <= vDist) {
      awakeE.push(entities[i]);
    } else {
      continue;
    }
  }
}

function awake(e) 
{
  if (p5.Vector.dist(e.loc, player.loc2) <= vDist)
  {
    return true;
  } else 
  {
    return false;
  }
}

function removeEntity(e)
{
  for (var i = 0; i <= entities.length-1; i++)
  {
    if (entities[i] === e)
    {
      entities.splice(i, 1)
        return;
    }
  }
}

function EBlob(x, y, player, score){
	Place.call(this, x, y);
	this.score = score;
	this.player = player;
}

function Place(x, y){
		this.x = x;
		this.y = y;
}

 


Game Play


Once the networking was done I had it all working and the game was able to run in real time with other players. To run the game you would use a batch file to run the server which would run in a command prompt then all the player has to do is connect to the server. They would give the server ip then their name, then play.

Command Prompt (Server):

In-Game:

 

Leave a Reply

Your email address will not be published. Required fields are marked *