37 Commits

Author SHA1 Message Date
Neal Finger
1c68beb77a player animation
-all points for root animation are kept
-rendering lines will only render lines that are in the camera
2023-02-12 20:42:07 -07:00
Neal Finger
09870bfa81 Update player.py
bug fix
2023-02-12 13:29:38 -07:00
Neal Finger
f34883b84f smooth root render
-root will always draw a line to the player position.  This makes the root growing animation look smoother and show the exact player position.
-dpad up toggles this
-button b toggles the debug render
2023-02-12 13:21:21 -07:00
Neal Finger
00f03652eb player movement
-input class rough draft
-use of time per frame for x movement
-y speed needed to be halved for some reason
-animation line drawing starts from player then moves outward to prepare for storing more points than can fit on the screen (scrolling up after complete stage)
2023-02-12 12:24:06 -07:00
Neal Finger
81bce7fe63 Update player.py
player root animation grows based on distance not just the y value

refactor the PlayerAnimation class
2023-02-11 14:10:35 -07:00
Neal Finger
5d3f661411 Merge pull request #12 from Markil3/title_screen
Title screen
2023-02-05 16:16:17 -07:00
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
William Hubbard
8d90129dbd Merge branch 'master' into negative-scroll 2023-02-05 14:04:52 -07:00
William Hubbard
05dd0a1cfd Fixes some debug code. 2023-02-05 14:04:42 -07:00
William Hubbard
518bf5f5e6 Fixes collision code. 2023-02-05 13:58:32 -07:00
William Hubbard
e03d74be36 Adds rooms constants
Forgot to commit this.
2023-02-05 13:09:30 -07:00
William Hubbard
b80c215d98 Sets the depth to be handled by the player
Just a lot simpler.
2023-02-05 13:09:05 -07:00
William Hubbard
2c4584e05b Merge pull request #8 from Markil3/end-game
Adds an end screen.
2023-02-05 11:43:38 -07:00
William Hubbard
a4e87421bb Fixes the leaf 2023-02-05 11:23:00 -07:00
William Hubbard
e73eb5a765 Adds an end screen. 2023-02-05 11:17:30 -07:00
William Hubbard
ad4a5de10e Fixes a few art assets 2023-02-05 09:45:20 -07:00
William Hubbard
e25a5dd9cc Adds the rest of the art assets. 2023-02-05 08:50:30 -07:00
CostaRic
15e3c33c87 Merge branch 'master' of https://github.com/Markil3/Throot 2023-02-04 23:31:30 -07:00
CostaRic
9dd1c7df90 Update ThrootArtAssets.py
aupdted Throot Art Asset Library
2023-02-04 23:31:24 -07:00
Neal Finger
df4fd1228e Merge pull request #7 from Markil3/player_animation_fix
take out print
2023-02-04 23:21:14 -07:00
Neal Finger
176d89ea14 take out print
take out print
2023-02-04 23:20:57 -07:00
Neal Finger
6cd766a848 Merge pull request #6 from Markil3/player_animation_fix
player animation fix
2023-02-04 22:59:17 -07:00
Neal Finger
4b8eb17ba1 player animation fix
edited throop, player
2023-02-04 22:58:07 -07:00
William Hubbard
f2c7f19d96 Merge pull request #5 from Markil3/subpixel-collisions
Adds sub-pixel collisions
2023-02-04 22:21:16 -07:00
CostaRic
8ad6236ff7 Create flowerpetal.raw
flowerpetal.raw added to repository
2023-02-04 20:50:55 -07:00
CostaRic
d0cf8f2a20 Throot Art asset update Added ThrootTiltle.raw to repository
Throot Art asset update

Added ThrootTiltle.raw to repository
2023-02-04 20:48:59 -07:00
9 changed files with 506 additions and 196 deletions

221
Throot.py
View File

