12 Commits

Author SHA1 Message Date
Neal Finger
bcd3715a9e Merge branch 'master' into title_screen 2023-02-05 16:16:09 -07:00
Neal Finger
6c05beaba0 thing
desc
2023-02-05 16:14:45 -07:00
William Hubbard
77fd56b7b8 Merge branch 'master' into collectibles 2023-02-05 16:10:55 -07:00
William Hubbard
8aab7d94f1 Adds a sky. 2023-02-05 16:10:11 -07:00
Neal Finger
5acb279226 title screen
sprite, player root animation calm down
2023-02-05 15:59:55 -07:00
William Hubbard
51be611e2f Merge pull request #11 from Markil3/collectibles
Collectibles
2023-02-05 15:57:53 -07:00
William Hubbard
3afe5322b3 Adds a seed entity.
Graphics!
2023-02-05 15:57:11 -07:00
William Hubbard
674ae01057 Adds collectibles and fixes score 2023-02-05 15:50:20 -07:00
William Hubbard
feafdfbac2 Implements scaling difficulty.
Probably not balanced, but it is there.
2023-02-05 15:19:16 -07:00
Neal Finger
b743e61b10 Merge pull request #10 from Markil3/build_update_finalish
general update
2023-02-05 14:59:45 -07:00
Neal Finger
15d86a7391 general update
merge water, update game loop
2023-02-05 14:59:08 -07:00
Neal Finger
05d5734c66 Merge pull request #9 from Markil3/negative-scroll
Cleans up the scrolling code
2023-02-05 14:12:45 -07:00
7 changed files with 205 additions and 68 deletions

148
Throot.py
View File

