japanese-drum-game/public/src/js/view.js

1321 lines
33 KiB
JavaScript
Raw Normal View History

2018-10-25 22:18:41 +08:00
class View{
constructor(controller){
2018-09-06 00:46:26 +08:00
this.controller = controller
2018-10-25 22:18:41 +08:00
this.canvas = document.getElementById("canvas")
this.ctx = this.canvas.getContext("2d")
2018-09-06 00:46:26 +08:00
2018-09-18 06:37:59 +08:00
this.pauseMenu = document.getElementById("pause-menu")
this.cursor = document.getElementById("cursor")
2018-10-06 01:03:59 +08:00
this.gameDiv = document.getElementById("game")
2018-09-18 06:37:59 +08:00
2018-10-25 22:18:41 +08:00
this.portraitClass = false
this.touchp2Class = false
this.currentScore = {
ms: -Infinity,
type: 0
2018-09-13 01:10:00 +08:00
}
2018-10-25 22:18:41 +08:00
this.noteFace = {
small: 0,
big: 3
2018-09-14 06:55:23 +08:00
}
2018-09-06 00:46:26 +08:00
this.nextBeat = 0
2018-09-20 07:20:26 +08:00
this.gogoTime = 0
2018-09-15 22:34:53 +08:00
this.drumroll = []
2018-10-11 06:13:24 +08:00
this.beatInterval = this.controller.parsedSongData.beatInfo.beatInterval
2018-10-25 22:18:41 +08:00
this.font = "TnT, Meiryo, sans-serif"
this.draw = new CanvasDraw()
2018-09-20 07:20:26 +08:00
this.assets = new ViewAssets(this)
2018-10-06 01:03:59 +08:00
2018-10-25 22:18:41 +08:00
this.titleCache = new CanvasCache()
this.comboCache = new CanvasCache()
this.multiplayer = this.controller.multiplayer
this.touchEnabled = this.controller.touchEnabled
this.touch = -Infinity
2018-10-06 01:03:59 +08:00
2018-10-25 22:18:41 +08:00
if(this.multiplayer !== 2){
pageEvents.add(window, "resize", () => {
if(this.controller.game.isPaused()){
this.refresh()
setTimeout(this.refresh.bind(this), 100)
}
})
2018-10-06 01:03:59 +08:00
2018-10-25 22:18:41 +08:00
if(this.controller.touchEnabled){
this.touchDrumDiv = document.getElementById("touch-drum")
this.touchDrumImg = document.getElementById("touch-drum-img")
if(this.controller.autoPlayEnabled){
this.touchDrumDiv.style.display = "none"
}else{
pageEvents.add(this.canvas, "touchstart", this.ontouch.bind(this))
}
2018-10-25 22:18:41 +08:00
this.gameDiv.classList.add("touch-visible")
document.getElementById("version").classList.add("version-hide")
this.touchFullBtn = document.getElementById("touch-full-btn")
pageEvents.add(this.touchFullBtn, "touchend", toggleFullscreen)
if(!fullScreenSupported){
this.touchFullBtn.style.display = "none"
}
this.touchPauseBtn = document.getElementById("touch-pause-btn")
pageEvents.add(this.touchPauseBtn, "touchend", () => {
this.controller.togglePauseMenu()
})
if(this.multiplayer){
this.touchPauseBtn.style.display = "none"
}
2018-10-13 02:04:28 +08:00
}
2018-10-06 01:03:59 +08:00
}
2018-09-06 00:46:26 +08:00
}
run(){
this.setBackground()
2018-10-03 17:48:18 +08:00
this.lastMousemove = this.controller.getElapsedTime()
pageEvents.mouseAdd(this, this.onmousemove.bind(this))
2018-09-06 00:46:26 +08:00
this.refresh()
}
2018-10-25 22:18:41 +08:00
refresh(){
var ctx = this.ctx
var winW = innerWidth
var winH = lastHeight
if(winW / 32 > winH / 9){
winW = winH / 9 * 32
}
this.portrait = winW < winH
var touchMultiplayer = this.touchEnabled && this.multiplayer && !this.portrait
this.pixelRatio = window.devicePixelRatio || 1
winW *= this.pixelRatio
winH *= this.pixelRatio
if(this.portrait){
var ratioX = winW / 720
var ratioY = winH / 1280
}else{
var ratioX = winW / 1280
var ratioY = winH / 720
}
var ratio = (ratioX < ratioY ? ratioX : ratioY)
if(this.winW !== winW || this.winH !== winH){
this.winW = winW
this.winH = winH
this.ratio = ratio
if(this.multiplayer !== 2){
this.canvas.width = winW
this.canvas.height = winH
ctx.scale(ratio, ratio)
this.canvas.style.width = (winW / this.pixelRatio) + "px"
this.canvas.style.height = (winH / this.pixelRatio) + "px"
this.titleCache.resize(640, 80, ratio)
2018-09-27 09:04:01 +08:00
}
2018-10-25 22:18:41 +08:00
this.fillComboCache()
}else if(this.multiplayer !== 2){
ctx.clearRect(0, 0, winW / ratio, winH / ratio)
}
winW /= ratio
winH /= ratio
var ms = this.getMS()
if(this.portrait){
var frameTop = winH / 2 - 1280 / 2
var frameLeft = winW / 2 - 720 / 2
}else{
var frameTop = winH / 2 - 720 / 2
var frameLeft = winW / 2 - 1280 / 2
}
if(this.multiplayer === 2){
frameTop += this.multiplayer === 2 ? 165 : 176
}
if(touchMultiplayer){
if(!this.touchp2Class){
this.touchp2Class = true
this.gameDiv.classList.add("touchp2")
2018-09-27 09:04:01 +08:00
}
2018-10-25 22:18:41 +08:00
frameTop -= 90
}else if(this.touchp2Class){
this.touchp2Class = false
this.gameDiv.classList.remove("touchp2")
2018-09-27 09:04:01 +08:00
}
2018-10-25 22:18:41 +08:00
ctx.save()
ctx.translate(0, frameTop)
this.drawGogoTime()
if(!touchMultiplayer){
this.assets.drawAssets("background")
}
if(this.multiplayer !== 2){
this.titleCache.get({
ctx: ctx,
x: winW - (touchMultiplayer && fullScreenSupported ? 750 : 650),
y: touchMultiplayer ? 75 : 10,
w: 640,
h: 80,
id: "title"
}, ctx => {
this.draw.layeredText({
ctx: ctx,
text: this.controller.selectedSong.title,
fontSize: 40,
fontFamily: this.font,
x: 620,
y: 20,
width: 600,
align: "right"
}, [
{outline: "#000", letterBorder: 10},
{fill: "#fff"}
])
})
}
var score = this.controller.getGlobalScore()
var gaugePercent = Math.round(score.gauge / 2) / 50
if(this.multiplayer === 2){
var scoreImg = "bg_score_p2"
var scoreFill = "#6bbec0"
2018-09-06 00:46:26 +08:00
}else{
2018-10-25 22:18:41 +08:00
var scoreImg = "bg_score_p1"
var scoreFill = "#fa4529"
}
if(this.portrait){
// Portrait
if(!this.portraitClass){
this.portraitClass = true
this.gameDiv.classList.add("portrait")
}
this.slotPos = {
x: 66,
y: frameTop + 375,
size: 100,
paddingLeft: 0
}
this.scorePos = {x: 363, y: frameTop + (this.multiplayer === 2 ? 520 : 227)}
var animPos = {
x1: this.slotPos.x + 13,
y1: this.slotPos.y + (this.multiplayer === 2 ? 27 : -27),
x2: winW - 38,
y2: frameTop + (this.multiplayer === 2 ? 484 : 293)
}
var taikoPos = {
x: 19,
y: frameTop + (this.multiplayer === 2 ? 464 : 184),
w: 111,
h: 130
}
ctx.fillStyle = "#000"
ctx.fillRect(
0,
this.multiplayer === 2 ? 306 : 288,
winW,
this.multiplayer === 1 ? 184 : 183
)
ctx.beginPath()
if(this.multiplayer === 2){
ctx.moveTo(0, 467)
ctx.lineTo(384, 467)
ctx.lineTo(384, 512)
ctx.lineTo(184, 560)
ctx.lineTo(0, 560)
}else{
ctx.moveTo(0, 217)
ctx.lineTo(184, 217)
ctx.lineTo(384, 265)
ctx.lineTo(384, 309)
ctx.lineTo(0, 309)
}
ctx.fill()
// Left side
ctx.fillStyle = scoreFill
var leftSide = (ctx, mul) => {
ctx.beginPath()
if(this.multiplayer === 2){
ctx.moveTo(0, 468 * mul)
ctx.lineTo(380 * mul, 468 * mul)
ctx.lineTo(380 * mul, 512 * mul)
ctx.lineTo(184 * mul, 556 * mul)
ctx.lineTo(0, 556 * mul)
}else{
ctx.moveTo(0, 221 * mul)
ctx.lineTo(184 * mul, 221 * mul)
ctx.lineTo(380 * mul, 265 * mul)
ctx.lineTo(380 * mul, 309 * mul)
ctx.lineTo(0, 309 * mul)
}
}
leftSide(ctx, 1)
ctx.fill()
ctx.globalAlpha = 0.5
this.draw.pattern({
ctx: ctx,
img: assets.image[scoreImg],
shape: leftSide,
dx: 0,
dy: 45,
scale: 1.55
})
ctx.globalAlpha = 1
// Score background
ctx.fillStyle = "#000"
ctx.beginPath()
if(this.multiplayer === 2){
this.draw.roundedCorner(ctx, 184, 512, 20, 0)
ctx.lineTo(384, 512)
this.draw.roundedCorner(ctx, 384, 560, 12, 2)
ctx.lineTo(184, 560)
}else{
ctx.moveTo(184, 217)
this.draw.roundedCorner(ctx, 384, 217, 12, 1)
ctx.lineTo(384, 265)
this.draw.roundedCorner(ctx, 184, 265, 20, 3)
}
ctx.fill()
// Difficulty
var badgeImg = assets.image["muzu_" + this.controller.selectedSong.difficulty]
var badgeW = badgeImg.width / badgeImg.height * 53
ctx.drawImage(badgeImg,
157 - badgeW / 2,
this.multiplayer === 2 ? 497 : 228,
badgeW,
53
)
// Badges
if(this.controller.autoPlayEnabled && !this.controller.multiplayer){
this.ctx.drawImage(assets.image["badge_auto"],
183,
this.multiplayer === 2 ? 490 : 265,
23,
23
)
}
// Gauge
ctx.fillStyle = "#000"
ctx.beginPath()
var gaugeX = winW - 788 * 0.7 - 32
if(this.multiplayer === 2){
ctx.moveTo(gaugeX, 464)
ctx.lineTo(winW, 464)
ctx.lineTo(winW, 489)
this.draw.roundedCorner(ctx, gaugeX, 489, 12, 3)
}else{
this.draw.roundedCorner(ctx, gaugeX, 288, 12, 0)
ctx.lineTo(winW, 288)
ctx.lineTo(winW, 314)
ctx.lineTo(gaugeX, 314)
}
ctx.fill()
this.draw.gauge({
ctx: ctx,
x: winW,
y: this.multiplayer === 2 ? 468 : 273,
clear: 25 / 50,
percentage: gaugePercent,
font: this.font,
scale: 0.7,
multiplayer: this.multiplayer === 2
})
this.draw.soul({
ctx: ctx,
x: winW - 40,
y: this.multiplayer === 2 ? 484 : 293,
scale: 0.75,
cleared: gaugePercent - 1 / 50 >= 25 / 50
})
// Note bar
ctx.fillStyle = "#2c2a2c"
ctx.fillRect(0, 314, winW, 122)
ctx.fillStyle = "#847f84"
ctx.fillRect(0, 440, winW, 24)
}else{
// Landscape
if(this.portraitClass){
this.portraitClass = false
this.gameDiv.classList.remove("portrait")
}
this.slotPos = {
x: 413,
y: frameTop + 257,
size: 106,
paddingLeft: 332
}
this.scorePos = {
x: 155,
y: frameTop + (this.multiplayer === 2 ? 318 : 193)
}
var animPos = {
x1: this.slotPos.x + 14,
y1: this.slotPos.y + (this.multiplayer === 2 ? 29 : -29),
x2: winW - 55,
y2: frameTop + (this.multiplayer === 2 ? 378 : 165)
}
var taikoPos = {x: 179, y: frameTop + 190, w: 138, h: 162}
ctx.fillStyle = "#000"
ctx.fillRect(
0,
184,
winW,
this.multiplayer === 1 ? 177 : 176
)
ctx.beginPath()
if(this.multiplayer === 2){
ctx.moveTo(328, 351)
ctx.lineTo(winW, 351)
ctx.lineTo(winW, 385)
this.draw.roundedCorner(ctx, 328, 385, 10, 3)
}else{
ctx.moveTo(328, 192)
this.draw.roundedCorner(ctx, 328, 158, 10, 0)
ctx.lineTo(winW, 158)
ctx.lineTo(winW, 192)
}
ctx.fill()
// Gauge
this.draw.gauge({
ctx: ctx,
x: winW,
y: this.multiplayer === 2 ? 357 : 135,
clear: 25 / 50,
percentage: gaugePercent,
font: this.font,
multiplayer: this.multiplayer === 2
})
this.draw.soul({
ctx: ctx,
x: winW - 57,
y: this.multiplayer === 2 ? 378 : 165,
cleared: gaugePercent - 1 / 50 >= 25 / 50
})
// Note bar
ctx.fillStyle = "#2c2a2c"
ctx.fillRect(332, 192, winW - 332, 130)
ctx.fillStyle = "#847f84"
ctx.fillRect(332, 326, winW - 332, 26)
// Left side
ctx.fillStyle = scoreFill
ctx.fillRect(0, 192, 328, 160)
ctx.globalAlpha = 0.5
this.draw.pattern({
ctx: ctx,
img: assets.image[scoreImg],
x: 0,
y: 192,
w: 328,
h: 160,
dx: 0,
dy: 45,
scale: 1.55
})
ctx.globalAlpha = 1
// Difficulty
var badgeImg = assets.image["muzu_" + this.controller.selectedSong.difficulty]
var badgeW = badgeImg.width / badgeImg.height * 120
ctx.drawImage(badgeImg,
87 - badgeW / 2, this.multiplayer === 2 ? 194 : 232,
badgeW, 120
)
// Badges
if(this.controller.autoPlayEnabled && !this.controller.multiplayer){
this.ctx.drawImage(assets.image["badge_auto"],
125, 235, 34, 34
)
}
// Score background
ctx.fillStyle = "#000"
ctx.beginPath()
if(this.multiplayer === 2){
ctx.moveTo(0, 312)
this.draw.roundedCorner(ctx, 176, 312, 20, 1)
ctx.lineTo(176, 353)
ctx.lineTo(0, 353)
}else{
ctx.moveTo(0, 191)
ctx.lineTo(176, 191)
this.draw.roundedCorner(ctx, 176, 232, 20, 2)
ctx.lineTo(0, 232)
}
ctx.fill()
}
ctx.restore()
animPos.w = animPos.x2 - animPos.x1
animPos.h = animPos.y1 - animPos.y2
this.animateBezier = [{
// 427, 228
x: animPos.x1,
y: animPos.y1
}, {
// 560, 10
x: animPos.x1 + animPos.w / 6,
y: animPos.y1 - animPos.h * (this.multiplayer === 2 ? 2.5 : 3.5)
}, {
// 940, -150
x: animPos.x2 - animPos.w / 3,
y: animPos.y2 - animPos.h * (this.multiplayer === 2 ? 3.5 : 5)
}, {
// 1225, 165
x: animPos.x2,
y: animPos.y2
}]
var touchTop = frameTop + (touchMultiplayer ? 135 : 0)
2018-10-06 01:03:59 +08:00
this.touchDrum = (() => {
var sw = 842
var sh = 340
var x = 0
2018-10-25 22:18:41 +08:00
var y = this.portrait ? touchTop + 477 : touchTop + 365
var paddingTop = 13
var w = winW
var maxH = winH - y
2018-10-06 01:03:59 +08:00
var h = maxH - paddingTop
if(w / h >= sw / sh){
w = h / sh * sw
2018-10-25 22:18:41 +08:00
x = (winW - w) / 2
2018-10-06 01:03:59 +08:00
y += paddingTop
}else{
h = w / sw * sh
y = y + (maxH - h)
}
return {
x: x, y: y, w: w, h: h
}
})()
2018-10-25 22:18:41 +08:00
this.touchCircle = {
x: winW / 2,
y: winH + this.touchDrum.h * 0.1,
rx: this.touchDrum.w / 2 - this.touchDrum.h * 0.03,
ry: this.touchDrum.h * 1.07
}
2018-09-06 00:46:26 +08:00
2018-10-25 22:18:41 +08:00
if(this.multiplayer !== 2){
this.mouseIdle()
this.drawTouch()
}
2018-09-09 12:09:15 +08:00
2018-10-25 22:18:41 +08:00
// Score
ctx.save()
ctx.font = "30px " + this.font
ctx.fillStyle = "#fff"
ctx.strokeStyle = "#fff"
ctx.lineWidth = 0.3
ctx.textAlign = "center"
ctx.textBaseline = "top"
var glyph = 29
var pointsText = score.points.toString().split("")
ctx.translate(this.scorePos.x, this.scorePos.y)
ctx.scale(0.7, 1)
for(var i in pointsText){
var x = glyph * (i - pointsText.length + 1)
ctx.strokeText(pointsText[i], x, 0)
ctx.fillText(pointsText[i], x, 0)
2018-10-06 01:03:59 +08:00
}
2018-10-25 22:18:41 +08:00
ctx.restore()
// Bar pressed keys
var keyTime = this.controller.getKeyTime()
var sound = keyTime["don"] > keyTime["ka"] ? "don" : "ka"
var padding = this.slotPos.paddingLeft
var mul = this.slotPos.size / 106
var barY = this.slotPos.y - 65 * mul
var barH = 130 * mul
if(this.gogoTime || ms <= this.gogoTimeStarted + 100){
var grd = ctx.createLinearGradient(0, 0, this.winW, 0)
grd.addColorStop(0, "#512a2c")
grd.addColorStop(0.46, "#6f2a2d")
grd.addColorStop(0.76, "#8a4763")
grd.addColorStop(1, "#2c2a2c")
ctx.fillStyle = grd
if(!this.touchEnabled){
var alpha = Math.min(100, ms - this.gogoTimeStarted) / 100
if(!this.gogoTime){
alpha = 1 - alpha
}
ctx.globalAlpha = alpha
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
ctx.fillRect(padding, barY, winW - padding, barH)
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
if(keyTime[sound] > ms - 200){
var gradients = {
"don": ["#f54c25", "#232323"],
"ka": ["#75cee9", "#232323"]
}
var grd = ctx.createLinearGradient(0, 0, this.winW, 0)
grd.addColorStop(0, gradients[sound][0])
grd.addColorStop(1, gradients[sound][1])
ctx.fillStyle = grd
ctx.globalAlpha = 1 - (ms - keyTime[sound]) / 200
ctx.fillRect(padding, barY, winW - padding, barH)
}
ctx.globalAlpha = 1
2018-09-06 00:46:26 +08:00
2018-10-25 22:18:41 +08:00
// Taiko
ctx.drawImage(assets.image["taiko"],
0, 0, 138, 162,
taikoPos.x, taikoPos.y, taikoPos.w, taikoPos.h
2018-09-06 00:46:26 +08:00
)
2018-10-25 22:18:41 +08:00
// Taiko pressed keys
var kbd = this.controller.getBindings()
var keys = ["ka_l", "ka_r", "don_l", "don_r"]
for(var i = 0; i < keys.length; i++){
var keyMS = ms - keyTime[kbd[keys[i]]]
if(keyMS < 130){
if(keyMS > 70 && !this.touchEnabled){
ctx.globalAlpha = this.draw.easeOut(1 - (keyMS - 70) / 60)
}
ctx.drawImage(assets.image["taiko"],
0, 162 * (i + 1), 138, 162,
taikoPos.x, taikoPos.y, taikoPos.w, taikoPos.h
)
2015-07-17 16:22:46 +08:00
}
2018-10-25 22:18:41 +08:00
}
ctx.globalAlpha = 1
// Combo
var scoreMS = ms - this.currentScore.ms
2018-09-06 00:46:26 +08:00
var comboCount = this.controller.getCombo()
if(comboCount >= 10){
2018-10-09 14:59:36 +08:00
var comboText = comboCount.toString().split("")
2018-10-25 22:18:41 +08:00
var mul = this.portrait ? 0.8 : 1
var comboX = taikoPos.x + taikoPos.w / 2
var comboY = taikoPos.y + taikoPos.h * 0.09
var comboScale = 0
if(this.currentScore !== 0 && scoreMS < 100){
comboScale = this.draw.fade(scoreMS / 100)
}
var glyphW = 51
var glyphH = 64
var letterSpacing = (comboText.length >= 4 ? 38 : 42) * mul
var orange = comboCount >= 100 ? "1" : "0"
var w = glyphW * mul
var h = glyphH * mul * (1 + comboScale / 8)
2018-09-06 00:46:26 +08:00
for(var i in comboText){
2018-10-25 22:18:41 +08:00
var textX = comboX + letterSpacing * (i - (comboText.length - 1) / 2)
this.comboCache.get({
ctx: ctx,
x: textX - w / 2,
y: comboY + glyphH * mul - h,
w: w,
h: h,
id: orange + "combo" + comboText[i]
})
2015-07-17 16:22:46 +08:00
}
2018-09-06 00:46:26 +08:00
2018-10-25 22:18:41 +08:00
var fontSize = 24 * mul
var comboTextY = taikoPos.y + taikoPos.h * 0.63
if(orange === "1"){
var grd = ctx.createLinearGradient(
0,
comboTextY - fontSize * 0.6,
0,
comboTextY + fontSize * 0.1
)
grd.addColorStop(0, "#ff2000")
grd.addColorStop(0.5, "#ffc321")
grd.addColorStop(1, "#ffedb7")
ctx.fillStyle = grd
2018-09-06 01:35:57 +08:00
}else{
2018-10-25 22:18:41 +08:00
ctx.fillStyle = "#fff"
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
ctx.font = fontSize + "px " + this.font
ctx.lineWidth = 7 * mul
ctx.textAlign = "center"
ctx.strokeText("コンボ", comboX, comboTextY)
ctx.fillText("コンボ", comboX, comboTextY)
2015-07-17 16:22:46 +08:00
}
2018-10-25 22:18:41 +08:00
// Slot
this.draw.slot(ctx, this.slotPos.x, this.slotPos.y, this.slotPos.size)
// Measures
ctx.save()
ctx.rect(this.slotPos.paddingLeft, 0, winW - this.slotPos.paddingLeft, winH)
ctx.clip()
this.drawMeasures()
ctx.restore()
// Go go time fire
this.assets.drawAssets("bar")
// Hit notes shadow
if(scoreMS < 300 && this.currentScore.type){
var fadeOut = scoreMS > 120 && !this.touchEnabled
if(fadeOut){
ctx.globalAlpha = 1 - (scoreMS - 120) / 180
}
var scoreId = this.currentScore.type === 230 ? 0 : 1
if(this.currentScore.bigNote){
scoreId += 2
}
ctx.drawImage(assets.image["notes_hit"],
0, 128 * scoreId, 128, 128,
this.slotPos.x - 64, this.slotPos.y - 64,
128, 128
2018-09-06 00:46:26 +08:00
)
2018-10-25 22:18:41 +08:00
if(fadeOut){
ctx.globalAlpha = 1
}
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
// Future notes
this.updateNoteFaces()
ctx.save()
ctx.rect(this.slotPos.paddingLeft, 0, winW - this.slotPos.paddingLeft, winH)
ctx.clip()
this.drawCircles(this.controller.getCircles())
ctx.restore()
// Hit notes explosion
// Good, OK, Bad
if(scoreMS < 300){
var mul = this.slotPos.size / 106
var scores = {
"0": "bad",
"230": "ok",
"450": "good"
}
var yOffset = scoreMS < 70 ? scoreMS * (13 / 70) : 0
var fadeOut = scoreMS > 250 && !this.touchEnabled
if(fadeOut){
ctx.globalAlpha = 1 - (scoreMS - 250) / 50
}
this.draw.score({
ctx: ctx,
score: scores[this.currentScore.type],
x: this.slotPos.x,
y: this.slotPos.y - 98 * mul - yOffset,
scale: 1.35 * mul,
align: "center"
})
if(fadeOut){
ctx.globalAlpha = 1
}
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
// Animating notes
this.drawAnimatedCircles(this.controller.getCircles())
this.drawAnimatedCircles(this.drumroll)
// Go-go time fireworks
if(!this.touchEnabled && !this.portrait && !this.multiplayer){
this.assets.drawAssets("foreground")
2018-09-06 00:46:26 +08:00
}
}
2018-10-25 22:18:41 +08:00
setBackground(){
var gameDiv = document.getElementById("game")
var selectedSong = this.controller.selectedSong
var bg = "/songs/" + selectedSong.folder + "/bg.png"
if(selectedSong.defaultBg){
var categories = {
"J-POP": 0,
"アニメ": 1,
"ボーカロイド™曲": 2,
"バラエティ": 3,
"クラシック": 4,
"ゲームミュージック": 5,
"ナムコオリジナル": 6
}
var catId = 7
if(selectedSong.category in categories){
catId = categories[selectedSong.category]
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
bg = assets.image["bg_genre_" + catId].src
gameDiv.classList.add("default-bg")
2015-07-17 16:22:46 +08:00
}
2018-10-25 22:18:41 +08:00
gameDiv.style.backgroundImage = "url('" + bg + "')"
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
drawMeasures(){
var measures = this.controller.parsedSongData.measures
var ms = this.getMS()
var mul = this.slotPos.size / 106
var distanceForCircle = this.winW / this.ratio - this.slotPos.x
var measureY = this.slotPos.y - 65 * mul
var measureH = 130 * mul
measures.forEach(measure => {
var timeForDistance = this.posToMs(distanceForCircle, measure.speed)
if(ms >= measure.ms - timeForDistance && ms <= measure.ms + 350){
var measureX = this.slotPos.x + this.msToPos(measure.ms - ms, measure.speed)
this.ctx.strokeStyle = "#bdbdbd"
this.ctx.lineWidth = 3
this.ctx.beginPath()
this.ctx.moveTo(measureX, measureY)
this.ctx.lineTo(measureX, measureY + measureH)
this.ctx.stroke()
}
})
2018-09-15 22:34:53 +08:00
}
2018-10-25 22:18:41 +08:00
updateNoteFaces(){
var ms = this.getMS()
while(ms >= this.nextBeat){
this.nextBeat += this.beatInterval
if(this.controller.getCombo() >= 50){
var face = Math.floor(ms / this.beatInterval) % 2
this.noteFace = {
small: face,
big: face + 2
}
}else{
this.noteFace = {
small: 0,
big: 3
}
}
}
2018-09-15 22:34:53 +08:00
}
drawCircles(circles){
2018-10-25 22:18:41 +08:00
var distanceForCircle = this.winW / this.ratio - this.slotPos.x
var ms = this.controller.getElapsedTime()
2018-09-06 00:46:26 +08:00
for(var i = circles.length; i--;){
var circle = circles[i]
2018-09-15 22:34:53 +08:00
var speed = circle.getSpeed()
2015-07-17 16:22:46 +08:00
2018-10-25 22:18:41 +08:00
var timeForDistance = this.posToMs(distanceForCircle + this.slotPos.size / 2, speed)
2018-09-06 00:46:26 +08:00
var startingTime = circle.getMS() - timeForDistance
2018-10-25 22:18:41 +08:00
var finishTime = circle.getEndTime() + this.posToMs(this.slotPos.x - this.slotPos.paddingLeft + this.slotPos.size * 2, speed)
2018-09-06 00:46:26 +08:00
2018-09-22 04:31:35 +08:00
if(circle.getPlayed() <= 0 || circle.getScore() === 0){
if(ms >= startingTime && ms <= finishTime && circle.getPlayed() !== -1){
2018-09-15 22:34:53 +08:00
this.drawCircle(circle)
}
}else if(!circle.isAnimated()){
2018-10-25 22:18:41 +08:00
// Start animation to gauge
circle.animate(ms)
2018-09-06 00:46:26 +08:00
}
2018-09-20 07:20:26 +08:00
if(ms >= circle.ms && !circle.gogoChecked){
if(this.gogoTime != circle.gogoTime){
this.toggleGogoTime(circle)
}
circle.gogoChecked = true
}
2018-10-25 22:18:41 +08:00
}
}
drawAnimatedCircles(circles){
var ms = this.controller.getElapsedTime()
for(var i = 0; i < circles.length; i++){
var circle = circles[i]
2018-09-15 22:34:53 +08:00
if(circle.isAnimated()){
2018-10-25 22:18:41 +08:00
var animT = circle.getAnimT()
2018-10-25 22:18:41 +08:00
if(ms < animT + 490){
var animPoint = (ms - animT) / 490
var bezierPoint = this.calcBezierPoint(this.draw.easeOut(animPoint), this.animateBezier)
2018-09-15 22:34:53 +08:00
this.drawCircle(circle, {x: bezierPoint.x, y: bezierPoint.y})
2018-10-25 22:18:41 +08:00
}else if(ms < animT + 810){
var pos = this.animateBezier[3]
this.drawCircle(circle, pos, (ms - animT - 490) / 160)
}else{
2018-09-15 22:34:53 +08:00
circle.endAnimation()
2015-07-17 16:22:46 +08:00
}
}
2018-09-06 00:46:26 +08:00
}
}
calcBezierPoint(t, data){
var at = 1 - t
2018-10-25 22:18:41 +08:00
data = data.slice()
2015-07-17 16:22:46 +08:00
2018-09-06 00:46:26 +08:00
for(var i = 1; i < data.length; i++){
for(var k = 0; k < data.length - i; k++){
2015-07-17 16:22:46 +08:00
data[k] = {
2018-09-06 00:46:26 +08:00
x: data[k].x * at + data[k + 1].x * t,
y: data[k].y * at + data[k + 1].y * t
}
2015-07-17 16:22:46 +08:00
}
}
2018-09-06 00:46:26 +08:00
return data[0]
2015-07-17 16:22:46 +08:00
}
2018-10-25 22:18:41 +08:00
drawCircle(circle, circlePos, fade){
var ctx = this.ctx
var mul = this.slotPos.size / 106
var bigCircleSize = 106 * mul / 2
var circleSize = 70 * mul / 2
var lyricsSize = 20 * mul
2018-09-06 00:46:26 +08:00
var fill, size, faceID
2018-09-15 22:34:53 +08:00
var type = circle.getType()
2018-10-03 17:48:18 +08:00
var ms = this.controller.getElapsedTime()
2018-09-15 22:34:53 +08:00
var circleMs = circle.getMS()
var endTime = circle.getEndTime()
var animated = circle.isAnimated()
var speed = circle.getSpeed()
var played = circle.getPlayed()
2018-10-25 22:18:41 +08:00
var drumroll = 0
var endX = 0
2018-09-15 22:34:53 +08:00
2018-09-06 00:46:26 +08:00
if(!circlePos){
circlePos = {
2018-10-25 22:18:41 +08:00
x: this.slotPos.x + this.msToPos(circleMs - ms, speed),
y: this.slotPos.y
2018-09-06 00:46:26 +08:00
}
}
2018-09-15 22:34:53 +08:00
if(animated){
2018-10-25 22:18:41 +08:00
var noteFace = {
small: 0,
big: 3
}
2018-09-06 00:46:26 +08:00
}else{
2018-10-25 22:18:41 +08:00
var noteFace = this.noteFace
2018-09-06 00:46:26 +08:00
}
if(type === "don" || type === "daiDon" && played === 1){
fill = "#f34728"
2018-10-25 22:18:41 +08:00
size = circleSize
faceID = noteFace.small
}else if(type === "ka" || type === "daiKa" && played === 1){
fill = "#65bdbb"
2018-10-25 22:18:41 +08:00
size = circleSize
faceID = noteFace.small
}else if(type === "daiDon"){
fill = "#f34728"
2018-10-25 22:18:41 +08:00
size = bigCircleSize
faceID = noteFace.big
}else if(type === "daiKa"){
fill = "#65bdbb"
2018-10-25 22:18:41 +08:00
size = bigCircleSize
faceID = noteFace.big
}else if(type === "balloon"){
if(animated){
2018-09-15 22:34:53 +08:00
fill = "#f34728"
2018-10-25 22:18:41 +08:00
size = bigCircleSize * 0.8
faceID = noteFace.big
}else{
2018-09-15 22:34:53 +08:00
fill = "#f87700"
2018-10-25 22:18:41 +08:00
size = circleSize
faceID = noteFace.small
2018-09-15 22:34:53 +08:00
var h = size * 1.8
if(circleMs < ms && ms <= endTime){
2018-10-25 22:18:41 +08:00
circlePos.x = this.slotPos.x
2018-09-15 22:34:53 +08:00
}else if(ms > endTime){
2018-10-25 22:18:41 +08:00
circlePos.x = this.slotPos.x + this.msToPos(endTime - ms, speed)
2018-09-15 22:34:53 +08:00
}
2018-10-25 22:18:41 +08:00
ctx.drawImage(assets.image["balloon"],
circlePos.x + size - 4,
circlePos.y - h / 2 + 2,
2018-09-15 22:34:53 +08:00
h / 61 * 115,
h
)
}
}else if(type === "drumroll" || type === "daiDrumroll"){
fill = "#f3b500"
if(type == "drumroll"){
2018-10-25 22:18:41 +08:00
size = circleSize
faceID = noteFace.small
}else{
2018-10-25 22:18:41 +08:00
size = bigCircleSize
faceID = noteFace.big
}
2018-10-25 22:18:41 +08:00
endX = this.msToPos(endTime - circleMs, speed)
drumroll = endX > 50 ? 2 : 1
ctx.fillStyle = fill
ctx.strokeStyle = "#000"
ctx.lineWidth = 3
ctx.beginPath()
ctx.moveTo(circlePos.x, circlePos.y - size + 1.5)
ctx.arc(circlePos.x + endX, circlePos.y, size - 1.5, Math.PI / -2, Math.PI / 2)
ctx.lineTo(circlePos.x, circlePos.y + size - 1.5)
ctx.fill()
ctx.stroke()
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
if(!fade || fade < 1){
// Main circle
ctx.fillStyle = fill
ctx.beginPath()
ctx.arc(circlePos.x, circlePos.y, size - 1, 0, Math.PI * 2)
ctx.fill()
// Face on circle
var drawSize = size
if(faceID < 2){
drawSize *= bigCircleSize / circleSize
}
ctx.drawImage(assets.image[drumroll ? "notes_drumroll" : "notes"],
0, 172 * faceID,
172, 172,
circlePos.x - drawSize - 4,
circlePos.y - drawSize - 4,
drawSize * 2 + 8,
drawSize * 2 + 8
)
}
2018-10-25 22:18:41 +08:00
if(fade && !this.touchEnabled){
ctx.globalAlpha = this.draw.easeOut(fade < 1 ? fade : 2 - fade)
ctx.fillStyle = "#fff"
ctx.beginPath()
ctx.arc(circlePos.x, circlePos.y, size - 1, 0, Math.PI * 2)
ctx.fill()
ctx.globalAlpha = 1
}
if(!circle.isAnimated()){
// Text
var text = circle.getText()
var textX = circlePos.x
var textY = circlePos.y + 83 * mul
ctx.font = lyricsSize + "px Kozuka"
ctx.textBaseline = "middle"
ctx.textAlign = "center"
if(drumroll === 2){
var longText = text.split("ー")
text = longText[0]
var text0Width = ctx.measureText(longText[0]).width
var text1Width = ctx.measureText(longText[1]).width
}
ctx.fillStyle = "#fff"
ctx.strokeStyle = "#000"
ctx.lineWidth = 5
ctx.strokeText(text, textX, textY)
if(drumroll === 2){
ctx.strokeText(longText[1], textX + endX, textY)
ctx.lineWidth = 4
var x1 = textX + text0Width / 2
var x2 = textX + endX - text1Width / 2
ctx.beginPath()
ctx.moveTo(x1, textY - 2)
ctx.lineTo(x2, textY - 2)
ctx.lineTo(x2, textY + 1)
ctx.lineTo(x1, textY + 1)
ctx.closePath()
ctx.stroke()
ctx.fill()
}
ctx.strokeStyle = "#fff"
ctx.lineWidth = 0.5
ctx.strokeText(text, textX, textY)
ctx.fillText(text, textX, textY)
if(drumroll === 2){
2018-10-25 22:18:41 +08:00
ctx.strokeText(longText[1], textX + endX, textY)
ctx.fillText(longText[1], textX + endX, textY)
}
2018-10-03 17:48:18 +08:00
}
2018-09-06 00:46:26 +08:00
}
2018-10-25 22:18:41 +08:00
fillComboCache(){
var fontSize = 58
var letterSpacing = fontSize * 0.67
var glyphW = 50
var glyphH = 64
var textX = 11
var textY = 5
var letterBorder = fontSize * 0.15
2018-09-06 00:46:26 +08:00
2018-10-25 22:18:41 +08:00
this.comboCache.resize((glyphW + 1) * 20, glyphH + 1, this.ratio)
for(var orange = 0; orange < 2; orange++){
for(var i = 0; i < 10; i++){
this.comboCache.set({
w: glyphW,
h: glyphH,
id: orange + "combo" + i
}, ctx => {
ctx.scale(0.9, 1)
if(orange){
var grd = ctx.createLinearGradient(
(glyphW - glyphH) / 2,
0,
(glyphW + glyphH) / 2,
glyphH
)
grd.addColorStop(0.3, "#ff2000")
grd.addColorStop(0.5, "#ffc321")
grd.addColorStop(0.6, "#ffedb7")
grd.addColorStop(0.8, "#ffffce")
var fill = grd
}else{
var fill = "#fff"
}
this.draw.layeredText({
ctx: ctx,
text: i.toString(),
fontSize: fontSize,
fontFamily: this.font,
x: textX,
y: textY
}, [
{x: -2, y: -1, outline: "#000", letterBorder: letterBorder},
{x: 3.5, y: 1.5},
{x: 3, y: 1},
{},
{x: -2, y: -1, fill: "#fff"},
{x: 3.5, y: 1.5, fill: fill},
{x: 3, y: 1, fill: "rgba(0, 0, 0, 0.5)"},
{fill: fill}
])
})
2018-09-20 07:20:26 +08:00
}
}
2018-10-25 22:18:41 +08:00
this.globalAlpha = 0
this.comboCache.get({
ctx: this.ctx,
x: 0,
y: 0,
w: 54,
h: 77,
id: "combo0"
})
this.globalAlpha = 1
2018-09-06 00:46:26 +08:00
}
2018-09-20 07:20:26 +08:00
toggleGogoTime(circle){
this.gogoTime = circle.gogoTime
this.gogoTimeStarted = circle.ms
2018-09-20 21:11:19 +08:00
2018-09-20 07:20:26 +08:00
if(this.gogoTime){
this.assets.fireworks.forEach(fireworksAsset => {
fireworksAsset.setAnimation("normal")
fireworksAsset.setAnimationStart(circle.ms)
var length = fireworksAsset.getAnimationLength("normal")
fireworksAsset.setAnimationEnd(length, () => {
2018-09-20 07:20:26 +08:00
fireworksAsset.setAnimation(false)
})
})
this.assets.fire.setAnimation("normal")
var don = this.assets.don
don.setAnimation("gogostart")
2018-09-20 21:11:19 +08:00
var length = don.getAnimationLength("gogo")
don.setUpdateSpeed(4 / length)
2018-09-20 21:11:19 +08:00
var start = circle.ms - (circle.ms % this.beatInterval)
don.setAnimationStart(start)
2018-09-20 07:20:26 +08:00
var length = don.getAnimationLength("gogostart")
don.setAnimationEnd(length, don.normalAnimation)
2018-09-14 06:55:23 +08:00
}
}
2018-09-20 07:20:26 +08:00
drawGogoTime(){
2018-10-25 22:18:41 +08:00
var ms = this.getMS()
2018-09-20 21:11:19 +08:00
2018-09-20 07:20:26 +08:00
if(this.gogoTime){
var circles = this.controller.parsedSongData.circles
var lastCircle = circles[circles.length - 1]
var endTime = lastCircle.getEndTime() + 3000
if(ms >= endTime){
this.toggleGogoTime({
gogoTime: 0,
ms: endTime
})
}
}else{
2018-09-26 08:26:42 +08:00
var animation = this.assets.don.getAnimation()
2018-10-25 22:18:41 +08:00
var gauge = this.controller.getGlobalScore().gauge
var cleared = Math.round(gauge / 2) - 1 >= 25
if(animation === "gogo" || cleared && animation === "normal" || !cleared && animation === "clear"){
2018-09-20 21:11:19 +08:00
this.assets.don.normalAnimation()
2018-09-20 07:20:26 +08:00
}
if(ms >= this.gogoTimeStarted + 100){
this.assets.fire.setAnimation(false)
}
}
}
2018-09-14 06:55:23 +08:00
updateCombo(combo){
2018-09-20 07:20:26 +08:00
var don = this.assets.don
var animation = don.getAnimation()
if(
combo > 0
&& combo % 10 === 0
&& animation !== "10combo"
&& animation !== "gogostart"
&& animation !== "gogo"
){
don.setAnimation("10combo")
2018-10-03 17:48:18 +08:00
var ms = this.controller.getElapsedTime()
2018-09-20 07:20:26 +08:00
don.setAnimationStart(ms)
2018-09-26 08:26:42 +08:00
var length = don.getAnimationLength("normal")
don.setUpdateSpeed(4 / length)
2018-09-20 07:20:26 +08:00
var length = don.getAnimationLength("10combo")
don.setAnimationEnd(length, don.normalAnimation)
2018-09-14 06:55:23 +08:00
}
}
2018-10-25 22:18:41 +08:00
displayScore(score, notPlayed, bigNote){
if(!notPlayed){
this.currentScore.ms = this.getMS()
this.currentScore.type = score
this.currentScore.bigNote = bigNote
}
}
posToMs(pos, speed){
var circleSize = 70 * this.slotPos.size / 106 / 2
return 140 / circleSize * pos / speed
}
msToPos(ms, speed){
var circleSize = 70 * this.slotPos.size / 106 / 2
return speed / (140 / circleSize) * ms
}
togglePauseMenu(){
if(this.controller.game.isPaused()){
this.pauseMenu.style.display = "block"
this.lastMousemove = this.controller.getElapsedTime()
this.cursorHidden = false
this.mouseIdle()
}else{
this.pauseMenu.style.display = ""
}
}
2018-10-06 01:03:59 +08:00
drawTouch(){
if(this.touchEnabled){
2018-10-25 22:18:41 +08:00
var ms = this.getMS()
var mul = this.ratio / this.pixelRatio
2018-10-06 01:03:59 +08:00
2018-10-25 22:18:41 +08:00
var drumWidth = this.touchDrum.w * mul
var drumHeight = this.touchDrum.h * mul
2018-10-06 01:03:59 +08:00
if(drumHeight !== this.touchDrumHeight || drumWidth !== this.touchDrumWidth){
this.touchDrumWidth = drumWidth
this.touchDrumHeight = drumHeight
this.touchDrumDiv.style.width = drumWidth + "px"
this.touchDrumDiv.style.height = drumHeight + "px"
}
2018-10-06 22:53:39 +08:00
if(this.touch > ms - 100){
if(!this.drumPadding){
this.drumPadding = true
this.touchDrumImg.style.paddingTop = "1%"
2018-10-06 01:03:59 +08:00
}
}else if(this.drumPadding){
this.drumPadding = false
this.touchDrumImg.style.paddingTop = ""
2018-10-06 01:03:59 +08:00
}
}
}
ontouch(event){
for(let touch of event.changedTouches){
2018-10-06 01:03:59 +08:00
event.preventDefault()
2018-10-25 22:18:41 +08:00
var pageX = touch.pageX * this.pixelRatio
var pageY = touch.pageY * this.pixelRatio
2018-10-06 01:03:59 +08:00
var c = this.touchCircle
var pi = Math.PI
var inPath = () => this.ctx.isPointInPath(pageX, pageY)
this.ctx.beginPath()
this.ctx.ellipse(c.x, c.y, c.rx, c.ry, 0, pi, 0)
2018-10-06 01:03:59 +08:00
if(inPath()){
if(pageX < this.winW / 2){
this.touchNote("don_l")
}else{
this.touchNote("don_r")
}
2018-10-06 01:03:59 +08:00
}else{
if(pageX < this.winW / 2){
this.touchNote("ka_l")
2018-10-06 01:03:59 +08:00
}else{
this.touchNote("ka_r")
2018-10-06 01:03:59 +08:00
}
}
}
}
touchNote(note){
var keyboard = this.controller.keyboard
var kbd = keyboard.getBindings()
var ms = this.controller.game.getAccurateTime()
this.touch = ms
keyboard.setKey(kbd[note], false)
keyboard.setKey(kbd[note], true, ms)
2018-10-06 01:03:59 +08:00
}
onmousemove(event){
2018-10-03 17:48:18 +08:00
this.lastMousemove = this.controller.getElapsedTime()
this.cursorHidden = false
}
mouseIdle(){
var lastMouse = pageEvents.getMouse()
if(lastMouse && !this.cursorHidden){
2018-10-03 17:48:18 +08:00
if(this.controller.getElapsedTime() >= this.lastMousemove + 2000){
this.cursor.style.top = lastMouse.clientY + "px"
this.cursor.style.left = lastMouse.clientX + "px"
this.cursor.style.pointerEvents = "auto"
this.cursorHidden = true
}else{
this.cursor.style.top = ""
this.cursor.style.left = ""
this.cursor.style.pointerEvents = ""
}
}
}
changeBeatInterval(beatMS){
this.beatInterval = beatMS
this.assets.changeBeatInterval(beatMS)
}
2018-10-25 22:18:41 +08:00
getMS(){
return this.controller.getElapsedTime()
}
2018-09-18 06:37:59 +08:00
clean(){
2018-10-25 23:50:10 +08:00
this.draw.clean()
this.assets.clean()
this.titleCache.clean()
2018-10-25 22:18:41 +08:00
this.comboCache.clean()
if(this.multiplayer !== 2){
pageEvents.remove(window, "resize")
2018-10-25 23:50:10 +08:00
if(this.touchEnabled){
pageEvents.remove(this.canvas, "touchstart")
pageEvents.remove(this.touchFullBtn, "touchend")
pageEvents.remove(this.touchPauseBtn, "touchend")
this.gameDiv.classList.remove("touch-visible")
document.getElementById("version").classList.remove("version-hide")
delete this.touchDrumDiv
delete this.touchDrumImg
delete this.touchFullBtn
delete this.touchPauseBtn
}
2018-10-06 01:03:59 +08:00
}
2018-10-25 23:50:10 +08:00
pageEvents.mouseRemove(this)
2018-09-18 06:37:59 +08:00
delete this.pauseMenu
delete this.cursor
2018-10-06 01:03:59 +08:00
delete this.gameDiv
2018-09-18 06:37:59 +08:00
delete this.canvas
delete this.ctx
}
2018-09-09 12:09:15 +08:00
}