Skip to content

Commit

Permalink
Only send the part of the world that the player can see
Browse files Browse the repository at this point in the history
  • Loading branch information
kothique committed Mar 6, 2018
1 parent 3f0f9b8 commit 8f1c532
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 39 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@

### Physics
* verlet integration
* spacial partitioning instead of brute*force
* _spacial partitioning instead of brute-force_

### **Game Mechanics**
* all 18 skills

### **UI**
* game page
* skill icons
* 404 not found page
* _404 not found page_
* user profile
* persistent redux state
* mobile menu
* _persistent redux state_
* _mobile menu_

### Optimization
* UDP (WebRTC??)
Expand Down
6 changes: 6 additions & 0 deletions server/game/__tests__/world.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,10 @@ describe('World', () => {

expect(world.toBuffer().equals(buffer)).toBeTruthy()
})

/** @todo */
test('should serialize a rectangle correctly')

/** @todo */
test('rectangleToBuffer() should give the same result as serializeToBuffer(*new buffer*, 0)')
})
23 changes: 23 additions & 0 deletions server/game/collision-detector.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

const merge = require('lodash/merge')
const forIn = require('lodash/forIn')

const { Vector, V } = require('../../common/vector')

Expand Down Expand Up @@ -107,6 +108,28 @@ class CollisionDetector {
}
}
}

/**
* Return all entitites in the specified rectangle.
*
* @param {object} rectangle
* @param {Vector} rectangle.p1
* @param {Vector} rectangle.p2
* @return {array}
*/
queryRectangle({ p1, p2 }) {
const result = []

forIn(this.boxes, (box, id) => {
if (box.p1.x < p2.x && box.p2.x > p1.x &&
box.p1.y < p2.y && box.p2.y > p1.y) {

result.push(id)
}
})

return result
}
}

module.exports = CollisionDetector
6 changes: 6 additions & 0 deletions server/game/entities/__tests__/orb.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,10 @@ describe('Orb', () => {
expect(buffer.readDoubleBE(offset)).toBe(10)
offset += 8
})

/** @todo */
test('should serialize skills correctly')

/** @todo */
test('skillsToBuffer() is the same as serializeSkills(*new buffer*, 0)')
})
14 changes: 14 additions & 0 deletions server/game/entities/orb.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ class Orb extends Entity {

/**
* The size of the orb's skills serialized.
*
* @return {number}
*/
serializedSkillsLength() {
const skills = this.skillManager.skills
Expand All @@ -153,6 +155,18 @@ class Orb extends Entity {
skills.skillA2.serializedLength()
}

/**
* Create a new buffer with skills written to it.
*
* @return {Buffer}
*/
skillsToBuffer() {
const buffer = Buffer.allocUnsafe(this.serializedSkillsLength())
this.serializeSkills(buffer)

return buffer
}

/**
* Mark the entity as dead.
*
Expand Down
57 changes: 36 additions & 21 deletions server/game/simulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ const World = require('./world')
const Orb = require('./entities/orb')
const { Vector, V } = require('../../common/vector')

/**
* Define the rectangle around the orb that can be seen by the player.
*/
const VISION = V(1366, 768).divide(2)

/**
* @class
*
Expand All @@ -28,7 +33,7 @@ const Simulator = {
*/
init(options = Object.create(null)) {
this.world = options.world || new World({
size: V(800, 600)
size: V(10000, 10000)
})

this.world.on('death', (orbID) => {
Expand Down Expand Up @@ -156,26 +161,7 @@ const Simulator = {
}

if (integrated) {
const skills = Object.create(null)

forIn(this.world.entities, (entity, id) => {
if (entity instanceof Orb) {
const buffer = Buffer.allocUnsafe(entity.serializedSkillsLength())
entity.serializeSkills(buffer)

skills[id] = buffer
}
})

process.send({
type: 'FRAME',
frame: {
world: this.world.toBuffer(),
skills,
timestamp: this.t
},
})

this.sendFrames()
this.world.handleCollisions()
}

Expand All @@ -196,6 +182,35 @@ const Simulator = {
this.continue = false

console.log(`Simulation stopped (PID: ${process.pid})`)
},

/**
* Send frames to the parent process.
*/
sendFrames() {
const frames = Object.create(null)

forIn(this.world.entities, (entity, id) => {
if (entity instanceof Orb) {
const orb = entity,
skills = orb.skillsToBuffer(),
world = this.world.rectangleToBuffer({
p1: Vector.subtract(orb.position, VISION),
p2: Vector. add(orb.position, VISION)
})

frames[id] = {
world,
skills
}
}
})

process.send({
type: 'FRAMES',
frames,
timestamp: this.t
})
}
}

Expand Down
46 changes: 46 additions & 0 deletions server/game/world.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,52 @@ class World extends EventEmitter {

return buffer
}

/**
* Serialize only a limited rectangle area in the world.
*
* @param {object} rectangle
* @param {Vector} rectangle.p1
* @param {Vector} rectangle.p2
* @return {Buffer}
*/
rectangleToBuffer(rectangle) {
const ids = this.detector.queryRectangle(rectangle),
entities = ids.map((id) => {
const entity = this.entities[id]

return {
id,
entity,
length: entity.serializedLength()
}
}),
entitiesLength = entities.reduce((acc, { length }) => acc + 2 + 1 + length, 0),
buffer = Buffer.allocUnsafe(2 + 2 + 2 + entitiesLength)

let offset = 0
buffer.writeUInt16BE(this.size.x, offset)
offset += 2

buffer.writeUInt16BE(this.size.y, offset)
offset += 2

buffer.writeUInt16BE(entities.length, offset)
offset += 2

entities.forEach(({ id, entity, length }) => {
buffer.writeUInt16BE(id, offset)
offset += 2

buffer.writeUInt8(entity.constructor.getType(), offset)
offset += 1

entity.serialize(buffer, offset)
offset += length
})

return buffer
}
}

module.exports = World
27 changes: 13 additions & 14 deletions server/region.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ const forIn = require('lodash/forIn')
const child_process = require('child_process')
const WebSocket = require('ws')

/**
* Used to to feed different ports to different children.
*/
let childIndex = 1

/**
* @class
*
Expand All @@ -36,15 +31,19 @@ class Region {
let player

switch (msg.type) {
case 'FRAME':
const { world, skills, timestamp } = msg.frame

forIn(this.playersByID, ({ socket, orbID }, playerID) => {
socket.emit('frame', {
world,
skills: skills[orbID],
timestamp
})
case 'FRAMES':
const { frames, timestamp } = msg

forIn(frames, ({ world, skills }, orbID) => {
const player = this.playersByOrbID[orbID]

if (player) {
player.socket.emit('frame', {
world,
skills,
timestamp
})
}
})
break

Expand Down

0 comments on commit 8f1c532

Please sign in to comment.