diff --git a/engine/Circle.ts b/engine/Circle.ts index 86edba9..427c075 100644 --- a/engine/Circle.ts +++ b/engine/Circle.ts @@ -1,13 +1,9 @@ -import type { Vector } from "#/engine/Vector.ts"; - export type Circle = { - position: Vector; radius: number; }; -export function createCircle(position: Vector, radius: number): Circle { +export function createCircle(radius: number): Circle { return { - position, radius, }; } @@ -15,6 +11,8 @@ export function createCircle(position: Vector, radius: number): Circle { export function strokeCircle( ctx: CanvasRenderingContext2D, circle: Circle, + x: number, + y: number, color: string = "#fff", alpha: number = 1.0, lineWidth: number = 1, @@ -24,7 +22,7 @@ export function strokeCircle( ctx.globalAlpha = alpha; ctx.lineWidth = lineWidth; ctx.beginPath(); - ctx.arc(circle.position.x, circle.position.y, circle.radius, 0, 2 * Math.PI); + ctx.arc(x, y, circle.radius, 0, 2 * Math.PI); ctx.stroke(); ctx.restore(); } @@ -32,6 +30,8 @@ export function strokeCircle( export function fillCircle( ctx: CanvasRenderingContext2D, circle: Circle, + x: number, + y: number, color: string = "#fff", alpha: number = 1.0, ): void { @@ -39,7 +39,7 @@ export function fillCircle( ctx.fillStyle = color; ctx.globalAlpha = alpha; ctx.beginPath(); - ctx.arc(circle.position.x, circle.position.y, circle.radius, 0, 2 * Math.PI); + ctx.arc(x, y, circle.radius, 0, 2 * Math.PI); ctx.fill(); ctx.restore(); } diff --git a/engine/Rect.ts b/engine/Rect.ts index 64d3d8d..93c562b 100644 --- a/engine/Rect.ts +++ b/engine/Rect.ts @@ -1,18 +1,12 @@ -import { copyVector, type Vector } from "#/engine/Vector.ts"; +import type { Vector } from "#/engine/Vector.ts"; export type Rect = { - position: Vector; width: number; height: number; }; -export function createRect( - position: Vector, - width: number, - height: number, -): Rect { +export function createRect(width: number, height: number): Rect { return { - position, width, height, }; @@ -20,7 +14,6 @@ export function createRect( export function fromMinMax(min: Vector, max: Vector): Rect { return { - position: copyVector(min), width: max.x - min.x, height: max.y - min.y, }; @@ -29,6 +22,8 @@ export function fromMinMax(min: Vector, max: Vector): Rect { export function strokeRect( ctx: CanvasRenderingContext2D, rect: Rect, + x: number, + y: number, color: string = "#fff", alpha: number = 1.0, lineWidth: number = 1, @@ -37,24 +32,21 @@ export function strokeRect( ctx.strokeStyle = color; ctx.globalAlpha = alpha; ctx.lineWidth = lineWidth; - ctx.strokeRect( - rect.position.x + 0.5, - rect.position.y + 0.5, - rect.width - 1, - rect.height - 1, - ); + ctx.strokeRect(x + 0.5, y + 0.5, rect.width - 1, rect.height - 1); ctx.restore(); } export function fillRect( ctx: CanvasRenderingContext2D, rect: Rect, + x: number, + y: number, color: string = "#fff", alpha: number = 1.0, ): void { ctx.save(); ctx.fillStyle = color; ctx.globalAlpha = alpha; - ctx.fillRect(rect.position.x, rect.position.y, rect.width, rect.height); + ctx.fillRect(x, y, rect.width, rect.height); ctx.restore(); } diff --git a/engine/Sprite.ts b/engine/Sprite.ts index 79ea592..8641565 100644 --- a/engine/Sprite.ts +++ b/engine/Sprite.ts @@ -1,4 +1,5 @@ import { loadImage } from "#/engine/lib/loadImage.ts"; +import { createVector, type Vector } from "#/engine/Vector.ts"; export type Sprite = { src: string; @@ -6,6 +7,7 @@ export type Sprite = { yStart: number; width: number; height: number; + offset: Vector; }; export type SpriteSheet = { @@ -44,6 +46,7 @@ export function createSpriteSheet( export function createSprite( src: string, + offset: Vector = createVector(), xStartFrame: number = 0, yStartFrame: number = 0, ): Sprite { @@ -55,6 +58,7 @@ export function createSprite( yStart: yStartFrame * spriteSheet.frameHeight, width: spriteSheet.frameWidth, height: spriteSheet.frameHeight, + offset, }; } diff --git a/engine/dev/main.tsx b/engine/dev/main.tsx index dc8fdf6..e47c1be 100644 --- a/engine/dev/main.tsx +++ b/engine/dev/main.tsx @@ -100,16 +100,11 @@ const DevTools: Component<{ gameContainer: HTMLElement }> = ({ ctx.lineWidth = 1; ctx.strokeStyle = "#6572f5"; ctx.strokeRect( - position.value.x - 0.5, - position.value.y - 0.5, + position.value.x - 0.5 + sprite.value.offset.x, + position.value.y - 0.5 + sprite.value.offset.y, spriteSheet.frameWidth + 1, spriteSheet.frameHeight + 1, ); - - ctx.fillStyle = "#ee459e"; - ctx.fillRect(position.value.x - 1, position.value.y - 1, 2, 2); - ctx.beginPath(); - ctx.fill(); ctx.restore(); }; }; diff --git a/engine/system/drawSprites.ts b/engine/system/drawSprites.ts index e9246a0..806face 100644 --- a/engine/system/drawSprites.ts +++ b/engine/system/drawSprites.ts @@ -35,8 +35,8 @@ export function drawSprites(ctx: CanvasRenderingContext2D): void { spriteSheetMap.get(sprite.src)!, sprite.xStart, sprite.yStart, - position.x, - position.y, + position.x + sprite.offset.x, + position.y + sprite.offset.y, ); } } diff --git a/engine/system/physics.ts b/engine/system/physics.ts index 967386b..346f1dd 100644 --- a/engine/system/physics.ts +++ b/engine/system/physics.ts @@ -1,6 +1,6 @@ import type { Circle } from "#/engine/Circle.ts"; import type { Polygon } from "#/engine/Polygon.ts"; -import { createRect, type Rect } from "#/engine/Rect.ts"; +import type { Rect } from "#/engine/Rect.ts"; import $ from "#/engine/Scene.ts"; import { createVector, getDotProduct, type Vector } from "#/engine/Vector.ts"; @@ -25,16 +25,20 @@ type CollisionBuffer = { type Collide = ( collision: Collision, cid: string, - self: Collider, - velocity: Vector, - other: Collider, + colliderA: Collider, + positionA: Vector, + velocityA: Vector, + colliderB: Collider, + positionB: Vector, ) => void; export type Collider = { shape: Shape; body: Circle | Rect | Polygon; - aabb: Rect; buffer: CollisionBuffer; + offset: Vector; + width: number; + height: number; }; export type Collision = { @@ -46,26 +50,37 @@ export type Collision = { export function createCollider( shape: Shape, body: Circle | Rect | Polygon, + offset?: Vector, bufferSize: number = 8, ): Collider { - let aabb = createRect(createVector(), 0, 0); + let width = 0; + let height = 0; + switch (shape) { case CIRCLE: { const radius = (body as Circle).radius; const size = radius * 2; - aabb.width = size; - aabb.height = size; - aabb.position.x = body.position.x - radius; - aabb.position.y = body.position.y - radius; + width = size; + height = size; + if (offset === undefined) { + offset = createVector(); + } break; } case RECT: { - aabb = body as Rect; + width = (body as Rect).width; + height = (body as Rect).height; + if (offset === undefined) { + offset = createVector(-(width / 2), -(height / 2)); + } break; } case POLYGON: { // TODO + if (offset === undefined) { + offset = createVector(); + } break; } } @@ -73,35 +88,40 @@ export function createCollider( return { shape, body, - aabb, buffer: createCollisionBuffer(bufferSize), + offset, + width, + height, }; } -const inflAABB = createRect(createVector(), 0, 0); +const inflAABB = { + x: 0, + y: 0, + width: 0, + height: 0, +}; function sweptAABB( - self: Collider, - velocity: Vector, - other: Collider, + colliderA: Collider, + positionA: Vector, + velocityA: Vector, + colliderB: Collider, + positionB: Vector, delta: number, ): null | number { - inflAABB.position.x = other.aabb.position.x - self.aabb.width; - inflAABB.position.y = other.aabb.position.y - self.aabb.height; - inflAABB.width = other.aabb.width + self.aabb.width; - inflAABB.height = other.aabb.height + self.aabb.height; + inflAABB.x = positionB.x - colliderA.width; + inflAABB.y = positionB.y - colliderA.height; + inflAABB.width = colliderB.width + colliderA.width; + inflAABB.height = colliderB.height + colliderA.height; - const displacementX = velocity.x * delta; - const displacementY = velocity.y * delta; + const disX = velocityA.x * delta; + const disY = velocityA.y * delta; - let txNear = (inflAABB.position.x - self.aabb.position.x) / displacementX; - let txFar = - (inflAABB.position.x + inflAABB.width - self.aabb.position.x) / - displacementX; + let txNear = (inflAABB.x - positionA.x) / disX; + let txFar = (inflAABB.x + inflAABB.width - positionA.x) / disX; - let tyNear = (inflAABB.position.y - self.aabb.position.y) / displacementY; - let tyFar = - (inflAABB.position.y + inflAABB.height - self.aabb.position.y) / - displacementY; + let tyNear = (inflAABB.y - positionA.y) / disY; + let tyFar = (inflAABB.y + inflAABB.height - positionA.y) / disY; if (txNear > txFar) { [txNear, txFar] = [txFar, txNear]; @@ -131,9 +151,11 @@ function sweptAABB( function collideWithCirle( collision: Collision, cid: string, - self: Collider, - velocity: Vector, - other: Collider, + colliderA: Collider, + positionA: Vector, + velocityA: Vector, + colliderB: Collider, + positionB: Vector, ) { collision.cid = cid; // TODO @@ -142,13 +164,15 @@ function collideWithCirle( function collideWithRect( collision: Collision, cid: string, - self: Collider, - velocity: Vector, - other: Collider, + colliderA: Collider, + positionA: Vector, + velocityA: Vector, + colliderB: Collider, + positionB: Vector, ) { collision.cid = cid; - const x = self.aabb.position.x - other.aabb.position.x; - const y = self.aabb.position.y - other.aabb.position.y; + const x = positionA.x - positionB.x; + const y = positionA.y - positionB.y; if (Math.abs(x) > Math.abs(y)) { collision.normal.y = 0; collision.normal.x = x > 0 ? 1 : -1; @@ -161,9 +185,11 @@ function collideWithRect( function collideWithPolygon( collision: Collision, cid: string, - self: Collider, - velocity: Vector, - other: Collider, + colliderA: Collider, + positionA: Vector, + velocityA: Vector, + colliderB: Collider, + positionB: Vector, ) { collision.cid = cid; // TODO @@ -224,9 +250,11 @@ export function moveAndCollide( delta: number, ): Array { const colliders = $("collider")!; - const self = colliders.get(id)!.value; + const positions = $("position")!; + const selfColl = colliders.get(id)!.value; + const selfPos = positions.get(id)!.value; - const slice = self.buffer.slice; + const slice = selfColl.buffer.slice; slice.length = 0; for (const [cid, collider] of colliders.entries()) { @@ -234,25 +262,57 @@ export function moveAndCollide( continue; } - const other = collider.value; - const tHit = sweptAABB(self, velocity, other, delta); + const otherColl = collider.value; + const otherPos = positions.get(cid)!.value; + const tHit = sweptAABB( + selfColl, + selfPos, + velocity, + otherColl, + otherPos, + delta, + ); if (tHit === null) { continue; } - const collision = next(self.buffer); + const collision = next(selfColl.buffer); collision.time = tHit; - switch (self.shape) { + switch (selfColl.shape) { case CIRCLE: { - circleCollide[other.shape](collision, cid, self, velocity, other); + circleCollide[otherColl.shape]( + collision, + cid, + selfColl, + selfPos, + velocity, + otherColl, + otherPos, + ); break; } case RECT: { - rectCollide[other.shape](collision, cid, self, velocity, other); + rectCollide[otherColl.shape]( + collision, + cid, + selfColl, + selfPos, + velocity, + otherColl, + otherPos, + ); break; } case POLYGON: { - polygonCollide[other.shape](collision, cid, self, velocity, other); + polygonCollide[otherColl.shape]( + collision, + cid, + selfColl, + selfPos, + velocity, + otherColl, + otherPos, + ); break; } } @@ -271,7 +331,6 @@ export function moveAndSlide( delta: number, ): void { let timeLeft = 1; - const aabb = $("collider").get(id)!.value.aabb; const position = $("position").get(id)!.value; for (let i = 0; i < 4; i++) { @@ -288,13 +347,10 @@ export function moveAndSlide( } const time = closest.time; - if (time === -Infinity) { - break; // TODO - } const scale = Math.max(0, time - 0.001); - aabb.position.x += velocity.x * (delta * timeLeft) * scale; - aabb.position.y += velocity.y * (delta * timeLeft) * scale; + position.x += velocity.x * delta * timeLeft * scale; + position.y += velocity.y * delta * timeLeft * scale; const dot = getDotProduct(velocity, closest.normal); velocity.x -= closest.normal.x * dot; @@ -305,7 +361,4 @@ export function moveAndSlide( velocity.x *= timeLeft; velocity.y *= timeLeft; - - position.x = aabb.position.x; - position.y = aabb.position.y; } diff --git a/game/entities/hero/entity.ts b/game/entities/hero/entity.ts index 347d279..4a5ae47 100644 --- a/game/entities/hero/entity.ts +++ b/game/entities/hero/entity.ts @@ -2,7 +2,7 @@ import { defineEntity } from "#/engine/Entity.ts"; import { keyboardInput } from "#/engine/Keyboard.ts"; import { createEffect, createSignal } from "#/engine/lib/Signal.ts"; import { pointer } from "#/engine/Pointer.ts"; -import { createRect, fillRect, strokeRect } from "#/engine/Rect.ts"; +import { createRect } from "#/engine/Rect.ts"; import $ from "#/engine/Scene.ts"; import { createSprite, @@ -10,7 +10,6 @@ import { defineSpriteSheet, } from "#/engine/Sprite.ts"; import { - type Collider, createCollider, moveAndSlide, Shape, @@ -21,7 +20,6 @@ import { scale, type Vector, } from "#/engine/Vector.ts"; -import { viewport } from "#/engine/Viewport.ts"; import charIdle from "#/game/assets/char-idle.png"; const [speed, setSpeed] = createSignal(0.1); @@ -29,10 +27,10 @@ const idleSrc = await defineSpriteSheet(charIdle, 2, 4); const process = defineEntity("hero", { position: createVector(), - sprite: createSprite(idleSrc), + sprite: createSprite(idleSrc, createVector(-8, -8)), animation: createSpriteAnimation(500, 2), velocity: createVector(), - collider: createCollider(Shape.Rect, createRect(createVector(), 16, 16)), + collider: createCollider(Shape.Rect, createRect(24, 16)), }); createEffect(() => { @@ -44,7 +42,6 @@ let onceGuard = true; process((id, delta) => { const position = $("position").get(id)!.value; const velocity = $("velocity").get(id)!.value; - const collider = $("collider").get(id)!.value; if (pointer.isDown) { position.x = pointer.position.x; @@ -73,7 +70,4 @@ process((id, delta) => { velocity.x = 0; velocity.y = 0; - - fillRect(viewport.ctx, collider.aabb, "orange"); - strokeRect(viewport.ctx, collider.aabb, "red"); }); diff --git a/game/entities/portal/entity.ts b/game/entities/portal/entity.ts index d788b00..f7af10e 100644 --- a/game/entities/portal/entity.ts +++ b/game/entities/portal/entity.ts @@ -1,32 +1,20 @@ import { defineEntity } from "#/engine/Entity.ts"; -import { createRect, fillRect, strokeRect } from "#/engine/Rect.ts"; -import $ from "#/engine/Scene.ts"; +import { createRect } from "#/engine/Rect.ts"; import { createSprite, createSpriteAnimation, defineSpriteSheet, } from "#/engine/Sprite.ts"; -import { - type Collider, - createCollider, - Shape, -} from "#/engine/system/physics.ts"; -import { createVector, type Vector } from "#/engine/Vector.ts"; -import { viewport } from "#/engine/Viewport"; +import { createCollider, Shape } from "#/engine/system/physics.ts"; +import { createVector } from "#/engine/Vector.ts"; import portal from "#/game/assets/portal.png"; const portalSrc = await defineSpriteSheet(portal, 3, 1); +const position = createVector(); -const process = defineEntity("portal", { - position: createVector(), - sprite: createSprite(portalSrc), +defineEntity("portal", { + position, + sprite: createSprite(portalSrc, createVector(-8, -8)), animation: createSpriteAnimation(400, 0), - collider: createCollider(Shape.Rect, createRect(createVector(), 16, 16)), -}); - -process((id, delta) => { - const collider = $("collider").get(id)!.value; - - fillRect(viewport.ctx, collider.aabb, "orange"); - strokeRect(viewport.ctx, collider.aabb, "red"); + collider: createCollider(Shape.Rect, createRect(16, 16)), }); diff --git a/game/entities/testEntity/entity.ts b/game/entities/testEntity/entity.ts index 744dab5..b701ce5 100644 --- a/game/entities/testEntity/entity.ts +++ b/game/entities/testEntity/entity.ts @@ -23,6 +23,7 @@ const sprites = { yStart: 0, width: 16, height: 16, + offset: createVector(-8, -8), }, walk: { src: srcs.walk, @@ -30,6 +31,7 @@ const sprites = { yStart: 0, width: 16, height: 16, + offset: createVector(-8, -8), }, }; @@ -38,6 +40,8 @@ const animations = { walk: createSpriteAnimation(250, 2), }; +const position = createVector(); + const process = defineEntity<{ position: Vector; animation: SpriteAnimation; @@ -52,7 +56,7 @@ const process = defineEntity<{ }; stuff: number; }>("testEntity", { - position: createVector(), + position, animation: animations.idle, animations: animations, sprite: sprites.idle, diff --git a/game/scenes/testScene/scene.ts b/game/scenes/testScene/scene.ts index 2053b03..79d4ed3 100644 --- a/game/scenes/testScene/scene.ts +++ b/game/scenes/testScene/scene.ts @@ -16,12 +16,10 @@ postProcess(() => { console.log("post: ", data.name); }); -const cirle = createCircle(createVector(), 4); +const cirle = createCircle(4); process((ctx, _delta) => { if (!pointer.isDown) { - cirle.position.x = pointer.position.x; - cirle.position.y = pointer.position.y; - strokeCircle(ctx, cirle); + strokeCircle(ctx, cirle, pointer.position.x, pointer.position.y); } }); diff --git a/main.ts b/main.ts index 8d52764..95f9290 100644 --- a/main.ts +++ b/main.ts @@ -9,18 +9,36 @@ import { resizeCanvas, viewport, } from "#/engine/Viewport.ts"; +import type { Sprite } from "./engine/Sprite"; import type { Collider } from "./engine/system/physics"; import type { Vector } from "./engine/Vector"; -function updateAABBs() { - const colliders = $("collider"); +function debug(ctx: CanvasRenderingContext2D) { const positions = $("position"); + const colliders = $("collider"); + const sprites = $("sprite"); for (const [id, collider] of colliders.entries()) { - const position = positions.get(id)!.value; - const aabb = collider.value.aabb; - aabb.position.x = position.x; - aabb.position.y = position.y; + const sprite = sprites.get(id)!.value; + const pos = positions.get(id)!.value; + const col = collider.value; + + ctx.beginPath(); + ctx.fillStyle = "#ee459e"; + ctx.strokeStyle = "#ee459e"; + ctx.strokeRect( + pos.x + col.offset.x + 0.5, + pos.y + col.offset.y + 0.5, + col.width - 1, + col.height - 1, + ); + ctx.fillRect(pos.x - 1, pos.y - 1, 2, 2); + ctx.fillRect( + pos.x - 1 + sprite.offset.x, + pos.y - 1 + sprite.offset.y, + 2, + 2, + ); } } @@ -32,13 +50,13 @@ function animate(timestamp: number): void { resetCtx(); - updateAABBs(); - currentScene.process(viewport.ctx, delta); animateSprites(delta); drawSprites(viewport.ctx); + debug(viewport.ctx); + requestAnimationFrame(animate); }