@@ -7,15 +7,15 @@ 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.player import Player from Games.Throot.artrepository import *
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 CONST_FPS from Games.Throot.constants import *
class GameManager: class GameManager:
EVENT_ROOM_CHANGE = {'start': 0, 'main': 1, 'end': 2, 'next': -1}
def __init__(self, *rooms): def __init__(self, *rooms):
self.rooms = rooms self.rooms = rooms
self.room_index = 0 self.room_index = 0
@@ -24,13 +24,14 @@ class GameManager:
# Set the FPS (without this call, the default fps is 30) # Set the FPS (without this call, the default fps is 30)
thumby.display.setFPS(CONST_FPS) thumby.display.setFPS(CONST_FPS)
def room_goto(self, index): def room_goto(self, event):
index = event['room']
if index < 0: if index < 0:
self.room_index = min(self.room_index + 1, len(self.rooms)) self.room_index = min(self.room_index + 1, len(self.rooms))
else: else:
self.room_index = index self.room_index = index
self.rooms[self.room_index].start() self.rooms[self.room_index].start(event)
def restart(self): def restart(self):
self.room_index = 0 self.room_index = 0
@@ -41,11 +42,14 @@ class GameManager:
thumby.display.update() thumby.display.update()
if event: if event:
self.room_goto(self.EVENT_ROOM_CHANGE[event]) self.room_goto(event)
class Room: class Room:
def start(self): def __init__(self):
self.ind_button = 0
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
@@ -58,82 +62,217 @@ class RoomTitle(Room):
super().update(tpf) super().update(tpf)
if thumby.inputPressed(): if thumby.inputPressed():
return 'main' return {
'room': ROOM_MAIN
}
# 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]
self.entities = set() self.entities = set()
self.player = Player() self.player = 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):
self.entities.clear()
self.entities.add(
self.seed
)
self.current_obstacle_slice = OBSTACLE_SLICES[0]
self.player = Player()
self.player.yspeed = 10
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)
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):
""" """
Executes one tick of the game Executes one tick of the game
""" """
super().update(tpf) super().update(tpf)
prev_depth = self.current_depth
self.diving_velocity += self.diving_accel * tpf
self.current_depth += self.diving_velocity * tpf
#print("%f (%f)" % (self.current_depth, self.current_depth % ObstacleSlice.height)) self.player.update_phys(tpf, self.camera)
#print("%f -> %f: %f -> %f" % (prev_depth, self.current_depth, prev_depth % ObstacleSlice.height, self.current_depth % ObstacleSlice.height)) self.camera.update_phys()
if (
self.current_depth > prev_depth and self.current_depth % ObstacleSlice.height < prev_depth % ObstacleSlice.height
or self.current_depth < prev_depth and self.current_depth % ObstacleSlice.height > prev_depth % ObstacleSlice.height
):
self.current_obstacle_slice = self.get_obstacle_slice()
self.player.update_phys(self.current_depth, prev_depth)
# Generate new entities as needed # Generate new entities as needed
self.entities |= update_entities(self.current_obstacle_slice, self.current_depth, prev_depth) 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()
for entity in self.entities: for entity in self.entities:
if not entity.update(tpf, self.current_depth, prev_depth): if not entity.update(tpf, self.player, self.camera) or abs(entity.y - self.player.y) > self.camera.height * 1.5:
to_remove.add(entity) to_remove.add(entity)
elif entity.intersects_pixel(self.player.xsub, self.player.ysub): elif entity.intersects_pixel(self.player.xsub, self.player.ysub):
if isinstance(entity, Obstacle): if isinstance(entity, Obstacle):
print("Hit!") return {
'room': ROOM_END,
'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 = int(self.current_depth * 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.current_depth, prev_depth) entity.render(tpf, self.camera)
self.player.update_draw(self.current_depth, prev_depth) 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)
thumby.display.update()
class EndRoom(Room):
def __init__(self):
self.score = 0
def start(self, event):
self.score = event['score']
# Used to make sure that we aren't triggering from input mean't for the game.
self.still_pressed = thumby.inputPressed()
def update(self, tpf):
if not thumby.inputPressed():
self.still_pressed = False
elif not self.still_pressed and thumby.inputPressed():
return {
'room': ROOM_START
}
thumby.display.fill(0)
thumby.display.drawText("Final Score:", (thumby.display.width - 60) // 2, 1, 1)
thumby.display.drawText(str(self.score), (thumby.display.width - (len(str(self.score)) * 5)) // 2, 9, 1)
def main(): def main():
game = GameManager(RoomTitle(), GameLoop()) game = GameManager(RoomTitle(), GameLoop(), EndRoom())
game.start() game.start()
prev_time = 0 prev_time = 0

BIN
ThrootTitle.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
ThrootTitle.raw Normal file

Binary file not shown.

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]),
@@ -122,13 +143,85 @@ bigdoritoobstacle = SpriteImage(
) )
# BITMAP: width: 16, height: 16 # BITMAP: width: 16, height: 16
leaf = SpriteImage( leaf = SpriteImage(
bytearray([128,1,1,1,3,3,3,71,135,15,15,31,63,255,255,255,255,254,252,248,240,240,224,224,192,193,194,196,200,144,1,63]), bytearray([127,254,254,254,252,252,252,184,120,240,240,224,192,0,0,0,
0,1,3,7,15,15,31,31,63,62,61,59,55,111,254,192]),
None, None,
16, 16,
16 16
) )
UNDERGROUND_OBSTACLES = [skull, pizza, boulder, ball, steve, mathroot, robot, dinoskull, dinojaw, dinospine, bone, bigdinohead, bigboneddiag, bigdoritoobstacle] annoydead = SpriteImage(
OVERGROUND_OBSTACLES = [spaceinv, leaf] bytearray([0,240,214,236,54,190,214,238,212,254,248,240,224,192,192,128,128,128,128,240,0,
0,63,255,254,126,126,254,255,127,127,127,127,127,255,255,127,127,255,255,31,0,
0,0,1,0,0,0,3,1,0,0,0,0,0,3,1,0,0,1,0,0,0]),
None,
21,
19
)
OBSTACLES = UNDERGROUND_OBSTACLES + OVERGROUND_OBSTACLES flowerpetals = SpriteImage(
bytearray([0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,252,254,254,254,254,252,248,
0,0,0,0,0,252,254,255,255,255,255,255,254,254,223,63,255,255,255,255,255,255,
0,224,248,252,252,252,255,255,255,247,239,239,223,255,255,255,255,255,251,255,255,255]),
None,
22,
24
)
flowerpetalanim = SpriteImage(
bytearray([255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,63,191,191,7,55,231,227,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,127,127,191,191,191,7,115,249,253,252,254,252,255,255,255,255,255,31,79,239,231,224,238,223,223,223,191,255,254,248,243,255,255,255,255,255,255,255,255,255,127,127,127,127,127,127,127,7,241,252,254,254,254,254,253,249,255,255,255,255,1,252,254,255,255,255,255,255,254,254,223,63,255,255,255,255,255,255,15,227,249,253,253,252,255,255,255,247,239,239,223,255,255,255,255,255,251,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,31,207,247,243,251,243,247,199,255,255,255,255,255,15,231,243,249,253,253,253,253,252,254,247,231,207,255,255,255,255,255,31,199,243,251,250,248,255,255,239,223,191,255,255,255,255,255,255,255,255,255,255]),
None,
22,
24
)
lvl4wall = SpriteImage(
bytearray([255,255,254,254,252,252,248,248,240,240,224,224,192,192,192,128,128,0,0,0,0,224,240,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,240,224,0,0,0,224,240,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,127,127,63,0,0,0,0,63,127,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,127,127,31,0,0,0,63,127,127,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,63,63,31,31,15,15,7,7,3,3,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]),
None,
72,
30
)
ballobstaclelvl3 = SpriteImage(
bytearray([0,0,0,0,0,0,0,0,192,224,240,240,248,252,252,252,254,254,254,254,254,254,252,252,252,248,240,240,224,192,0,0,0,0,0,0,0,0,0,0,192,224,240,240,248,252,252,252,254,254,254,254,254,254,252,252,252,248,240,240,224,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,240,0,0,0,0,240,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254,240,0,0,0,0,0,0,0,0,0,0,0,0,3,31,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,63,31,3,0,0,0,0,3,31,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,63,31,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,3,7,15,15,15,31,31,31,31,31,31,15,15,15,7,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,1,3,3,7,15,15,15,31,31,31,31,31,31,15,15,15,7,3,3,1,0,0,0,0,0,0,0,0,0,0,0]),
None,
72,
30
)
Doritoobstaclelvl5 = SpriteImage(
bytearray([254,254,254,254,254,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,248,248,248,248,248,248,248,248,248,248,248,248,248,240,224,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,192,192,224,224,240,248,248,252,252,254,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,127,63,31,31,15,7,3,3,3,3,1,0,0,0,0,0,0,128,128,192,224,224,240,240,248,252,252,254,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,127,127,63,63,31,31,31,15,15,7,7,7,3,3,1,1,1,0,0,0,0,0,0,0,0,128,128,192,224,240,248,248,252,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,7,3,3,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15]),
None,
72,
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]

