PageSourceSearch

https://ctn-playfun.pages.dev/js/components/Player.js?v=3

js ctn-playfun.pages.dev collected 2026-10-03 06:38:21 UTC 15,956 bytes, 369 lines download raw bytes

1class Player extends Phaser.Physics.Arcade.Sprite {
2
3    // --- Physics Constants (Adjust these!) ---
4    MOVE_SPEED = 320;          // Max horizontal speed (scaled from 2.2)
5    ACCELERATION = 1050;       // Horizontal acceleration (scaled from 0.10, needs to be higher)
6    DRAG = 1600;               // Horizontal drag (scaled from 0.10, friction/deceleration)
7    JUMP_VELOCITY = -620;      // Vertical jump velocity (scaled/interpreted from 10.0)
8    GRAVITY = 1450;            // World gravity (scaled from 0.35)
9    AIR_CONTROL_FACTOR = 0.71; // How much control in air (scaled from 0.25)
10    ATTACK_DURATION = 300;    // Duration of the attack animation (adjust based on frameRate)
11    HITBOX_OFFSET_X = 45;     // Horizontal offset - RE-EVALUATE BASED ON SCALE/ANIM
12    HITBOX_OFFSET_Y = -10;    // Vertical offset - RE-EVALUATE
13    HITBOX_WIDTH = 75;        // Width - RE-EVALUATE
14    HITBOX_HEIGHT = 50;       // Height - RE-EVALUATE
15    // Baseline frame height captured from first frame; used to keep collider bottom constant across differently trimmed frames
16    baseFrameHeight = null;
17    // ----------------------------------------
18
19    // --- Scaling Factor ---
20    SPRITE_SCALE = 0.4; // Enlarged scale
21
22    // --- Collider Tuning (edit here!) ---
23    IDLE_WIDTH_MULT   = 0.60; // body width when not attacking
24    ATTACK_WIDTH_MULT = 0.28; // narrower body while attacking frames are wider
25    HEIGHT_MULT       = 1; // shorter body so feet sit on ground (0.80–0.90 range)
26    FEET_FUDGE        = -25;    // Downward adjustment so shoes touch ground
27    // --------------------
28
29    constructor(scene, x, y) { // Remove texture default
30        console.log("Player Constructor Start");
31        // Use the atlas key and the initial idle frame
32        super(scene, x, y, 'player_atlas', 'idle.png');
33        console.log("Player super() called with player_atlas");
34
35        scene.add.existing(this);
36        console.log("Player scene.add.existing called");
37        scene.physics.add.existing(this);
38        console.log("Player scene.physics.add.existing called");
39
40        this.isHoldingLoan = false; // Initialize here
41
42        // --- Set Origin and Scale FIRST ---
43        this.setOrigin(0.5, 0.5); // Set origin to center for better alignment
44        this.setScale(this.SPRITE_SCALE);
45        console.log(`Player scale set to: ${this.SPRITE_SCALE}, Origin set to (0.5,0.5)`);
46
47        // --- Physics Setup ---
48        this.setGravityY(this.GRAVITY);
49        this.setDragX(this.DRAG);
50        this.setMaxVelocity(this.MOVE_SPEED, Math.abs(this.JUMP_VELOCITY * 2));
51        this.setCollideWorldBounds(true);
52        this.body.onWorldBounds = true;
53
54        // Store baseline trimmed frame height (idle frame) once
55        this.baseFrameHeight = this.frame.height;
56
57        // Configure physics body using shared utility, computing vertical adjust dynamically
58        const widthMult     = this.isAttacking ? this.ATTACK_WIDTH_MULT : this.IDLE_WIDTH_MULT;
59        const heightMult    = this.HEIGHT_MULT;
60        const verticalAdjust = ((this.frame.height - this.baseFrameHeight) / 2) - this.FEET_FUDGE;
61        applyBodyFromFrame(this, { widthMult, heightMult, verticalAdjust, debug: window.APPLY_BODY_DEBUG });
62
63        // Extra per-frame debug so we can see collider consistency without
64        // having to toggle anything from the console.
65        if (window.APPLY_BODY_DEBUG) {
66            console.log(`[PlayerCollider] frame=${this.frame.name} fh=${this.frame.height} ` +
67                        `base=${this.baseFrameHeight} vertAdj=${verticalAdjust.toFixed(1)} ` +
68                        `offY=${this.body.offset.y.toFixed(1)} onFloor=${this.body.onFloor()}`);
69        }
70
71        console.log("Player Physics Setup Done");
72
73        // --- Input Setup ---
74        this.cursors = scene.input.keyboard.createCursorKeys();
75        this.keyA = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A);
76        this.keyD = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D);
77        this.keySpace = scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
78        console.log("Player Input Setup Done");
79
80        this.movementEnabled = true;
81        this.isAttacking = false;
82        this.attackTimer = null; // Keep timer for now, maybe switch to anim complete later
83
84        // --- Guitar Hitbox ---
85        console.log("Creating Guitar Hitbox...");
86        this.guitarHitbox = scene.add.rectangle(0, 0, this.HITBOX_WIDTH, this.HITBOX_HEIGHT, 0xff0000, 0.5)
87                                   .setOrigin(0.5)
88                                   .setVisible(false);
89        console.log("Guitar Hitbox Rectangle Created (No Physics Body)");
90
91        // --- Attack Input ---
92        scene.input.on('pointerdown', this.attack, this);
93        console.log("Player Attack Input Listener Added (global canvas tap)");
94
95        // --- Start Idle Animation ---
96        this.play('player_idle');
97
98        console.log("Player initialized with keys, attack input, and atlas.");
99
100        this.jumpKeyHeld = false;
101        this.hasJumped = false;
102    }
103
104    disableMovement() {
105        this.movementEnabled = false;
106        this.setVelocityX(0);
107        this.play('player_idle'); // Go to idle when disabled
108        console.log("Player movement DISABLED");
109    }
110
111    enableMovement() {
112        this.movementEnabled = true;
113        console.log("Player movement ENABLED");
114    }
115
116    // --- NEW: Methods for Aave Loan State ---
117    startHoldingLoan() {
118        this.isHoldingLoan = true;
119        this.play('player_aave_loan', true); // Play and ignore if already playing
120        this.updateBodyOffset(); // Ensure body is correct for this sprite
121        console.log("Player started holding Aave loan");
122    }
123
124    stopHoldingLoan() { // Call this if the loan state can end
125        this.isHoldingLoan = false;
126        // The regular animation logic in update() will take over
127        console.log("Player stopped holding Aave loan");
128    }
129    // --- END NEW ---
130
131    attack() {
132        const onGround = this.body.blocked.down || this.body.touching.down;
133        if (!this.movementEnabled || this.isAttacking /*|| !onGround*/) {
134            return;
135        }
136
137        console.log("Player attacking!");
138        this.isAttacking = true;
139        this.play('player_attack', true); // Play attack animation, ignoreIfPlaying = true
140        
141        // --- Play Swing Guitar Sound ---
142        const soundManager = this.scene.registry.get('soundManager');
143        if (soundManager) {
144            soundManager.playSound('swingGuitarSound', { volume: 0.1 });
145        } else {
146            console.warn('Player: SoundManager not found in scene registry. Cannot play swing guitar sound.');
147        }
148        // --- End Sound ---
149        
150        // Recalculate body offset for attack animation
151        this.updateBodyOffset();
152
153        // Hitbox positioning logic is moved to update for continuous positioning
154
155        // Reset attack state after duration (could also use animation complete event)
156        if (this.attackTimer) this.attackTimer.remove();
157        this.attackTimer = this.scene.time.delayedCall(this.ATTACK_DURATION, () => {
158            this.isAttacking = false;
159            this.guitarHitbox.setVisible(false);
160            // Don't force idle here, let update() handle the next state
161            // console.log("Attack finished.");
162        }, [], this);
163
164        // Make hitbox visible immediately when attack starts
165        // Position will be set in update
166        const hitboxX = this.x + (this.flipX ? -this.HITBOX_OFFSET_X : this.HITBOX_OFFSET_X);
167        const hitboxY = this.y + this.HITBOX_OFFSET_Y;
168        this.guitarHitbox.setPosition(hitboxX, hitboxY);
169        this.checkHitboxOverlap(); // Check overlap while attacking
170
171        // Re-apply body offset each attack frame
172        this.updateBodyOffset();
173    }
174
175    checkHitboxOverlap() {
176        if (!this.scene.enemyManager) {
177            return;
178        }
179        const enemyGroup = this.scene.enemyManager.getGroup();
180        const hitboxGeom = this.guitarHitbox.getBounds();
181
182        enemyGroup.children.each(enemy => {
183            if (enemy.active && !enemy.isHit && Phaser.Geom.Intersects.RectangleToRectangle(hitboxGeom, enemy.getBounds())) {
184                console.log("Hitbox overlapped with enemy!");
185                this.handleEnemyHit(enemy);
186            }
187        });
188    }
189
190    handleEnemyHit(enemy) {
191        if (!enemy || typeof enemy.takeHit !== 'function') return;
192
193        const hitFromLeft = this.x < enemy.x;
194        enemy.takeHit(hitFromLeft);
195
196        // --- Play Hit Sound ---
197        const soundManager = this.scene.registry.get('soundManager');
198        if (soundManager) {
199            soundManager.playSound('playerHitSound', { volume: 0.2 }); 
200        } else {
201            console.warn('Player: SoundManager not found in scene registry. Cannot play hit sound.');
202        }
203        // --- End Hit Sound ---
204
205        const scene = this.scene;
206
207        // 1. Camera shake for impact
208        if (scene.cameras && scene.cameras.main) {
209            scene.cameras.main.shake(120, 0.008);
210        }
211
212        // 2. Quick white flash on enemy
213        enemy.setTintFill(0xffffff);
214        setTimeout(() => enemy.clearTint(), 40);
215
216        // 3. Dust/spark particle burst
217        if (!scene.textures.exists('hit-dust')) {
218            const gfx = scene.add.graphics({ x: 0, y: 0, add: false });
219            gfx.fillStyle(0xffffff, 1);
220            gfx.fillCircle(4, 4, 4);
221            gfx.generateTexture('hit-dust', 8, 8);
222            gfx.destroy();
223        }
224        const emitter = scene.add.particles(enemy.x, enemy.y, 'hit-dust', {
225            speed: { min: -120, max: 120 },
226            angle: { min: 0, max: 360 },
227            lifespan: 350,
228            quantity: 15,
229            scale: { start: 0.7, end: 0 },
230            alpha: { start: 1, end: 0 },
231            gravityY: 300
232        });
233        emitter.setDepth(enemy.depth + 1);
234        scene.time.delayedCall(400, () => emitter.destroy());
235
236        // 4. Floating score popup
237        const popup = scene.add.text(enemy.x, enemy.y - enemy.displayHeight / 2, '+100', {
238            fontFamily: '"Press Start 2P", monospace',
239            fontSize: '14px',
240            fill: '#ffffff',
241            stroke: '#000',
242            strokeThickness: 4
243        }).setOrigin(0.5).setDepth(enemy.depth + 2);
244        scene.tweens.add({
245            targets: popup,
246            y: popup.y - 30,
247            alpha: 0,
248            duration: 800,
249            ease: 'Cubic.easeOut',
250            onComplete: () => popup.destroy()
251        });
252
253        // (Hit-stop removed temporarily to avoid game freeze)
254
255        console.log("Enemy hit by guitar!");
256    }
257
258    update(time, delta) {
259        const onGround = this.body.blocked.down || this.body.touching.down;
260        const touch = this.scene.game.touchControls ? this.scene.game.touchControls.directions : {};
261
262        // --- Handle Attack Input (from touch controls / mann.cool virtual controller) ---
263        if (touch.action && !this.isAttacking) {
264            this.attack(); 
265        }
266
267        // --- Handle Movement/Input (only if enabled) ---
268        if (this.movementEnabled) {
269            const currentAcceleration = onGround ? this.ACCELERATION : this.ACCELERATION * this.AIR_CONTROL_FACTOR;
270
271            if (this.cursors.left.isDown || this.keyA.isDown || touch.left) {
272                this.setAccelerationX(-currentAcceleration);
273                this.setFlipX(true);
274            } else if (this.cursors.right.isDown || this.keyD.isDown || touch.right) {
275                this.setAccelerationX(currentAcceleration);
276                this.setFlipX(false);
277            } else {
278                this.setAccelerationX(0);
279            }
280
281            // --- Variable Jump Height ---
282            const jumpPressed = this.cursors.up.isDown || this.keySpace.isDown || touch.up;
283            if (onGround && jumpPressed && !this.jumpKeyHeld) {
284                this.setVelocityY(this.JUMP_VELOCITY);
285                this.jumpKeyHeld = true;
286                this.hasJumped = true;
287
288                // --- Play Jump Sound ---
289                const soundManager = this.scene.registry.get('soundManager');
290                if (soundManager) {
291                    soundManager.playSound('playerJumpSound', { volume: 0.1 });
292                } else {
293                    console.warn('Player: SoundManager not found in scene registry. Cannot play jump sound.');
294                }
295            }
296            if (!jumpPressed) {
297                this.jumpKeyHeld = false;
298            }
299            // If jump key released early and player is still moving up, cut velocity for short hop
300            if (this.hasJumped && !jumpPressed && this.body.velocity.y < 0) {
301                this.setVelocityY(this.body.velocity.y * 0.45); // Cut upward velocity for short hop
302                this.hasJumped = false;
303            }
304            if (onGround) {
305                this.hasJumped = false;
306            }
307            if (!onGround && this.body.blocked.left && this.body.velocity.x < 0) {
308                this.setAccelerationX(0);
309            }
310            if (!onGround && this.body.blocked.right && this.body.velocity.x > 0) {
311                this.setAccelerationX(0);
312            }
313        }
314
315        // --- Animation Control ---
316        // Priority: Aave Loan > Attack > Jump > Walk > Idle
317        if (this.isHoldingLoan) {
318            this.play('player_aave_loan', true); // Ensure loan animation stays if active
319            // Recalculate body offset for loan animation
320            this.updateBodyOffset();
321        } else if (this.isAttacking) {
322            // Attack animation is already playing, do nothing here
323            // Ensure hitbox stays positioned correctly
324            const hitboxX = this.x + (this.flipX ? -this.HITBOX_OFFSET_X : this.HITBOX_OFFSET_X);
325            const hitboxY = this.y + this.HITBOX_OFFSET_Y;
326            this.guitarHitbox.setPosition(hitboxX, hitboxY);
327            this.checkHitboxOverlap(); // Check overlap while attacking
328
329            // Re-apply body offset each attack frame
330            this.updateBodyOffset();
331        } else if (!onGround) {
332            this.play('player_jump', true); // Play jump animation if in the air (and not attacking)
333            // Recalculate body offset for jump animation
334            this.updateBodyOffset();
335        } else if (this.body.velocity.x !== 0) {
336            this.play('player_walk', true); // Play walk animation if moving horizontally on ground
337            // Recalculate body offset for walk animation
338            this.updateBodyOffset();
339        } else {
340            this.play('player_idle', true); // Play idle animation if on ground and not moving
341            // Recalculate body offset for idle animation
342            this.updateBodyOffset();
343        }
344
345        // --- Update Hitbox Position (even if not attacking, just keep it hidden) ---
346        // We position it during attack state now, and hide it otherwise via the attack t
346imer.
347        // If not attacking, ensure it's hidden (belt and suspenders)
348        // if (!this.isAttacking && this.guitarHitbox.visible) {
349        //     this.guitarHitbox.setVisible(false);
350        // }
351
352        // Note: checkHitboxOverlap() is now called only when isAttacking is true.
353    }
354
355    handleCollision(otherObject) {
356        // console.log('Player collided with:', otherObject);
357    }
358    
359    // Helper method to update body offset based on current animation frame
360    updateBodyOffset() {
361        const widthMult     = this.isAttacking ? this.ATTACK_WIDTH_MULT : this.IDLE_WIDTH_MULT;
362        const heightMult    = this.HEIGHT_MULT;
363        const verticalAdjust = ((this.frame.height - this.baseFrameHeight) / 2) - this.FEET_FUDGE;
364        applyBodyFromFrame(this, { widthMult, heightMult, verticalAdjust, debug: window.APPLY_BODY_DEBUG });
365    }
366}
367
368// Make class available for import (if using modules later)
369// export default Player;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.