Major refactor, input handler overhaul

This commit is contained in:
Chen Asraf
2017-06-28 23:29:23 +03:00
parent 628f71d81e
commit 8ebb45c037
11 changed files with 3033 additions and 135 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
.tags
node_modules/
docs/
dist/

View File

@@ -5,9 +5,6 @@
</head>
<body>
<script src="vendor/p5.min.js"></script>
<script src="lib/sprite.js"></script>
<script src="lib/character.js"></script>
<script src="lib/tile.js"></script>
<script src="lib/game.js"></script>
<script src="dist/bundle.js"></script>
</body>
</html>

View File

@@ -1,3 +1,6 @@
import Sprite from './sprite'
import p from './game'
/**
* Represents a moveable character with a sprite
* @extends {Sprite}
@@ -8,18 +11,20 @@
class Character extends Sprite {
constructor(i, j, w, h, options = {}) {
super(i, j)
this.w = w - 8
this.w = w
this.h = h
this.z = 10
this.img = options.img
this.movementR = 3
this.movementGridVisible = false
this.speed = 10
this.spriteSlice = options.spriteSlice || [44, 36, 36, 52]
this.clickPropagates = false
}
show() {
super.show()
image(this.img, ...this._getPos(), ...this.spriteSlice)
p.image(this.img, ...this._getPos(), ...this.spriteSlice)
}
__moveToRandom() {
@@ -38,3 +43,5 @@ class Character extends Sprite {
return [this.x + 2, this.y, this.w, this.h]
}
}
export default Character

8
lib/game-settings.js Normal file
View File

@@ -0,0 +1,8 @@
const w = 720,
h = 480,
gridSize = 48,
partySize = 5,
cols = Math.floor(w / gridSize),
rows = Math.floor(h / gridSize)
export { w, h, gridSize, partySize, cols, rows, tiles, party }

View File

@@ -1,68 +1,69 @@
const w = 720,
h = 480,
gridSize = 48,
partySize = 5,
cols = Math.floor(w / gridSize),
rows = Math.floor(h / gridSize)
console.log("HELP!")
import { w, h, gridSize, partySize, cols, rows, tiles, party } from './game-settings'
import Tile from './tile'
import Character from './character'
import InputHandler from './input-handler'
let tiles, party
let grassTile, grassHoverTile, chars, hoverChars
function game(p) {
let grassTile, grassHoverTile, chars, hoverChars
let tiles, party
function index(i, j) {
return i + j * cols
}
function preload() {
grassTile = loadImage('images/tiles/medievalTile_58.png')
grassHoverTile = loadImage('images/tiles/medievalTile_58.png')
chars = new Array(partySize)
hoverChars = new Array(partySize)
for (let i = 0; i < chars.length; i++) {
let img = `images/chars/medievalUnit_${("0"+(i+1)).slice(-2)}.png`
chars[i] = loadImage(img)
hoverChars[i] = loadImage(img)
}
}
function setup() {
createCanvas(w, h)
background(0)
tiles = new Array(cols * rows)
party = new Array(partySize)
for (let i = 0; i < party.length; i++) {
party[i] = new Character(floor(random(cols)), floor(random(rows)), gridSize, gridSize, {
img: chars[i]
})
function index(i, j) {
return i + j * cols
}
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
tiles[index(i, j)] = new Tile(i, j, {
img: grassTile,
hoverImg: grassHoverTile
})
p.preload = function() {
grassTile = p.loadImage('images/tiles/medievalTile_58.png')
grassHoverTile = p.loadImage('images/tiles/medievalTile_58.png')
chars = new Array(partySize)
hoverChars = new Array(partySize)
for (let i = 0; i < chars.length; i++) {
let img = `images/chars/medievalUnit_${("0"+(i+1)).slice(-2)}.png`
chars[i] = p.loadImage(img)
hoverChars[i] = p.loadImage(img)
}
}
}
p.setup = function() {
p.createCanvas(w, h)
p.background(0)
function draw() {
for (let tile of tiles) {
tile.show()
}
for (let char of party) {
char.show()
char.update()
tiles = new Array(cols * rows)
party = new Array(partySize)
for (let i = 0; i < party.length; i++) {
party[i] = new Character(p.floor(p.random(cols)), p.floor(p.random(rows)), gridSize - 8, gridSize, {
img: chars[i]
})
}
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
tiles[index(i, j)] = new Tile(i, j, {
img: grassTile,
hoverImg: grassHoverTile
})
}
}
}
// ellipse(mouseX, mouseY, 10)
// fill(255, 255, 255, 100)
fill(0)
textSize(12)
text(`x${mouseX.toFixed(2)}y${mouseY.toFixed(2)}`, 3, 10)
p.draw = function() {
InputHandler.update()
for (let tile of tiles) {
tile.show()
}
for (let char of party) {
char.show()
char.update()
}
// ellipse(p.mouseX, p.mouseY, 10)
// fill(255, 255, 255, 100)
p.fill(0)
p.textSize(12)
p.text(`x${p.mouseX.toFixed(2)}y${p.mouseY.toFixed(2)}`, 3, 10)
}
}
export default new p5(game)

90
lib/input-handler.js Normal file
View File

@@ -0,0 +1,90 @@
import p from './game'
import Sprite from './sprite'
import {
h,
w
} from './game-settings'
class InputHandler {
constructor() {
this._boundEvents = {}
this._clickHoldCounter = 0
this._wasClicking = false
for (let e of 'click hold drag hover'.split(' ')) {
this._boundEvents[e] = []
}
}
on(self, name, cb) {
if (!(typeof cb === 'function'))
throw new TypeError('callback must be ')
if (!(name in this._boundEvents))
throw new TypeError(`event name must be one of: ${Object.keys(this._boundEvents).join(', ')}`)
let idx = this._boundEvents[name].findIndex((x) => {
return x.self && x.self.z !== undefined && x.self.z > x.self.z
})
idx = idx >= 0 ? idx : this._boundEvents[name].length - 1
this._boundEvents[name].splice(idx, 0, {
self,
callback: cb
})
}
update() {
if (!this._interacting) {
if (p.mouseIsPressed) {
if (!this._startedClick) {
this._startedClick = true
this._wasClicking = false
this._clickHoldCounter = 0
} else {
this._wasClicking = true
this._clickHoldCounter += 1
}
} else {
if (this._startedClick) {
this._startedClick = false
this._interacting = true
let evName = 'click'
if (this._clickHoldCounter > 300)
evName = 'hold'
if (this._boundEvents[evName].length)
for (let ev of this._boundEvents[evName]) {
let res
if (this.isMouseOver(ev.self))
res = ev.callback()
if (res === false)
break
}
setTimeout(() => {
this._interacting = false
}, 300)
}
this._startedClick = false
this._wasClicking = false
this._clickHoldCounter = 0
}
}
}
isMouseOver(obj) {
return p.mouseX > 0 && p.mouseY > 0 &&
p.mouseX < w && p.mouseY < h &&
p.mouseX >= obj.x &&
p.mouseX < obj.x + obj.w &&
p.mouseY >= obj.y &&
p.mouseY < obj.y + obj.w
}
}
export default new InputHandler()

View File

@@ -1,5 +1,10 @@
const INTERACTION_INTERVAL = 50
const MIN_INTERACTION_HOLDING = INTERACTION_INTERVAL * 2
import { gridSize, w, h } from './game-settings'
import p from './game'
import InputHandler from './input-handler'
import Tile from './tile'
/**
* Base class for objects on screen.
* @param {number} i Sprite's grid X
@@ -19,28 +24,31 @@ class Sprite {
this.speed = 1
this._destI = this._destJ = null
this._circleGridCache = []
this.clickDisabled = false
this.clickPropagates = true
this.control = {
active: false,
continuous: false,
holdTime: 0,
}
InputHandler.on(this, 'click', this._onClick.bind(this))
}
_spriteImage(img) {
this.img = img
}
/**
Returns true if the mouse is positioned inside the boundaries of the sprite.
@return {boolean}
*/
isMouseOver() {
return mouseX > 0 && mouseY > 0 &&
mouseX < w && mouseY < h &&
mouseX >= this.x &&
mouseX < this.x + this.w &&
mouseY >= this.y &&
mouseY < this.y + this.w
_onClick() {
console.log('on click firing', this)
if (typeof this.onClick === 'function' && this.visible) {
this.onClick(...arguments)
}
if (!this.clickPropagates)
return false
return true
}
/**
@@ -52,28 +60,37 @@ class Sprite {
moveTo(i, j) {
this._destX = i * gridSize
this._destY = j * gridSize
this.movementGridVisible = false
this._circleGridCache.length = 0
}
_doMovement() {
if (!(this._destX || this._destY))
return
let diffX = Math.abs(this.x - this._destX),
diffY = Math.abs(this.y - this._destY),
dirX = (this.x > this._destX ? -1 : 1),
dirY = (this.y > this._destY ? -1 : 1)
this.i = this.x / gridSize
this.j = this.y / gridSize
this.i = (this.x - this.x % gridSize) / gridSize
this.j = (this.y - this.x % gridSize) / gridSize
if (diffX < 1 && diffY < 1) {
console.log({diffX, diffY})
this.clickDisabled = false
this.x = this._destX
this.y = this._destY
this._destX = this._destY = null
return
} else {
this.clickDisabled = true
}
if (diffX > 0)
this.x += Math.min(this.speed, diffX - 1) * dirX
this.x += Math.min(this.speed, diffX - 0.1) * dirX
if (diffY > 0)
this.y += Math.min(this.speed, diffY - 1) * dirY
this.y += Math.min(this.speed, diffY - 0.1) * dirY
}
/**
@@ -83,6 +100,7 @@ class Sprite {
* @see {@link Sprite#update}
*/
show() {
this.visible = true
if (this.movementGridVisible)
this.drawCircleGrid(this.movementR)
}
@@ -95,13 +113,6 @@ class Sprite {
*/
update() {
this._doMovement()
if (typeof this.onClick === 'function') {
if (this.isMouseClicking()) {
this._startInteraction()
this.onClick()
}
}
}
/**
@@ -120,7 +131,7 @@ class Sprite {
}
_calcCircleGrid(r) {
this._circleGridCache = []
this._circleGridCache.length = 0
this._circleGridRadius = r
let i = r,
@@ -129,7 +140,6 @@ class Sprite {
cache = {}
while (i >= j) {
let startI = -i + this.i,
endI = i + this.i + 1
@@ -163,58 +173,34 @@ class Sprite {
* @return {Array.<Tile>} Tiles created
*/
drawHorizontalLineGrid(startI, endI, j) {
let circleColor = color(30, 167, 255, 100),
let circleColor = p.color(30, 167, 255, 100),
out = []
for (let curI = startI; curI < endI; curI++) {
let tile = new Tile(curI, j, {
color: circleColor,
stroke: false
})
}), _this = this
setTimeout(() => {
tile.onClick = function() {
_this.moveTo(curI, j)
for (let tile of this._circleGridCache) {
tile.destroy()
}
this._circleGridCache.length = 0
}
}, 100)
out.push(tile)
}
return out
}
/**
* Returns true if the mouse is currently clicking while positioned on the sprite
* @see {@link Sprite#isMouseHolding}
* @return {boolean} Mouse is currently clicking
*/
isMouseClicking() {
return !this.control.active && mouseIsPressed && this.isMouseOver() && !this.control.continuous
}
/**
* Returns true if the mouse is currently being held clicked while positioned on the sprite.
* Being held is defined by clicking for over 100ms continuously.
* @return {boolean} Mouse is currently being held beyond 100ms
*/
isMouseHolding(immediate = true) {
return !this.control.active && mouseIsPressed && this.isMouseOver() && (immediate && true || this.continuous)
}
_startInteraction() {
this.control.active = Boolean(mouseIsPressed && this.isMouseOver())
this.control.continuous = false
this.numbereractionInterval = setInterval(() => this._endInteraction(), INTERACTION_INTERVAL)
}
_endInteraction() {
this.control.active = Boolean(mouseIsPressed && this.isMouseOver())
if (this.control.active) {
this.control.holdTime += INTERACTION_INTERVAL
this.control.continuous = this.control.holdTime >= MIN_INTERACTION_HOLDING
} else {
clearInterval(this.numbereractionInterval)
this.control.holdTime = 0
this.control.continuous = false
}
}
_toggleMovementGrid() {
this.movementGridVisible = !this.movementGridVisible
}
}
export default Sprite