View File

@@ -1 +1,7 @@
CONST_FPS = 60 CONST_FPS = 60
ROOM_START = 0
ROOM_MAIN = 1
ROOM_END = 2
ROOM_NEXT = -1

1
flowerpetal.raw Normal file
View File

@@ -0,0 +1 @@
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?؟؟7هك<D987><D983><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>؟؟؟s<><73><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>هـَ<D980><D98E><EFBFBD>؟<EFBFBD><D89F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ّ<><D991><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><01><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ك<><D983><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ُُ<D98F><D98F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>د<><D8AF><EFBFBD><EFBFBD><EFBFBD>ا<EFBFBD><D8A7><EFBFBD><EFBFBD><EFBFBD>ه<><D987><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>هد<D987><D8AF><EFBFBD><EFBFBD><EFBFBD>ا<><D8A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ُ<EFBFBD>؟<EFBFBD><D89F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

266
player.py
View File

@@ -2,174 +2,224 @@ import time
import thumby import thumby
import math import math
import random import random
from collections import namedtuple
from Games.Throot.constants import CONST_FPS from Games.Throot.constants import CONST_FPS
# didplay width 72 # didplay width 72
# display height 40 # display height 40
# constants class Input:
CONST_PL_SCREEN_Y = int((0.5) * thumby.display.height) def move_right(self):
return (
thumby.buttonA.pressed() or
thumby.buttonR.pressed()
)
CONST_INP_MOVE_LEFT = thumby.buttonL def move_left(self):
CONST_INP_MOVE_RIGHT = thumby.buttonA return thumby.buttonL.pressed()
# classes inp = Input()
### classes
class Player: class Player:
def __init__(self): def __init__(self):
self.prev_x = 0
self.prev_y = 0
self.xsub = float(thumby.display.width / 2) self.xsub = float(thumby.display.width / 2)
self.ysub = float(0) self.ysub = float(0)
self.x = int(self.xsub) self.x = int(self.xsub)
self.y = int(self.ysub) self.y = int(self.ysub)
self.movespeed = 32 / CONST_FPS
self.isdecend = 1 self.isdecend = 1
self.xspeed = 32 / CONST_FPS self.input_x = float(0)
self.yspeed = 32 / CONST_FPS
self.accx = 1 / (CONST_FPS * 4)
self.accaccx = self.accx
self.anim = PlayerAnimation(self.x, self.y) # constants
self.debugvisible = True self.speed_x_start = 32
self.speed_x_max = 200
self.acc_x_start = 64
self.jerk_x = 128
def update_phys(self, current_depth, prev_depth): self.speed_x = self.speed_x_start
inp = int(0) self.acc_x = self.acc_x_start
if CONST_INP_MOVE_LEFT.pressed():
inp -= 1
if CONST_INP_MOVE_RIGHT.pressed():
inp += 1
# debug visible # y speed is constant
if thumby.buttonU.pressed(): self.speed_y = 16
self.debugvisible = not self.debugvisible
self.anim = PlayerAnimation(self)
def update_phys(self, tpf, camera: Camera):
# prevoius sub pixel position
self.prev_x = self.xsub
self.prev_y = self.ysub
### x movement ### x movement
self.xspeed += self.accx
self.accx += self.accaccx
if (inp == 0) and (self.xspeed > self.movespeed): # x input
self.xspeed = self.movespeed if inp.move_left() and (not inp.move_right()):
self.accx = self.accaccx self.input_x = -1
xx = self.xsub + inp * self.xspeed if inp.move_right() and (not inp.move_left()):
self.input_x = 1
# clamp to screen if (not inp.move_left()) and (not inp.move_right()):
self.xsub = max(min(xx, thumby.display.width - 1), 0) self.input_x = 0
# acceleration and jerk
self.speed_x += self.acc_x * tpf
self.acc_x += self.jerk_x * tpf
# limit speed
self.speed_x = min(self.speed_x_max, self.speed_x)
# reset speed and acceleration if no input
if (self.input_x == 0) and (self.speed_x != self.speed_x_start):
self.speed_x = self.speed_x_start
self.acc_x = self.acc_x_start
# set x sub pixel position
self.xsub += self.speed_x * tpf * self.input_x
### y movement ### y movement
self.ysub = current_depth - (thumby.display.height / 2)
# set y sub pixel position
self.ysub += self.speed_y * tpf
### stay in world bounds
self.xsub = max(min(self.xsub, thumby.display.width - 1), 0)
### pixel positions ### pixel positions
self.x = int(self.xsub) self.x = int(self.xsub)
self.y = int(self.ysub) self.y = int(self.ysub)
# animation ### animation physics
self.anim.update_phys(self.x, self.y, CONST_PL_SCREEN_Y) self.anim.update_phys(camera)
def update_draw(self, current_depth, prev_depth): def update_draw(self, camera):
# debug draw ### animation render
if self.debugvisible: self.anim.update_draw(camera, self.isdecend)
pix_x = self.x
pix_y = int(current_depth - self.y)
thumby.display.setPixel(pix_x, pix_y, self.isdecend)
# animation
self.anim.update_draw(current_depth, prev_depth, self.isdecend)
class Camera: class Camera:
def __init__(self, target): def __init__(self, target):
self.x = int(0) self.x = int(0)
self.y = int(0) self.y = int(0)
self.width = thumby.display.width
self.height = thumby.display.height
self.offset = -self.height / 2
self.target = target self.target = target
def update_phys(self, plscreeny): def entity_in_camera(self, ent_x, ent_y, ent_width = 0, ent_height = 0) -> bool:
# position player return ent_x + ent_width >= self.x and ent_y + ent_height >= self.y and ent_x < self.x + self.width and ent_y < self.y + self.height
self.y = self.target.y - plscreeny
def relative_to_camera(self, global_x, global_y):
return (global_x - self.x, global_y - self.y)
def update_phys(self):
# position player
self.y = self.target.y + self.offset
class PlayerAnimation: class PlayerAnimation:
# width and height of range bounding box MAX_ROOT_OFFSET = int(4)
RANDOM_RANGE = int(7)
POS_RANGE = int(3)
POS_X = int(0)
POS_Y = int(1)
def __init__(self, plworldx, plworldy): MAX_GROW_DISTANCE = int(6)#int(3)
self.poslist = [(plworldx, plworldy)] MIN_GROW_DISTANCE = int(2)
self.debugrandomrange = self.RANDOM_RANGE
self.debugposrange = self.POS_RANGE
def update_phys(self, plworldx, plworldy, plscreeny): Point = namedtuple("Point", ["x", "y"])
### exit if player y pos less than random range def __init__(self, player):
# # debug change random range self.player = player
# inp = int(0)
# if thumby.buttonD.justPressed():
# inp -= 1
# if thumby.buttonU.justPressed():
# inp += 1
# if inp != 0: self.root_offset = self.MAX_ROOT_OFFSET
# self.debugposrange = max(self.debugposrange + inp * 2, 1) self.grow_distance = self.MAX_GROW_DISTANCE
# print('debugposrange: ', self.debugposrange)
# # if plworldy < self.poslist[-1][self.POS_Y] + random.randint(1, self.debugposrange): self.points = []
# # return self.add_point()
# if plworldy < self.poslist[-1][self.POS_Y] + self.debugposrange: self.debug_visible = False
# return self.debug_exact = True
if plworldy < self.poslist[-1][self.POS_Y] + self.POS_RANGE: def add_point(self):
self.points.append(self.Point(self.player.x, self.player.y))
def update_phys(self, camera):
# debug visible
if thumby.buttonB.justPressed():
self.debug_visible = not self.debug_visible
# debug exact
if thumby.buttonU.justPressed():
self.debug_exact = not self.debug_exact
# exit if distance from player to last root point is less than gorw distance
dist_sqr = (
(self.player.x - self.points[-1].x) ** 2 +
(self.player.y - self.points[-1].y) ** 2
)
if dist_sqr < self.grow_distance ** 2:
return return
### get new position # set new grow distance
rad = self.RANDOM_RANGE // 2 self.grow_distance = random.randint(self.MIN_GROW_DISTANCE, self.MAX_GROW_DISTANCE)
# # debug change random range # offset last root point to look random
# inp = int(0) self.points[-1] = self.Point(
# if thumby.buttonB.justPressed(): self.points[-1].x + random.randrange(-self.root_offset, self.root_offset),
# inp -= 1 self.points[-1].y + random.randrange(-self.root_offset, self.root_offset)
# 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
self.poslist[-1] = (
plworldx + random.randrange(-rad, rad),
plworldy + random.randrange(-rad, rad)
) )
# new pos is perfectly on player # new root point created exactly on player
self.poslist.append((plworldx, plworldy)) self.add_point()
# # debug # points are not removed so the camera can pan back up
# self.poslist.append((plworldx, plworldy)) # # remove points in list that would render lines off the camera
# for point in self.points[1:]:
# if camera.entity_in_camera(point.x, point.y):
# break
### remove the fist item in the list if its full line is off screen # self.points.pop(0)
#if self.poslist[1][self.POS_Y] < plworldy - plscreeny:
# self.poslist.pop(0)
#print(len(self.poslist)) def update_draw(self, camera, isdecend=1):
# debug render
if self.debug_visible:
pix_x, pix_y = camera.relative_to_camera(self.player.x, self.player.y)
thumby.display.setPixel(int(pix_x), int(pix_y), isdecend)
return
def update_draw(self, current_depth, prev_depth, isdecend=1): # draw line from last point to player
### draw line from first position to next if self.debug_exact:
x1, y1 = camera.relative_to_camera(self.player.x, self.player.y)
for i in range(len(self.poslist) - 1): x2, y2 = camera.relative_to_camera(self.points[-1].x, self.points[-1].y)
thumby.display.drawLine( thumby.display.drawLine(
int(self.poslist[i][self.POS_X]), int(x1),
int(current_depth - self.poslist[i][self.POS_Y]), int(y1),
int(self.poslist[i + 1][self.POS_X]), int(x2),
int(current_depth - self.poslist[i + 1][self.POS_Y]), int(y2),
isdecend
)
# draw line from last point to previous
point_off_camera_count = 2
point_prev = self.points[-1]
for point in self.points[-2::-1]:
# break if entire line is off camera
if not camera.entity_in_camera(point.x, point.y):
point_off_camera_count -= 1
if point_off_camera_count <= 0:
break
x1, y1 = camera.relative_to_camera(point_prev.x, point_prev.y)
x2, y2 = camera.relative_to_camera(point.x, point.y)
point_prev = point
thumby.display.drawLine(
int(x1),
int(y1),
int(x2),
int(y2),
isdecend isdecend
) )

