mirror of
https://github.com/chenasraf/strange-tactics.git
synced 2026-05-17 17:58:04 +00:00
Major refactor, input handler overhaul
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
.tags
|
||||
node_modules/
|
||||
docs/
|
||||
dist/
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
8
lib/game-settings.js
Normal 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 }
|
||||
115
lib/game.js
115
lib/game.js
@@ -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
90
lib/input-handler.js
Normal 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()
|
||||
112
lib/sprite.js
112
lib/sprite.js
@@ -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
|
||||
|
||||
21
lib/tile.js
21
lib/tile.js
@@ -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
2768
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -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
22
webpack.config.js
Normal 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']
|
||||
// }
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user