Final
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								assets/ball.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/ball.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 141 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/jet.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/jet.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 268 B | 
							
								
								
									
										
											BIN
										
									
								
								assets/pad.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/pad.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 653 B | 
							
								
								
									
										18
									
								
								ball.odin
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								ball.odin
									
									
									
									
									
								
							| @ -22,7 +22,7 @@ ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool { | ||||
| 	} else { | ||||
| 		ball_position_prev := ball.position | ||||
| 		ball.velocity = rl.Vector2Rotate(ball.velocity, ball.angular_velocity * delta) | ||||
| 		if abs(ball.velocity.y) < 50 { | ||||
| 		if abs(ball.velocity.y) < 100 && ball.angular_velocity == 0 { | ||||
| 			ball.velocity.y += math.sign(ball.velocity.y) * 100 * delta | ||||
| 		} | ||||
| 		ball.position += ball.velocity * delta | ||||
| @ -55,7 +55,7 @@ ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool { | ||||
| 			offset_x := ball.position.x - pad.position.x | ||||
| 			dir.x = offset_x / (pad.size.x / 2) | ||||
| 			ball.velocity = rl.Vector2Normalize(dir) * rl.Vector2Length(ball.velocity) | ||||
| 			ball.angular_velocity = -pad.velocity.x / PAD_MAX_SPEED * math.PI / 2 | ||||
| 			ball.angular_velocity = -pad.velocity.x / PAD_MAX_SPEED * math.PI / 3 | ||||
| 		} | ||||
|  | ||||
| 		for &brick, i in bricks { | ||||
| @ -77,11 +77,17 @@ ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool { | ||||
| 			diff := clamped - ball.position | ||||
| 			normal := rl.Vector2Normalize(-diff) | ||||
| 			if rl.Vector2LengthSqr(diff) < ball.radius * ball.radius { | ||||
| 				if brick.type != .SOLID{ | ||||
| 					brick.hits -= 1 | ||||
| 				} | ||||
| 				if brick.hits <= 0 && !brick.is_flying { | ||||
| 					brick.is_flying = true | ||||
| 					brick.velocity = ball.velocity / 2 | ||||
| 					brick.angular_velocity = brick.velocity.x | ||||
| 					game.score += brick.score | ||||
| 				} | ||||
| 				if brick.hit_callback != nil { | ||||
| 					brick.hit_callback(&brick, game) | ||||
| 				} | ||||
| 				ball.position = clamped + normal * ball.radius | ||||
| 				ball.velocity = linalg.reflect(ball.velocity, normal) | ||||
| @ -92,3 +98,11 @@ ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool { | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| ball_draw :: proc(ball: ^Ball) { | ||||
| 	draw_ball_virtual(ball.position, ball.radius) | ||||
| } | ||||
|  | ||||
| draw_ball_virtual :: proc(position: Vec2, radius: f32) { | ||||
| 	rl.DrawCircleV(position, radius, rl.Color{80, 120, 255, 255}) | ||||
| 	rl.DrawCircleV(position - radius / 3, radius / 5, rl.Color{80, 160, 255, 255}) | ||||
| } | ||||
|  | ||||
							
								
								
									
										150
									
								
								brick.odin
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								brick.odin
									
									
									
									
									
								
							| @ -1,6 +1,58 @@ | ||||
| package main | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
| import "core:fmt" | ||||
|  | ||||
| Brick_Type :: enum { | ||||
| 	NORMAL, | ||||
| 	EXPLOSIVE, | ||||
| 	BONUS, | ||||
| 	HARD, | ||||
| 	SOLID | ||||
| } | ||||
|  | ||||
| brick_names := [Brick_Type]cstring { | ||||
| 	.NORMAL = "обычный", | ||||
| 	.EXPLOSIVE = "взрывной", | ||||
| 	.BONUS = "восстанавливает здоровье", | ||||
| 	.HARD = "крепкий", | ||||
| 	.SOLID = "нерушимый" | ||||
| } | ||||
|  | ||||
| brick_colors := [Brick_Type]rl.Color { | ||||
| 	.NORMAL = rl.Color{190, 190, 190, 255}, | ||||
| 	.EXPLOSIVE = rl.Color{250, 30, 30, 255}, | ||||
| 	.BONUS = rl.Color{30, 250, 30, 255}, | ||||
| 	.HARD = rl.Color{190, 190, 255, 255}, | ||||
| 	.SOLID = rl.Color{80, 80, 100, 255} | ||||
| } | ||||
|  | ||||
|  | ||||
| brick_scores := [Brick_Type]u32 { | ||||
| 	.NORMAL = 100, | ||||
| 	.HARD = 500, | ||||
| 	.BONUS = 1000, | ||||
| 	.EXPLOSIVE = 1000, | ||||
| 	.SOLID = 0, | ||||
| } | ||||
|  | ||||
| brick_hits := [Brick_Type]i32 { | ||||
| 	.NORMAL = 1, | ||||
| 	.EXPLOSIVE = 1, | ||||
| 	.BONUS = 1, | ||||
| 	.HARD = 2, | ||||
| 	.SOLID = 10000000 | ||||
| } | ||||
|  | ||||
| BrickHitCallback :: proc(brick: ^Brick, game: ^Game) | ||||
|  | ||||
| brick_callbacks := [Brick_Type]BrickHitCallback { | ||||
| 	.NORMAL = nil, | ||||
| 	.EXPLOSIVE = brick_explode, | ||||
| 	.BONUS = nil, | ||||
| 	.HARD = nil, | ||||
| 	.SOLID = nil | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -13,32 +65,114 @@ Brick :: struct { | ||||
| 	is_flying: bool, | ||||
| 	is_fixed: bool, | ||||
| 	color: rl.Color, | ||||
| 	hits: u8, | ||||
| 	hits: i32, | ||||
| 	is_to_free: bool, | ||||
| 	type: Brick_Type, | ||||
| 	score: u32, | ||||
| 	hit_callback: proc(brick: ^Brick, game: ^Game) | ||||
| } | ||||
|  | ||||
| spawn_brick :: proc(position: Vec2, size: Vec2, is_fixed: bool = true, color: rl.Color = rl.RED, hits : u8 = 1) -> Brick { | ||||
| spawn_brick :: proc(position: Vec2, size: Vec2, type: Brick_Type = Brick_Type.NORMAL) -> Brick { | ||||
| 	return Brick { | ||||
| 		position = position, | ||||
| 		velocity = Vec2{}, | ||||
| 		size = Vec2{30,20}, | ||||
| 		size = size, | ||||
| 		is_flying = false, | ||||
| 		is_fixed = is_fixed, | ||||
| 		color = color, | ||||
| 		hits = hits, | ||||
| 		is_fixed = true, | ||||
| 		type = type, | ||||
| 		color = brick_colors[type], | ||||
| 		hits = brick_hits[type], | ||||
| 		score = brick_scores[type], | ||||
| 		hit_callback = brick_callbacks[type], | ||||
| 		angle = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| brick_update :: proc(brick: ^Brick, camera: rl.Camera2D, delta: f32) { | ||||
| brick_update :: proc(brick: ^Brick, game: ^Game, delta: f32) { | ||||
| 	if !brick.is_flying { return } | ||||
| 	brick.velocity.y += 500 * delta | ||||
| 	brick.position += brick.velocity * delta | ||||
| 	brick.angle += brick.angular_velocity * delta | ||||
| 	screen_pos := rl.GetWorldToScreen2D(brick.position, camera)  | ||||
| 	screen_pos := rl.GetWorldToScreen2D(brick.position, game.camera)  | ||||
|  | ||||
| 	if brick.position.x < brick.size.x / 2 { | ||||
| 		brick.position.x = brick.size.x / 2 | ||||
| 		brick.velocity.x = -brick.velocity.x | ||||
| 	} | ||||
| 	if brick.position.x > GameField.x - brick.size.x / 2 { | ||||
| 		brick.position.x = GameField.x - brick.size.x / 2 | ||||
| 		brick.velocity.x = -brick.velocity.x | ||||
| 	} | ||||
|  | ||||
| 	if rl.CheckCollisionCircleRec(brick.position, brick.size.x / 2, rl.Rectangle{ | ||||
| 		game.pad.position.x - game.pad.size.x / 2, | ||||
| 		game.pad.position.y - game.pad.size.y / 2, | ||||
| 		game.pad.size.x, | ||||
| 		game.pad.size.y | ||||
| 	}) { | ||||
| 		if brick.type != .BONUS { | ||||
| 			game.pad.health -= 30 | ||||
| 		} else { | ||||
| 			game.pad.health = min(game.pad.health + 30, 100) | ||||
| 		} | ||||
| 		brick.is_to_free = true | ||||
| 	} | ||||
| 	if !rl.CheckCollisionRecs(rl.Rectangle{screen_pos.x - brick.size.x / 2, screen_pos.y - brick.size.y / 2, brick.size.x, brick.size.y}, | ||||
| 							 rl.Rectangle{0, 0, WINDOWF.x, WINDOWF.y} | ||||
| 	) { | ||||
| 		brick.is_to_free = true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| brick_draw :: proc(brick: ^Brick) { | ||||
| 	rl.DrawRectanglePro( | ||||
| 		rl.Rectangle{brick.position.x, brick.position.y, brick.size.x, brick.size.y}, | ||||
| 		brick.size / 2, | ||||
| 		brick.angle, | ||||
| 		brick.color | ||||
| 	) | ||||
| 	if brick.type == .HARD && brick.hits <= 1 { | ||||
| 		rl.DrawRectanglePro( | ||||
| 			rl.Rectangle{brick.position.x, brick.position.y, brick.size.x / 1.5, brick.size.y / 1.5}, | ||||
| 			brick.size / 3, | ||||
| 			brick.angle, | ||||
| 			rl.ColorTint(brick.color, rl.Color{180, 180, 180, 255}) | ||||
| 		) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| brick_explode :: proc(brick: ^Brick, game: ^Game) { | ||||
| 	radius := brick.size.x * 2 | ||||
| 	brick.is_to_free = true | ||||
| 	 | ||||
| 	explode(brick.position, radius, 0.4) | ||||
| 	for &other, i in game.bricks { | ||||
| 		if other.is_to_free { | ||||
| 			continue | ||||
| 		} | ||||
| 		if &other == brick { | ||||
| 			continue | ||||
| 		} | ||||
| 		diff := other.position - brick.position | ||||
| 		dir := rl.Vector2Normalize(diff) | ||||
| 		dis := rl.Vector2Length(diff) | ||||
| 		if dis > radius { | ||||
| 			continue | ||||
| 		} | ||||
| 		switch other.type { | ||||
| 		case .NORMAL, .HARD, .SOLID:  | ||||
| 			other.hits = 0 | ||||
| 			other.is_flying = true | ||||
| 			other.velocity += dir * (radius - dis + 50) * 3 | ||||
| 			other.angular_velocity = other.velocity.x * 2 | ||||
| 			game.score += other.score | ||||
| 		case .EXPLOSIVE, .BONUS: | ||||
| 		  	if other.hit_callback != nil { | ||||
| 				other.hit_callback(&other, game) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										46
									
								
								explosion.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								explosion.odin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| package main | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
| import "core:slice" | ||||
| import "core:math/ease" | ||||
|  | ||||
| Explosion :: struct{ | ||||
| 	position: Vec2, | ||||
| 	radius: f32, | ||||
| 	max_radius: f32, | ||||
| 	time: f32, | ||||
| 	duration: f32, | ||||
| 	to_free: bool, | ||||
| } | ||||
|  | ||||
| explosions_buf : [256]Explosion | ||||
| explosions : [dynamic]Explosion | ||||
|  | ||||
| explosions_init :: proc() { | ||||
| 	explosions = slice.into_dynamic(explosions_buf[:]) | ||||
| } | ||||
|  | ||||
| explode :: proc(position: Vec2, max_radius: f32, duration: f32) { | ||||
| 	append(&explosions, Explosion { | ||||
| 		position = position, | ||||
| 		max_radius = max_radius, | ||||
| 		duration = duration | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| explosions_process :: proc(delta: f32) { | ||||
| 	#reverse for &e, i in explosions { | ||||
| 		e.time += delta | ||||
| 		e.radius = ease.back_out(e.time / e.duration) * e.max_radius | ||||
| 		if e.time > e.duration { | ||||
| 			unordered_remove(&explosions, i) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| explosions_draw :: proc() { | ||||
| 	#reverse for &e, i in explosions { | ||||
| 		color := rl.ColorAlpha(rl.YELLOW, 1 - ease.exponential_in(e.time / e.duration)) | ||||
| 		rl.DrawCircleV(e.position, e.radius, color) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										158
									
								
								game.odin
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								game.odin
									
									
									
									
									
								
							| @ -5,6 +5,9 @@ import "vendor:raylib/rlgl" | ||||
| import "core:fmt" | ||||
| import "core:math" | ||||
| import "core:math/ease" | ||||
| import "core:math/rand" | ||||
| import "core:strings" | ||||
| import "core:strconv" | ||||
|  | ||||
| // Virtual game field dimensions | ||||
| GameField := Vec2{800, 600} | ||||
| @ -16,42 +19,93 @@ Game :: struct { | ||||
| 	balls: [dynamic]Ball, | ||||
| 	bricks: [dynamic]Brick, | ||||
| 	camera: rl.Camera2D, | ||||
| 	score: u32, | ||||
| 	levels_done: u32, | ||||
|  | ||||
| 	levelsize: Vec2i, | ||||
|  | ||||
| 	background: rl.Texture | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| game_init :: proc(prev: ^GameState = nil) -> ^GameState { | ||||
| 	state := new(Game) | ||||
| 	state.previous = prev | ||||
| 	state.variant = state | ||||
| 	state.draw = game_draw | ||||
| 	state.update = game_update | ||||
| 	state.free = game_free | ||||
| 	state.lives = 3 | ||||
| 	state.previous = prev | ||||
| 	state.camera = rl.Camera2D{ | ||||
| 	state.levelsize = Vec2i{6, 4} | ||||
|  | ||||
| 	backimage := rl.GenImageChecked(800, 600, 80, 60, rl.Color{70, 20, 120, 255}, rl.Color{80, 30, 130, 255}) | ||||
| 	defer rl.UnloadImage(backimage) | ||||
| 	state.background = rl.LoadTextureFromImage(backimage) | ||||
|  | ||||
| 	game_setup(state) | ||||
| 	return state | ||||
| } | ||||
|  | ||||
| game_setup :: proc(game: ^Game) { | ||||
| 	#reverse for ball, i in game.balls { | ||||
| 		unordered_remove(&game.balls, i) | ||||
| 	} | ||||
| 	#reverse for brick, i in game.bricks { | ||||
| 		unordered_remove(&game.bricks, i) | ||||
| 	} | ||||
| 	game.lives = 3 | ||||
| 	game.camera = rl.Camera2D{ | ||||
| 		zoom = 1, | ||||
| 		target = GameField / 2, | ||||
| 	} | ||||
| 	state.pad = Pad{ | ||||
| 	game.pad = Pad{ | ||||
| 		position = Vec2{GameField.x / 2, GameField.y - 40}, | ||||
| 		size = {80, 10}, | ||||
| 		size = {80, 20}, | ||||
| 		health = 100, | ||||
| 	} | ||||
| 	append(&state.balls, Ball{ | ||||
| 	game_gen_level(game) | ||||
| } | ||||
|  | ||||
| game_init_ball :: proc(game: ^Game) { | ||||
| 	game_clear_balls(game) | ||||
| 	append(&game.balls, Ball{ | ||||
| 		radius = 8, | ||||
| 		position = Vec2{GameField.x / 2, GameField.y - 40 - 8}, | ||||
| 		position = Vec2{GameField.x / 2, -100}, | ||||
| 		is_on_pad = true, | ||||
| 		pad_offset = Vec2{0, -16}, | ||||
| 		pad_offset = Vec2{0, -18}, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| game_clear_balls :: proc(game: ^Game) { | ||||
| 	#reverse for &ball, i in game.balls { | ||||
| 		unordered_remove(&game.balls, i) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| game_clear_bricks :: proc(game: ^Game) { | ||||
| 	#reverse for &brick, i in game.bricks { | ||||
| 		unordered_remove(&game.bricks, i) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| game_gen_level :: proc(game: ^Game) { | ||||
| 	explosions_init() | ||||
| 	game_init_ball(game) | ||||
| 	game_clear_bricks(game) | ||||
|  | ||||
| 	brick_size := Vec2{40, 30} | ||||
| 	brick_margin := brick_size + 10 | ||||
| 	center := Vec2{GameField.x / 2, 100} | ||||
|  | ||||
| 	offset : f32 = 0 | ||||
| 	for y : f32 = 40; y < GameField.y / 2; y += 35 { | ||||
| 		for x : f32 = 40 + offset; x < GameField.x - 40; x += 40 { | ||||
| 			append(&state.bricks, spawn_brick(Vec2{x, y}, Vec2{30, 20}, color = rl.YELLOW)) | ||||
| 	for y : i32 = 0; y < game.levelsize.y; y += 1 { | ||||
| 		for x : i32 = 0; x < game.levelsize.x / 2; x += 1{ | ||||
| 			choice := rand.choice([]Brick_Type{.NORMAL, .NORMAL, .NORMAL, .HARD, .HARD, .SOLID, .EXPLOSIVE, .BONUS}) | ||||
| 			append(&game.bricks, spawn_brick(center + {-brick_margin.x / 2 - f32(x) * brick_margin.x, f32(y) * brick_margin.y}, brick_size, choice)) | ||||
| 			append(&game.bricks, spawn_brick(center + {+brick_margin.x / 2 + f32(x) * brick_margin.x, f32(y) * brick_margin.y}, brick_size, choice)) | ||||
| 		} | ||||
| 		if offset == 0 { offset = 20 } else { offset = 0 } | ||||
| 	} | ||||
| 	return state | ||||
|  | ||||
| } | ||||
|  | ||||
| game_update :: proc(state: ^GameState, delta: f32) { | ||||
| @ -63,6 +117,11 @@ game_update :: proc(state: ^GameState, delta: f32) { | ||||
| 		pause := pause_init(game) | ||||
| 		stack_push(pause) | ||||
| 	} | ||||
| 	if rl.IsKeyPressed(rl.KeyboardKey.P) { | ||||
| 		for &brick in bricks { | ||||
| 			brick.is_flying = true | ||||
| 		} | ||||
| 	} | ||||
| 	pad_update(&game.pad, delta) | ||||
| 	if rl.IsKeyPressed(rl.KeyboardKey.SPACE) { | ||||
| 		for &ball, i in balls { | ||||
| @ -86,62 +145,97 @@ game_update :: proc(state: ^GameState, delta: f32) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if pad.health <= 0 { | ||||
|  | ||||
| 			go := gameover_init(game) | ||||
| 			stack_push(go) | ||||
| 			return | ||||
| 	} | ||||
|  | ||||
| 	if all_balls_outside { | ||||
| 		if lives == 0 {  | ||||
| 			go := gameover_init(game) | ||||
| 			stack_push(go) | ||||
| 			return | ||||
| 		} | ||||
| 		lives -= 1 | ||||
| 		append(&balls, Ball{ | ||||
| 			radius = 8, | ||||
| 			position = Vec2{GameField.x / 2, GameField.y - 40 - 8}, | ||||
| 			is_on_pad = true, | ||||
| 			pad_offset = Vec2{0, -16}, | ||||
| 			pad_offset = Vec2{0, -18}, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	only_solid := true | ||||
| 	#reverse for &brick, i in bricks { | ||||
| 		brick_update(&brick, camera, delta) | ||||
| 		brick_update(&brick, game, delta) | ||||
| 		if brick.type != .SOLID { | ||||
| 			only_solid = false | ||||
| 		} | ||||
| 		if brick.is_to_free { | ||||
| 			unordered_remove(&bricks, i) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if only_solid { | ||||
| 		leveldone := leveldone_init(game) | ||||
| 		stack_push(leveldone) | ||||
| 	} | ||||
|  | ||||
| 	explosions_process(delta) | ||||
|  | ||||
| 	camera.offset = WINDOWF / 2 | ||||
| 	camera.zoom = (WINDOWF.y / GameField.y) / 1.1 | ||||
| } | ||||
|  | ||||
| game_draw :: proc(state: ^GameState) { | ||||
| 	game := transmute(^Game)state | ||||
| 	using game | ||||
|  | ||||
| 	rl.BeginMode2D(camera) | ||||
| 	rl.BeginMode2D(game.camera) | ||||
|  | ||||
|  | ||||
| 	rl.DrawTextureV(game.background, {}, rl.WHITE) | ||||
| //	rl.DrawRectangleGradientV(0, 0, i32(GameField.x), i32(GameField.y), rl.RED, rl.RAYWHITE) | ||||
| 	for &ball, i in game.balls { | ||||
| 		ball_draw(&ball) | ||||
| 	} | ||||
| 	pad_draw(&game.pad) | ||||
|  | ||||
| 	rl.DrawRectangleGradientV(0, 0, i32(GameField.x), i32(GameField.y), rl.RED, rl.RAYWHITE) | ||||
| 	rl.DrawRectanglePro(rl.Rectangle{pad.position.x, pad.position.y, pad.size.x, pad.size.y}, pad.size / 2, 0, rl.GREEN) | ||||
|  | ||||
| 	for ball, i in balls { | ||||
| 		rl.DrawCircleV(ball.position, ball.radius, rl.BLUE) | ||||
| 	for &brick, i in game.bricks { | ||||
| 		brick_draw(&brick) | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	for brick, i in bricks { | ||||
| 		rl.DrawRectanglePro(rl.Rectangle{brick.position.x, brick.position.y, brick.size.x, brick.size.y}, brick.size / 2, brick.angle, brick.color) | ||||
| 	} | ||||
|  | ||||
| 	//rl.DrawText(rl.TextFormat("%f\n%f\n%f\n%f", pad.position.x, pad.position.y, pad.velocity.x, pad.velocity.y), 0, 0, 20, rl.BLACK) | ||||
| 	explosions_draw() | ||||
| 	 | ||||
| 	rl.EndMode2D() | ||||
|  | ||||
| 	for i : u8 = 0; i < lives; i += 1 { | ||||
| 		rl.DrawCircleV(Vec2{20 + 20 * f32(i), 20}, 10, rl.BLUE) | ||||
| 	game_field_zoomed := GameField * game.camera.zoom | ||||
|  | ||||
| 	buf : [12]byte | ||||
| 	score_string := strconv.itoa(buf[:], int(game.score)) | ||||
| 	score_ui := strings.right_justify(score_string, 8, "0") | ||||
| 	score_ui_c := strings.unsafe_string_to_cstring(score_ui) | ||||
|  | ||||
| 	size := rl.MeasureTextEx(FontUI, score_ui_c, 48, 2) | ||||
| 	rl.DrawTextPro(FontUI, score_ui_c, {WINDOWF.x / 2, WINDOWF.y - 28}, size / 2, 0, 48, 2, rl.WHITE) | ||||
|  | ||||
| 	level_string := rl.TextFormat("Уровень %d", game.levels_done + 1) | ||||
| 	size = rl.MeasureTextEx(FontUI, level_string, 48, 2) | ||||
| 	rl.DrawTextPro(FontUI, level_string, {WINDOWF.x / 2 + game_field_zoomed.x / 2, WINDOWF.y - 28}, {size.x, size.y / 2}, 0, 48, 2, rl.WHITE) | ||||
| 	 | ||||
| 	for i : u8 = 0; i < game.lives; i += 1 { | ||||
| 		draw_ball_virtual({WINDOWF.x / 2 - game_field_zoomed.x / 2 + 16 + f32(i * 40), WINDOWF.y - 28}, 16) | ||||
| 	} | ||||
| 	rl.DrawTextEx(FontUI, rl.TextFormat("%d", len(bricks)), {}, f32(48), 2, rl.BLACK) | ||||
| 	rl.DrawTextEx(FontUI, rl.TextFormat("%d", len(balls)), {0, 100}, f32(48), 2, rl.BLACK) | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| game_free :: proc(state: ^GameState) { | ||||
| 	game := transmute(^Game)state  | ||||
|  | ||||
| 	rl.UnloadTexture(game.background) | ||||
|  | ||||
| 	delete(game.bricks) | ||||
| 	delete(game.balls) | ||||
|  | ||||
|  | ||||
							
								
								
									
										84
									
								
								gameover.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								gameover.odin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| package main | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
| import "core:math/ease" | ||||
|  | ||||
|  | ||||
| GameOver :: struct { | ||||
| 	using state: GameState, | ||||
| 	position: Vec2, | ||||
| 	size: Vec2, | ||||
| 	ready_to_go: bool, | ||||
| } | ||||
|  | ||||
| gameover_init :: proc(prev: ^GameState = nil) -> ^GameState { | ||||
| 	state := new(GameOver) | ||||
| 	state.variant = state | ||||
| 	state.position = Vec2{WINDOWF.x / 2, WINDOWF.y + 300} | ||||
| 	state.size = {700, 500} | ||||
| 	state.update = gameover_update | ||||
| 	state.draw = gameover_draw | ||||
| 	state.free = gameover_free | ||||
|  | ||||
| 	state.previous = prev | ||||
| 	tween_to(&state.position.y, WINDOWF.y / 2, 1, ease.Ease.Back_Out, state, gameover_ready) | ||||
| 	 | ||||
| 	return state | ||||
| } | ||||
|  | ||||
| gameover_update :: proc(state: ^GameState, delta: f32) { | ||||
| 	gameover := transmute(^GameOver)state | ||||
| 	if gameover.ready_to_go { | ||||
| 		if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) { | ||||
| 			stack_pop() | ||||
| 			stack_pop() | ||||
| 			return  | ||||
| 		} | ||||
| 		if rl.IsKeyPressed(rl.KeyboardKey.ENTER) { | ||||
| 			stack_pop() | ||||
| 			game := transmute(^Game)stack_top() | ||||
| 			game_setup(game) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| gameover_draw :: proc(state: ^GameState) { | ||||
| 	gameover := transmute(^GameOver)state | ||||
| 	 | ||||
| 	if state.previous != nil { | ||||
| 		state.previous->draw() | ||||
| 	} | ||||
|  | ||||
| 	TitleFontSize :: 96 | ||||
| 	TitleSpacing :: 3 | ||||
| 	TitleText :: "ИГРА ОКОНЧЕНА" | ||||
| 	TitleSize := rl.MeasureTextEx(FontTitle, TitleText, TitleFontSize, TitleSpacing) | ||||
|  | ||||
| 	SubtitleText := [3]cstring{"Нажмите Enter", "чтобы начать сначала", "Или Escape для выхода"} | ||||
| 	SubtitleFontSize :: 48 | ||||
| 	SubtitleSpacing :: 2 | ||||
| 	SubtitleSizes := [3]Vec2{} | ||||
| 	for c, i in SubtitleText { | ||||
| 		SubtitleSizes = rl.MeasureTextEx(FontUI, c, SubtitleFontSize, SubtitleSpacing) | ||||
| 	} | ||||
|  | ||||
| 	 | ||||
| 	rl.DrawRectangleV(gameover.position - gameover.size / 2, gameover.size, rl.Color{90, 30, 150, 255}) | ||||
|  | ||||
| 	rl.DrawTextPro(FontTitle, TitleText, gameover.position - {0, 100}, TitleSize / 2, 0, TitleFontSize, TitleSpacing, rl.WHITE) | ||||
| 	for c, i in SubtitleText { | ||||
| 		rl.DrawTextPro(FontUI, c, gameover.position - {0, f32(10 - i * 50)}, SubtitleSizes[i] / 2, 0, SubtitleFontSize, SubtitleSpacing, rl.WHITE) | ||||
| 	} | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| gameover_free :: proc(state: ^GameState) { | ||||
| 	free(state) | ||||
| } | ||||
|  | ||||
| gameover_ready :: proc(state: rawptr) { | ||||
| 	gameover := transmute(^GameOver)state | ||||
| 	gameover.ready_to_go = true | ||||
| } | ||||
							
								
								
									
										62
									
								
								howtoplay.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								howtoplay.odin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| package main | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
| import "core:math/ease" | ||||
|  | ||||
|  | ||||
| Howtoplay :: struct { | ||||
| 	using state: GameState, | ||||
| } | ||||
|  | ||||
| howtoplay_init :: proc(prev: ^GameState = nil) -> ^GameState { | ||||
| 	state := new(Howtoplay) | ||||
| 	state.variant = state | ||||
| 	state.update = howtoplay_update | ||||
| 	state.draw = howtoplay_draw  | ||||
| 	state.free = howtoplay_free | ||||
| 	state.previous = prev | ||||
|  | ||||
| 	return state | ||||
| } | ||||
|  | ||||
| howtoplay_update :: proc(state: ^GameState, delta: f32) { | ||||
| 	howtoplay := transmute(^Howtoplay)state | ||||
| 	if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) { | ||||
| 		stack_pop() | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| howtoplay_draw :: proc(state: ^GameState) { | ||||
| 	howtoplay := transmute(^Howtoplay)state | ||||
|  | ||||
| 	TitleFontSize :: 96 | ||||
| 	TitleSpacing :: 3 | ||||
| 	TitleText :: "ARKADODGE" | ||||
| 	TitleSize := rl.MeasureTextEx(FontTitle, TitleText, TitleFontSize, TitleSpacing) | ||||
| 	rl.DrawTextPro(FontTitle, "ARKADODGE", {WINDOWF.x - 50, 50}, {TitleSize.x, 0}, 0, 96, 3, rl.WHITE) | ||||
|  | ||||
| 	Text :: [?]cstring{ | ||||
| 		"Используй Пробел для запуска шара", | ||||
| 		"Стрелки для перемещения биты", | ||||
| 		"Не дай шарику упасть вниз!", | ||||
| 		"", | ||||
| 		"Также блоки, падающие на биту, уменьшают её здоровье!" | ||||
| 	} | ||||
|  | ||||
| 	for t, y in Text { | ||||
| 		rl.DrawTextPro(FontUI, t, {10, 100 + f32(y) * 38}, {0, 0}, 0, 32, 1, rl.WHITE) | ||||
| 	} | ||||
|  | ||||
| 	for c, t in brick_names { | ||||
| 		y := cast(i32)t | ||||
| 		rl.DrawRectangleV({WINDOWF.x / 2, 100 + f32(y) * 40}, {30, 20}, brick_colors[t]) | ||||
| 		rl.DrawTextPro(FontUI, c, {WINDOWF.x / 2 + 40, 100 + f32(y) * 40}, {0, 14}, 0, 32, 2, rl.WHITE) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| howtoplay_free :: proc(state: ^GameState) { | ||||
| 	free(state) | ||||
| } | ||||
							
								
								
									
										79
									
								
								leveldone.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								leveldone.odin
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| package main | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
| import "core:math/ease" | ||||
|  | ||||
|  | ||||
| LevelDone :: struct { | ||||
| 	using state: GameState, | ||||
| 	position: Vec2, | ||||
| 	size: Vec2, | ||||
| 	ready_to_go: bool, | ||||
| } | ||||
|  | ||||
| leveldone_init :: proc(prev: ^GameState = nil) -> ^GameState { | ||||
| 	state := new(LevelDone) | ||||
| 	state.variant = state | ||||
| 	state.position = Vec2{WINDOWF.x / 2, WINDOWF.y + 300} | ||||
| 	state.size = {800, 400} | ||||
| 	state.update = leveldone_update | ||||
| 	state.draw = leveldone_draw | ||||
| 	state.free = leveldone_free | ||||
|  | ||||
| 	state.previous = prev | ||||
| 	tween_to(&state.position.y, WINDOWF.y / 2, 1, ease.Ease.Back_Out, state, leveldone_ready) | ||||
| 	 | ||||
| 	return state | ||||
| } | ||||
|  | ||||
| leveldone_update :: proc(state: ^GameState, delta: f32) { | ||||
| 	leveldone := transmute(^LevelDone)state | ||||
| 	if leveldone.ready_to_go { | ||||
| 		if rl.IsKeyPressed(rl.KeyboardKey.ENTER) { | ||||
| 			stack_pop() | ||||
| 			game := transmute(^Game)stack_top() | ||||
| 			game.levels_done += 1 | ||||
| 			if game.levels_done > 0 && game.levels_done % 2 == 0 { | ||||
| 				game.levelsize += {2, 1} | ||||
| 				game.levelsize.x = min(game.levelsize.x, 13) | ||||
| 				game.levelsize.y = min(game.levelsize.y, 8) | ||||
| 			} | ||||
| 			game_gen_level(game) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| leveldone_draw :: proc(state: ^GameState) { | ||||
| 	leveldone := transmute(^LevelDone)state | ||||
|  | ||||
| 	if state.previous != nil { | ||||
| 		state.previous->draw() | ||||
| 	} | ||||
|  | ||||
| 	TitleFontSize :: 96 | ||||
| 	TitleSpacing :: 3 | ||||
| 	TitleText :: "УРОВЕНЬ ПРОЙДЕН" | ||||
| 	TitleSize := rl.MeasureTextEx(FontTitle, TitleText, TitleFontSize, TitleSpacing) | ||||
|  | ||||
| 	SubtitleText :: "Нажмите Enter чтобы продолжить" | ||||
| 	SubtitleFontSize :: 48 | ||||
| 	SubtitleSpacing :: 2 | ||||
| 	SubtitleSize := rl.MeasureTextEx(FontUI, SubtitleText, SubtitleFontSize, SubtitleSpacing) | ||||
|  | ||||
| 	 | ||||
| 	rl.DrawRectangleV(leveldone.position - leveldone.size / 2, leveldone.size, rl.Color{90, 30, 150, 255}) | ||||
|  | ||||
| 	rl.DrawTextPro(FontTitle, TitleText, leveldone.position - {0, 50}, TitleSize / 2, 0, TitleFontSize, TitleSpacing, rl.WHITE) | ||||
| 	rl.DrawTextPro(FontUI, SubtitleText, leveldone.position + {0, 20}, SubtitleSize / 2, 0, SubtitleFontSize, SubtitleSpacing, rl.WHITE) | ||||
|  | ||||
| } | ||||
|  | ||||
| leveldone_free :: proc(state: ^GameState) { | ||||
| 	free(state) | ||||
| } | ||||
|  | ||||
| leveldone_ready :: proc(state: rawptr) { | ||||
| 	leveldone := transmute(^LevelDone)state | ||||
| 	leveldone.ready_to_go = true | ||||
| } | ||||
| @ -19,7 +19,7 @@ FontUI: rl.Font | ||||
| WindowShouldExit := false | ||||
|  | ||||
| main :: proc() { | ||||
| 	rl.SetConfigFlags(rl.ConfigFlags{.FULLSCREEN_MODE, .VSYNC_HINT, .WINDOW_MAXIMIZED,  .WINDOW_UNDECORATED}) | ||||
| 	rl.SetConfigFlags(rl.ConfigFlags{.FULLSCREEN_MODE, .VSYNC_HINT, }) | ||||
| 	monitor := rl.GetCurrentMonitor() | ||||
| 	rl.InitWindow(0, 0, "SinePong") | ||||
| 	rl.SetTargetFPS(9999) | ||||
| @ -76,6 +76,7 @@ main :: proc() { | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| /* | ||||
|  | ||||
|  TODO: | ||||
| @ -83,7 +84,7 @@ main :: proc() { | ||||
| [ ] Проверка завершения уровня | ||||
| [ ] Генерация уровней | ||||
| [ ] (Опционально) редактор уровней с сохранением в файл | ||||
| [ ] Меню | ||||
| [x] Меню | ||||
| [ ] Гейм овер | ||||
| [ ] Бонусы | ||||
| [ ] Визуальное оформление | ||||
|  | ||||
| @ -7,14 +7,12 @@ import "core:math/ease" | ||||
| Menu_Buttons :: enum { | ||||
| 	START, | ||||
| 	HOW_TO_PLAY, | ||||
| 	FULLSCREEN, | ||||
| 	EXIT | ||||
| } | ||||
|  | ||||
| menu_strings := [Menu_Buttons]cstring { | ||||
| 	.START = "Старт", | ||||
| 	.HOW_TO_PLAY = "Как играть?", | ||||
| 	.FULLSCREEN = "Полный экран", | ||||
| 	.EXIT = "Выход" | ||||
| } | ||||
|  | ||||
| @ -30,7 +28,7 @@ menu_init :: proc(prev: ^GameState = nil) -> ^GameState { | ||||
| 	state.variant = state | ||||
| 	state.list = MenuList(Menu_Buttons){ | ||||
| 		state = state, | ||||
| 		position = {300, 300}, | ||||
| 		position = {100, WINDOWF.y / 2}, | ||||
| 		line_size = 60, | ||||
| 		font_size = 48, | ||||
| 		elements = &menu_strings, | ||||
| @ -47,6 +45,7 @@ menu_init :: proc(prev: ^GameState = nil) -> ^GameState { | ||||
|  | ||||
| menu_update :: proc(state: ^GameState, delta: f32) { | ||||
| 	menu := transmute(^Menu)state | ||||
| 	menu.list.position.y = WINDOWF.y / 2 | ||||
| 	 | ||||
| 	menu_list_update(&menu.list) | ||||
| } | ||||
| @ -56,8 +55,9 @@ menu_button_pressed :: proc(state: ^GameState, el: Menu_Buttons) { | ||||
| 		case .START: | ||||
| 			game := game_init(state) | ||||
| 			stack_push(game) | ||||
| 		case .FULLSCREEN: | ||||
| 		case .HOW_TO_PLAY: | ||||
| 			howtoplay := howtoplay_init(state) | ||||
| 			stack_push(howtoplay) | ||||
| 		case .EXIT: | ||||
| 			WindowShouldExit = true | ||||
| 			return | ||||
|  | ||||
							
								
								
									
										9
									
								
								pad.odin
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								pad.odin
									
									
									
									
									
								
							| @ -13,10 +13,12 @@ Pad :: struct { | ||||
| 	position: Vec2,  | ||||
| 	velocity: Vec2, | ||||
| 	size: Vec2, | ||||
| 	health: f32, | ||||
| } | ||||
|  | ||||
|  | ||||
| pad_update :: proc(pad: ^Pad, delta: f32) { | ||||
| 	pad.health = min(pad.health + delta, 100) | ||||
| 	target : f32 = 0 | ||||
| 	if rl.IsKeyDown(rl.KeyboardKey.LEFT) {  | ||||
| 		target = -PAD_MAX_SPEED | ||||
| @ -53,4 +55,11 @@ pad_update :: proc(pad: ^Pad, delta: f32) { | ||||
| 		pad.position.x = GameField.x - pad_half_width  | ||||
| 		pad.velocity.x = 0 | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| pad_draw :: proc(pad: ^Pad) { | ||||
| 	rl.DrawRectanglePro(rl.Rectangle{pad.position.x, pad.position.y, pad.size.x, pad.size.y}, pad.size / 2, 0, rl.RED) | ||||
| 	hbar_x := pad.health / 100 * pad.size.x | ||||
| 	rl.DrawRectanglePro(rl.Rectangle{pad.position.x, pad.position.y, hbar_x, pad.size.y/2}, {hbar_x / 2, pad.size.y / 4}, 0, rl.GREEN) | ||||
| } | ||||
|  | ||||
| @ -6,13 +6,11 @@ import "core:math/ease" | ||||
|  | ||||
| Pause_Buttons :: enum { | ||||
| 	RESUME, | ||||
| 	FULLSCREEN, | ||||
| 	EXIT | ||||
| } | ||||
|  | ||||
| pause_strings := [Pause_Buttons]cstring { | ||||
| 	.RESUME = "Продолжить", | ||||
| 	.FULLSCREEN = "Полный экран", | ||||
| 	.EXIT = "Выход" | ||||
| } | ||||
|  | ||||
| @ -46,6 +44,10 @@ pause_init :: proc(prev: ^GameState = nil) -> ^GameState { | ||||
| pause_update :: proc(state: ^GameState, delta: f32) { | ||||
| 	menu := transmute(^Pause)state | ||||
| 	 | ||||
| 	if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) { | ||||
| 		stack_pop() | ||||
| 		return | ||||
| 	} | ||||
| 	menu_list_update(&menu.list) | ||||
| } | ||||
|  | ||||
| @ -53,7 +55,6 @@ pause_button_pressed :: proc(state: ^GameState, el: Pause_Buttons) { | ||||
| 	switch el { | ||||
| 		case .RESUME: | ||||
| 			stack_pop() | ||||
| 		case .FULLSCREEN: | ||||
| 		case .EXIT: | ||||
| 			stack_pop() | ||||
| 			stack_pop() | ||||
| @ -77,5 +78,6 @@ pause_draw :: proc(state: ^GameState) { | ||||
| } | ||||
|  | ||||
| pause_free :: proc(state: ^GameState) { | ||||
| 	pause := transmute(^Pause)state | ||||
| 	free(state) | ||||
| } | ||||
|  | ||||
| @ -2,7 +2,7 @@ package main | ||||
|  | ||||
| import "core:slice" | ||||
|  | ||||
| StateVariant :: union{^Game, ^Menu, ^Pause} | ||||
| StateVariant :: union{^Game, ^Menu, ^Pause, ^GameOver, ^LevelDone, ^Howtoplay} | ||||
|  | ||||
| GameState :: struct { | ||||
| 	update: proc(state: ^GameState, delta: f32), | ||||
| @ -46,3 +46,7 @@ stack_pop :: proc() -> (bool) { | ||||
| 	state->free() | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| stack_top :: proc() -> ^GameState { | ||||
| 	return state_stack[len(state_stack)-1] | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user