View File

@@ -1,9 +1,11 @@
from random import randint import math
from random import randrange, random
import thumby import thumby
from Games.Throot.map import ObstacleSlice from Games.Throot.map import ObstacleSlice
from Games.Throot.artrepository import OBSTACLES from Games.Throot.player import Player, Camera
from Games.Throot.artrepository import *
class Entity: class Entity:
""" """
@@ -35,9 +37,9 @@ class Entity:
return False return False
rel_x = int(loc_x - self.x) rel_x = int(loc_x - self.x)
rel_y = int(loc_y - self.y) rel_y = int(loc_y - self.y)
return bool((self.collision[rel_x] >> (self.height - rel_y)) & 1) return bool((self.collision[rel_x] >> (rel_y)) & 1)
def can_render(self, current_depth: int) -> bool: def can_render(self, camera: Camera) -> bool:
""" """
Checks to see if this sprite is within the game screen. Checks to see if this sprite is within the game screen.
@@ -48,15 +50,9 @@ class Entity:
:return: :return:
True if the sprite shows up on screen in any capacity, false otherwise. True if the sprite shows up on screen in any capacity, false otherwise.
""" """
if self.x < -self.width or self.x >= thumby.display.width: return camera.entity_in_camera(self.x, self.y, self.width, self.height)
return False
max = current_depth
min = max - thumby.display.height
if self.y < (min - self.height) or self.y > max:
return False
return True
def update(self, tpf: float, current_depth: int, prev_depth: int) -> bool: def update(self, tpf: float, player: Player, camera: Camera) -> bool:
""" """
Updates the state of the entity based on the game state. Updates the state of the entity based on the game state.
@@ -71,9 +67,9 @@ 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, current_depth: int, prev_depth: int) -> bool: def render(self, tpf: float, camera: Camera) -> bool:
""" """
Sets the position of the sprite and draws it. Sets the position of the sprite and draws it.
@@ -89,19 +85,28 @@ class Entity:
should be hidden. should be hidden.
""" """
if self.sprite: if self.sprite:
self.sprite.y = int(self.y - current_depth + thumby.display.height) coordinates = camera.relative_to_camera(self.x, self.y)
self.sprite.x = int(self.x) self.sprite.x = coordinates[0]
if self.can_render(current_depth): self.sprite.y = coordinates[1]
if self.can_render(camera):
thumby.display.drawSprite(self.sprite) thumby.display.drawSprite(self.sprite)
return True return True
return False return False
class Obstacle(Entity): class Obstacle(Entity):
def update(self, tpf: float, current_depth: int, prev_depth: int) -> bool: def update(self, tpf: float, player: Player, camera: Camera) -> bool:
return self.can_render(current_depth) 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(current_slice: ObstacleSlice, current_depth: int, prev_depth: int): def update_entities(entities, difficulty: int, current_depth: int, prev_depth: int):
""" """
Checks the current game state and generates new sprites based on said state. Checks the current game state and generates new sprites based on said state.
@@ -118,24 +123,40 @@ def update_entities(current_slice: ObstacleSlice, current_depth: int, prev_depth
:return: :return:
A list of entities to generate this tick. A list of entities to generate this tick.
""" """
OBSTACLE_BUFFER = 10
if current_depth == prev_depth: if current_depth == prev_depth:
# If we haven't moved, we don't need to spawn anything # If we haven't moved, we don't need to spawn anything
return set() return set()
# Local y relative to the top of the slice
slice_y = (current_depth) % ObstacleSlice.height
slice_prev_y = (prev_depth) % ObstacleSlice.height
if prev_depth < current_depth and slice_prev_y > slice_y:
slice_prev_y -= ObstacleSlice.height
elif current_depth < prev_depth and slice_y > slice_prev_y:
slice_prev_y += ObstacleSlice.height
min_y = min(slice_prev_y, slice_y)
max_y = max(slice_prev_y, slice_y)
spawned = set() spawned = set()
for entity in current_slice.obstacle_map: max_spawned = randrange(0, difficulty + 1)
# The position the entity should spawn at, relative to the slice top 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:
entity_y = entity[1] if random() < 0.2:
if entity_y >= min_y and entity_y < max_y: Clazz = Collectible
sprite_image = OBSTACLES[entity[2]] if current_depth >= 0:
sprite = thumby.Sprite(sprite_image.width, sprite_image.height, sprite_image.image, 0, 0, 0, entity[3], entity[4]) sprite_image = collectibleunder
spawned.add(Obstacle(entity[0], entity_y + current_depth // ObstacleSlice.height * ObstacleSlice.height, sprite_image.width, sprite_image.height, sprite_image.collision or sprite_image.image, sprite)) 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:
x = 0
else:
x = randrange(0, thumby.display.width - sprite_image.width)
y = current_depth
if current_depth > prev_depth:
y += thumby.display.height // 2
elif current_depth < prev_depth:
y -= thumby.display.height // 2 + sprite_image.height
error = False
for entity in entities | spawned:
if x > (entity.x - OBSTACLE_BUFFER) and x <= (entity.x + entity.width + OBSTACLE_BUFFER) and y > (entity.y - OBSTACLE_BUFFER) and y <= (entity.y + entity.height + OBSTACLE_BUFFER):
error = True
break
if not error:
sprite = thumby.Sprite(sprite_image.width, sprite_image.height, sprite_image.image, 0, 0, 0, False, False)
spawned.add(Clazz(x, y, sprite_image.width, sprite_image.height, sprite_image.collision or sprite_image.image, sprite))
return spawned return spawned