View File

@@ -1,3 +1,6 @@
import Sprite from './sprite'
import p from './game'
class Tile extends Sprite {
constructor(i, j, options = {}) {
super(i, j)
@@ -5,23 +8,29 @@ class Tile extends Sprite {
this.hoverImg = options.hoverImg
this.stroke = options.stroke || [177, 218, 131, 90]
this.color = options.color
this._isMouseOver = false
this.z = 0
if (this.hoverImg)
this.hoverImg.filter(GRAY)
this.hoverImg.filter(p.GRAY)
}
show() {
super.show()
if (this.color) {
fill(this.color)
rect(this.x, this.y, this.w, this.w)
p.fill(this.color)
p.rect(this.x, this.y, this.w, this.w)
if (this.stroke) {
strokeWeight(2)
stroke(...this.stroke)
p.strokeWeight(2)
p.stroke(...this.stroke)
}
} else {
image(this.isMouseOver() ? this.hoverImg : this.img, this.x, this.y, this.w, this.w)
p.image(this._isMouseOver ? this.hoverImg : this.img, this.x, this.y, this.w, this.w)
}
}
}
export default Tile

2768
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
{
"name": "strange-tactics",
"version": "0.0.1",
"description": "",
"main": "index.html",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
@@ -23,6 +22,16 @@
"homepage": "https://github.com/chenasraf/strange-tactics#readme",
"dependencies": {},
"devDependencies": {
"jsdoc": "^3.4.3"
}
"babel-core": "^6.25.0",
"babel-loader": "^7.1.1",
"babel-preset-env": "^1.5.2",
"babel-preset-es2016": "^6.24.1",
"jsdoc": "^3.4.3",
"webpack": "^3.0.0"
},
"directories": {
"doc": "docs",
"lib": "lib"
},
"description": ""
}

22
webpack.config.js Normal file
View File

@@ -0,0 +1,22 @@
let path = require('path')
module.exports = {
entry: './lib/game.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devtool: "source-map",
// module: {
// rules: [{
// test: /\.js$/,
// exclude: /(node_modules|bower_components)/,
// use: {
// loader: 'babel-loader',
// options: {
// presets: ['env', 'es2016']
// }
// }
// }]
// }
}