From 7eac11fe2ce31329004a2aaf69939a5f23979bc9 Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Sat, 29 Nov 2025 23:04:24 +0700 Subject: [PATCH 1/9] Super Mario --- entities/FireFlower.py | 0 entities/Mario.py | 1 + levels/Level1-3.json | 71 ++++++++++++++++++++++++++++++++++++++++++ main.py | 13 ++++++++ requirements.txt | 5 +-- 5 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 entities/FireFlower.py create mode 100644 levels/Level1-3.json diff --git a/entities/FireFlower.py b/entities/FireFlower.py new file mode 100644 index 00000000..e69de29b diff --git a/entities/Mario.py b/entities/Mario.py index 8321284c..567d5764 100644 --- a/entities/Mario.py +++ b/entities/Mario.py @@ -58,6 +58,7 @@ def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8): self.restart = False self.pause = False self.pauseObj = Pause(screen, self, dashboard) + self.lives = 3 def update(self): if self.invincibilityFrames > 0: diff --git a/levels/Level1-3.json b/levels/Level1-3.json new file mode 100644 index 00000000..5e179a64 --- /dev/null +++ b/levels/Level1-3.json @@ -0,0 +1,71 @@ +{ + "id": 1, + "length": 80, + "level": { + "objects": { + "bush": [ + [2, 12], [17, 12], [24, 12], + [34, 12], [40, 12], [44, 12], + [60, 12] + ], + "sky": [ + [48, 13], [49, 13], + [48, 14], [49, 14] + ], + "cloud": [ + [5, 5], [13, 3], [26, 5], + [32, 3], [42, 6], [55, 4], + [65, 3], [70, 5] + ], + "pipe": [ + [8, 10, 4], [12, 12, 4], [22, 12, 4], + [29, 9, 6], [45, 10, 5], [62, 12, 4] + ], + "ground": [ + [40, 9], [41, 9], [42, 9], [43, 9], + [44, 9], [45, 9], [46, 9], [47, 9], + [50, 9], [51, 9], [52, 9], [53, 9], + [54, 9], [55, 9], [54, 8], [55, 8], + [56, 8], [57, 8], [58, 8], [59, 8], + [60, 10], [61, 10], [62, 10], [63, 10], + [70, 8], [71, 8], [72, 8], + + [38, 9], [39, 9] + ] + }, + + "layers": { + "sky": { "x": [0, 80], "y": [0, 13] }, + "ground": { "x": [0, 80], "y": [14, 16] } + }, + + "entities": { + "CoinBox": [ + [3, 8], [15, 7], [35, 6], [52, 5], [70, 4] + ], + "coinBrick": [ + [37, 9], [4, 8], [20, 8], [21, 8], [22, 8] + ], + "coin": [ + [18, 7], [19, 7], [20, 7], [21, 7], + [30, 6], [31, 6], [32, 6], + [55, 9], [56, 9], [57, 9], + [75, 6], [76, 6], [77, 6], [78, 6] + ], + "Goomba": [ + [10, 13], [18, 13], [25, 13], + [30, 13], [43, 13], [50, 13], + [68, 13] + ], + "Koopa": [ + [12, 13], [28, 13], [45, 13], + [62, 13], [72, 13] + ], + "RandomBox": [ + [5, 8, "RedMushroom"], + [5, 4, "FireFlower"] + + ] + } + } +} diff --git a/main.py b/main.py index 9a2aba34..e25f7b6f 100644 --- a/main.py +++ b/main.py @@ -42,3 +42,16 @@ def main(): exitmessage = 'restart' while exitmessage == 'restart': exitmessage = main() + +font = pygame.font.SysFont("Arial", 26) + +def draw_hud(screen, dashboard, level): + lives_text = font.render(f"Lives: {level.mario.lives}", True, (255, 255, 255)) + score_text = font.render(f"Score: {dashboard.points}", True, (255, 255, 0)) + coin_text = font.render(f"Coins: {dashboard.coins}", True, (255, 255, 0)) + level_text = font.render(f"Level: {level.world}", True, (0, 255, 255)) + + screen.blit(lives_text, (20, 15)) + screen.blit(score_text, (20, 45)) + screen.blit(coin_text, (20, 75)) + screen.blit(level_text, (20, 105)) diff --git a/requirements.txt b/requirements.txt index 30c14272..7301819e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -pygame==2.0.0.dev10 -scipy==1.4.1 +pygame>=2.5.0 +scipy>=1.10.0 +numpy>=1.21.0 \ No newline at end of file From 797fccea47c5a656727b61558227945dd4166f88 Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Sat, 29 Nov 2025 23:16:11 +0700 Subject: [PATCH 2/9] Add lives system and HUD display --- classes/Dashboard.py | 3 +++ entities/Mario.py | 16 ++++++++++++++++ main.py | 12 ++++++++++++ 3 files changed, 31 insertions(+) diff --git a/classes/Dashboard.py b/classes/Dashboard.py index aeb36e76..98aee170 100644 --- a/classes/Dashboard.py +++ b/classes/Dashboard.py @@ -15,6 +15,9 @@ def __init__(self, filePath, size, screen): self.time = 0 def update(self): + lives_text = self.font.render(f"LIVES {self.level.mario.lives}", True, (255, 255, 255)) + self.screen.blit(lives_text, (500, 10)) + self.drawText("MARIO", 50, 20, 15) self.drawText(self.pointString(), 50, 37, 15) diff --git a/entities/Mario.py b/entities/Mario.py index 567d5764..11c207d4 100644 --- a/entities/Mario.py +++ b/entities/Mario.py @@ -37,6 +37,8 @@ class Mario(EntityBase): def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8): super(Mario, self).__init__(x, y, gravity) + self.lives = 3 + self.dead = False self.camera = Camera(self.rect, self) self.sound = sound self.input = Input(self) @@ -187,3 +189,17 @@ def powerup(self, powerupID): self.traits['goTrait'].updateAnimation(bigAnimation) self.rect = pygame.Rect(self.rect.x, self.rect.y-32, 32, 64) self.invincibilityFrames = 20 +def die(self): + if not self.dead: + self.dead = True + self.lives -= 1 + self.sound.stomp() + + if self.lives <= 0: + self.level.restart = True + else: + self.respawn() # buat function respawn di bawah +def respawn(self): + self.rect.x = 0 + self.rect.y = 0 + self.dead = False diff --git a/main.py b/main.py index e25f7b6f..a5f62a9f 100644 --- a/main.py +++ b/main.py @@ -18,6 +18,7 @@ def main(): sound = Sound() level = Level(screen, sound, dashboard) menu = Menu(screen, dashboard, level, sound) + lives = 3 while not menu.start: menu.update() @@ -25,6 +26,13 @@ def main(): mario = Mario(0, 0, level, screen, dashboard, sound) clock = pygame.time.Clock() +if player_died_condition: + lives -= 1 + if lives <= 0: + game_over = True + else: + reset_player_position() # atau respawn + while not mario.restart: pygame.display.set_caption("Super Mario running with {:d} FPS".format(int(clock.get_fps()))) if mario.pause: @@ -55,3 +63,7 @@ def draw_hud(screen, dashboard, level): screen.blit(score_text, (20, 45)) screen.blit(coin_text, (20, 75)) screen.blit(level_text, (20, 105)) + + +text = font.render(f"Lives: {lives}", True, (255,255,255)) +screen.blit(text, (10, 10)) From 8aab11b78a6c99ed38cacd7175d435650529a633 Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Sat, 29 Nov 2025 23:29:40 +0700 Subject: [PATCH 3/9] Fix main.py: correct Mario initialization and Dashboard link --- classes/Dashboard.py | 4 ++-- main.py | 43 +++++++++++++++++-------------------------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/classes/Dashboard.py b/classes/Dashboard.py index 98aee170..d20d7601 100644 --- a/classes/Dashboard.py +++ b/classes/Dashboard.py @@ -13,10 +13,10 @@ def __init__(self, filePath, size, screen): self.coins = 0 self.ticks = 0 self.time = 0 + self.font = pygame.font.Font(None, 32) def update(self): - lives_text = self.font.render(f"LIVES {self.level.mario.lives}", True, (255, 255, 255)) - self.screen.blit(lives_text, (500, 10)) + self.drawText("MARIO", 50, 20, 15) self.drawText(self.pointString(), 50, 37, 15) diff --git a/main.py b/main.py index a5f62a9f..d1849ee7 100644 --- a/main.py +++ b/main.py @@ -14,35 +14,43 @@ def main(): pygame.init() screen = pygame.display.set_mode(windowSize) max_frame_rate = 60 + + # Buat dashboard, sound, level, menu dashboard = Dashboard("./img/font.png", 8, screen) sound = Sound() level = Level(screen, sound, dashboard) menu = Menu(screen, dashboard, level, sound) - lives = 3 + # Tunggu menu start while not menu.start: menu.update() + # Buat Mario dulu mario = Mario(0, 0, level, screen, dashboard, sound) - clock = pygame.time.Clock() -if player_died_condition: - lives -= 1 - if lives <= 0: - game_over = True - else: - reset_player_position() # atau respawn + # Hubungkan Mario ke level dan dashboard + level.mario = mario + dashboard.mario = mario + dashboard.level = level + + clock = pygame.time.Clock() + # Loop utama game while not mario.restart: - pygame.display.set_caption("Super Mario running with {:d} FPS".format(int(clock.get_fps()))) + pygame.display.set_caption( + "Super Mario running with {:d} FPS".format(int(clock.get_fps())) + ) + if mario.pause: mario.pauseObj.update() else: level.drawLevel(mario.camera) dashboard.update() mario.update() + pygame.display.update() clock.tick(max_frame_rate) + return 'restart' @@ -50,20 +58,3 @@ def main(): exitmessage = 'restart' while exitmessage == 'restart': exitmessage = main() - -font = pygame.font.SysFont("Arial", 26) - -def draw_hud(screen, dashboard, level): - lives_text = font.render(f"Lives: {level.mario.lives}", True, (255, 255, 255)) - score_text = font.render(f"Score: {dashboard.points}", True, (255, 255, 0)) - coin_text = font.render(f"Coins: {dashboard.coins}", True, (255, 255, 0)) - level_text = font.render(f"Level: {level.world}", True, (0, 255, 255)) - - screen.blit(lives_text, (20, 15)) - screen.blit(score_text, (20, 45)) - screen.blit(coin_text, (20, 75)) - screen.blit(level_text, (20, 105)) - - -text = font.render(f"Lives: {lives}", True, (255,255,255)) -screen.blit(text, (10, 10)) From 3cc4bb1952f8c832f412838da02d840b7a160a34 Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Thu, 4 Dec 2025 23:11:27 +0700 Subject: [PATCH 4/9] update progres terbaru --- TATALETAK.txt | 50 +++++++++++++++++++++ classes/Dashboard.py | 6 ++- classes/Level.py | 5 ++- entities/FireFlower.py | 17 ++++++++ entities/Flower.py | 10 +++++ entities/Mario.py | 97 ++++++++++++++++++----------------------- sprites/Animations.json | 9 ++++ 7 files changed, 138 insertions(+), 56 deletions(-) create mode 100644 TATALETAK.txt create mode 100644 entities/Flower.py diff --git a/TATALETAK.txt b/TATALETAK.txt new file mode 100644 index 00000000..4ed1b68d --- /dev/null +++ b/TATALETAK.txt @@ -0,0 +1,50 @@ +super-mario-python/ # root project +│ +├── README.md # dokumentasi dasar: cara running, dependensi, struktur +├── requirements.txt # daftar dependensi (pygame, etc) +├── .gitignore # file / folder yang di-ignore git (misalnya env, __pycache__, build, dsb) +├── setup.py (opsional) # jika kamu ingin struktur paket / distribusi +│ +├── src/ # kode utama sebagai package +│ ├── __main__.py # entry-point ketika menjalankan `python -m src` atau `python src/` +│ ├── game/ # modul utama game +│ │ ├── __init__.py +│ │ ├── main.py # game loop, init, state switching +│ │ ├── config.py # konfigurasi global (screen size, FPS, tile size, dsb) +│ │ ├── state_manager.py # jika kamu mengelola berbagai state: menu, playing, pause, game over +│ │ +│ ├── entities/ # kelas / definisi objek (player, enemy, item, dsb) +│ │ ├── __init__.py +│ │ ├── player.py +│ │ ├── enemy.py +│ │ └── ... +│ │ +│ ├── levels/ # definisi level / data level / loader level +│ │ ├── level1.py (atau level1.json / txt) +│ │ └── ... +│ │ +│ ├── rendering/ # kode berkaitan dengan gambar / drawing / camera / viewport +│ │ ├── __init__.py +│ │ ├── renderer.py +│ │ └── camera.py +│ │ +│ ├── utils/ # utilitas umum: helper functions, collision, math, dsb +│ │ ├── __init__.py +│ │ └── helper.py +│ │ +│ └── audio/ # pengaturan musik / sound effects +│ ├── __init__.py +│ └── sound_manager.py +│ +├── assets/ # semua resource: sprites, images, sounds, levels data +│ ├── sprites/ # sprite sheet, karakter, musuh, animasi +│ ├── images/ # background, tiles, UI, dsb +│ ├── sounds/ # efek suara, musik latar +│ └── levels/ # data level (tile map, layout, dsb) jika format external +│ +├── tests/ # (opsional tapi disarankan) unit / integration tests +│ ├── __init__.py +│ └── test_*.py +│ +└── docs/ # dokumentasi tambahan: design doc, panduan kontribusi, dsb + └── ... diff --git a/classes/Dashboard.py b/classes/Dashboard.py index d20d7601..41bc9765 100644 --- a/classes/Dashboard.py +++ b/classes/Dashboard.py @@ -14,9 +14,13 @@ def __init__(self, filePath, size, screen): self.ticks = 0 self.time = 0 self.font = pygame.font.Font(None, 32) + self.font = pygame.font.Font(None, 32) + self.mario = None def update(self): - + if self.mario: + lives_text = self.font.render(f"LIVES {self.mario.lives}", True, (255, 255, 255)) + self.screen.blit(lives_text, (20, 15)) self.drawText("MARIO", 50, 20, 15) self.drawText(self.pointString(), 50, 37, 15) diff --git a/classes/Level.py b/classes/Level.py index 9283f6e3..3b43c49a 100644 --- a/classes/Level.py +++ b/classes/Level.py @@ -10,7 +10,7 @@ from entities.Koopa import Koopa from entities.CoinBox import CoinBox from entities.RandomBox import RandomBox - +from entities.FireFlower import FireFlower class Level: def __init__(self, screen, sound, dashboard): @@ -203,3 +203,6 @@ def addRedMushroom(self, x, y): self.entityList.append( RedMushroom(self.screen, self.sprites.spriteCollection, x, y, self, self.sound) ) + + flower = FireFlower(300, 350) + self.entityList.append(flower) diff --git a/entities/FireFlower.py b/entities/FireFlower.py index e69de29b..86b57638 100644 --- a/entities/FireFlower.py +++ b/entities/FireFlower.py @@ -0,0 +1,17 @@ +import pygame +from entities.EntityBase import EntityBase +from classes.Sprites import Sprites + +spriteCollection = Sprites().spriteCollection + +class FireFlower(EntityBase): + def __init__(self, x, y): + super().__init__(x, y, gravity=0) # tidak jatuh + self.type = "Item" + self.powerType = "fire" + # pakai sprite yang sudah kamu tambahkan di JSON tadi + self.image = spriteCollection["flower"].image + self.rect = self.image.get_rect(topleft=(x, y)) + + def update(self): + pass diff --git a/entities/Flower.py b/entities/Flower.py new file mode 100644 index 00000000..3bc457a0 --- /dev/null +++ b/entities/Flower.py @@ -0,0 +1,10 @@ +import pygame + +class Flower(pygame.sprite.Sprite): + def __init__(self, x, y, image): + super().__init__() + self.image = image + self.rect = self.image.get_rect(topleft=(x, y)) + + def update(self): + pass diff --git a/entities/Mario.py b/entities/Mario.py index 11c207d4..cb66fd55 100644 --- a/entities/Mario.py +++ b/entities/Mario.py @@ -38,6 +38,7 @@ class Mario(EntityBase): def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8): super(Mario, self).__init__(x, y, gravity) self.lives = 3 + self.powerup = None # None, "fire", "mushroom" self.dead = False self.camera = Camera(self.rect, self) self.sound = sound @@ -60,7 +61,6 @@ def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8): self.restart = False self.pause = False self.pauseObj = Pause(screen, self, dashboard) - self.lives = 3 def update(self): if self.invincibilityFrames > 0: @@ -79,7 +79,7 @@ def moveMario(self): self.collision.checkX() def checkEntityCollision(self): - for ent in self.levelObj.entityList: + for ent in self.levelObj.entityList[:]: collisionState = self.EntityCollider.check(ent) if collisionState.isColliding: if ent.type == "Item": @@ -91,9 +91,15 @@ def checkEntityCollision(self): def _onCollisionWithItem(self, item): self.levelObj.entityList.remove(item) - self.dashboard.points += 100 - self.dashboard.coins += 1 - self.sound.play_sfx(self.sound.coin) + if item.type == "fire": + self.get_powerup("fire") + elif item.type == "mushroom": + self.get_powerup("mushroom") + else: + # koin + self.dashboard.points += 100 + self.dashboard.coins += 1 + self.sound.play_sfx(self.sound.coin) def _onCollisionWithBlock(self, block): if not block.triggered: @@ -103,7 +109,7 @@ def _onCollisionWithBlock(self, block): def _onCollisionWithMob(self, mob, collisionState): if isinstance(mob, RedMushroom) and mob.alive: - self.powerup(1) + self.get_powerup("mushroom") self.killEntity(mob) self.sound.play_sfx(self.sound.powerup) elif collisionState.isTop and (mob.alive or mob.bouncing): @@ -111,30 +117,14 @@ def _onCollisionWithMob(self, mob, collisionState): self.rect.bottom = mob.rect.top self.bounce() self.killEntity(mob) - elif collisionState.isTop and mob.alive and not mob.active: - self.sound.play_sfx(self.sound.stomp) - self.rect.bottom = mob.rect.top - mob.timer = 0 - self.bounce() - mob.alive = False - elif collisionState.isColliding and mob.alive and not mob.active and not mob.bouncing: - mob.bouncing = True - if mob.rect.x < self.rect.x: - mob.leftrightTrait.direction = -1 - mob.rect.x += -5 - self.sound.play_sfx(self.sound.kick) - else: - mob.rect.x += 5 - mob.leftrightTrait.direction = 1 - self.sound.play_sfx(self.sound.kick) elif collisionState.isColliding and mob.alive and not self.invincibilityFrames: if self.powerUpState == 0: self.gameOver() elif self.powerUpState == 1: self.powerUpState = 0 self.traits['goTrait'].updateAnimation(smallAnimation) - x, y = self.rect.x, self.rect.y - self.rect = pygame.Rect(x, y + 32, 32, 32) + bottom = self.rect.bottom + self.rect = pygame.Rect(self.rect.x, bottom - 32, 32, 32) self.invincibilityFrames = 60 self.sound.play_sfx(self.sound.pipe) @@ -142,14 +132,7 @@ def bounce(self): self.traits["bounceTrait"].jump = True def killEntity(self, ent): - if ent.__class__.__name__ != "Koopa": - ent.alive = False - else: - ent.timer = 0 - ent.leftrightTrait.speed = 1 - ent.alive = True - ent.active = False - ent.bouncing = False + ent.alive = False self.dashboard.points += 100 def gameOver(self): @@ -181,25 +164,31 @@ def getPos(self): def setPos(self, x, y): self.rect.x = x self.rect.y = y - - def powerup(self, powerupID): - if self.powerUpState == 0: - if powerupID == 1: - self.powerUpState = 1 - self.traits['goTrait'].updateAnimation(bigAnimation) - self.rect = pygame.Rect(self.rect.x, self.rect.y-32, 32, 64) - self.invincibilityFrames = 20 -def die(self): - if not self.dead: - self.dead = True - self.lives -= 1 - self.sound.stomp() - - if self.lives <= 0: - self.level.restart = True - else: - self.respawn() # buat function respawn di bawah -def respawn(self): - self.rect.x = 0 - self.rect.y = 0 - self.dead = False + + def get_powerup(self, powerup_type): + bottom = self.rect.bottom + if powerup_type == "fire": + self.powerup = "fire" + self.load_fire_sprite() + elif powerup_type == "mushroom": + self.powerup = "mushroom" + # pakai animasi big + self.traits['goTrait'].updateAnimation(bigAnimation) + self.powerUpState = 1 + self.rect = pygame.Rect(self.rect.x, bottom - 64, 32, 64) + self.invincibilityFrames = 20 + self.sound.play_sfx(self.sound.powerup) + + def die(self): + if not self.dead: + self.dead = True + self.lives -= 1 + self.sound.stomp() + if self.lives <= 0: + self.level.restart = True + else: + self.respawn() + + def respawn(self): + self.rect.x, self.rect.y = 0, 0 # atau checkpoint + self.dead = False diff --git a/sprites/Animations.json b/sprites/Animations.json index bbe769fa..d95f09ab 100644 --- a/sprites/Animations.json +++ b/sprites/Animations.json @@ -45,6 +45,15 @@ ], "deltaTime":10, "colorKey": -1 + }, + { + "name": "flower", + "images": [ + { "x": 1, "y": 3, "scale": 2 }, + { "x": 2, "y": 3, "scale": 2 }, + { "x": 3, "y": 3, "scale": 2 } + ], + "colorKey": -1 } ] } From 073df949b5fa0506b8fb2f295a255071eae740bf Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Fri, 5 Dec 2025 23:03:37 +0700 Subject: [PATCH 5/9] fix: perbaikan spawn FireFlower di atas blok --- TATALETAK.txt | 2 +- classes/Dashboard.py | 2 +- classes/Level.py | 10 ++++++++-- classes/Sprites.py | 2 +- entities/CoinBox.py | 9 ++++++++- entities/FireFlower.py | 30 +++++++++++++++++------------- entities/Mario.py | 35 +++++++++++++++++++++++++++++++---- entities/RandomBox.py | 18 ++++++++++++++++-- levels/Level1-3.json | 8 ++++---- sprites/Animations.json | 9 --------- sprites/ItemAnimations.json | 10 ++++++++++ 11 files changed, 97 insertions(+), 38 deletions(-) diff --git a/TATALETAK.txt b/TATALETAK.txt index 4ed1b68d..4f9b02b5 100644 --- a/TATALETAK.txt +++ b/TATALETAK.txt @@ -1,4 +1,4 @@ -super-mario-python/ # root project +├──super-mario-python/ # root project │ ├── README.md # dokumentasi dasar: cara running, dependensi, struktur ├── requirements.txt # daftar dependensi (pygame, etc) diff --git a/classes/Dashboard.py b/classes/Dashboard.py index 41bc9765..2753c7d4 100644 --- a/classes/Dashboard.py +++ b/classes/Dashboard.py @@ -20,7 +20,7 @@ def __init__(self, filePath, size, screen): def update(self): if self.mario: lives_text = self.font.render(f"LIVES {self.mario.lives}", True, (255, 255, 255)) - self.screen.blit(lives_text, (20, 15)) + self.screen.blit(lives_text, (0, 0)) self.drawText("MARIO", 50, 20, 15) self.drawText(self.pointString(), 50, 37, 15) diff --git a/classes/Level.py b/classes/Level.py index 3b43c49a..6d47c0c7 100644 --- a/classes/Level.py +++ b/classes/Level.py @@ -38,6 +38,7 @@ def loadEntities(self, data): [self.addCoin(x, y) for x, y in data["level"]["entities"]["coin"]] [self.addCoinBrick(x, y) for x, y in data["level"]["entities"]["coinBrick"]] [self.addRandomBox(x, y, item) for x, y, item in data["level"]["entities"]["RandomBox"]] + [self.addFireFlower(x, y) for x, y in data["level"]["entities"].get("FireFlower", [])] except: # if no entities in Level pass @@ -203,6 +204,11 @@ def addRedMushroom(self, x, y): self.entityList.append( RedMushroom(self.screen, self.sprites.spriteCollection, x, y, self, self.sound) ) + def addFireFlower(self, x, y): + from entities.FireFlower import FireFlower + flower = FireFlower(self.screen, self.sprites.spriteCollection, x, y, self, self.sound) + self.entityList.append(flower) # wajib append ke entityList + + + - flower = FireFlower(300, 350) - self.entityList.append(flower) diff --git a/classes/Sprites.py b/classes/Sprites.py index 9e48020c..23cdb1a2 100644 --- a/classes/Sprites.py +++ b/classes/Sprites.py @@ -15,7 +15,7 @@ def __init__(self): "./sprites/Animations.json", "./sprites/BackgroundSprites.json", "./sprites/ItemAnimations.json", - "./sprites/RedMushroom.json" + "./sprites/RedMushroom.json", ] ) diff --git a/entities/CoinBox.py b/entities/CoinBox.py index e35d2b82..1ca76ba7 100644 --- a/entities/CoinBox.py +++ b/entities/CoinBox.py @@ -24,7 +24,14 @@ def update(self, cam): self.animation.update() else: self.animation.image = self.spriteCollection.get("empty").image - self.item.spawnCoin(cam, self.sound, self.dashboard) + # Jika CoinBox mengandung FireFlower + if hasattr(self, "contains") and self.contains == "fireflower": + from entities.Item import Item + fireflower = Item("FireFlower", self.rect.x, self.rect.y - 32) + self.screen.game.addEntity(fireflower) + else: + self.item.spawnCoin(cam, self.sound, self.dashboard) + if self.time < self.maxTime: self.time += 1 self.rect.y -= self.vel diff --git a/entities/FireFlower.py b/entities/FireFlower.py index 86b57638..5b8c5768 100644 --- a/entities/FireFlower.py +++ b/entities/FireFlower.py @@ -1,17 +1,21 @@ -import pygame from entities.EntityBase import EntityBase -from classes.Sprites import Sprites - -spriteCollection = Sprites().spriteCollection class FireFlower(EntityBase): - def __init__(self, x, y): - super().__init__(x, y, gravity=0) # tidak jatuh - self.type = "Item" - self.powerType = "fire" - # pakai sprite yang sudah kamu tambahkan di JSON tadi - self.image = spriteCollection["flower"].image - self.rect = self.image.get_rect(topleft=(x, y)) + def __init__(self, screen, spriteCollection, x, y, level, sound, gravity=0): + super(FireFlower, self).__init__(x, y + 32, gravity) # mulai di dalam blok + self.screen = screen + self.spriteCollection = spriteCollection + self.animation = self.spriteCollection.get("FireFlower").animation + self.level = level + self.sound = sound + self.alive = True + self.target_y = y # posisi akhir bunga + self.vel = 2 # kecepatan naik - def update(self): - pass + def update(self, cam): + if self.alive: + # animasi naik ke target_y + if self.rect.y > self.target_y: + self.rect.y -= self.vel + self.animation.update() + self.screen.blit(self.animation.image, (self.rect.x + cam.x, self.rect.y)) diff --git a/entities/Mario.py b/entities/Mario.py index cb66fd55..37d120fd 100644 --- a/entities/Mario.py +++ b/entities/Mario.py @@ -37,6 +37,9 @@ class Mario(EntityBase): def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8): super(Mario, self).__init__(x, y, gravity) + # Simpan posisi spawn awal + self.spawn_x = x + self.spawn_y = y self.lives = 3 self.powerup = None # None, "fire", "mushroom" self.dead = False @@ -71,6 +74,11 @@ def update(self): self.applyGravity() self.checkEntityCollision() self.input.checkForInput() + # Update checkpoint posisi spawn berdasarkan kamera & posisi Mario + # Update checkpoint posisi spawn berdasarkan posisi Mario di dunia (bukan kamera) + self.spawn_x = self.rect.x + self.spawn_y = self.rect.y + def moveMario(self): self.rect.y += self.vel.y @@ -119,7 +127,7 @@ def _onCollisionWithMob(self, mob, collisionState): self.killEntity(mob) elif collisionState.isColliding and mob.alive and not self.invincibilityFrames: if self.powerUpState == 0: - self.gameOver() + self.die() elif self.powerUpState == 1: self.powerUpState = 0 self.traits['goTrait'].updateAnimation(smallAnimation) @@ -183,12 +191,31 @@ def die(self): if not self.dead: self.dead = True self.lives -= 1 - self.sound.stomp() + self.sound.play_sfx(self.sound.stomp) + if self.lives <= 0: - self.level.restart = True + self.gameOver() # tampilkan animasi game over else: + self.invincibilityFrames = 60 self.respawn() + def respawn(self): - self.rect.x, self.rect.y = 0, 0 # atau checkpoint + # Posisikan Mario di checkpoint + self.rect.x = self.spawn_x - 250 + self.rect.y = self.spawn_y - 500 + + self.vel.x = 0 + self.vel.y = 0 + + # Kamera ikut respawn dengan benar + self.camera.x = self.spawn_x - 200 # sedikit ke belakang Mario biar kelihatan + if self.camera.x < 0: + self.camera.x = 0 + + self.camera.target_rect = self.rect self.dead = False + + + + diff --git a/entities/RandomBox.py b/entities/RandomBox.py index 86dfa85f..24265486 100644 --- a/entities/RandomBox.py +++ b/entities/RandomBox.py @@ -1,8 +1,6 @@ from copy import copy - from entities.EntityBase import EntityBase - class RandomBox(EntityBase): def __init__(self, screen, spriteCollection, x, y, item, sound, dashboard, level, gravity=0): super(RandomBox, self).__init__(x, y, gravity) @@ -19,15 +17,30 @@ def __init__(self, screen, spriteCollection, x, y, item, sound, dashboard, level self.item = item self.level = level + # simpan posisi asli blok untuk spawn item + self.original_x = x + 1 + self.original_y = y + 1 + def update(self, cam): if self.alive and not self.triggered: self.animation.update() else: self.animation.image = self.spriteCollection.get("empty").image + if self.item == 'RedMushroom': self.level.addRedMushroom(self.rect.y // 32 - 1, self.rect.x // 32) self.sound.play_sfx(self.sound.powerup_appear) + + elif self.item == 'FireFlower': + # spawn bunga 1 tile di atas blok + flower_x = self.rect.x + flower_y = self.rect.y - 32 # target_y + self.level.addFireFlower(flower_x, flower_y) + self.sound.play_sfx(self.sound.powerup_appear) + + self.item = None + if self.time < self.maxTime: self.time += 1 self.rect.y -= self.vel @@ -35,6 +48,7 @@ def update(self, cam): if self.time < self.maxTime * 2: self.time += 1 self.rect.y += self.vel + self.screen.blit( self.spriteCollection.get("sky").image, (self.rect.x + cam.x, self.rect.y + 2), diff --git a/levels/Level1-3.json b/levels/Level1-3.json index 5e179a64..4066f635 100644 --- a/levels/Level1-3.json +++ b/levels/Level1-3.json @@ -62,10 +62,10 @@ [62, 13], [72, 13] ], "RandomBox": [ - [5, 8, "RedMushroom"], - [5, 4, "FireFlower"] - - ] + [5, 8, "RedMushroom"], + [5, 4, "FireFlower"], + [3, 4, "FireFlower"] + ] } } } diff --git a/sprites/Animations.json b/sprites/Animations.json index d95f09ab..bbe769fa 100644 --- a/sprites/Animations.json +++ b/sprites/Animations.json @@ -45,15 +45,6 @@ ], "deltaTime":10, "colorKey": -1 - }, - { - "name": "flower", - "images": [ - { "x": 1, "y": 3, "scale": 2 }, - { "x": 2, "y": 3, "scale": 2 }, - { "x": 3, "y": 3, "scale": 2 } - ], - "colorKey": -1 } ] } diff --git a/sprites/ItemAnimations.json b/sprites/ItemAnimations.json index aae82a91..4ea5b93c 100644 --- a/sprites/ItemAnimations.json +++ b/sprites/ItemAnimations.json @@ -28,6 +28,16 @@ ], "deltaTime":10, "colorKey": -1 + }, + { + "name": "FireFlower", + "images": [ + { "x": 1, "y": 2, "scale": 2 }, + { "x": 2, "y": 2, "scale": 2 }, + { "x": 3, "y": 2, "scale": 2 } + ], + "deltaTime": 10, + "colorKey": -1 } ] } From 7eebdd0a2c292f2490f61630204a248116ef07a4 Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Fri, 5 Dec 2025 23:12:21 +0700 Subject: [PATCH 6/9] fix: perbaikan spawn FireFlower di atas blok --- entities/FireFlower.py | 40 ++++++++++++++++++++++++++-------------- entities/RandomBox.py | 6 +----- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/entities/FireFlower.py b/entities/FireFlower.py index 5b8c5768..9c34d6b2 100644 --- a/entities/FireFlower.py +++ b/entities/FireFlower.py @@ -1,21 +1,33 @@ from entities.EntityBase import EntityBase +from classes.Collider import Collider +from classes.EntityCollider import EntityCollider +from classes.Animation import Animation +from entities.EntityBase import EntityBase +from classes.Maths import Vec2D class FireFlower(EntityBase): - def __init__(self, screen, spriteCollection, x, y, level, sound, gravity=0): - super(FireFlower, self).__init__(x, y + 32, gravity) # mulai di dalam blok - self.screen = screen - self.spriteCollection = spriteCollection + def __init__(self, screen, spriteColl, x, y, level, sound): + super(FireFlower, self).__init__(y, x - 1, 0) # sama seperti jamur, tapi gravity 0 + self.spriteCollection = spriteColl self.animation = self.spriteCollection.get("FireFlower").animation - self.level = level + self.screen = screen + self.collision = Collider(self, level) + self.EntityCollider = EntityCollider(self) + self.levelObj = level + self.type = "Mob" + self.dashboard = level.dashboard self.sound = sound - self.alive = True - self.target_y = y # posisi akhir bunga - self.vel = 2 # kecepatan naik - def update(self, cam): + def update(self, camera): if self.alive: - # animasi naik ke target_y - if self.rect.y > self.target_y: - self.rect.y -= self.vel - self.animation.update() - self.screen.blit(self.animation.image, (self.rect.x + cam.x, self.rect.y)) + self.drawFireFlower(camera) + else: + self.alive = None + + def drawFireFlower(self, camera): + self.screen.blit(self.animation.image, (self.rect.x + camera.x, self.rect.y)) + self.animation.update() + + + + \ No newline at end of file diff --git a/entities/RandomBox.py b/entities/RandomBox.py index 24265486..c2f4893b 100644 --- a/entities/RandomBox.py +++ b/entities/RandomBox.py @@ -32,13 +32,9 @@ def update(self, cam): self.sound.play_sfx(self.sound.powerup_appear) elif self.item == 'FireFlower': - # spawn bunga 1 tile di atas blok - flower_x = self.rect.x - flower_y = self.rect.y - 32 # target_y - self.level.addFireFlower(flower_x, flower_y) + self.level.addFireFlower(self.rect.y // 32 - 1, self.rect.x // 32) self.sound.play_sfx(self.sound.powerup_appear) - self.item = None if self.time < self.maxTime: From 4ce146174e4212082acb57b7bc24eec91fb4c59c Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Wed, 10 Dec 2025 16:51:08 +0700 Subject: [PATCH 7/9] fix: perbaikan spawn FireFlower di atas blok --- entities/FireFlower.py | 2 +- levels/Level1-3.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/entities/FireFlower.py b/entities/FireFlower.py index 9c34d6b2..a2e44977 100644 --- a/entities/FireFlower.py +++ b/entities/FireFlower.py @@ -7,7 +7,7 @@ class FireFlower(EntityBase): def __init__(self, screen, spriteColl, x, y, level, sound): - super(FireFlower, self).__init__(y, x - 1, 0) # sama seperti jamur, tapi gravity 0 + super(FireFlower, self).__init__(y, x , 0) self.spriteCollection = spriteColl self.animation = self.spriteCollection.get("FireFlower").animation self.screen = screen diff --git a/levels/Level1-3.json b/levels/Level1-3.json index 4066f635..d28b3f32 100644 --- a/levels/Level1-3.json +++ b/levels/Level1-3.json @@ -63,8 +63,7 @@ ], "RandomBox": [ [5, 8, "RedMushroom"], - [5, 4, "FireFlower"], - [3, 4, "FireFlower"] + [5, 4, "FireFlower"] ] } } From 8b789a2c0791977d0057e36065656eaaf1ea890b Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Wed, 10 Dec 2025 18:06:42 +0700 Subject: [PATCH 8/9] update progres terbaru --- classes/Sprites.py | 1 + entities/FireFlower.py | 2 +- entities/Mario.py | 42 +++++++++++++++++++++---- sprites/FireFlower.json | 17 ++++++++++ sprites/ItemAnimations.json | 10 ------ sprites/Mario.json | 62 ++++++++++++++++++++++++++++++++++++- 6 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 sprites/FireFlower.json diff --git a/classes/Sprites.py b/classes/Sprites.py index 23cdb1a2..3e4cff27 100644 --- a/classes/Sprites.py +++ b/classes/Sprites.py @@ -16,6 +16,7 @@ def __init__(self): "./sprites/BackgroundSprites.json", "./sprites/ItemAnimations.json", "./sprites/RedMushroom.json", + "./sprites/FireFlower.json", ] ) diff --git a/entities/FireFlower.py b/entities/FireFlower.py index a2e44977..ea2e060b 100644 --- a/entities/FireFlower.py +++ b/entities/FireFlower.py @@ -14,7 +14,7 @@ def __init__(self, screen, spriteColl, x, y, level, sound): self.collision = Collider(self, level) self.EntityCollider = EntityCollider(self) self.levelObj = level - self.type = "Mob" + self.type = "item" self.dashboard = level.dashboard self.sound = sound diff --git a/entities/Mario.py b/entities/Mario.py index 37d120fd..72a2d557 100644 --- a/entities/Mario.py +++ b/entities/Mario.py @@ -32,6 +32,15 @@ spriteCollection["mario_big_idle"].image, spriteCollection["mario_big_jump"].image, ) +putihAnimation = Animation( + [ + spriteCollection["mario_putih_run1"].image, + spriteCollection["mario_putih_run2"].image, + spriteCollection["mario_putih_run3"].image, + ], + spriteCollection["mario_putih_idle"].image, + spriteCollection["mario_putih_jump"].image, +) class Mario(EntityBase): @@ -103,6 +112,8 @@ def _onCollisionWithItem(self, item): self.get_powerup("fire") elif item.type == "mushroom": self.get_powerup("mushroom") + elif item.type == "FireFlower": + self.get_powerup("FireFlower") else: # koin self.dashboard.points += 100 @@ -175,17 +186,36 @@ def setPos(self, x, y): def get_powerup(self, powerup_type): bottom = self.rect.bottom + + # Power-up Fire (Mario besar putih) if powerup_type == "fire": self.powerup = "fire" self.load_fire_sprite() + self.traits['goTrait'].updateAnimation(bigAnimation) + self.powerUpState = 1 + self.rect = pygame.Rect(self.rect.x, bottom - 64, 32, 64) + self.invincibilityFrames = 20 + self.sound.play_sfx(self.sound.powerup) + + # Power-up Mushroom (mario kecil → besar) elif powerup_type == "mushroom": self.powerup = "mushroom" - # pakai animasi big - self.traits['goTrait'].updateAnimation(bigAnimation) - self.powerUpState = 1 - self.rect = pygame.Rect(self.rect.x, bottom - 64, 32, 64) - self.invincibilityFrames = 20 - self.sound.play_sfx(self.sound.powerup) + self.traits['goTrait'].updateAnimation(bigAnimation) + self.powerUpState = 1 + self.rect = pygame.Rect(self.rect.x, bottom - 64, 32, 64) + self.invincibilityFrames = 20 + self.sound.play_sfx(self.sound.powerup) + + # Power-up FireFlower (mario besar → fire) + elif powerup_type == "FireFlower": + self.powerup = "FireFlower" + self.load_fire_sprite() + self.traits['goTrait'].updateAnimation(putihAnimation) + self.powerUpState = 1 + self.rect = pygame.Rect(self.rect.x, bottom - 64, 32, 64) + self.invincibilityFrames = 20 + self.sound.play_sfx(self.sound.powerup) + def die(self): if not self.dead: diff --git a/sprites/FireFlower.json b/sprites/FireFlower.json new file mode 100644 index 00000000..db09569e --- /dev/null +++ b/sprites/FireFlower.json @@ -0,0 +1,17 @@ +{ + "spriteSheetURL": "./img/Items.png", + "type":"animation", + "sprites":[ + { + "name": "FireFlower", + "images": [ + { "x": 1, "y": 2, "scale": 2 }, + { "x": 2, "y": 2, "scale": 2 }, + { "x": 3, "y": 2, "scale": 2 } + ], + "deltaTime": 10, + "colorKey": -1 + } + ] +} + diff --git a/sprites/ItemAnimations.json b/sprites/ItemAnimations.json index 4ea5b93c..aae82a91 100644 --- a/sprites/ItemAnimations.json +++ b/sprites/ItemAnimations.json @@ -28,16 +28,6 @@ ], "deltaTime":10, "colorKey": -1 - }, - { - "name": "FireFlower", - "images": [ - { "x": 1, "y": 2, "scale": 2 }, - { "x": 2, "y": 2, "scale": 2 }, - { "x": 3, "y": 2, "scale": 2 } - ], - "deltaTime": 10, - "colorKey": -1 } ] } diff --git a/sprites/Mario.json b/sprites/Mario.json index a9fe7e09..2430dc7f 100644 --- a/sprites/Mario.json +++ b/sprites/Mario.json @@ -119,7 +119,67 @@ "collision":false, "xsize":16, "ysize":32 + }, + { + "name":"mario_putih_idle", + "x":259, + "y":122, + "scalefactor":2, + "colorKey":-1, + "collision":false, + "xsize":16, + "ysize":32 + }, + { + "name":"mario_putih_run1", + "x":296, + "y":122, + "scalefactor":2, + "colorKey":-1, + "collision":false, + "xsize":16, + "ysize":32 + }, + { + "name":"mario_putih_run2", + "x":315, + "y":122, + "scalefactor":2, + "colorKey":-1, + "collision":false, + "xsize":16, + "ysize":32 + }, + { + "name":"mario_putih_run3", + "x":332, + "y":122, + "scalefactor":2, + "colorKey":-1, + "collision":false, + "xsize":16, + "ysize":32 + }, + { + "name":"mario_putih_jump", + "x":369, + "y":122, + "scalefactor":2, + "colorKey":-1, + "collision":false, + "xsize":16, + "ysize":32 + }, + { + "name":"mario_putih_break", + "x":369, + "y":122, + "scalefactor":2, + "colorKey":-1, + "collision":false, + "xsize":16, + "ysize":32 } - ] + ] } From 361f86a65061de19966f134adbc73a563721cdd0 Mon Sep 17 00:00:00 2001 From: SuluhAuliaArifin Date: Fri, 12 Dec 2025 18:02:27 +0700 Subject: [PATCH 9/9] update progres terbaru --- entities/Mario.py | 203 +++++++++++++++++++++++++++------------------- 1 file changed, 120 insertions(+), 83 deletions(-) diff --git a/entities/Mario.py b/entities/Mario.py index 72a2d557..8740facd 100644 --- a/entities/Mario.py +++ b/entities/Mario.py @@ -13,7 +13,11 @@ from traits.jump import JumpTrait from classes.Pause import Pause +# ------------------------------------------------- +# ANIMASI GLOBAL +# ------------------------------------------------- spriteCollection = Sprites().spriteCollection + smallAnimation = Animation( [ spriteCollection["mario_run1"].image, @@ -23,6 +27,7 @@ spriteCollection["mario_idle"].image, spriteCollection["mario_jump"].image, ) + bigAnimation = Animation( [ spriteCollection["mario_big_run1"].image, @@ -32,6 +37,7 @@ spriteCollection["mario_big_idle"].image, spriteCollection["mario_big_jump"].image, ) + putihAnimation = Animation( [ spriteCollection["mario_putih_run1"].image, @@ -43,22 +49,33 @@ ) +# ============================================================ +# M A R I O C L A S S +# ============================================================ + class Mario(EntityBase): + def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8): super(Mario, self).__init__(x, y, gravity) - # Simpan posisi spawn awal + + # Spawn checkpoint self.spawn_x = x self.spawn_y = y + self.lives = 3 - self.powerup = None # None, "fire", "mushroom" + self.powerup = None self.dead = False + self.camera = Camera(self.rect, self) self.sound = sound self.input = Input(self) + self.inAir = False self.inJump = False self.powerUpState = 0 self.invincibilityFrames = 0 + + # Traits self.traits = { "jumpTrait": JumpTrait(self), "goTrait": GoTrait(smallAnimation, screen, self.camera, self), @@ -67,86 +84,137 @@ def __init__(self, x, y, level, screen, dashboard, sound, gravity=0.8): self.levelObj = level self.collision = Collider(self, level) - self.screen = screen self.EntityCollider = EntityCollider(self) + + self.screen = screen self.dashboard = dashboard self.restart = False + self.pause = False self.pauseObj = Pause(screen, self, dashboard) + # ---------------------------------------------------------- + # UPDATE + # ---------------------------------------------------------- def update(self): if self.invincibilityFrames > 0: self.invincibilityFrames -= 1 + self.updateTraits() self.moveMario() self.camera.move() self.applyGravity() self.checkEntityCollision() self.input.checkForInput() - # Update checkpoint posisi spawn berdasarkan kamera & posisi Mario - # Update checkpoint posisi spawn berdasarkan posisi Mario di dunia (bukan kamera) + + # Save checkpoint self.spawn_x = self.rect.x self.spawn_y = self.rect.y - + # ---------------------------------------------------------- + # MOVEMENT + # ---------------------------------------------------------- def moveMario(self): self.rect.y += self.vel.y self.collision.checkY() + self.rect.x += self.vel.x self.collision.checkX() + # ---------------------------------------------------------- + # ENTITY COLLISION + # ---------------------------------------------------------- def checkEntityCollision(self): for ent in self.levelObj.entityList[:]: - collisionState = self.EntityCollider.check(ent) - if collisionState.isColliding: - if ent.type == "Item": - self._onCollisionWithItem(ent) - elif ent.type == "Block": - self._onCollisionWithBlock(ent) - elif ent.type == "Mob": - self._onCollisionWithMob(ent, collisionState) + state = self.EntityCollider.check(ent) + + if not state.isColliding: + continue + + if ent.type.lower() == "item": + self._onCollisionWithItem(ent) + elif ent.type == "Block": + self._onCollisionWithBlock(ent) + + elif ent.type == "Mob": + self._onCollisionWithMob(ent, state) + + # ---------------------------------------------------------- + # ITEM COLLISION + # ---------------------------------------------------------- def _onCollisionWithItem(self, item): - self.levelObj.entityList.remove(item) - if item.type == "fire": - self.get_powerup("fire") - elif item.type == "mushroom": + + # Hindari item diambil 2x + if hasattr(item, "taken") and item.taken: + return + item.taken = True + + item.alive = False + + # mainkan suara (jika ada) + try: + item.sound.itemSound.play() + except: + pass + + cls = item.__class__.__name__ + + # FIRE FLOWER → Mario Putih + if cls == "FireFlower": + self.get_powerup("fireflower") + return + + # RED MUSHROOM + if cls == "RedMushroom": self.get_powerup("mushroom") - elif item.type == "FireFlower": - self.get_powerup("FireFlower") - else: - # koin + return + + # COIN + if cls == "Coin": self.dashboard.points += 100 self.dashboard.coins += 1 self.sound.play_sfx(self.sound.coin) + return + # ---------------------------------------------------------- + # BLOCK COLLISION + # ---------------------------------------------------------- def _onCollisionWithBlock(self, block): if not block.triggered: self.dashboard.coins += 1 self.sound.play_sfx(self.sound.bump) block.triggered = True - def _onCollisionWithMob(self, mob, collisionState): + # ---------------------------------------------------------- + # MOB COLLISION + # ---------------------------------------------------------- + def _onCollisionWithMob(self, mob, state): if isinstance(mob, RedMushroom) and mob.alive: self.get_powerup("mushroom") self.killEntity(mob) self.sound.play_sfx(self.sound.powerup) - elif collisionState.isTop and (mob.alive or mob.bouncing): + + elif state.isTop and (mob.alive or mob.bouncing): self.sound.play_sfx(self.sound.stomp) self.rect.bottom = mob.rect.top self.bounce() self.killEntity(mob) - elif collisionState.isColliding and mob.alive and not self.invincibilityFrames: + + elif state.isColliding and mob.alive and not self.invincibilityFrames: if self.powerUpState == 0: self.die() - elif self.powerUpState == 1: + else: self.powerUpState = 0 self.traits['goTrait'].updateAnimation(smallAnimation) + bottom = self.rect.bottom self.rect = pygame.Rect(self.rect.x, bottom - 32, 32, 32) + self.invincibilityFrames = 60 self.sound.play_sfx(self.sound.pipe) + # ---------------------------------------------------------- def bounce(self): self.traits["bounceTrait"].jump = True @@ -154,69 +222,36 @@ def killEntity(self, ent): ent.alive = False self.dashboard.points += 100 - def gameOver(self): - srf = pygame.Surface((640, 480)) - srf.set_colorkey((255, 255, 255), pygame.RLEACCEL) - srf.set_alpha(128) - self.sound.music_channel.stop() - self.sound.music_channel.play(self.sound.death) - - for i in range(500, 20, -2): - srf.fill((0, 0, 0)) - pygame.draw.circle( - srf, - (255, 255, 255), - (int(self.camera.x + self.rect.x) + 16, self.rect.y + 16), - i, - ) - self.screen.blit(srf, (0, 0)) - pygame.display.update() - self.input.checkForInput() - while self.sound.music_channel.get_busy(): - pygame.display.update() - self.input.checkForInput() - self.restart = True - - def getPos(self): - return self.camera.x + self.rect.x, self.rect.y - - def setPos(self, x, y): - self.rect.x = x - self.rect.y = y - + # ---------------------------------------------------------- + # POWER UP SYSTEM + # ---------------------------------------------------------- def get_powerup(self, powerup_type): + powerup_type = powerup_type.lower() bottom = self.rect.bottom - # Power-up Fire (Mario besar putih) - if powerup_type == "fire": - self.powerup = "fire" - self.load_fire_sprite() - self.traits['goTrait'].updateAnimation(bigAnimation) - self.powerUpState = 1 - self.rect = pygame.Rect(self.rect.x, bottom - 64, 32, 64) - self.invincibilityFrames = 20 - self.sound.play_sfx(self.sound.powerup) - - # Power-up Mushroom (mario kecil → besar) - elif powerup_type == "mushroom": + # Jamur → Mario Besar + if powerup_type == "mushroom": self.powerup = "mushroom" self.traits['goTrait'].updateAnimation(bigAnimation) self.powerUpState = 1 + self.rect = pygame.Rect(self.rect.x, bottom - 64, 32, 64) self.invincibilityFrames = 20 self.sound.play_sfx(self.sound.powerup) - # Power-up FireFlower (mario besar → fire) - elif powerup_type == "FireFlower": - self.powerup = "FireFlower" - self.load_fire_sprite() + # Fire Flower → Mario Putih + elif powerup_type in ("fireflower", "fire"): + self.powerup = "fireflower" self.traits['goTrait'].updateAnimation(putihAnimation) self.powerUpState = 1 + self.rect = pygame.Rect(self.rect.x, bottom - 64, 32, 64) self.invincibilityFrames = 20 self.sound.play_sfx(self.sound.powerup) - + # ---------------------------------------------------------- + # LIFE & DEATH + # ---------------------------------------------------------- def die(self): if not self.dead: self.dead = True @@ -224,28 +259,30 @@ def die(self): self.sound.play_sfx(self.sound.stomp) if self.lives <= 0: - self.gameOver() # tampilkan animasi game over + self.gameOver() else: self.invincibilityFrames = 60 self.respawn() - + # ---------------------------------------------------------- def respawn(self): - # Posisikan Mario di checkpoint self.rect.x = self.spawn_x - 250 self.rect.y = self.spawn_y - 500 self.vel.x = 0 self.vel.y = 0 - # Kamera ikut respawn dengan benar - self.camera.x = self.spawn_x - 200 # sedikit ke belakang Mario biar kelihatan - if self.camera.x < 0: - self.camera.x = 0 - + self.camera.x = max(self.spawn_x - 200, 0) self.camera.target_rect = self.rect - self.dead = False - + self.dead = False + # ---------------------------------------------------------- + # POSITION UTILITY + # ---------------------------------------------------------- + def getPos(self): + return self.camera.x + self.rect.x, self.rect.y + def setPos(self, x, y): + self.rect.x = x + self.rect.y = y