Skip to content

Commit 9f356f6

Browse files
committed
initial commit
0 parents  commit 9f356f6

File tree

15 files changed

+4528
-0
lines changed

15 files changed

+4528
-0
lines changed

.eslintrc.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
env: {
3+
browser: true,
4+
es2021: true
5+
},
6+
extends: 'standard',
7+
overrides: [
8+
],
9+
parserOptions: {
10+
ecmaVersion: 'latest'
11+
},
12+
rules: {
13+
}
14+
}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.DS_Store

engine/game.js

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/* global Player, tileset, Vigilance */
2+
3+
const ctxObjects = document.getElementById('objects').getContext('2d')
4+
const ctxSight = document.getElementById('sight').getContext('2d')
5+
const [drawCollitions, noShadows] = [false, true]
6+
7+
// eslint-disable-next-line no-unused-vars
8+
const game = {
9+
objects: {},
10+
insideMap: [],
11+
collitionMap: [],
12+
timer: null,
13+
ticker () {
14+
if (this.timer) clearInterval(this.timer)
15+
Vigilance.ctx.clearRect(0, 0, 512, 512)
16+
Vigilance.instances
17+
.filter(vg => vg.props.start)
18+
.forEach(vg => {
19+
vg.frame()
20+
this.generateSight(vg)
21+
})
22+
this.timer = setInterval(() => {
23+
this.ticker()
24+
}, 800)
25+
},
26+
setPrototypes (objects) {
27+
this.prototypes = objects.filter(({ type }) => type === 'prototype')
28+
},
29+
setObjects (objects) {
30+
this.objects = objects.filter(({ type }) => type !== 'prototype')
31+
this.objects
32+
.map(object => {
33+
if (!object.properties) object.properties = {}
34+
this.prototypes.find(obj => obj.name === object.type).properties
35+
.forEach(({ name, value }) => {
36+
object.properties[name] = JSON.parse(value)
37+
})
38+
return object
39+
})
40+
.forEach(object => {
41+
this.drawObject(object)
42+
})
43+
},
44+
drawObject (object) {
45+
if (object.type === 'door') this.toggleVisibility(object)
46+
const proto = object.properties
47+
object.state = !!object.state
48+
object.col = {
49+
x: object.x + proto.col.x,
50+
y: object.y + proto.col.y,
51+
w: proto.col.w,
52+
h: proto.col.h
53+
}
54+
object.collitionIndex = this.computePosition(object.col.x, object.col.y).collitionIndex - 1
55+
ctxObjects.clearRect(object.x, object.y, object.width, object.height)
56+
ctxObjects.drawImage(
57+
tileset,
58+
proto[object.state].x, // source x
59+
proto[object.state].y, // source y
60+
object.width, // source width
61+
object.height, // source height
62+
object.x, // desination x
63+
object.y, // destination y
64+
object.width, // destination width
65+
object.height // destination height
66+
)
67+
if (drawCollitions) {
68+
// draw collition on object
69+
ctxObjects.rect(
70+
object.col.x,
71+
object.col.y,
72+
object.col.w,
73+
object.col.h
74+
)
75+
ctxObjects.stroke()
76+
}
77+
},
78+
computePosition (x, y) {
79+
const computed = { x: Math.floor(x / 16) + 2, y: Math.ceil(y / 16) + 2 }
80+
computed.collitionIndex = (computed.x - 1) + (computed.y - 1) * 32
81+
return computed
82+
},
83+
activateObject (x, y) {
84+
const ply = Player.getCurrent()
85+
const obj = this.objects.find(itm => {
86+
return itm.col.x <= x &&
87+
itm.col.x + itm.col.w >= x &&
88+
itm.col.y <= y &&
89+
itm.col.y + itm.col.h >= y
90+
})
91+
92+
let direction = { x: 32, y: 0 }
93+
if (x < (ply.position.x + 8)) direction.x = -32
94+
if (y < (ply.position.y + 32)) direction = { x: 0, y: -48 }
95+
if (y > (ply.position.y + 32)) direction = { x: 0, y: 48 }
96+
97+
if (!obj) return false
98+
ply.interactingWith = obj
99+
if (obj.state === true) {
100+
ply.move({ x: ply.position.x + direction.x, y: ply.position.y + direction.y }, true)
101+
if (obj.type === 'door') obj.state = false
102+
this.drawObject(obj)
103+
return false
104+
}
105+
106+
obj.state = true
107+
this.drawObject(obj)
108+
return false
109+
},
110+
toggleVisibility (obj) {
111+
const { collitionIndex } = this.computePosition(obj.x - 16, obj.y - 16)
112+
this.obstaclesMap[collitionIndex] = obj.state ? 0 : -1
113+
this.setSight()
114+
},
115+
generateShadow (collitionIndex, pl) {
116+
const shadow = new Array(1024).fill(0)
117+
const playerX = Math.floor(collitionIndex % 32)
118+
const playerY = Math.floor(collitionIndex / 32)
119+
for (let y = 0; y < 32; y++) {
120+
for (let x = 0; x < 32; x++) {
121+
// Calculate angle and distance from player to ever other item in obstaclesMap
122+
const dx = x - playerX
123+
const dy = y - playerY
124+
const angle = Math.atan2(dy, dx)
125+
const distance = Math.sqrt(dx ** 2 + dy ** 2)
126+
// Verificar si hay obstrucciones en la línea de vista
127+
let visibleObstacle = false
128+
for (let d = 1; d <= distance; d++) {
129+
const xObstruction = Math.round(playerX + d * Math.cos(angle))
130+
const yObstruction = Math.round(playerY + d * Math.sin(angle))
131+
const obstructionPosition = yObstruction * 32 + xObstruction
132+
if (xObstruction >= 0 && xObstruction < 32 && yObstruction >= 0 && yObstruction < 32) {
133+
if (this.obstaclesMap[obstructionPosition] !== 0) {
134+
if (!visibleObstacle) {
135+
shadow[obstructionPosition] = 1
136+
visibleObstacle = true
137+
}
138+
break
139+
} else {
140+
shadow[obstructionPosition] = 1
141+
}
142+
}
143+
}
144+
}
145+
}
146+
ctxSight.clearRect(pl.x + 8, pl.y + 16, 16, 16)
147+
shadow.forEach((val, index) => {
148+
if (val === 0) return
149+
ctxSight.clearRect((index % 32) * 16, Math.floor(index / 32) * 16, 16, 16)
150+
})
151+
ctxSight.closePath()
152+
},
153+
generateSight (vg) {
154+
const mapa = this.collitionMap
155+
const filas = 32
156+
const columnas = 32
157+
const tamanoMapa = filas * columnas
158+
const sombra = new Array(tamanoMapa).fill(0)
159+
let [x, y] = [vg.position.x, vg.position.y]
160+
161+
const apertura = 60 / 2
162+
let rangoAngulos
163+
if (['down', 'left', 'up'].includes(vg.direction)) x -= 1
164+
if (vg.direction === 'right') rangoAngulos = [360 - apertura, apertura]
165+
if (vg.direction === 'down') rangoAngulos = [90 - apertura, 90 + apertura]
166+
if (vg.direction === 'left') rangoAngulos = [180 - apertura, 180 + apertura]
167+
if (vg.direction === 'up') rangoAngulos = [270 - apertura, 270 + apertura]
168+
169+
const posicionUnidad = this.computePosition(x, y).collitionIndex
170+
171+
const xUnidad = Math.floor(posicionUnidad % columnas)
172+
const yUnidad = Math.floor(posicionUnidad / columnas)
173+
174+
for (let y = 0; y < filas; y++) {
175+
for (let x = 0; x < columnas; x++) {
176+
// Calcular ángulo y distancia desde la unidad hasta la posición actual
177+
const dx = x - xUnidad
178+
const dy = y - yUnidad
179+
const angulo = Math.atan2(dy, dx)
180+
181+
// Convertir el ángulo a grados
182+
const anguloGrados = (angulo * 180) / Math.PI
183+
184+
// Mapear el ángulo al rango [0, 360)
185+
const anguloMapeado = (anguloGrados + 360) % 360
186+
187+
// Verificar si el ángulo está dentro del rango especificado
188+
let anguloEnRango = false
189+
if (rangoAngulos[0] <= rangoAngulos[1]) {
190+
anguloEnRango = rangoAngulos[0] <= anguloMapeado && anguloMapeado <= rangoAngulos[1]
191+
} else {
192+
anguloEnRango =
193+
anguloMapeado >= rangoAngulos[0] || anguloMapeado <= rangoAngulos[1]
194+
}
195+
196+
if (anguloEnRango) {
197+
const distancia = Math.sqrt((dx ** 2) + (dy ** 2))
198+
199+
// Verificar si hay obstrucciones en la línea de vista
200+
let visibleObstacle = false
201+
for (let d = 1; d <= distancia; d++) {
202+
const xObstruccion = Math.round(xUnidad + d * Math.cos(angulo))
203+
const yObstruccion = Math.round(yUnidad + d * Math.sin(angulo))
204+
const posicionObstruccion = yObstruccion * columnas + xObstruccion
205+
206+
if (
207+
xObstruccion >= 0 &&
208+
xObstruccion < columnas &&
209+
yObstruccion >= 0 &&
210+
yObstruccion < filas
211+
) {
212+
if (mapa[posicionObstruccion] !== 0) {
213+
if (!visibleObstacle) {
214+
sombra[posicionObstruccion] = 1
215+
visibleObstacle = true
216+
}
217+
break
218+
} else {
219+
sombra[posicionObstruccion] = 1
220+
}
221+
}
222+
}
223+
}
224+
}
225+
}
226+
227+
Vigilance.ctx.clearRect(vg.x + 8, vg.y + 16, 16, 16)
228+
sombra.forEach((val, index) => {
229+
if (val === 0) return
230+
Vigilance.ctx.rect((index % 32) * 16, Math.floor(index / 32) * 16, 16, 16)
231+
})
232+
Vigilance.ctx.fillStyle = `${this.palette[11]}66`
233+
Vigilance.ctx.fill()
234+
Vigilance.ctx.closePath()
235+
},
236+
setSight () {
237+
if (noShadows) return
238+
const players = Player.instances.map(({ position }) => position)
239+
ctxSight.beginPath()
240+
ctxSight.fillStyle = this.palette[0]
241+
ctxSight.rect(0, 0, 512, 512)
242+
ctxSight.fill()
243+
ctxSight.closePath()
244+
players.forEach(pl => {
245+
this.generateShadow(this.computePosition(pl.x, pl.y).collitionIndex, pl)
246+
})
247+
}
248+
}

