From 5f727c92685549dcdaffec596290c96d684f0b83 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Sat, 2 Nov 2024 02:36:34 +0200 Subject: [PATCH 1/6] Add zombies walking effect --- public/entities/zombie-walking-step.svg | 60 ++++++++++++++++++++++++ renderer/Assets.ts | 35 +++++++++----- renderer/Effect.ts | 20 +++++--- renderer/Renderer.ts | 61 +++++++++++++++++-------- 4 files changed, 140 insertions(+), 36 deletions(-) create mode 100644 public/entities/zombie-walking-step.svg diff --git a/public/entities/zombie-walking-step.svg b/public/entities/zombie-walking-step.svg new file mode 100644 index 0000000..bb921da --- /dev/null +++ b/public/entities/zombie-walking-step.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/renderer/Assets.ts b/renderer/Assets.ts index 03b0263..1543a42 100644 --- a/renderer/Assets.ts +++ b/renderer/Assets.ts @@ -9,6 +9,7 @@ export interface RendererAssets { zombie: HTMLImageElement | null; zombieDead: HTMLImageElement | null; zombieWalking: HTMLImageElement | null; + zombieWalkingStep: HTMLImageElement | null; } export const assets: RendererAssets = { @@ -22,6 +23,7 @@ export const assets: RendererAssets = { zombie: null, zombieDead: null, zombieWalking: null, + zombieWalkingStep: null, }; export async function loadAssets() { @@ -31,17 +33,27 @@ export async function loadAssets() { assets.loading = true; - const [bg, box, landmine, player, rock, zombie, zombieDead, zombieWalking] = - await Promise.all([ - loadAssetImage("/map.webp"), - loadAssetImage("/entities/box.svg"), - loadAssetImage("/entities/landmine.svg"), - loadAssetImage("/entities/player-attacking.svg"), - loadAssetImage("/entities/rock.svg"), - loadAssetImage("/entities/zombie-idle.svg"), - loadAssetImage("/entities/zombie-dead.svg"), - loadAssetImage("/entities/zombie-walking.svg"), - ]); + const [ + bg, + box, + landmine, + player, + rock, + zombie, + zombieDead, + zombieWalking, + zombieWalkingStep, + ] = await Promise.all([ + loadAssetImage("/map.webp"), + loadAssetImage("/entities/box.svg"), + loadAssetImage("/entities/landmine.svg"), + loadAssetImage("/entities/player-attacking.svg"), + loadAssetImage("/entities/rock.svg"), + loadAssetImage("/entities/zombie-idle.svg"), + loadAssetImage("/entities/zombie-dead.svg"), + loadAssetImage("/entities/zombie-walking.svg"), + loadAssetImage("/entities/zombie-walking-step.svg"), + ]); assets.loaded = true; assets.bg = bg; @@ -52,6 +64,7 @@ export async function loadAssets() { assets.zombie = zombie; assets.zombieDead = zombieDead; assets.zombieWalking = zombieWalking; + assets.zombieWalkingStep = zombieWalkingStep; } export async function loadAssetImage(src: string): Promise { diff --git a/renderer/Effect.ts b/renderer/Effect.ts index ae325f2..427db48 100644 --- a/renderer/Effect.ts +++ b/renderer/Effect.ts @@ -1,23 +1,31 @@ import { type Position } from "@/simulators/zombie-survival"; export enum RendererEffectType { + AssetSwap, HueRotate, - Move, Opacity, + PositionTo, } export type RendererEffect = + | { + type: RendererEffectType.AssetSwap; + duration: number; + every: number; + startedAt: number; + to: HTMLImageElement; + } | { type: RendererEffectType.HueRotate; degree: number; } | { - type: RendererEffectType.Move; + type: RendererEffectType.Opacity; + value: number; + } + | { + type: RendererEffectType.PositionTo; duration: number; startedAt: number; to: Position; - } - | { - type: RendererEffectType.Opacity; - value: number; }; diff --git a/renderer/Renderer.ts b/renderer/Renderer.ts index 2175bbc..89a9374 100644 --- a/renderer/Renderer.ts +++ b/renderer/Renderer.ts @@ -1,5 +1,5 @@ import { assets, loadAssets } from "./Assets"; -import { type RendererEffect, RendererEffectType } from "./Effect"; +import { RendererEffectType } from "./Effect"; import { RendererItem } from "./Item"; import { REPLAY_SPEED } from "@/constants/visualizer"; import { @@ -93,8 +93,8 @@ export class Renderer { let x = item.position.x; let y = item.position.y; - if (item.hasEffect(RendererEffectType.Move)) { - const effect = item.getEffect(RendererEffectType.Move); + if (item.hasEffect(RendererEffectType.PositionTo)) { + const effect = item.getEffect(RendererEffectType.PositionTo); const timePassed = Date.now() - effect.startedAt; const delta = timePassed / effect.duration; @@ -102,12 +102,24 @@ export class Renderer { y += (effect.to.y - y) * delta; } + let asset = item.data; + + if (item.hasEffect(RendererEffectType.AssetSwap)) { + const effect = item.getEffect(RendererEffectType.AssetSwap); + const assets = [asset, effect.to]; + const timePassed = Date.now() - effect.startedAt; + const assetIdx = Math.floor((timePassed / effect.every) % assets.length); + + console.log(assetIdx); + asset = assets[assetIdx]; + } + if (item.hasEffect(RendererEffectType.HueRotate)) { const effect = item.getEffect(RendererEffectType.HueRotate); this.ctx2.clearRect(0, 0, this.cellSize, this.cellSize); this.ctx2.filter = `hue-rotate(${effect.degree}deg)`; - this.ctx2.drawImage(item.data, 0, 0, this.cellSize, this.cellSize); + this.ctx2.drawImage(asset, 0, 0, this.cellSize, this.cellSize); this.ctx2.filter = "none"; this.ctx2.globalCompositeOperation = "destination-in"; @@ -116,7 +128,7 @@ export class Renderer { this.ctx.drawImage(this.canvas2, x, y, this.cellSize, this.cellSize); } else { - this.ctx.drawImage(item.data, x, y, item.width, item.height); + this.ctx.drawImage(asset, x, y, item.width, item.height); } this.ctx.globalAlpha = 1; @@ -200,15 +212,20 @@ export class Renderer { return; } - const effects: RendererEffect[] = []; - const position: Position = { x: entity.getPosition().x * this.cellSize, y: entity.getPosition().y * this.cellSize, }; + const rendererItem = new RendererItem( + entityImage, + this.cellSize, + position, + this.cellSize, + ); + if (entity.hasChange(ChangeType.Hit)) { - effects.push({ + rendererItem.addEffect({ type: RendererEffectType.HueRotate, degree: 300, }); @@ -221,8 +238,8 @@ export class Renderer { position.x = from.x * this.cellSize; position.y = from.y * this.cellSize; - effects.push({ - type: RendererEffectType.Move, + rendererItem.addEffect({ + type: RendererEffectType.PositionTo, duration: REPLAY_SPEED, startedAt: Date.now(), to: { @@ -230,16 +247,19 @@ export class Renderer { y: to.y * this.cellSize, }, }); + + if (assets.zombieWalkingStep !== null) { + rendererItem.addEffect({ + type: RendererEffectType.AssetSwap, + duration: REPLAY_SPEED, + every: REPLAY_SPEED / 6, + startedAt: Date.now(), + to: assets.zombieWalkingStep, + }); + } } - this.items.push( - new RendererItem( - entityImage, - this.cellSize, - position, - this.cellSize, - ).addEffect(...effects), - ); + this.items.push(rendererItem); } private shouldAnimate(): boolean { @@ -249,7 +269,10 @@ export class Renderer { } for (const effect of item.effects) { - if (effect.type === RendererEffectType.Move) { + if ( + effect.type === RendererEffectType.AssetSwap || + effect.type === RendererEffectType.PositionTo + ) { if (Date.now() < effect.startedAt + effect.duration) { return true; } From c2bcd08ef6e484d4a62709a7188540dc515e9a0c Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Sat, 2 Nov 2024 03:17:35 +0200 Subject: [PATCH 2/6] Flip entity if walking right --- renderer/Canvas.ts | 13 +++++++++++ renderer/Effect.ts | 4 ++++ renderer/Renderer.ts | 53 +++++++++++++++++++++++++++++++------------- 3 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 renderer/Canvas.ts diff --git a/renderer/Canvas.ts b/renderer/Canvas.ts new file mode 100644 index 0000000..04f5960 --- /dev/null +++ b/renderer/Canvas.ts @@ -0,0 +1,13 @@ +const imagesMap = new Map(); + +export function toImage(canvas: HTMLCanvasElement): HTMLImageElement { + const url = canvas.toDataURL(); + + if (!imagesMap.has(url)) { + const image = document.createElement("img"); + image.src = url; + imagesMap.set(url, image); + } + + return imagesMap.get(url)!; +} diff --git a/renderer/Effect.ts b/renderer/Effect.ts index 427db48..e8f547a 100644 --- a/renderer/Effect.ts +++ b/renderer/Effect.ts @@ -2,6 +2,7 @@ import { type Position } from "@/simulators/zombie-survival"; export enum RendererEffectType { AssetSwap, + FlipHorizontal, HueRotate, Opacity, PositionTo, @@ -15,6 +16,9 @@ export type RendererEffect = startedAt: number; to: HTMLImageElement; } + | { + type: RendererEffectType.FlipHorizontal; + } | { type: RendererEffectType.HueRotate; degree: number; diff --git a/renderer/Renderer.ts b/renderer/Renderer.ts index 89a9374..58bd063 100644 --- a/renderer/Renderer.ts +++ b/renderer/Renderer.ts @@ -1,4 +1,5 @@ import { assets, loadAssets } from "./Assets"; +import * as Canvas from "./Canvas"; import { RendererEffectType } from "./Effect"; import { RendererItem } from "./Item"; import { REPLAY_SPEED } from "@/constants/visualizer"; @@ -102,35 +103,44 @@ export class Renderer { y += (effect.to.y - y) * delta; } - let asset = item.data; + let source: HTMLImageElement = item.data; if (item.hasEffect(RendererEffectType.AssetSwap)) { const effect = item.getEffect(RendererEffectType.AssetSwap); - const assets = [asset, effect.to]; + const assets = [item.data, effect.to]; const timePassed = Date.now() - effect.startedAt; const assetIdx = Math.floor((timePassed / effect.every) % assets.length); - console.log(assetIdx); - asset = assets[assetIdx]; + source = assets[assetIdx]; + } + + if (item.hasEffect(RendererEffectType.FlipHorizontal)) { + this.ctx2.clearRect(0, 0, item.width, item.height); + this.ctx2.save(); + this.ctx2.translate(item.width, 0); + this.ctx2.scale(-1, 1); + this.ctx2.drawImage(source, 0, 0, item.width, item.height); + this.ctx2.restore(); + + source = Canvas.toImage(this.canvas2); } if (item.hasEffect(RendererEffectType.HueRotate)) { const effect = item.getEffect(RendererEffectType.HueRotate); - this.ctx2.clearRect(0, 0, this.cellSize, this.cellSize); + this.ctx2.clearRect(0, 0, item.width, item.height); this.ctx2.filter = `hue-rotate(${effect.degree}deg)`; - this.ctx2.drawImage(asset, 0, 0, this.cellSize, this.cellSize); + this.ctx2.drawImage(source, 0, 0, item.width, item.height); this.ctx2.filter = "none"; this.ctx2.globalCompositeOperation = "destination-in"; - this.ctx2.fillRect(0, 0, this.cellSize, this.cellSize); + this.ctx2.fillRect(0, 0, item.width, item.height); this.ctx2.globalCompositeOperation = "source-over"; - this.ctx.drawImage(this.canvas2, x, y, this.cellSize, this.cellSize); - } else { - this.ctx.drawImage(asset, x, y, item.width, item.height); + source = Canvas.toImage(this.canvas2); } + this.ctx.drawImage(source, x, y, item.width, item.height); this.ctx.globalAlpha = 1; } @@ -197,12 +207,19 @@ export class Renderer { y: offsetY, }; - this.items.push( - new RendererItem(assets.bg, drawHeight, position, drawWidth).addEffect({ - type: RendererEffectType.Opacity, - value: 50, - }), + const rendererItem = new RendererItem( + assets.bg, + drawHeight, + position, + drawWidth, ); + + rendererItem.addEffect({ + type: RendererEffectType.Opacity, + value: 50, + }); + + this.items.push(rendererItem); } private registerEntity(entity: Entity) { @@ -248,6 +265,12 @@ export class Renderer { }, }); + if (from.x < to.x) { + rendererItem.addEffect({ + type: RendererEffectType.FlipHorizontal, + }); + } + if (assets.zombieWalkingStep !== null) { rendererItem.addEffect({ type: RendererEffectType.AssetSwap, From c521080da740e9f8ee25c6ead5ef994408287a52 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Sat, 2 Nov 2024 03:29:58 +0200 Subject: [PATCH 3/6] Add actual zombie walking animation --- public/entities/zombie-walking-frame1.png | Bin 0 -> 1146 bytes public/entities/zombie-walking-frame2.png | Bin 0 -> 1037 bytes public/entities/zombie-walking-frame3.png | Bin 0 -> 1117 bytes public/entities/zombie-walking-frame4.png | Bin 0 -> 1026 bytes public/entities/zombie-walking-step.svg | 60 ---------------------- public/entities/zombie-walking.svg | 60 ---------------------- renderer/Assets.ts | 30 +++++++---- renderer/Effect.ts | 2 +- renderer/Renderer.ts | 18 +++++-- 9 files changed, 34 insertions(+), 136 deletions(-) create mode 100644 public/entities/zombie-walking-frame1.png create mode 100644 public/entities/zombie-walking-frame2.png create mode 100644 public/entities/zombie-walking-frame3.png create mode 100644 public/entities/zombie-walking-frame4.png delete mode 100644 public/entities/zombie-walking-step.svg delete mode 100644 public/entities/zombie-walking.svg diff --git a/public/entities/zombie-walking-frame1.png b/public/entities/zombie-walking-frame1.png new file mode 100644 index 0000000000000000000000000000000000000000..eadbb51b71ab896586fc3eb426cd40f622046d47 GIT binary patch literal 1146 zcmV-=1cm#FP)K~#7F?O9Do z6;TvEud=8`m_L~kE}oGUR#Ip|E&>xmS}2rB)TV;Own9?-aM3a%im+u!U~T%7b{1t3 zR-zVZBw?6Tu8Ojn-gkWGcHg|8nLBgG7Wu)vckj&HdEa->&pq!B(E=8*fPVppEMXBv z569JZ8tUrY=K~{Z0Zwa6Gk?=x;tX3NViX;iTv7nz)8w>j7nT+C_qCqhP&}d%z%ws| zK~^X4Mqht_U@nnbU|W0(Hww#Es*?~?U0ccR!GTvwSWpp5C_2QRjlWPL03m<>29u@Z zrw{RW@6|pEiuuSw43KY_UCiyHH|GSr9iHItFmVu-1=6z1>!)MXuysAR-*tB;X1|U{~^gyj|np+1zJ`o?JuM0XL zH0f5JJR=csGT|!wiVEoP$Z*;X5CH+1AfjZA5NmaIbYA zekbXqU>`{`KkbuSLQ5F+qJ7-fNV3t-X00XNZS zPkb7#l|+m@CeVw#=S2f#Ty^LA#cp5r``eC>d+Z7j@_L~r#hi!`wx2R2yWaKR7d z?Wp1!h%9bs2}0FEx`+?oKI7o}1|o1xECO77JVa3-NDMfo%L{1js@2><>ddmE!1nAy^}pB_{0iWJgf9!=`|cGt+t{;@9&+REfjwj!##sm=kbPhYgUk}c9~*45X#a_= zz_=RKH=UAc(#V%H?G?=z5RCk$g@VJzGg9C?h_h`2ngZEKf9*se#rSq1*NSUg_PU|J zdP=yYh%kPp-vOLPWibMhRcFfhMA_^lYMq>Gf^JEDQa0D_r86BT(w^xBV*w}$M2M?& z_Wdk;h>+b)N9g6ioxm?Sj06aDO9(Pq#Os+9j7#7_UcqLqMEJ0_AW9-5XuyTIiGqSCiB{FBTDFjWfkE3Mim;_fU=b7~Ed<+C zgtkzVoJd45vxXK$S>5J&XWlbsZq3Z~+;e6T^T6eN%-8e0pXc5os-cD&YA}FDmavB0 z!}HC(?o`UZZw!|Lm~79<<7%EK&afpSLT}ClSb0wvZt{5Q@C6=GR^`%5FI6bDwv(6zT+CYfNMi$? znVcwk5rTlh8;|H!`Zei&0iKdQDY265y_fq+a&^|o`r8^5VId^R3qUAHqx#?id63Lk zBL=Dzu5z#`K!wls@oBXQ5h#@q(G%Uydyl&g@+hRWi`V(2=Dewt;MS8^Vjf}Ay1zw* z0f-D_$2)X_Vng@Jk`ibFOTtzTDC_G@E4PmNV>#&WdlZ15RRM*HQ#j}3D~b{UM4!Fo zB#+&`MPYf_5Kwf=?D+Yc(#C2qQ1Pv4IyLEg}}bXUH|I9;e0P2&d@o1T}5hrq-_Dx2Q2T(`al=~OnzZujz%7iZ~_Z? zR3U_sM%>vGKvMzg4otWH)RS?(Tvi=FOe~|w!}>rx-o*F6IK097+VR5mvsd#*2J%8& z;7eNPVb&XVXJavC`p9E!*lrE|3Cf!jNy0mUC5CF00000NkvXX Hu0mjf!UE@5 literal 0 HcmV?d00001 diff --git a/public/entities/zombie-walking-frame3.png b/public/entities/zombie-walking-frame3.png new file mode 100644 index 0000000000000000000000000000000000000000..5e8aad0b8fa2c928af9e5829e1772216909d20e5 GIT binary patch literal 1117 zcmV-j1fu(iP)QRnzSg)>NVe;cdqxnd5-tJcW=0u4<7fwDyw`VV!HNcwkKfUunkpJ1Zv2wd3Xs8lXbZDe z$PrAqr@MDSTTK%1M4XPUcG*X`1R!D%J25g%2Zx>rgB2Sbx9|A%Q%dB<39!#kjen=c zy$v#Kx^bQ+&YY#%TQ?Vbw;%!G`$!hS6oJ5`RrsCsyMoU91p%G#YLviy#F?*i*d3Sz zz#?~NVj;WOrAopC0Z*0L{QEEE>T4-IHbD(_DLOT9Uz`UKl>`7H01MtnOv^SS1bCDY zE;0ISyw4a}%ay6RP0(oYsvL9ZpkaT68MbtStI- z?#D0DPR!2Gr=Qch@6sA|6-H)&ja30sFurzGCAma(3bfDz8jAo7WCW4exNg1Y8XJf` z1AY=gM22T_Xsj$>V`-pnbyd+xxD24`;^Vh3VRR4*v1S0qvcU}7F5`#wojK%&*2Ar2 z3q}a2v%r1U0A}+CZ!|anO#ND^?LgAzqrz|HzUx zM4bW8vJWLRpt{nt@i(4x&_alWrj5uhgptRA$`hqCVm-%ubr#@K4Vi7yjZAAHN?eu; z7>vBKXbCKtxn5FXGO^M8oK?v(Y()Y1o0g*+Pe~F;d z!X&~FaQwSyI*XOOyCg8`tO4bVjS>8hZ)5^$K2Se9QA~vKML7d(2%->gSZO><7JrCN z3=X%N&nQCJ8;VE(J6jIWLD=t1B|$&phfd9MCl01LOx20*|#G ji1?gRes5*bv!wA4H$bklsi&%^00000NkvXXu0mjf`6}2`u_V7smh%;=7h><%mxvT)j=lLZyuT92zyfZu!iH9ozY#vc9wk$hBMUJ=zH4P8&(Ge^2zWm^%VU&t;K~DQvdh~SGt_mUndcvd23D=#NH*Z& z40wZA2Y4c(BGBW1`1(V-eC-0A>h7l~+-wWrnD6cHRvY?3?L3jG10cV8K1i<%Iv_L~ zR)IX72)MW!D#z+-=*-mQnm0fM1muH=k{Ti28n`rAkgKypJk(O7EDl1r`r^BI{iyQ@ z>7?ZX$!t1hzMgay4A|Lo-3CLS@GwZlNmc#zOMbMQd1(S;vGVC5dee$OGkIBwI-}=bwGN4&iAmskoS?vsm@aD8Q~_RVb&4ld+cr~msxIe6t)d3~ z4tr6nzT&Y!e%8|H%D11ioc^TNn9qz)h5dL;Kk%{_>Vp&w$^!trpkwaqM;;fxe+m0r zp{%YWss~~d%jv*3wI%mp2y+8pvO%qd5Uh_d%7;SxHemG(=p#_SrJ7vB#vK{L$V+U^ z^f(|bA{KuvlWSOQ=EUL%r}(}kHSE}_)~?{>exRv+FV_IcLNdC5l}aq(1HkGiXi^gl zQfW!B>H!HEKY5pkDuCPQj^iC_1L30smUm@+Ac_DczpyYzV-LpI`a&LA2w|iVclHR= zRDikzgY|JqxJ*BOqL2ZV8l4cvm9zgCF>-%m*$_k@1;4fT)=kwF1-)Ri1g-q-Ft^JJ}1XF7j w^n##V-Z1q*OslYEX&DL - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/entities/zombie-walking.svg b/public/entities/zombie-walking.svg deleted file mode 100644 index 820bce1..0000000 --- a/public/entities/zombie-walking.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/renderer/Assets.ts b/renderer/Assets.ts index 1543a42..b129ddd 100644 --- a/renderer/Assets.ts +++ b/renderer/Assets.ts @@ -8,8 +8,10 @@ export interface RendererAssets { rock: HTMLImageElement | null; zombie: HTMLImageElement | null; zombieDead: HTMLImageElement | null; - zombieWalking: HTMLImageElement | null; - zombieWalkingStep: HTMLImageElement | null; + zombieWalkingFrame1: HTMLImageElement | null; + zombieWalkingFrame2: HTMLImageElement | null; + zombieWalkingFrame3: HTMLImageElement | null; + zombieWalkingFrame4: HTMLImageElement | null; } export const assets: RendererAssets = { @@ -22,8 +24,10 @@ export const assets: RendererAssets = { rock: null, zombie: null, zombieDead: null, - zombieWalking: null, - zombieWalkingStep: null, + zombieWalkingFrame1: null, + zombieWalkingFrame2: null, + zombieWalkingFrame3: null, + zombieWalkingFrame4: null, }; export async function loadAssets() { @@ -41,8 +45,10 @@ export async function loadAssets() { rock, zombie, zombieDead, - zombieWalking, - zombieWalkingStep, + zombieWalkingFrame1, + zombieWalkingFrame2, + zombieWalkingFrame3, + zombieWalkingFrame4, ] = await Promise.all([ loadAssetImage("/map.webp"), loadAssetImage("/entities/box.svg"), @@ -51,8 +57,10 @@ export async function loadAssets() { loadAssetImage("/entities/rock.svg"), loadAssetImage("/entities/zombie-idle.svg"), loadAssetImage("/entities/zombie-dead.svg"), - loadAssetImage("/entities/zombie-walking.svg"), - loadAssetImage("/entities/zombie-walking-step.svg"), + loadAssetImage("/entities/zombie-walking-frame1.png"), + loadAssetImage("/entities/zombie-walking-frame2.png"), + loadAssetImage("/entities/zombie-walking-frame3.png"), + loadAssetImage("/entities/zombie-walking-frame4.png"), ]); assets.loaded = true; @@ -63,8 +71,10 @@ export async function loadAssets() { assets.rock = rock; assets.zombie = zombie; assets.zombieDead = zombieDead; - assets.zombieWalking = zombieWalking; - assets.zombieWalkingStep = zombieWalkingStep; + assets.zombieWalkingFrame1 = zombieWalkingFrame1; + assets.zombieWalkingFrame2 = zombieWalkingFrame2; + assets.zombieWalkingFrame3 = zombieWalkingFrame3; + assets.zombieWalkingFrame4 = zombieWalkingFrame4; } export async function loadAssetImage(src: string): Promise { diff --git a/renderer/Effect.ts b/renderer/Effect.ts index e8f547a..41c8137 100644 --- a/renderer/Effect.ts +++ b/renderer/Effect.ts @@ -14,7 +14,7 @@ export type RendererEffect = duration: number; every: number; startedAt: number; - to: HTMLImageElement; + steps: HTMLImageElement[]; } | { type: RendererEffectType.FlipHorizontal; diff --git a/renderer/Renderer.ts b/renderer/Renderer.ts index 58bd063..52865ec 100644 --- a/renderer/Renderer.ts +++ b/renderer/Renderer.ts @@ -107,7 +107,7 @@ export class Renderer { if (item.hasEffect(RendererEffectType.AssetSwap)) { const effect = item.getEffect(RendererEffectType.AssetSwap); - const assets = [item.data, effect.to]; + const assets = [item.data, ...effect.steps]; const timePassed = Date.now() - effect.startedAt; const assetIdx = Math.floor((timePassed / effect.every) % assets.length); @@ -162,7 +162,7 @@ export class Renderer { if (entity.hasChange(ChangeType.Killed)) { return assets.zombieDead; } else if (entity.hasChange(ChangeType.Walking)) { - return assets.zombieWalking; + return assets.zombieWalkingFrame1; } else { return assets.zombie; } @@ -265,19 +265,27 @@ export class Renderer { }, }); - if (from.x < to.x) { + if (from.x >= to.x) { rendererItem.addEffect({ type: RendererEffectType.FlipHorizontal, }); } - if (assets.zombieWalkingStep !== null) { + if ( + assets.zombieWalkingFrame2 !== null && + assets.zombieWalkingFrame3 !== null && + assets.zombieWalkingFrame4 !== null + ) { rendererItem.addEffect({ type: RendererEffectType.AssetSwap, duration: REPLAY_SPEED, every: REPLAY_SPEED / 6, startedAt: Date.now(), - to: assets.zombieWalkingStep, + steps: [ + assets.zombieWalkingFrame2, + assets.zombieWalkingFrame3, + assets.zombieWalkingFrame4, + ], }); } } From 471076e956f4cf25d81eaae779f05fff492ed4f0 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Sat, 2 Nov 2024 03:53:48 +0200 Subject: [PATCH 4/6] Remove renderer hue-rotate effect --- renderer/Renderer.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/renderer/Renderer.ts b/renderer/Renderer.ts index 52865ec..1e01933 100644 --- a/renderer/Renderer.ts +++ b/renderer/Renderer.ts @@ -241,13 +241,6 @@ export class Renderer { this.cellSize, ); - if (entity.hasChange(ChangeType.Hit)) { - rendererItem.addEffect({ - type: RendererEffectType.HueRotate, - degree: 300, - }); - } - if (entity.hasChange(ChangeType.Walking)) { const change = entity.getChange(ChangeType.Walking); const { to, from } = change; @@ -279,7 +272,7 @@ export class Renderer { rendererItem.addEffect({ type: RendererEffectType.AssetSwap, duration: REPLAY_SPEED, - every: REPLAY_SPEED / 6, + every: REPLAY_SPEED / 4, startedAt: Date.now(), steps: [ assets.zombieWalkingFrame2, From 62b08998c015dbc6bbf9b409eb57e6f3f3c112e8 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Sat, 2 Nov 2024 04:29:05 +0200 Subject: [PATCH 5/6] Display zombie health bar --- renderer/Item.ts | 6 +++--- renderer/Renderer.ts | 41 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/renderer/Item.ts b/renderer/Item.ts index 01b24e6..5cd892b 100644 --- a/renderer/Item.ts +++ b/renderer/Item.ts @@ -2,17 +2,17 @@ import { type RendererEffect, type RendererEffectType } from "./Effect"; import { type Position } from "@/simulators/zombie-survival"; export class RendererItem { - data: HTMLImageElement; + data: HTMLImageElement | string; effects: RendererEffect[] = []; height: number; position: Position; width: number; constructor( - data: HTMLImageElement, - height: number, + data: HTMLImageElement | string, position: Position, width: number, + height: number, ) { this.data = data; this.height = height; diff --git a/renderer/Renderer.ts b/renderer/Renderer.ts index 1e01933..5bcdc55 100644 --- a/renderer/Renderer.ts +++ b/renderer/Renderer.ts @@ -1,12 +1,13 @@ import { assets, loadAssets } from "./Assets"; import * as Canvas from "./Canvas"; -import { RendererEffectType } from "./Effect"; +import { type RendererEffect, RendererEffectType } from "./Effect"; import { RendererItem } from "./Item"; import { REPLAY_SPEED } from "@/constants/visualizer"; import { type Entity, EntityType, type Position, + Zombie, type ZombieSurvival, } from "@/simulators/zombie-survival"; import { ChangeType } from "@/simulators/zombie-survival/Change"; @@ -103,6 +104,13 @@ export class Renderer { y += (effect.to.y - y) * delta; } + if (typeof item.data === "string") { + this.ctx.fillStyle = item.data; + this.ctx.fillRect(x, y, item.width, item.height); + this.ctx.globalAlpha = 1; + return; + } + let source: HTMLImageElement = item.data; if (item.hasEffect(RendererEffectType.AssetSwap)) { @@ -209,9 +217,9 @@ export class Renderer { const rendererItem = new RendererItem( assets.bg, - drawHeight, position, drawWidth, + drawHeight, ); rendererItem.addEffect({ @@ -234,11 +242,25 @@ export class Renderer { y: entity.getPosition().y * this.cellSize, }; + const healthBarItem = new RendererItem( + "#F00", + position, + (entity.getHealth() / Zombie.Health) * this.cellSize, + 2, + ); + + const healthBarBgItem = new RendererItem( + "#FFF", + position, + this.cellSize, + 2, + ); + const rendererItem = new RendererItem( entityImage, - this.cellSize, position, this.cellSize, + this.cellSize, ); if (entity.hasChange(ChangeType.Walking)) { @@ -248,7 +270,7 @@ export class Renderer { position.x = from.x * this.cellSize; position.y = from.y * this.cellSize; - rendererItem.addEffect({ + const positionToEffect: RendererEffect = { type: RendererEffectType.PositionTo, duration: REPLAY_SPEED, startedAt: Date.now(), @@ -256,7 +278,11 @@ export class Renderer { x: to.x * this.cellSize, y: to.y * this.cellSize, }, - }); + }; + + healthBarItem.addEffect(positionToEffect); + healthBarBgItem.addEffect(positionToEffect); + rendererItem.addEffect(positionToEffect); if (from.x >= to.x) { rendererItem.addEffect({ @@ -284,6 +310,11 @@ export class Renderer { } this.items.push(rendererItem); + + if (entity.getType() === EntityType.Zombie && !entity.dead()) { + this.items.push(healthBarBgItem); + this.items.push(healthBarItem); + } } private shouldAnimate(): boolean { From 17a69427f147ea27e2a491f97ebf54caa7d1cfb7 Mon Sep 17 00:00:00 2001 From: Aaron Delasy Date: Sat, 2 Nov 2024 04:29:18 +0200 Subject: [PATCH 6/6] Add more zombie sprites --- public/entities/zombie-dead.png | Bin 0 -> 935 bytes public/entities/zombie-dead.svg | 44 ------------------ public/entities/zombie-idle-frame1.png | Bin 0 -> 1151 bytes public/entities/zombie-idle-frame2.png | Bin 0 -> 1160 bytes public/entities/zombie-idle-frame3.png | Bin 0 -> 1148 bytes public/entities/zombie-idle-frame4.png | Bin 0 -> 1129 bytes public/entities/zombie-idle.svg | 60 ------------------------- renderer/Assets.ts | 27 ++++++++--- renderer/Renderer.ts | 20 ++++++++- 9 files changed, 40 insertions(+), 111 deletions(-) create mode 100644 public/entities/zombie-dead.png delete mode 100644 public/entities/zombie-dead.svg create mode 100644 public/entities/zombie-idle-frame1.png create mode 100644 public/entities/zombie-idle-frame2.png create mode 100644 public/entities/zombie-idle-frame3.png create mode 100644 public/entities/zombie-idle-frame4.png delete mode 100644 public/entities/zombie-idle.svg diff --git a/public/entities/zombie-dead.png b/public/entities/zombie-dead.png new file mode 100644 index 0000000000000000000000000000000000000000..b91e6b6ca9eaa40d98e4b569b884356cab0b4447 GIT binary patch literal 935 zcmV;Y16cftP)x*R=! zJvjf%!W(m6HAh`I(xuCr&tE&^&~J~Ah$~3cpqk>rK=UqLo|~L-+OguZFBT#V7+P3T z(_fcV@%TQ|cJJDhiUZ>Nwl}H12aiqLhwoq1o~=7fdHPbnnJ-`nNW?Yht>lZ}*3#_M zzZHt=cISRA#0CM5wKq9!FyqkScBjoZA5Z-KnQz&-vmj5LGj(c z{^$~dfe3;jgY$DLge^>X*Sb@eFalR-Orzi^$UL)y8I}6EQC|>?QW(EGS#us|2jGtw zA>t9d7~mg;z4_0oZFh^)mfieYm-beCG-VQ|nOEi({$&^7%r`%I>pUCcB?gGUmCopU zKXIz6_D~YkMqD|1tTK3$`=*2m5RP_R42%eg2QUCh6FCCMW$|Od7>9cpfo<%A#8wY& z$2<=q_lJ%pI zBT_X1TIGAe=#G3>iU|UOy!z~ELi+5nSiz_*FXHXhzj&Z)*&D<*H=D2YA=3{X>~M^M zA^wWn&xU9m*%SRqQ};}8sQIyA3l;Hxe@HxzhQDLPOj&v0n;?XC8ca#GHW@QujNl(r zJRfcKm{8Z(QV0<*NTQUy;2VbVp_PxrH^Wc5QO6S4CV=W!=;{~%{G$-sB{t=ZtjCwX z9usoE)*z3G39D|vN?A`6K>7cd#&W=V;G2Nm5!DyM9#05qn7=xNv~S<_JNf_s002ov JPDHLkV1nq#re**D literal 0 HcmV?d00001 diff --git a/public/entities/zombie-dead.svg b/public/entities/zombie-dead.svg deleted file mode 100644 index bb77ab7..0000000 --- a/public/entities/zombie-dead.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/entities/zombie-idle-frame1.png b/public/entities/zombie-idle-frame1.png new file mode 100644 index 0000000000000000000000000000000000000000..625134e0ad821dd30a28078251e0b6739bb281bf GIT binary patch literal 1151 zcmV-_1c3XAP)w5_(9qs-UQrE~NHh(6*2wY#9<*o1&zhMK3}_ z)Fh203=>OQ6lFEt?|1%l&y4pmXXcEX{NOTYX6~8!zVkotxkQUt#3B|E0uEWi5{e&= zZ|pQRH2ANFM%)6NmLtu4q!e+6EfEQd4@}M#z?hzxQsct1Vm{vP>W;)CE&;sqLKtLq z@_zL6_J-yXs|B`KY~?{=*=ltWVruKFdEEEntr8Yi#4?HxiPvLqln6k`KfcRk>C}Z| zeC)o}LtznD5If`xP>^q$UCQGV_vQq=9~|dnlsJgP0#5stHXgLKwyTRHuSTh9TNRH# zbhaf|zkJ?23L(O_1w1YTA>#iy_T>Fh_b#9B;N#K#XE~*6X?33UvcYx`vw(BSl|E2Z zK*xs$JvSQ<&eeggYxgM`AEb-Lxzq``o9k9NdBzsRoz(@@Slz$@D>+|>kOy%WiDV{H z#SUp$vQ^R*GrE^e7gFwXwSPoi#HvfGEPnEij}IDmGjW%Ef>PuXZaQTk&Ek^{HeHAX zh|fEJ z$~dV$M|vRH zkVCLy^@Y4f67Nw3P9Q-&wP%Am_sv-t0kDANcm!-q@j6wse15D7l!%cmqc?aM2XHYf z0-W0X3d&oxoKNyAiaa;9f|Tw(o@(pM`z$bsh{%NuRjL4ruloG#+ik1zOeV+IkpCl&!Zo)1tQ z2onQN>B<6Hzh*6;Aa!Q=LC)U;N>=2nxG;!^KnP_45Q1w|Hf&1U>$^z6DgdmJDb+@W zsQ~t2-F0&0daBv6nLqj*n=lc;nMd3L7=F~Ba}q3s1`k#P-0Wp7m`!|fp6};q71TU^ zmkx-s6X!=z3#fpU%mVU3EC8T8MT0jrFu_Ng2r7UB622^e@B1rmrn#4%J>|i}!+Xg# zjI$6#Ap5`)2AL&+A2!%#LBDSk#@(pCXXBXcg7O4S4HNcIn_wAP$5fZ!*ErTL|3}f)EyQLUIW`Dc367+-LoN@DEM=$yCl< R$aw$&002ovPDHLkV1nV_|A_zq literal 0 HcmV?d00001 diff --git a/public/entities/zombie-idle-frame2.png b/public/entities/zombie-idle-frame2.png new file mode 100644 index 0000000000000000000000000000000000000000..34101642ed87c19d266d914cfa45c92da5a00d8e GIT binary patch literal 1160 zcmV;31b6$1P)q?tuOLPOLf zjU)^+OBxhqb$e@j_i^8=&v(zg=i0;tmv`=et+UtO=iKv%CNYUg{115K2vaD1c%ime zRaqH69vf*3@D3f=%fB>2oZ(7DhSCF*^93*ldiza#UbcjPZ`C&>;*pjB?s*{$vYLDt zjZIClxuhC_^<`_gF)usE9E6y4o0oID`S}|oEUt)YlpZp#x?dX+fRKN5hso0MbBFo2 z;btSn#Y9vg2FO2W}lM=1r+ zn>ry;X8TT@MAOT&MTWBoNXbOPqS`c0|6|De?=d5^078KNZA;BQM3-K_CikQ+z*|>T zM$>1{;GLqf`GJ%EfTZ7JdRN3qRP+=Z?Wh(@EMQZ11r2@w#XBVx*}%!c?$7k1wmOl})+UqX_(*l~Irm6Sn}DP5u2$Ddzz9KA2efavxoWJ_4mC`^Q3weuX71QFm3wQ^mGtY_wUkAWeVyB2!ii}th03leTymIBJ z4;6(Bq5>cqx=Oi(tpN70woX@5`?xn$Y*@vczQ-kO1aRdMTL2voTMFKU2w{RJ)yc13 z&Q_;wyg17HV@wssyiYGP0!fw+h^F_A448gZ>>jbZ4ivsy+>ZE=^&sGchOvrgL=1|q3D zW%cHrbiVqO^)s!jkW2~Gh-za4UOJTZBG?JUfjH!CZt(OJLPtUnBO+c*F0pTN{xLF% a9R2}Q*w>n*A1dJh0000#oha$oviXTo| z*{QCqOuru(aSL!7>TCH-|A;f95|NIi$LEgDu24MU62L7lgh5u5 zPe*r8Phc*wMqp>*b}p<*uJUd|Oj$(Z!$CK7MOjz}x;2K8NWCaYbN8c6t45h^luKbNO9+ORDt?CDWL&X;K;xIx+gycTbB#8kT6aG`GT%vx8T2`!q90}q7g_#%ulAaV=Ipaq{7?=JFe z08e+M&TUjDKh4^B*~lBc#1ONNy3^PG%wtN7rc-fNev$S8ArI0x#Lwbm*vchuf_Y1$ zzG-s#uRklXQd@1E5S$Cv4I0WqTEvI%pK*Xd0wVD2{g;p4^DB@0 zC=LX%kh5V$E^S=BjxUfpGwdkv?*Q0J>||&qV+4u-2*Dafm0M;kR1^}33V>+nEEOee z1+a&;b%uHd=B!Yh(Pv1BrGyZ0t*{#v&b4DDp)V-^`?`XCat`z_lo8l=#17YrWoGggK;O$v<+wr zWF!5z6NMCGY@xIf*Lv2N4n1{FSyDt;ztgk=r%^psc4wWoAM{01H_En(19ZOmjP*OM zvye;))QD2#DO5>Z7Mi=3ZWw*2oMn`AeX?EB43OwAcemeH^#lJewEY! O0000Wi`F;_|EOT@jYkm%pF_g2gBSuGk50u?)kar-A6Q!dCcQqz#~UkK*_`N zwY~bzFzr<(MSQ;A)g6mRQUZA9g)qo! z@_zL6_D1HCs0DVEZR5tOk`?A6#8lN(aJ%oxD!%iS``E2%0dEIJ`5Y$>lCr>z?DG2AFx7A0$nAHX?djPsB^O8{ z2E2~*?c5Mo5orJKx_FyTUudDj4XqT1n`;5=^G&S{=7J8?x&zbe0LZ7~gY>$f0ihYU z3gnrHfKzE#*}uAwjt&mYcmN_GAQwcGtPx_Z_O_0^ToVi8&Wb`a;~<2ixo9o-@6_!k zO6EgWzYJ$vHQ zaIFkd6fl87JF@^ zgB=3Hh=^Uxf&A3hA35(0_YTfBR1;${Dodr(?ZmTC*6eE$a!v4bY$(jPIF@WnYi9xk z5E4-UzmqIEMO}%RyatHB|MJnawMD8+HVL^PSWjr=Qeyo3q-le!^!p}9vYx}emR*%x z1ChfGEkUeWNQ?OJ?K2LpZy*Bq#3I0@$NiK9qQro=ZdoC%U%7@mNS%3h6!S*a+avBW?i< z-Fp!@2o^$t2de>@?By(&ZG3Tt?+26$W}Mz;1mbKZ`VrOwCLkrVfP5GW0H{t;;7#<8 zncu~(5LN&OBz#!_-}kS$`Np1m^pG2O5A7w_u+Bmdf$RfISY(zIe%N4}Mg1mr0_$p2 zZ#retq?Iq{+AEqXAQ<_d7K#oR@5n;vAkMc9XbWT`{kIdv6cgHod@HVX+3Sb?>nZJ$ zBEtHeVFz#;mBk3iRGqdT%(A4Ol&v-U=xp0b>vvjbAt?#eifZEqy!R>lpKvD-2cnR- v8Q|#>LR&%*!6IHnE|CWX&9bu+`M<&+w12w~e_ASL00000NkvXXu0mjf{=5Z^ literal 0 HcmV?d00001 diff --git a/public/entities/zombie-idle.svg b/public/entities/zombie-idle.svg deleted file mode 100644 index b757918..0000000 --- a/public/entities/zombie-idle.svg +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/renderer/Assets.ts b/renderer/Assets.ts index b129ddd..48e347c 100644 --- a/renderer/Assets.ts +++ b/renderer/Assets.ts @@ -6,8 +6,11 @@ export interface RendererAssets { landmine: HTMLImageElement | null; player: HTMLImageElement | null; rock: HTMLImageElement | null; - zombie: HTMLImageElement | null; zombieDead: HTMLImageElement | null; + zombieIdleFrame1: HTMLImageElement | null; + zombieIdleFrame2: HTMLImageElement | null; + zombieIdleFrame3: HTMLImageElement | null; + zombieIdleFrame4: HTMLImageElement | null; zombieWalkingFrame1: HTMLImageElement | null; zombieWalkingFrame2: HTMLImageElement | null; zombieWalkingFrame3: HTMLImageElement | null; @@ -22,8 +25,11 @@ export const assets: RendererAssets = { landmine: null, player: null, rock: null, - zombie: null, zombieDead: null, + zombieIdleFrame1: null, + zombieIdleFrame2: null, + zombieIdleFrame3: null, + zombieIdleFrame4: null, zombieWalkingFrame1: null, zombieWalkingFrame2: null, zombieWalkingFrame3: null, @@ -43,8 +49,11 @@ export async function loadAssets() { landmine, player, rock, - zombie, zombieDead, + zombieIdleFrame1, + zombieIdleFrame2, + zombieIdleFrame3, + zombieIdleFrame4, zombieWalkingFrame1, zombieWalkingFrame2, zombieWalkingFrame3, @@ -55,8 +64,11 @@ export async function loadAssets() { loadAssetImage("/entities/landmine.svg"), loadAssetImage("/entities/player-attacking.svg"), loadAssetImage("/entities/rock.svg"), - loadAssetImage("/entities/zombie-idle.svg"), - loadAssetImage("/entities/zombie-dead.svg"), + loadAssetImage("/entities/zombie-dead.png"), + loadAssetImage("/entities/zombie-idle-frame1.png"), + loadAssetImage("/entities/zombie-idle-frame2.png"), + loadAssetImage("/entities/zombie-idle-frame3.png"), + loadAssetImage("/entities/zombie-idle-frame4.png"), loadAssetImage("/entities/zombie-walking-frame1.png"), loadAssetImage("/entities/zombie-walking-frame2.png"), loadAssetImage("/entities/zombie-walking-frame3.png"), @@ -69,8 +81,11 @@ export async function loadAssets() { assets.landmine = landmine; assets.player = player; assets.rock = rock; - assets.zombie = zombie; assets.zombieDead = zombieDead; + assets.zombieIdleFrame1 = zombieIdleFrame1; + assets.zombieIdleFrame2 = zombieIdleFrame2; + assets.zombieIdleFrame3 = zombieIdleFrame3; + assets.zombieIdleFrame4 = zombieIdleFrame4; assets.zombieWalkingFrame1 = zombieWalkingFrame1; assets.zombieWalkingFrame2 = zombieWalkingFrame2; assets.zombieWalkingFrame3 = zombieWalkingFrame3; diff --git a/renderer/Renderer.ts b/renderer/Renderer.ts index 5bcdc55..3632f66 100644 --- a/renderer/Renderer.ts +++ b/renderer/Renderer.ts @@ -172,7 +172,7 @@ export class Renderer { } else if (entity.hasChange(ChangeType.Walking)) { return assets.zombieWalkingFrame1; } else { - return assets.zombie; + return assets.zombieIdleFrame1; } } } @@ -307,6 +307,24 @@ export class Renderer { ], }); } + } else if (entity.getType() === EntityType.Zombie && !entity.dead()) { + if ( + assets.zombieIdleFrame2 !== null && + assets.zombieIdleFrame3 !== null && + assets.zombieIdleFrame4 !== null + ) { + rendererItem.addEffect({ + type: RendererEffectType.AssetSwap, + duration: REPLAY_SPEED, + every: REPLAY_SPEED / 4, + startedAt: Date.now(), + steps: [ + assets.zombieIdleFrame2, + assets.zombieIdleFrame3, + assets.zombieIdleFrame4, + ], + }); + } } this.items.push(rendererItem);