Skip to content
Snippets Groups Projects
Commit 67daa9db authored by pumapaul's avatar pumapaul
Browse files

Add text UI to modularized structure

parent f9755d62
No related branches found
No related tags found
No related merge requests found
......@@ -5,6 +5,7 @@ mod enemy;
mod textures;
pub mod config;
mod tile;
mod combat_log;
use tetra::{Context, State};
use tetra::graphics::{self, Color, DrawParams};
......@@ -15,6 +16,9 @@ use crate::game_state::tile::Tile;
use tetra::math::Vec2;
use std::cmp::min;
use rand::{thread_rng, Rng};
use tetra::graphics::text::{VectorFontBuilder, Font, Text};
use crate::game_state::combat_log::PlayerAction;
use crate::game_state::combat_log::PlayerAction::Move;
type Point = (usize, usize);
......@@ -22,18 +26,25 @@ pub struct GameState {
current_run: Run,
high_score: usize,
textures: Textures,
font: Font,
}
impl GameState {
pub fn new(ctx: &mut Context) -> tetra::Result<GameState> {
let textures = Textures::init(ctx);
let font = VectorFontBuilder::new("./resources/corbel.ttf")?.with_size(ctx, 16.0);
if textures.is_ok() {
Ok(GameState {
current_run: Run::new(),
high_score: 0,
textures: textures.unwrap(),
})
if font.is_ok() {
Ok(GameState {
current_run: Run::new(),
high_score: 0,
textures: textures.unwrap(),
font: font.unwrap(),
})
} else {
Err(font.err().unwrap())
}
} else {
Err(textures.err().unwrap())
}
......@@ -54,6 +65,8 @@ impl State for GameState {
graphics::clear(ctx, Color::rgb(0.220, 0.220, 0.220));
self.draw_board(ctx);
self.draw_stats(ctx);
self.draw_combat_log(ctx);
Ok(())
}
......@@ -88,9 +101,12 @@ impl GameState {
let target_tile = self.current_run.level.get_tile_at(target_position);
match target_tile {
Tile::Empty => self.current_run.level.move_player_to(target_position),
Tile::Empty => {
self.current_run.level.move_player_to(target_position);
self.current_run.combat_log.player_action = Option::from(Move);
},
Tile::Wall => return false,
_ => {}
_ => ()
}
true
}
......@@ -177,7 +193,6 @@ impl GameState {
impl GameState {
fn finish_turn(&mut self) {
self.current_run.turn_count += 1;
self.current_run.level.turn_count += 1;
}
}
......@@ -256,4 +271,58 @@ impl GameState {
color: Color::WHITE,
}
}
fn draw_stats(&self, ctx: &mut Context) {
let text_top = GameState::calculate_text_top();
let health_string = format!("Health: {} / {}", self.current_run.player.current_hp, self.current_run.player.max_hp);
let turn_string = format!("Turn: {}", self.current_run.level.turn_count);
let level_string = format!("Level: {} High Score: {}", self.current_run.level_count, self.high_score);
self.draw_string(health_string, 50.0, text_top, ctx);
self.draw_string(turn_string, 50.0, text_top + 25.0, ctx);
self.draw_string(level_string, 50.0, text_top + 50.0, ctx);
}
fn draw_combat_log(&self, ctx: &mut Context) {
self.draw_player_action(ctx);
self.draw_enemy_action(ctx);
}
fn draw_player_action(&self, ctx: &mut Context) {
let text_top = GameState::calculate_text_top();
match self.current_run.combat_log.player_action {
Some(PlayerAction::Move) =>
self.draw_string("You moved.".parse().unwrap(), 300.0, text_top, ctx),
Some(PlayerAction::Wait) =>
self.draw_string("You waited.".parse().unwrap(), 300.0, text_top, ctx),
Some(PlayerAction::Attack(damage_done, remaining_hp)) =>
self.draw_string(
format!("You attacked for {} damage. The enemy has {} hit points remaining.", damage_done, remaining_hp),
300.0,
text_top,
ctx
),
None => ()
};
}
fn draw_enemy_action(&self, ctx: &mut Context) {
let text_top = GameState::calculate_text_top() + 25.0;
match self.current_run.combat_log.damage_taken {
Some(damage) =>
self.draw_string(format!("The enemy attacked you for {} damage.", damage), 300.0, text_top, ctx),
None => ()
};
}
fn draw_string(&self, string: String, x: f32, y: f32, ctx: &mut Context) {
Text::new(string, self.font.clone())
.draw(ctx, Vec2::new(x, y));
}
fn calculate_text_top() -> f32 {
33.0 * (config::BOARD_HEIGHT as f32 + 2.0) + 7.0
}
}
\ No newline at end of file
#[derive(Debug)]
pub struct CombatLog {
pub player_action: Option<PlayerAction>,
pub damage_taken: Option<usize>
}
#[derive(Debug, Copy, Clone)]
pub enum PlayerAction {
Move,
Wait,
Attack(usize, usize) // damage, remaining hp
}
\ No newline at end of file
pub const WINDOW_WIDTH: f32 = 1280.0;
pub const WINDOW_HEIGHT: f32 = 720.0;
pub const BOARD_WIDTH: usize = 35;
pub const BOARD_HEIGHT: usize = 19;
pub const WINDOW_WIDTH: f32 = 1920.0;
pub const WINDOW_HEIGHT: f32 = 1080.0;
pub const BOARD_WIDTH: usize = 50;
pub const BOARD_HEIGHT: usize = 28;
pub const PLAYER_STARTING_AP: usize = 1;
pub const PLAYER_STARTING_HP: usize = 10;
pub struct Player {
current_hp: usize,
max_hp: usize,
attack_power: usize,
pub current_hp: usize,
pub max_hp: usize,
pub attack_power: usize,
}
impl Player {
......
use crate::game_state::player::Player;
use crate::game_state::level::Level;
use crate::game_state::config;
use crate::game_state::combat_log::CombatLog;
pub struct Run {
pub turn_count: usize,
level_count: usize,
player: Player,
pub level_count: usize,
pub player: Player,
pub level: Level,
pub combat_log: CombatLog
}
impl Run {
......@@ -15,10 +16,13 @@ impl Run {
let ap = config::PLAYER_STARTING_AP;
Run {
turn_count: 0,
level_count: 0,
player: Player::new(ap, hp),
level: Level::new(0),
combat_log: CombatLog {
player_action: None,
damage_taken: None
}
}
}
}
\ No newline at end of file
use std::collections::HashMap;
use tetra::graphics::text::{Font, Text, VectorFontBuilder};
use tetra::graphics::{self, Color, DrawParams, Texture};
use tetra::input::{self, Key};
use tetra::math::Vec2;
use tetra::{Context, ContextBuilder, State};
use rand::{thread_rng, Rng};
mod game_state;
use tetra::ContextBuilder;
//constants
const WINDOW_WIDTH: f32 = 1920.0;
const WINDOW_HEIGHT: f32 = 1080.0;
const BOARD_WIDTH: usize = 58;
const BOARD_HEIGHT: usize = 28;
use crate::game_state::GameState;
use crate::game_state::config;
fn main() -> tetra::Result {
ContextBuilder::new("rusttest", WINDOW_WIDTH as i32, WINDOW_HEIGHT as i32)
ContextBuilder::new("rusttest", config::WINDOW_WIDTH as i32, config::WINDOW_HEIGHT as i32)
.quit_on_escape(true)
.build()?
.run(GameState::new)
}
struct GameState {
player: Entity,
foes: Foes,
tile: Texture,
world: World,
ui: UI,
stats: Stats,
}
impl GameState {
fn new(ctx: &mut Context) -> tetra::Result<GameState> {
//load graphics
let player_texture = Texture::new(ctx, "./resources/player.png")?;
let tile_texture = Texture::new(ctx, "./resources/tile.png")?;
//set initial position
let player_position = Vec2::new(5,7);
//UI
let font = VectorFontBuilder::new("./resources/corbel.ttf")?;
let font2 = font.with_size(ctx, 16.0)?;
Ok(GameState {
player: Entity::new_pos(player_texture, player_position, 1),
foes: Foes::generate(6, Texture::new(ctx, "./resources/spike.png")?),
tile: tile_texture,
world: World::new(),
ui: UI::new(font2),
stats: Stats::new(),
})
}
}
impl State for GameState {
// draw call
fn draw(&mut self, ctx: &mut Context) -> tetra::Result {
graphics::clear(ctx, Color::rgb(0.220, 0.220, 0.220));
for x in 0..BOARD_WIDTH{
for y in 0..BOARD_HEIGHT{
let tile = self.world.tiles[x][y] as WorldTile;
let mut c = Color::BLACK;
if tile.passable {
c = Color::WHITE;
}
self.tile.draw(ctx, DrawParams{
position: Vec2::new(x as f32*33.0,y as f32*33.0),
scale: Vec2::new(1.0,1.0),
origin: Vec2::new(0.0,0.0),
rotation: 0.0,
color: c,
})
}
}
self.player.draw(ctx);
let ids: Vec<&u32> = self.foes.list.keys().collect();
for id in &ids{
self.foes.list[id].draw(ctx);
}
self.ui.updateStats(ctx, self.stats);
self.ui.updateEvents(ctx, "Something happend.");
Ok(())
}
//game loop
fn update(&mut self, ctx: &mut Context) -> tetra::Result {
let mut tick = false;
//input
if input::is_key_pressed(ctx, Key::A) {
if self.world.is_passable(Vec2::new(self.player.position.x-1,self.player.position.y)) {
if self.world.is_occupied_by(Vec2::new(self.player.position.x-1,self.player.position.y))>1 {
//collision
}
else {
self.player.position.x -= 1;
}
}
self.player.transform.scale.x = -1.0;
tick = true;
}
if input::is_key_pressed(ctx, Key::D) {
if self.world.is_passable(Vec2::new(self.player.position.x+1,self.player.position.y)) {
self.player.position.x += 1;
tick = true;
}
self.player.transform.scale.x = 1.0;
}
if input::is_key_pressed(ctx, Key::W) {
if self.world.is_passable(Vec2::new(self.player.position.x,self.player.position.y-1)) {
self.player.position.y -= 1;
tick = true;
}
}
if input::is_key_pressed(ctx, Key::S) {
if self.world.is_passable(Vec2::new(self.player.position.x,self.player.position.y+1)) {
self.player.position.y += 1;
tick = true;
}
}
if input::is_key_pressed(ctx, Key::Space) {
tick = true;
}
// logic
if tick{
let ids: Vec<&u32> = self.foes.list.keys().collect();
for id in &ids{
// tried To move the logic into the entity, but run into reference ownership issues
//self.foes.list[id].update(self);
let mut newpos = self.foes.list[id].position;
let mut rng = thread_rng();
let n:u8 = rng.gen_range(0, 4);
match n{
0 => newpos.x += 1,
1 => newpos.x -= 1,
2 => newpos.y += 1,
3 => newpos.y -= 1,
_ => newpos.y -= 0, //impossible to reach, can we avoid this somehow?
}
if self.world.is_passable(newpos){
// ownership issues
//self.foes.list[id].position = newpos;
}
}
}
Ok(())
}
}
struct Entity {
texture: Texture,
transform: DrawParams,
position: Vec2<u32>,
id: u32,
}
impl Entity {
fn new_pos(texture: Texture, initial_position: Vec2<u32>, id: u32) -> Entity{
Entity::with_darwparams(texture, DrawParams{
position: Vec2::new(16.0+(initial_position.x as f32) * 33.0, 16.0+(initial_position.y as f32) * 33.0),
scale: Vec2::new(1.0,1.0),
origin: Vec2::new(16.0,16.0),
rotation: 0.0,
color: Color::WHITE,
}, initial_position,
id)
}
fn with_darwparams(texture: Texture, transform: DrawParams, position: Vec2<u32>, id: u32) -> Entity{
Entity{
texture,
transform,
position,
id,
}
}
fn get_drawparams(&self) -> DrawParams{
let mut dp = self.transform.clone();
dp.position= Vec2::new(16.0+(self.position.x as f32) * 33.0, 16.0+(self.position.y as f32) * 33.0);
dp
}
fn update(&self, gs: &GameState ){
// let mut newpos = self.position;
// let mut rng = thread_rng();
// let n:u8 = rng.gen_range(0, 4);
// match n{
// 0 => newpos.x += 1,
// 1 => newpos.x -= 1,
// 2 => newpos.y += 1,
// 3 => newpos.y -= 1,
// _ => newpos.y -= 0, //impossible to reach, can we avoid this somehow?
// }
// if gs.world.is_passable(newpos){
// self.position = newpos;
// }
}
fn draw(&self, ctx: &mut Context){
self.texture.draw(ctx, self.get_drawparams());
}
}
#[derive(Copy, Clone)]
struct WorldTile{
passable: bool,
occupant: u32,
}
struct World{
tiles: [[WorldTile; BOARD_HEIGHT]; BOARD_WIDTH],
}
impl World{
fn new() -> World{
let mut new_world: [[WorldTile; BOARD_HEIGHT]; BOARD_WIDTH] = [[WorldTile{passable: false, occupant:0}; BOARD_HEIGHT];BOARD_WIDTH];
for x in 0..BOARD_WIDTH{
for y in 0..BOARD_HEIGHT{
if x==0||x==BOARD_WIDTH-1||y==0||y==BOARD_HEIGHT-1 {
new_world[x][y].passable = false;
}
else{
let mut rng = thread_rng();
let n:u8 = rng.gen_range(0, 10);
if n<2{
new_world[x][y].passable = false;
}
else {new_world[x][y].passable = true;}
}
}
}
World{tiles: new_world}
}
fn is_passable(&self, pos:Vec2<u32>) -> bool{
let tile = self.tiles[pos.x as usize][pos.y as usize] as WorldTile;
tile.passable
}
fn is_occupied_by(&self, pos:Vec2<u32>) -> u32{
let tile = self.tiles[pos.x as usize][pos.y as usize] as WorldTile;
tile.occupant
}
}
struct Foes{
list: HashMap<u32, Entity>
}
impl Foes{
fn generate(n:u32, texture:Texture) -> Foes{
let mut entities = HashMap::new();
for i in 2..n{
// initial foe position to be calculated here
entities.insert(i, Entity::new_pos(texture.clone(),Vec2::new(i,i), i));
}
Foes{list:entities}
}
}
struct UI{
font: Font
}
impl UI{
fn new(font:Font) -> UI{
UI{
font: font,
}
}
fn updateStats(&mut self, ctx: &mut Context, stats:Stats){
let mut health = Text::new(format!("Health: {}", stats.health), self.font.clone());
let mut attack = Text::new(format!("Attack: {}", stats.attack), self.font.clone());
let mut level = Text::new(format!("Lvl: {}", stats.level), self.font.clone());
let mut score = Text::new(format!("Score: {}", stats.score), self.font.clone());
health.draw(ctx, Vec2::new(100.0, 950.0));
attack.draw(ctx, Vec2::new(100.0, 970.0));
level.draw(ctx, Vec2::new(100.0, 990.0));
score.draw(ctx, Vec2::new(100.0, 1010.0));
}
fn updateEvents(&mut self, ctx: &mut Context, event: &str){
let mut event1 = Text::new(event, self.font.clone());
event1.draw(ctx, Vec2::new(500.0, 950.0));
}
}
#[derive(Copy, Clone)]
struct Stats{
health: u32,
attack: u32,
level: u32,
score: u32,
}
impl Stats{
fn new() -> Stats{
Stats{
health: 100,
attack: 10,
level: 1,
score: 0,
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment