update spirit
This commit is contained in:
@@ -54,17 +54,22 @@ export class Node {
|
||||
public name: string = '';
|
||||
public active: boolean = true;
|
||||
public layer: number = 0;
|
||||
public isValid: boolean = true;
|
||||
public scale: Vec3 = new Vec3(1, 1, 1);
|
||||
|
||||
constructor(name?: string) {
|
||||
this.name = name ?? '';
|
||||
}
|
||||
|
||||
public addChild(_child: Node): void {}
|
||||
public removeFromParent(): void {}
|
||||
public destroy(): void { this.isValid = false; }
|
||||
public on(_ev: string, _cb: (...args: unknown[]) => void, _target?: unknown): void {}
|
||||
public off(_ev: string, _cb?: (...args: unknown[]) => void): void {}
|
||||
public getComponent<T>(_ctor: new (...args: unknown[]) => T): T | null { return null; }
|
||||
public addComponent<T>(_ctor: new (...args: unknown[]) => T): T { return {} as T; }
|
||||
public setPosition(..._args: unknown[]): void {}
|
||||
public setScale(..._args: unknown[]): void {}
|
||||
|
||||
public static EventType = {
|
||||
TOUCH_START: 'touch-start',
|
||||
@@ -158,6 +163,7 @@ export class Label {
|
||||
public horizontalAlign: number = 0;
|
||||
public verticalAlign: number = 0;
|
||||
public useSystemFont: boolean = true;
|
||||
public enableWrapText: boolean = true;
|
||||
}
|
||||
|
||||
export class Button {
|
||||
@@ -181,16 +187,39 @@ export class Sprite {
|
||||
public spriteFrame: unknown = null;
|
||||
public type: number = 0;
|
||||
public sizeMode: number = 0;
|
||||
public color: Color = Color.WHITE;
|
||||
public static Type = { SIMPLE: 0, SLICED: 1, TILED: 2, FILLED: 3 };
|
||||
public static SizeMode = { CUSTOM: 0, TRIMMED: 1, RAW: 2 };
|
||||
}
|
||||
|
||||
export class Rect {
|
||||
public x: number;
|
||||
public y: number;
|
||||
public width: number;
|
||||
public height: number;
|
||||
constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) {
|
||||
this.x = x; this.y = y; this.width = width; this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
export class Size {
|
||||
public width: number;
|
||||
public height: number;
|
||||
constructor(width: number = 0, height: number = 0) {
|
||||
this.width = width; this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
export class SpriteFrame {
|
||||
public texture: unknown = null;
|
||||
public rect: Rect = new Rect();
|
||||
public originalSize: Size = new Size();
|
||||
}
|
||||
|
||||
export class Texture2D {
|
||||
public image: unknown = null;
|
||||
public width: number = 0;
|
||||
public height: number = 0;
|
||||
public static PixelFormat = { RGBA8888: 35 };
|
||||
}
|
||||
|
||||
@@ -205,6 +234,21 @@ export class JsonAsset {
|
||||
export class Canvas {}
|
||||
export class EventTouch {}
|
||||
|
||||
/** Global input event source (Cocos Creator 3.8). */
|
||||
export const input = {
|
||||
on: (_ev: string, _cb: (...args: unknown[]) => void, _target?: unknown): void => {},
|
||||
off: (_ev: string, _cb?: (...args: unknown[]) => void, _target?: unknown): void => {},
|
||||
};
|
||||
|
||||
export const Input = {
|
||||
EventType: {
|
||||
TOUCH_START: 'touch-start',
|
||||
TOUCH_MOVE: 'touch-move',
|
||||
TOUCH_END: 'touch-end',
|
||||
TOUCH_CANCEL: 'touch-cancel',
|
||||
},
|
||||
};
|
||||
|
||||
export const resources = {
|
||||
load: (_path: string, _type: unknown, _cb: (err: Error | null, asset: unknown) => void): void => {},
|
||||
};
|
||||
|
||||
@@ -10,8 +10,8 @@ const bossCfg: IBossConfig = {
|
||||
princessCutsceneAtHpRatio: 0.5,
|
||||
phases: [
|
||||
{ hpThreshold: 1.0, mode: 'pair_pincer', actionIntervalSec: 2.2 },
|
||||
{ hpThreshold: 0.66, mode: 'fireball_spread', actionIntervalSec: 1.8 },
|
||||
{ hpThreshold: 0.33, mode: 'clone_confuse', actionIntervalSec: 1.4 },
|
||||
{ hpThreshold: 2 / 3, mode: 'fireball_spread', actionIntervalSec: 1.8 },
|
||||
{ hpThreshold: 1 / 3, mode: 'clone_confuse', actionIntervalSec: 1.4 },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ function newPair(color: PlayerColorState = PlayerColorState.Red) {
|
||||
aabb: { x: 0, y: 16, w: 16, h: 32 },
|
||||
platforms: [{ topY: 0, leftX: -500, rightX: 500 }],
|
||||
initialColorState: color,
|
||||
levelLengthPx: 2000,
|
||||
});
|
||||
motion.update(0.016); // settle on ground
|
||||
const jump = new JumpController(motion);
|
||||
|
||||
@@ -7,9 +7,10 @@ function makeGroundPlatform(): IPlatform {
|
||||
|
||||
function makeModel(color: PlayerColorState = PlayerColorState.Red) {
|
||||
return new PlayerMotionModel({
|
||||
aabb: { x: 0, y: 16, w: 16, h: 32 }, // character 16x32 resting on y=0 ground
|
||||
aabb: { x: 100, y: 16, w: 16, h: 32 }, // character 16x32 resting on y=0 ground; x=100 avoids level-edge clamp
|
||||
platforms: [makeGroundPlatform()],
|
||||
initialColorState: color,
|
||||
levelLengthPx: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ describe('PlayerMotionModel — horizontal movement (req 2.1, 5.1-5.2)', () => {
|
||||
m.setHorizontalInput(1);
|
||||
m.update(1);
|
||||
expect(m.vx).toBe(MOVE_SPEED[PlayerColorState.Red]);
|
||||
expect(m.aabb.x).toBeCloseTo(100, 1);
|
||||
expect(m.aabb.x).toBeCloseTo(200, 1);
|
||||
});
|
||||
|
||||
it('moves at 150 px/s in yellow state', () => {
|
||||
|
||||
@@ -44,21 +44,22 @@ describe('PlayerStateMachine — damage (req 5.3-5.6, 10.4-10.5)', () => {
|
||||
expect(sm.color).toBe(PlayerColorState.Red);
|
||||
});
|
||||
|
||||
it('Red + shuriken → death, consumes one life', () => {
|
||||
it('Red + shuriken → life loss (downgraded) when lives > 1', () => {
|
||||
const sm = new PlayerStateMachine(2);
|
||||
const out = sm.takeHit('shuriken');
|
||||
expect(out.kind).toBe('died');
|
||||
expect(out.kind).toBe('downgraded');
|
||||
expect(sm.lives).toBe(1);
|
||||
expect(sm.isDead).toBe(false);
|
||||
});
|
||||
|
||||
it('fireball is always lethal regardless of color', () => {
|
||||
it('fireball reduces one life regardless of color', () => {
|
||||
const sm = new PlayerStateMachine(2);
|
||||
sm.pickupCrystalJade();
|
||||
sm.pickupCrystalJade();
|
||||
const out = sm.takeHit('fireball');
|
||||
expect(out).toEqual({ kind: 'died', cause: 'fireball' });
|
||||
expect(sm.color).toBe(PlayerColorState.Red);
|
||||
expect(out.kind).toBe('downgraded');
|
||||
expect(sm.lives).toBe(1);
|
||||
expect(sm.isDead).toBe(false);
|
||||
});
|
||||
|
||||
it('smoke bomb is always lethal', () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
applySafeArea,
|
||||
classifyDirection,
|
||||
hitTest,
|
||||
isInsideCircle,
|
||||
isInsideRect,
|
||||
joystickDirection,
|
||||
ZERO_DIRECTION,
|
||||
@@ -21,8 +22,33 @@ describe('InputModel — layout geometry', () => {
|
||||
const r = { cx: 100, cy: 100, w: 40, h: 40 };
|
||||
expect(isInsideRect(r, 100, 100)).toBe(true);
|
||||
expect(isInsideRect(r, 120, 120)).toBe(true);
|
||||
expect(isInsideRect(r, 121, 100)).toBe(false);
|
||||
expect(isInsideRect(r, 100, 79)).toBe(false);
|
||||
// With HIT_TOLERANCE=15, the effective boundary is ±35 (=halfW/halfH + tolerance).
|
||||
// 121 is outside the visual box (120) but within tolerance (135).
|
||||
expect(isInsideRect(r, 121, 100)).toBe(true);
|
||||
// 79 is below the visual bottom (80) but within tolerance (65).
|
||||
expect(isInsideRect(r, 100, 79)).toBe(true);
|
||||
// Far outside even the tolerance range.
|
||||
expect(isInsideRect(r, 136, 100)).toBe(false);
|
||||
expect(isInsideRect(r, 100, 64)).toBe(false);
|
||||
});
|
||||
|
||||
it('isInsideCircle matches circular button shape', () => {
|
||||
const r = { cx: 765, cy: 100, w: 90, h: 90 }; // shuriken button
|
||||
// Centre — always a hit.
|
||||
expect(isInsideCircle(r, 765, 100)).toBe(true);
|
||||
// On the circle edge (radius = 45).
|
||||
expect(isInsideCircle(r, 765 + 45, 100)).toBe(true); // right edge
|
||||
expect(isInsideCircle(r, 765, 100 + 45)).toBe(true); // top edge (upper semicircle)
|
||||
expect(isInsideCircle(r, 765 - 45, 100)).toBe(true); // left edge
|
||||
expect(isInsideCircle(r, 765, 100 - 45)).toBe(true); // bottom edge
|
||||
// Within HIT_TOLERANCE (15) outside the circle.
|
||||
expect(isInsideCircle(r, 765 + 60, 100)).toBe(true); // right + tolerance
|
||||
expect(isInsideCircle(r, 765, 100 + 60)).toBe(true); // top + tolerance (upper arc)
|
||||
// Corner of the bounding rect but OUTSIDE the circle — should miss.
|
||||
// Distance from centre to (765+45, 100+45) = sqrt(45²+45²) ≈ 63.6 > 45+15=60
|
||||
expect(isInsideCircle(r, 765 + 45, 100 + 45)).toBe(false);
|
||||
// Well outside.
|
||||
expect(isInsideCircle(r, 765 + 70, 100)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user