engine/player.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/* global game, drawCollitions, tileset */
2+
3+
// eslint-disable-next-line no-unused-vars
4+
class Player {
5+
constructor (ctx, x, y, active = false) {
6+
this.position = {
7+
x, y, flip: true, inside: false, col: { x: 8, y: 32 }
8+
}
9+
this.active = active
10+
this.ctx = ctx
11+
if (!Player.instances) Player.instances = []
12+
Player.instances.push(this)
13+
}
14+
15+
static create (...params) {
16+
return new Player(...params)
17+
}
18+
19+
static getCurrent () {
20+
return Player.instances.find(player => player.active)
21+
}
22+
23+
setBound () {
24+
this.bound = {
25+
x: this.position.x + this.position.col.x,
26+
y: this.position.y + this.position.col.y
27+
}
28+
}
29+
30+
toggleActivation () {
31+
this.draw()
32+
this.active = !this.active
33+
if (!this.active) return
34+
const offset = this.position.flip ? 16 : 12
35+
this.ctx.beginPath()
36+
this.ctx.rect(this.position.x + offset, this.position.y + 24, 4, 4)
37+
this.ctx.fillStyle = game.palette[1]
38+
this.ctx.fill()
39+
this.ctx.closePath()
40+
}
41+
42+
move ({ x = this.position.x, y = this.position.y }, jump = false) {
43+
this.movement = 'up'
44+
if (x - this.position.x > 0) this.movement = 'right'
45+
if (x - this.position.x < 0) this.movement = 'left'
46+
if (y - this.position.y > 0) this.movement = 'down'
47+
this.setBound()
48+
if (this.interactingWith && this.interactingWith.properties.constraints) {
49+
const { x: oX, y: oY } = this.interactingWith
50+
const pX = this.position.x + 8
51+
const pY = this.position.col.y + this.position.y
52+
let relative = 'up'
53+
if (oX < pX) relative = 'left'
54+
else if (oX > pX) relative = 'right'
55+
else if (oY > pY) relative = 'down'
56+
57+
// dont let move char if not in constraints
58+
if (!this.interactingWith.properties.constraints.includes(this.movement)) return
59+
60+
if (
61+
(relative === 'up' && this.movement === 'down') ||
62+
(relative === 'down' && this.movement === 'up')
63+
) {
64+
this.interactingWith.state = false
65+
game.drawObject(this.interactingWith)
66+
delete this.interactingWith
67+
return
68+
}
69+
}
70+
71+
// we moved away, lets clear the object
72+
delete this.interactingWith
73+
74+
if (!jump) {
75+
const computed = game.computePosition(x, y + 16)
76+
if (computed.x < 1 || computed.x > 32 || computed.y < 2 || computed.y > 32) return
77+
if (game.activateObject(x + this.position.col.x, y + this.position.col.y)) return
78+
if (game.collitionMap[computed.collitionIndex] !== 0) return
79+
}
80+
this.position.x = x
81+
this.position.y = y
82+
this.draw()
83+
}
84+
85+
draw () {
86+
this.setBound()
87+
const { collitionIndex } = game.computePosition(this.position.x, this.position.y)
88+
this.position.inside = game.insideMap[collitionIndex] !== 0
89+
this.ctx.beginPath()
90+
this.ctx.clearRect(0, 0, 512, 512)
91+
const [spriteX, spriteY] = {
92+
// flipped, inside
93+
'false,false': [64, 80],
94+
'true,false': [32, 80],
95+
'true,true': [32, 128],
96+
'false,true': [64, 128]
97+
}[[this.position.flip, this.position.inside].join(',')]
98+
this.ctx.drawImage(tileset, spriteX, spriteY, 32, 48, this.position.x, this.position.y, 32, 48)
99+
if (drawCollitions) {
100+
// draw collition on player
101+
this.ctx.rect(this.bound.x, this.bound.y, 16, 16)
102+
this.ctx.strokeStyle = game.palette[1]
103+
this.ctx.stroke()
104+
}
105+
this.ctx.closePath()
106+
game.setSight()
107+
}
108+
}

0 commit comments

Comments
 (0)