@@ -7,12 +7,14 @@ import Games.Throot.map
import Games.Throot.sprites import Games.Throot.sprites
from random import randrange from random import randrange
from Games.Throot.artrepository import OBSTACLES
from Games.Throot.artrepository import *
from Games.Throot.player import Player, Camera from Games.Throot.player import Player, Camera
from Games.Throot.map import ObstacleSlice, OBSTACLE_SLICES from Games.Throot.map import ObstacleSlice, OBSTACLE_SLICES
from Games.Throot.sprites import Obstacle, update_entities from Games.Throot.sprites import Entity, Obstacle, Collectible, update_entities
from Games.Throot.constants import * from Games.Throot.constants import *
class GameManager: class GameManager:
def __init__(self, *rooms): def __init__(self, *rooms):
self.rooms = rooms self.rooms = rooms
@@ -44,6 +46,9 @@ class GameManager:
class Room: class Room:
def __init__(self):
self.ind_button = 0
def start(self, event): def start(self, event):
t0 = time.ticks_ms() # Get time (ms) t0 = time.ticks_ms() # Get time (ms)
thumby.display.fill(0) # Fill canvas to black thumby.display.fill(0) # Fill canvas to black
@@ -62,21 +67,40 @@ class RoomTitle(Room):
} }
# draw title sprite # draw title sprite
title_text = 'press a to start' #thumby.display.drawSprite(sprite_title.width, sprite_title.height, sprite_title.image)
chr_len = 5 thumby.display.blit(sprite_title.image, 0, 0, sprite_title.width, sprite_title.height,
x = int(round(thumby.display.width / 2 - (chr_len * len(title_text)) / 2)) -1, 0, 0)
y = int(round(thumby.display.height / 2 - chr_len / 2))
# title text
chrx = 5
chry = 7
buff = 2 buff = 2
w = chr_len * len(title_text) + buff
h = chr_len + buff x = 14#int(round(thumby.display.width / 2 - (chr_len * len(title_text)) / 2))
thumby.display.drawFilledRectangle(x - buff, y - buff, w, h, 1) y = 40 - chry - buff#int(round(thumby.display.height / 2 - chr_len / 2))
thumby.display.drawText(title_text, x, y, 0)
w = chrx * 6 + buff
h = chry + buff
thumby.display.drawFilledRectangle(x - buff, y - buff, w, h * 2 + buff, 0)
#thumby.display.drawText('press a', x, y, 0)
thumby.display.drawText('start', x, y, 1)
spr_b = thumby.Sprite(sprite_buttona1.width, sprite_buttona1.height, sprite_buttona1.image + sprite_buttona2.image, 2, 40 - 8,
-1, 0, 0)
# thumby.display.blit(sprite_buttona.image, 2, 40 - 8, sprite_buttona.width, sprite_buttona.height,
# -1, 0, 0)
self.ind_button = (self.ind_button + 1) % (2 * CONST_FPS)
spr_b.setFrame(self.ind_button // (2 * CONST_FPS))
#print(self.ind_button // CONST_FPS)
thumby.display.drawSprite(spr_b)
class GameLoop(Room): class GameLoop(Room):
def __init__(self): def __init__(self):
self.current_depth = 0
self.diving_velocity = 10 self.diving_velocity = 10
self.diving_accel = 0 self.diving_accel = 0
self.current_obstacle_slice = OBSTACLE_SLICES[0] self.current_obstacle_slice = OBSTACLE_SLICES[0]
@@ -86,18 +110,90 @@ class GameLoop(Room):
self.camera = None self.camera = None
self.score = 0 self.score = 0
self.level = 0
self.finish_depth = 0
self.waterx = float(0)
seed_sprite = thumby.Sprite(seed.width, seed.height, seed.image, 0, 0, 0, False, False)
self.seed = Entity((thumby.display.width - seed.width) // 2, 0, seed.width, seed.height, None, seed_sprite)
def start(self, event): def start(self, event):
self.current_depth = 0
self.entities.clear() self.entities.clear()
self.entities.add(
self.seed
)
self.current_obstacle_slice = OBSTACLE_SLICES[0] self.current_obstacle_slice = OBSTACLE_SLICES[0]
self.player = Player() self.player = Player()
self.player.yspeed = 10 self.player.yspeed = 10
self.score = 0 self.score = 'score' in event and event['score'] or 0
self.level = 'level' in event and event['level'] or 0
self.finish_depth = 100 + 5 * self.level ** 2
self.camera = Camera(self.player) self.camera = Camera(self.player)
def get_obstacle_slice(self): def get_obstacle_slice(self):
return OBSTACLE_SLICES[randrange(0, len(OBSTACLE_SLICES))] return OBSTACLE_SLICES[randrange(0, len(OBSTACLE_SLICES))]
def draw_sky(self):
sky_x = 0
sky_y = -thumby.display.height
sky_width = thumby.display.width
sky_height = thumby.display.height
if self.camera.entity_in_camera(sky_x, sky_y, sky_width, sky_height):
sky_x, sky_y = self.camera.relative_to_camera(sky_x, sky_y)
thumby.display.drawFilledRectangle(int(sky_x), int(sky_y), int(sky_width), int(sky_height), 1)
sky_x = 0
sky_y = 0
sky_width = thumby.display.width
sky_height = thumby.display.height
if self.camera.entity_in_camera(sky_x, sky_y, sky_width, sky_height):
sky_x, sky_y = self.camera.relative_to_camera(sky_x, sky_y)
thumby.display.drawFilledRectangle(int(sky_x), int(sky_y), int(sky_width), int(sky_height), 0)
def draw_water(self):
# bookwater
# exit if depth is less than 2 screens of the finish depth
if self.player.y + thumby.display.height < self.finish_depth:
return
self.waterx += sprite_water.width / CONST_FPS
if self.waterx > sprite_water.width:
self.waterx = 0
# water surface.
for i in range(math.ceil(thumby.display.width / sprite_water.width) + 1):
x, y = self.camera.relative_to_camera(
(int(self.waterx) - sprite_water.width) + i * sprite_water.width,
self.finish_depth
)
thumby.display.blit(
sprite_water[0],
int(x), int(y),
sprite_water.width, sprite_water.height,
0,
0, 0
)
# water below surface
x, y = self.camera.relative_to_camera(
0,
self.finish_depth
)
thumby.display.drawFilledRectangle(
int(x), int(y) + sprite_water.height,
thumby.display.width, self.finish_depth + thumby.display.height * 3,
1,
)
# win text
if self.player.y > self.finish_depth + (thumby.display.height // 2):
thumby.display.drawText('water reach!', 0, thumby.display.height // 4, 0)
thumby.display.drawText(f' stage {self.level + 1}', 0, thumby.display.height // 4 + 9, 0)
def update(self, tpf): def update(self, tpf):
""" """
@@ -110,7 +206,10 @@ class GameLoop(Room):
self.camera.update_phys() self.camera.update_phys()
# Generate new entities as needed # Generate new entities as needed
self.entities |= update_entities(self.entities, 5, self.player.ysub, self.player.prev_y) if abs(self.player.y) < abs(self.finish_depth):
self.entities |= update_entities(self.entities, self.level, self.player.ysub, self.player.prev_y)
else:
self.entities.clear()
# Updates the entities # Updates the entities
to_remove = set() to_remove = set()
@@ -121,20 +220,37 @@ class GameLoop(Room):
if isinstance(entity, Obstacle): if isinstance(entity, Obstacle):
return { return {
'room': ROOM_END, 'room': ROOM_END,
'score': self.score 'score': self.score,
'level': self.level + 1
} }
elif isinstance(entity, Collectible):
self.score += entity.score
to_remove.add(entity)
self.entities -= to_remove self.entities -= to_remove
self.score = self.player.x#int(abs(self.player.y) * 10) self.score += int(abs(self.player.ysub - self.player.prev_y) * 10)
# finish level
if self.player.y > self.finish_depth + int(thumby.display.height * 1.5):
self.score += 20
self.level += 1
self.start({
'score': self.score,
'level': self.level
})
thumby.display.fill(0) thumby.display.fill(0)
self.draw_sky()
# Draw the entities # Draw the entities
for entity in self.entities: for entity in self.entities:
entity.render(tpf, self.camera) entity.render(tpf, self.camera)
self.player.update_draw(self.camera) self.player.update_draw(self.camera)
self.draw_water()
thumby.display.drawText(str(self.score), thumby.display.width - (len(str(self.score)) + 2) * 5, 1, 1) thumby.display.drawText(str(self.score), thumby.display.width - (len(str(self.score)) + 2) * 5, 1, 1)
class EndRoom(Room): class EndRoom(Room):
def __init__(self): def __init__(self):
self.score = 0 self.score = 0

BIN
ThrootTitle.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
ThrootTitleSingleFrame.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -2,6 +2,27 @@ from collections import namedtuple
SpriteImage = namedtuple("SpriteImage", ["image", "collision", "width", "height"]) SpriteImage = namedtuple("SpriteImage", ["image", "collision", "width", "height"])
seed = SpriteImage(
bytearray([8,20,46,46,62,62,46,62,46,12,0]),
None,
11,
7
)
collectibleover = SpriteImage(
bytearray([9,4,6,9]),
None,
4,
4
)
collectibleunder = SpriteImage(
bytearray([6,11,9,6]),
None,
4,
4
)
# BITMAP: width: 8, height: 8 # BITMAP: width: 8, height: 8
skull = SpriteImage( skull = SpriteImage(
bytearray([0,28,102,126,38,30,28,0]), bytearray([0,28,102,126,38,30,28,0]),
@@ -175,4 +196,32 @@ Doritoobstaclelvl5 = SpriteImage(
30 30
) )
sprite_water = SpriteImage(
bytearray([224,240,240,248,252,248,240,240]),
None,
8,
8
)
sprite_title = SpriteImage(
bytearray([32,128,40,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,0,0,0,0,0,0,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,160,128,32,0,72,0,192,96,24,8,4,6,2,2,2,2,2,4,12,24,112,192,0,0,0,3,3,3,3,3,255,255,255,3,251,251,251,3,3,251,251,251,3,251,251,155,155,155,155,99,99,0,248,248,24,24,24,251,251,3,251,251,27,27,27,251,251,3,255,255,255,3,3,3,0,1,0,31,48,96,64,64,64,98,50,30,0,0,192,96,48,16,24,15,12,24,48,96,192,0,0,0,255,255,255,0,255,255,255,7,7,255,255,255,0,255,255,1,1,1,3,255,255,0,255,255,128,128,128,255,255,0,255,255,128,128,128,255,255,0,255,255,255,0,0,0,0,192,64,96,32,32,69,192,10,0,4,0,0,127,129,0,0,0,0,0,0,0,96,48,29,7,0,0,63,255,255,192,149,85,85,84,84,85,85,85,84,85,85,84,84,84,84,85,85,84,85,85,85,85,85,85,85,84,85,85,85,85,85,85,21,192,255,255,127,0,0,0,3,7,4,132,128,192,96,29,70,0,0,0,0,0,1,6,12,8,24,16,248,140,4,4,108,72,120,48,0,128,193,255,255,127,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,252,254,255,183,99,65,64,64,96,48,24,30,99,65,64,194,128,133,0,2,0,128,96,24,12,4,2,2,1,1]),
None,
72,
40
)
sprite_buttona1 = SpriteImage(
bytearray([60,98,221,203,203,221,98,60]),
None,
8,
8
)
sprite_buttona2 = SpriteImage(
bytearray([56,68,186,150,150,186,68,56]),
None,
8,
8
)
OBSTACLES = [skull, pizza, boulder, ball, steve, mathroot, leaf, robot, dinoskull, dinojaw, dinospine, bone, bigdinohead, bigboneddiag, bigdoritoobstacle, spaceinv,annoydead,lvl4wall,ballobstaclelvl3,Doritoobstaclelvl5] OBSTACLES = [skull, pizza, boulder, ball, steve, mathroot, leaf, robot, dinoskull, dinojaw, dinospine, bone, bigdinohead, bigboneddiag, bigdoritoobstacle, spaceinv,annoydead,lvl4wall,ballobstaclelvl3,Doritoobstaclelvl5]

View File

@@ -4,3 +4,4 @@ ROOM_START = 0
ROOM_MAIN = 1 ROOM_MAIN = 1
ROOM_END = 2 ROOM_END = 2
ROOM_NEXT = -1 ROOM_NEXT = -1

View File

@@ -33,7 +33,7 @@ class Player:
self.accaccx = self.accx self.accaccx = self.accx
self.anim = PlayerAnimation(self.x, self.y) self.anim = PlayerAnimation(self.x, self.y)
self.debugvisible = True self.debugvisible = False
def update_phys(self, tpf, camera: Camera): def update_phys(self, tpf, camera: Camera):
self.prev_x = self.xsub self.prev_x = self.xsub
@@ -103,8 +103,8 @@ class Camera:
class PlayerAnimation: class PlayerAnimation:
# width and height of range bounding box # width and height of range bounding box must be odd
RANDOM_RANGE = int(7) RANDOM_RANGE = int(5)
POS_RANGE = int(3) POS_RANGE = int(3)
POS_X = int(0) POS_X = int(0)
POS_Y = int(1) POS_Y = int(1)
@@ -115,26 +115,6 @@ class PlayerAnimation:
self.debugposrange = self.POS_RANGE self.debugposrange = self.POS_RANGE
def update_phys(self, plworldx, plworldy, camera): def update_phys(self, plworldx, plworldy, camera):
#print(plworldx, plworldy, plscreeny, current_depth)
### exit if player y pos less than random range
# # debug change random range
# inp = int(0)
# if thumby.buttonD.justPressed():
# inp -= 1
# if thumby.buttonU.justPressed():
# inp += 1
# if inp != 0:
# self.debugposrange = max(self.debugposrange + inp * 2, 1)
# print('debugposrange: ', self.debugposrange)
# # if plworldy < self.poslist[-1][self.POS_Y] + random.randint(1, self.debugposrange):
# # return
# if plworldy < self.poslist[-1][self.POS_Y] + self.debugposrange:
# return
if plworldy < self.poslist[-1][self.POS_Y] + self.POS_RANGE: if plworldy < self.poslist[-1][self.POS_Y] + self.POS_RANGE:
return return
@@ -142,23 +122,6 @@ class PlayerAnimation:
### get new position ### get new position
rad = self.RANDOM_RANGE // 2 rad = self.RANDOM_RANGE // 2
# # debug change random range
# inp = int(0)
# if thumby.buttonB.justPressed():
# inp -= 1
# if thumby.buttonA.justPressed():
# inp += 1
# if inp != 0:
# self.debugrandomrange = max(self.debugrandomrange + inp * 2, 1)
# print('debugrandomrange:', self.debugrandomrange)
# rad = max(self.debugrandomrange // 2, 1)
# vector int
# self.poslist.append((plworldx + random.randrange(-rad, rad),
# plworldy + random.randrange(-rad, rad)))
# move last pos # move last pos
self.poslist[-1] = ( self.poslist[-1] = (
plworldx + random.randrange(-rad, rad), plworldx + random.randrange(-rad, rad),
@@ -168,20 +131,13 @@ class PlayerAnimation:
# new pos is perfectly on player # new pos is perfectly on player
self.poslist.append((plworldx, plworldy)) self.poslist.append((plworldx, plworldy))
# # debug
# self.poslist.append((plworldx, plworldy))
### remove the fist item in the list if its full line is off screen ### remove the fist item in the list if its full line is off screen
if not camera.entity_in_camera(self.poslist[1][self.POS_X], self.poslist[1][self.POS_Y]): if not camera.entity_in_camera(self.poslist[1][self.POS_X], self.poslist[1][self.POS_Y]):
self.poslist.pop(0) self.poslist.pop(0)
#print(len(self.poslist))
def update_draw(self, camera, isdecend=1): def update_draw(self, camera, isdecend=1):
### draw line from first position to next ### draw line from first position to next
#print(topleft)
for i in range(len(self.poslist) - 1): for i in range(len(self.poslist) - 1):
x1, y1 = camera.relative_to_camera(self.poslist[i][self.POS_X], self.poslist[i][self.POS_Y]) x1, y1 = camera.relative_to_camera(self.poslist[i][self.POS_X], self.poslist[i][self.POS_Y])
x2, y2 = camera.relative_to_camera(self.poslist[i + 1][self.POS_X], self.poslist[i + 1][self.POS_Y]) x2, y2 = camera.relative_to_camera(self.poslist[i + 1][self.POS_X], self.poslist[i + 1][self.POS_Y])

View File

@@ -5,7 +5,7 @@ import thumby
from Games.Throot.map import ObstacleSlice from Games.Throot.map import ObstacleSlice
from Games.Throot.player import Player, Camera from Games.Throot.player import Player, Camera
from Games.Throot.artrepository import OBSTACLES from Games.Throot.artrepository import *
class Entity: class Entity:
""" """
@@ -67,7 +67,7 @@ class Entity:
True if the game should keep this entity, false if the game should drop True if the game should keep this entity, false if the game should drop
this entity from memory. this entity from memory.
""" """
return False return True
def render(self, tpf: float, camera: Camera) -> bool: def render(self, tpf: float, camera: Camera) -> bool:
""" """
@@ -97,6 +97,14 @@ class Obstacle(Entity):
def update(self, tpf: float, player: Player, camera: Camera) -> bool: def update(self, tpf: float, player: Player, camera: Camera) -> bool:
return True return True
class Collectible(Entity):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.score = 'score' in kwargs and kwargs['score'] or 10
def update(self, tpf: float, player: Player, camera: Camera) -> bool:
return True
def update_entities(entities, difficulty: int, current_depth: int, prev_depth: int): def update_entities(entities, difficulty: int, current_depth: int, prev_depth: int):
""" """
@@ -125,7 +133,15 @@ def update_entities(entities, difficulty: int, current_depth: int, prev_depth: i
spawned = set() spawned = set()
max_spawned = randrange(0, difficulty + 1) max_spawned = randrange(0, difficulty + 1)
while len(spawned) < max_spawned and random() < (2 * math.pow(difficulty + 0.01, 2)) / (3 * math.pow(difficulty + 0.01, 2) + 20 * (difficulty + 0.01)) + 0.05: while len(spawned) < max_spawned and random() < (2 * math.pow(difficulty + 0.01, 2)) / (3 * math.pow(difficulty + 0.01, 2) + 20 * (difficulty + 0.01)) + 0.05:
sprite_image = OBSTACLES[randrange(0, min((difficulty + 1) * 3, len(OBSTACLES)))] if random() < 0.2:
Clazz = Collectible
if current_depth >= 0:
sprite_image = collectibleunder
else:
sprite_image = collectibleover
else:
Clazz = Obstacle
sprite_image = OBSTACLES[randrange(0, min((difficulty + 1) * 3, len(OBSTACLES)))]
if sprite_image.width >= thumby.display.width: if sprite_image.width >= thumby.display.width:
x = 0 x = 0
else: else:
@@ -142,6 +158,5 @@ def update_entities(entities, difficulty: int, current_depth: int, prev_depth: i
break break
if not error: if not error:
sprite = thumby.Sprite(sprite_image.width, sprite_image.height, sprite_image.image, 0, 0, 0, False, False) sprite = thumby.Sprite(sprite_image.width, sprite_image.height, sprite_image.image, 0, 0, 0, False, False)
print("Adding obstacle of to (%d, %d) of size (%d, %d)" % (x, y, sprite_image.width, sprite_image.height)) spawned.add(Clazz(x, y, sprite_image.width, sprite_image.height, sprite_image.collision or sprite_image.image, sprite))
spawned.add(Obstacle(x, y, sprite_image.width, sprite_image.height, sprite_image.collision or sprite_image.image, sprite))
return spawned return spawned