feat: Implement object health system, damage mechanics, inventory management, and enhanced player movement
- Added health bars for objects with visual indicators - Implemented takeDamage and destroyObject functionality for trees and boulders - Created resource dropping system when objects are destroyed - Enhanced player movement with grid-based positioning and collision handling - Added inventory manager for item collection and storage - Integrated physics collisions between player, objects, and collectibles - Improved UI with updated time of day text and font styling
This commit is contained in:
parent
67c207e54e
commit
321b491c7b
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 120 KiB |
Binary file not shown.
BIN
raw/objects.psd
BIN
raw/objects.psd
Binary file not shown.
10
src/cycle.js
10
src/cycle.js
|
|
@ -30,8 +30,12 @@ export class CycleManager {
|
|||
this.cycleText = this.scene.add.text(
|
||||
40,
|
||||
40,
|
||||
'Day',
|
||||
{ fontSize: '24px', fill: '#ffffff' }
|
||||
'Time of Day: Day',
|
||||
{
|
||||
fontSize: '36px',
|
||||
fill: '#ffffff',
|
||||
fontFamily: 'eraserDust, arial'
|
||||
}
|
||||
).setShadow(3,3, '#333', 5).setScrollFactor(0);
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +57,7 @@ export class CycleManager {
|
|||
|
||||
// Update display text
|
||||
if (this.cycleText) {
|
||||
this.cycleText.setText(this.currentCycle.charAt(0).toUpperCase() + this.currentCycle.slice(1));
|
||||
this.cycleText.setText('Time of Day: ' + this.currentCycle.charAt(0).toUpperCase() + this.currentCycle.slice(1));
|
||||
}
|
||||
|
||||
// Apply tint to the terrain layer
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
export class InventoryManager {
|
||||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.isOpen = false;
|
||||
this.inventory = {};
|
||||
this.backpackIcon = null;
|
||||
this.inventoryUI = null;
|
||||
this.closeButton = null;
|
||||
}
|
||||
|
||||
init() {
|
||||
const cam = this.scene.cameras.main;
|
||||
this.backpackIcon = this.scene.physics.add.image(cam.worldView.width-150, cam.worldView.height-150, 'backpack')
|
||||
.setOrigin(0.5)
|
||||
.setScale(.5)
|
||||
.setScrollFactor(0)
|
||||
.setDepth(99);
|
||||
|
||||
// Create inventory UI (hidden initially)
|
||||
this.createInventoryUI();
|
||||
|
||||
// Add click event to toggle inventory
|
||||
this.backpackIcon.setInteractive();
|
||||
this.backpackIcon.on('pointerdown', () => {
|
||||
console.log('hit');
|
||||
this.toggleInventory();
|
||||
});
|
||||
}
|
||||
|
||||
createInventoryUI() {
|
||||
const scene = this.scene;
|
||||
|
||||
// Create inventory background
|
||||
this.inventoryUI = scene.add.rectangle(
|
||||
scene.game.config.width / 2,
|
||||
scene.game.config.height / 2,
|
||||
400,
|
||||
300,
|
||||
0x2F4F4F
|
||||
).setOrigin(0.5).setAlpha(0.9).setScrollFactor(0).setDepth(150);
|
||||
|
||||
// Create close button
|
||||
this.closeButton = scene.add.rectangle(
|
||||
scene.game.config.width / 2 + 180,
|
||||
scene.game.config.height / 2 - 130,
|
||||
30,
|
||||
30,
|
||||
0xFF4444
|
||||
).setOrigin(0.5).setScrollFactor(0).setDepth(150);
|
||||
|
||||
// Add X to close button
|
||||
this.closeButtonText = scene.add.text(
|
||||
scene.game.config.width / 2 + 180,
|
||||
scene.game.config.height / 2 - 130,
|
||||
'X',
|
||||
{ fontSize: '24px', fill: '#FFFFFF' }
|
||||
).setOrigin(0.5).setScrollFactor(0).setDepth(150);
|
||||
|
||||
// Hide inventory UI initially
|
||||
this.inventoryUI.setVisible(false);
|
||||
this.closeButton.setVisible(false);
|
||||
this.closeButtonText.setVisible(false);
|
||||
|
||||
// Add click event to close button
|
||||
this.closeButton.setInteractive();
|
||||
this.closeButton.on('pointerdown', () => {
|
||||
this.toggleInventory();
|
||||
});
|
||||
}
|
||||
|
||||
toggleInventory() {
|
||||
this.isOpen = !this.isOpen;
|
||||
|
||||
if (this.isOpen) {
|
||||
this.scene.player.stop();
|
||||
this.scene.player.canMove = false;
|
||||
// Show inventory UI and close button
|
||||
this.inventoryUI.setVisible(true);
|
||||
this.closeButton.setVisible(true);
|
||||
this.closeButtonText.setVisible(true);
|
||||
} else {
|
||||
// Hide inventory UI and close button
|
||||
this.inventoryUI.setVisible(false);
|
||||
this.closeButton.setVisible(false);
|
||||
this.closeButtonText.setVisible(false);
|
||||
this.scene.time.delayedCall(200, () => {
|
||||
this.scene.player.canMove = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addItem(item) {
|
||||
const type = item.props.type;
|
||||
const amount = item.props.amount;
|
||||
if (type in this.inventory) {
|
||||
this.inventory[type] += amount;
|
||||
} else {
|
||||
this.inventory[type] = amount;
|
||||
}
|
||||
this.scene.tweens.add({
|
||||
targets: this.backpackIcon,
|
||||
angle: 360,
|
||||
delay: 500,
|
||||
duration: 500
|
||||
});
|
||||
this.scene.tweens.add({
|
||||
targets: this.backpackIcon,
|
||||
scale: 0.8,
|
||||
delay: 500,
|
||||
yoyo: true,
|
||||
ease: 'Bounce',
|
||||
duration: 500
|
||||
});
|
||||
}
|
||||
|
||||
removeItem(item) {
|
||||
const index = this.inventory.indexOf(item);
|
||||
if (index > -1) {
|
||||
this.inventory.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
update(delta) {
|
||||
// Inventory update logic can go here
|
||||
}
|
||||
}
|
||||
192
src/objects.js
192
src/objects.js
|
|
@ -2,6 +2,7 @@ export class ObjectManager {
|
|||
constructor(scene) {
|
||||
this.scene = scene;
|
||||
this.objects = [];
|
||||
this.healthBars = new Map(); // Store references to health bars for cleanup
|
||||
}
|
||||
|
||||
init() {
|
||||
|
|
@ -37,58 +38,182 @@ export class ObjectManager {
|
|||
const palmTree = this.scene.physics.add.sprite(x, y, 'objects', 0);
|
||||
this.scene.objects.add(palmTree);
|
||||
palmTree.setImmovable(true).setSize(60,100);
|
||||
palmTree.props = {
|
||||
type: 'palmTree',
|
||||
health: 100,
|
||||
fullHealth: 100,
|
||||
yield: 'wood',
|
||||
amount: [0,1],
|
||||
doneSprite: 10
|
||||
};
|
||||
this.createHealthBar(palmTree);
|
||||
} else if (key === 'forestTree' && rand <= objectType[key]) {
|
||||
const forestRand = Phaser.Math.Between(1,4);
|
||||
const forestTree = this.scene.physics.add.sprite(x, y, 'objects', forestRand);
|
||||
this.scene.objects.add(forestTree);
|
||||
forestTree.setImmovable(true).setSize(50,100);
|
||||
forestTree.props = {
|
||||
type: 'forestTree',
|
||||
health: 200,
|
||||
fullHealth: 200,
|
||||
yield: 'wood',
|
||||
amount: [0,3],
|
||||
doneSprite: 11
|
||||
};
|
||||
this.createHealthBar(forestTree);
|
||||
} else if (key === 'boulder' && rand <= objectType[key]) {
|
||||
const boulder = this.scene.physics.add.sprite(x, y, 'objects', 20);
|
||||
this.scene.objects.add(boulder);
|
||||
boulder.setImmovable(true);
|
||||
boulder.props = {
|
||||
type: 'boulder',
|
||||
health: 200,
|
||||
fullHealth: 200,
|
||||
yield: 'rock',
|
||||
amount: [1,3],
|
||||
doneSprite: null
|
||||
};
|
||||
this.createHealthBar(boulder);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
createEnemy(x, y, properties) {
|
||||
// Placeholder for enemy creation logic
|
||||
console.log(`Creating enemy at (${x}, ${y}) with properties:`, properties);
|
||||
createHealthBar(object) {
|
||||
// Create a graphics object for the health bar
|
||||
const healthBar = this.scene.add.graphics();
|
||||
|
||||
// Example implementation:
|
||||
// const enemy = this.scene.physics.add.sprite(
|
||||
// x * tileWidth + tileWidth / 2,
|
||||
// y * tileHeight + tileHeight / 2,
|
||||
// 'enemy-sprite'
|
||||
// );
|
||||
// this.objects.push(enemy);
|
||||
// Position the health bar above the object (adjust offset as needed)
|
||||
const offsetX = 0;
|
||||
const offsetY = -50; // Adjust to position above the object
|
||||
|
||||
// Store reference to health bar for updates and cleanup
|
||||
object.healthBar = healthBar;
|
||||
|
||||
// Add to our map for cleanup
|
||||
this.healthBars.set(object, healthBar);
|
||||
|
||||
// Draw initial health bar (will be updated in update loop)
|
||||
this.updateHealthBar(object, true);
|
||||
}
|
||||
|
||||
createCollectible(x, y, properties) {
|
||||
// Placeholder for collectible creation logic
|
||||
console.log(`Creating collectible at (${x}, ${y}) with properties:`, properties);
|
||||
updateHealthBar(object, justCreated) {
|
||||
if (!object.healthBar || !object.props) return;
|
||||
|
||||
// Example implementation:
|
||||
// const collectible = this.scene.physics.add.sprite(
|
||||
// x * tileWidth + tileWidth / 2,
|
||||
// y * tileHeight + tileHeight / 2,
|
||||
// 'collectible-sprite'
|
||||
// );
|
||||
// this.objects.push(collectible);
|
||||
const health = object.props.health;
|
||||
const fullHealth = object.props.fullHealth;
|
||||
|
||||
// Calculate health percentage
|
||||
const healthPercentage = Math.max(0, Math.min(1, health / fullHealth));
|
||||
|
||||
// Clear previous graphics
|
||||
object.healthBar.clear();
|
||||
|
||||
// Health bar dimensions
|
||||
const barWidth = 60;
|
||||
const barHeight = 8;
|
||||
const offsetX = -barWidth / 2; // Center the bar on object
|
||||
const offsetY = -50; // Position above object
|
||||
|
||||
// Draw background (red) - full health bar
|
||||
object.healthBar.fillStyle(0xff0000, 1);
|
||||
object.healthBar.fillRect(object.x + offsetX, object.y + offsetY, barWidth, barHeight).setAlpha(1);
|
||||
|
||||
// Draw health fill (green) - current health
|
||||
const greenWidth = barWidth * healthPercentage;
|
||||
if (greenWidth > 0) {
|
||||
object.healthBar.fillStyle(0x00ff00, 1);
|
||||
object.healthBar.fillRect(object.x + offsetX, object.y + offsetY, greenWidth, barHeight).setAlpha(1);
|
||||
}
|
||||
if (justCreated) {
|
||||
object.healthBar.setAlpha(0);
|
||||
}
|
||||
|
||||
if (object.alpha > 0) {
|
||||
this.scene.tweens.add({
|
||||
targets: object.healthBar,
|
||||
alpha: 0,
|
||||
delay: 10000
|
||||
});
|
||||
}
|
||||
|
||||
if (healthPercentage <= 0) {
|
||||
object.healthBar.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
createPlatform(x, y, properties) {
|
||||
// Placeholder for platform creation logic
|
||||
console.log(`Creating platform at (${x}, ${y}) with properties:`, properties);
|
||||
takeDamage(object, player) {
|
||||
const dmg = Phaser.Math.Between(player.dmgMin,player.dmgMax);
|
||||
if (player.targetX === object.x && player.targetY === object.y) {
|
||||
switch (object.props.type){
|
||||
case 'palmTree':
|
||||
object.props.health -= dmg;
|
||||
break;
|
||||
case 'forestTree':
|
||||
object.props.health -= dmg;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
this.updateHealthBar(object);
|
||||
if (object.props.health <= 0) {
|
||||
this.destroyObject(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example implementation:
|
||||
// const platform = this.scene.physics.add.sprite(
|
||||
// x * tileWidth + tileWidth / 2,
|
||||
// y * tileHeight + tileHeight / 2,
|
||||
// 'platform-sprite'
|
||||
// );
|
||||
// platform.setImmovable(true);
|
||||
// this.objects.push(platform);
|
||||
destroyObject(object) {
|
||||
// Replace object with doneSprite
|
||||
if (object.props.doneSprite !== null) {
|
||||
const doneSprite = this.scene.physics.add.sprite(object.x, object.y, 'objects', object.props.doneSprite);
|
||||
|
||||
}
|
||||
|
||||
// Drop Resources
|
||||
if (object.props.yield) {
|
||||
const amount = Phaser.Math.Between(object.props.amount[0], object.props.amount[1]);
|
||||
if (amount > 0) {
|
||||
let sprite = null;
|
||||
switch (object.props.yield) {
|
||||
case 'wood':
|
||||
if (amount === 3) {
|
||||
sprite = 14;
|
||||
} else if (amount === 2) {
|
||||
sprite = 13;
|
||||
} else {
|
||||
sprite = 12;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
const drop = this.scene.physics.add.sprite(object.x-5, object.y-50, 'objects', sprite).setScale(.8);
|
||||
this.scene.tweens.add({
|
||||
targets: drop,
|
||||
scale: 1,
|
||||
y: object.y,
|
||||
x: object.x,
|
||||
ease: 'Bounce',
|
||||
duration: 800,
|
||||
onComplete: () => {
|
||||
drop.postFX.addGlow(0x00ffc6);
|
||||
this.scene.items.add(drop);
|
||||
drop.props = {
|
||||
type: object.props.yield,
|
||||
amount: amount
|
||||
};
|
||||
drop.setInteractive();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from scene and cleanup
|
||||
object.destroy();
|
||||
|
||||
// Remove health bar reference
|
||||
if (object.healthBar && this.healthBars.has(object)) {
|
||||
this.healthBars.delete(object);
|
||||
}
|
||||
}
|
||||
|
||||
update(delta) {
|
||||
|
|
@ -97,6 +222,11 @@ export class ObjectManager {
|
|||
if (object.update) {
|
||||
object.update(delta);
|
||||
}
|
||||
|
||||
// Update health bars for objects that have them
|
||||
if (object.healthBar && object.props) {
|
||||
this.updateHealthBar(object);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -7,16 +7,23 @@ export class Player extends Phaser.GameObjects.Sprite {
|
|||
scene.physics.world.enable(this);
|
||||
this.body.setCollideWorldBounds(true);
|
||||
this.body.setSize(50,100);
|
||||
this.setDepth(100);
|
||||
|
||||
// Set player properties
|
||||
this.speed = 200;
|
||||
this.isMoving = false;
|
||||
this.targetX = x;
|
||||
this.targetY = y;
|
||||
this.dmgMin = 5;
|
||||
this.dmgMax = 25;
|
||||
this.inventory = {};
|
||||
this.canMove = true;
|
||||
|
||||
// Add input listener for mouse clicks
|
||||
scene.input.on('pointerdown', (pointer) => {
|
||||
this.moveToPoint(pointer.worldX, pointer.worldY);
|
||||
if (this.canMove) {
|
||||
this.moveToPoint(pointer.worldX, pointer.worldY);
|
||||
}
|
||||
});
|
||||
|
||||
// Create animations
|
||||
|
|
@ -56,15 +63,15 @@ export class Player extends Phaser.GameObjects.Sprite {
|
|||
|
||||
moveToPoint(x, y) {
|
||||
// Set target position
|
||||
this.targetX = x;
|
||||
this.targetY = y;
|
||||
this.targetX = Math.floor(x / 100) * 100 + 50;
|
||||
this.targetY = Math.floor(y / 100) * 100 + 50;
|
||||
|
||||
// Calculate direction to target
|
||||
const dx = x - this.x;
|
||||
const dy = y - this.y;
|
||||
const dx = this.targetX - this.x;
|
||||
const dy = this.targetY - this.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance > 100) { // Only move if target is far enough
|
||||
if (distance > 50) { // Only move if target is far enough
|
||||
this.isMoving = true;
|
||||
|
||||
// Set velocity towards target
|
||||
|
|
@ -142,7 +149,7 @@ export class Player extends Phaser.GameObjects.Sprite {
|
|||
|
||||
// Method to stop player movement
|
||||
stop() {
|
||||
this.setVelocity(0, 0);
|
||||
this.body.setVelocity(0, 0);
|
||||
this.isMoving = false;
|
||||
|
||||
// Play idle animation when stopping
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Player } from '../player.js';
|
||||
import { CycleManager } from '../cycle.js';
|
||||
import { ObjectManager } from '../objects.js';
|
||||
import { InventoryManager } from '../inventory.js';
|
||||
|
||||
export class Game extends Phaser.Scene {
|
||||
constructor() {
|
||||
|
|
@ -11,22 +12,20 @@ export class Game extends Phaser.Scene {
|
|||
}
|
||||
|
||||
preload() {
|
||||
// Load player sprite
|
||||
this.load.tilemapTiledJSON('game-map', 'assets/game-map.json');
|
||||
this.load.spritesheet('player', 'assets/images/player.png', {
|
||||
frameWidth: 100,
|
||||
frameHeight: 100
|
||||
});
|
||||
|
||||
// Load objects sprite
|
||||
this.load.spritesheet('objects', 'assets/images/objects.png', {
|
||||
frameWidth: 100,
|
||||
frameHeight: 100
|
||||
});
|
||||
|
||||
// Load tilemap
|
||||
this.load.tilemapTiledJSON('game-map', 'assets/game-map.json');
|
||||
// Load tileset image
|
||||
this.load.image('terrain-tileset', 'assets/images/terrain.png');
|
||||
this.load.image('backpack', 'assets/images/backpack.png');
|
||||
|
||||
this.load.font('eraserDust', 'assets/fonts/EraserDust.ttf');
|
||||
}
|
||||
|
||||
create() {
|
||||
|
|
@ -47,15 +46,41 @@ export class Game extends Phaser.Scene {
|
|||
|
||||
// Initialize object manager
|
||||
this.objects = this.physics.add.group();
|
||||
this.items = this.physics.add.group();
|
||||
this.objectManager = new ObjectManager(this);
|
||||
this.objectManager.init();
|
||||
|
||||
// Create player at center of screen
|
||||
this.player = new Player(this, 1600, 3100);
|
||||
|
||||
// Initialize inventory manager
|
||||
this.inventoryManager = new InventoryManager(this);
|
||||
this.inventoryManager.init();
|
||||
|
||||
// Physics Collisions
|
||||
this.physics.add.collider(this.player, this.mainLayer);
|
||||
this.physics.add.collider(this.player, this.objects);
|
||||
this.physics.add.collider(this.player, this.objects, (player, object) => {
|
||||
player.stop();
|
||||
this.objectManager.takeDamage(object, player);
|
||||
});
|
||||
this.physics.add.collider(this.player, this.items, (player, item) => {
|
||||
if (item.props.collected === true) {
|
||||
return;
|
||||
}
|
||||
item.props.collected = true;
|
||||
player.stop();
|
||||
this.inventoryManager.addItem(item);
|
||||
const cam = this.cameras.main.worldView;
|
||||
this.tweens.add({
|
||||
targets: item,
|
||||
x: cam.x+cam.width-150,
|
||||
y: cam.y+cam.height-150,
|
||||
duration: 500,
|
||||
onComplete: () => {
|
||||
item.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Make camera follow the player
|
||||
this.cameras.main.startFollow(this.player);
|
||||
|
|
@ -86,5 +111,9 @@ export class Game extends Phaser.Scene {
|
|||
if (this.objectManager) {
|
||||
this.objectManager.update(delta);
|
||||
}
|
||||
|
||||
if (this.inventoryManager) {
|
||||
this.inventoryManager.update(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue