add laser weapon
This commit is contained in:
parent
8d5e7da6a6
commit
ba264d7bc5
@ -7,6 +7,7 @@ const State = @import("./state.zig");
|
||||
const Engine = @import("./engine/root.zig");
|
||||
const Nanoseconds = Engine.Nanoseconds;
|
||||
const Vec2 = Engine.Vec2;
|
||||
const Line = Engine.Math.Line;
|
||||
const Rect = Engine.Math.Rect;
|
||||
const Range = Engine.Math.Range;
|
||||
const rgb = Engine.Math.rgb;
|
||||
@ -39,12 +40,19 @@ const Kinetic = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const Gun = enum {
|
||||
pistol,
|
||||
bomb,
|
||||
laser,
|
||||
};
|
||||
|
||||
const Player = struct {
|
||||
kinetic: Kinetic = .{},
|
||||
health: u32 = 0,
|
||||
max_health: u32 = 0,
|
||||
last_shot_at: ?Nanoseconds = null,
|
||||
invincible_until: ?Nanoseconds = null,
|
||||
gun: ?Gun = null,
|
||||
|
||||
pub fn getRect(self: Player) Rect {
|
||||
return getCenteredRect(self.kinetic.pos, 16);
|
||||
@ -60,6 +68,14 @@ const Bullet = struct {
|
||||
dead: bool = false
|
||||
};
|
||||
|
||||
const Laser = struct {
|
||||
origin: Vec2,
|
||||
dir: Vec2,
|
||||
size: f32,
|
||||
created_at: Nanoseconds,
|
||||
duration: Nanoseconds
|
||||
};
|
||||
|
||||
const Enemy = struct {
|
||||
kinetic: Kinetic,
|
||||
speed: f32,
|
||||
@ -105,9 +121,11 @@ gpa: Allocator,
|
||||
assets: *Assets,
|
||||
rng: RNGState,
|
||||
|
||||
enemies: std.ArrayList(Enemy) = .empty,
|
||||
|
||||
player: Player = .{},
|
||||
bullets: std.ArrayList(Bullet) = .empty,
|
||||
enemies: std.ArrayList(Enemy) = .empty,
|
||||
lasers: std.ArrayList(Laser) = .empty,
|
||||
|
||||
pickups: std.ArrayList(Pickup) = .empty,
|
||||
next_pickup_spawn_at: Nanoseconds,
|
||||
@ -131,6 +149,7 @@ pub fn init(gpa: Allocator, seed: u64, assets: *Assets, state: State) CombatScre
|
||||
.kinetic = .{
|
||||
.pos = world_size.divideScalar(2)
|
||||
},
|
||||
.gun = .pistol,
|
||||
.health = state.max_health,
|
||||
.max_health = state.max_health,
|
||||
},
|
||||
@ -144,6 +163,7 @@ pub fn deinit(self: *CombatScreen) void {
|
||||
self.waves.deinit(self.gpa);
|
||||
self.pickups.deinit(self.gpa);
|
||||
self.spawned_waves.deinit(self.gpa);
|
||||
self.lasers.deinit(self.gpa);
|
||||
}
|
||||
|
||||
pub fn spawnEnemy(self: *CombatScreen) !void {
|
||||
@ -291,6 +311,14 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
||||
}
|
||||
dir = dir.normalized();
|
||||
|
||||
if (frame.isKeyPressed(._1)) {
|
||||
self.player.gun = .pistol;
|
||||
} else if (frame.isKeyPressed(._2)) {
|
||||
self.player.gun = .bomb;
|
||||
} else if (frame.isKeyPressed(._3)) {
|
||||
self.player.gun = .laser;
|
||||
}
|
||||
|
||||
const acceleration = 1500;
|
||||
|
||||
self.player.kinetic.acc = dir.multiplyScalar(acceleration);
|
||||
@ -319,13 +347,31 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
||||
|
||||
if (frame.isMouseDown(.left) and cooldown_complete) {
|
||||
self.player.last_shot_at = frame.time_ns;
|
||||
try self.bullets.append(self.gpa, .{
|
||||
.kinetic = .{
|
||||
.pos = self.player.kinetic.pos,
|
||||
},
|
||||
.dir = bullet_dir,
|
||||
.speed = 50
|
||||
});
|
||||
|
||||
if (self.player.gun) |gun| {
|
||||
switch (gun) {
|
||||
.pistol => {
|
||||
try self.bullets.append(self.gpa, .{
|
||||
.kinetic = .{
|
||||
.pos = self.player.kinetic.pos,
|
||||
},
|
||||
.dir = bullet_dir,
|
||||
.speed = 50
|
||||
});
|
||||
},
|
||||
.bomb => {
|
||||
},
|
||||
.laser => {
|
||||
try self.lasers.append(self.gpa, .{
|
||||
.origin = self.player.kinetic.pos,
|
||||
.dir = bullet_dir,
|
||||
.size = 10,
|
||||
.created_at = frame.time_ns,
|
||||
.duration = std.time.ns_per_s
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,6 +400,29 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
||||
});
|
||||
}
|
||||
|
||||
for (self.lasers.items) |*laser| {
|
||||
const laser_length = 1000;
|
||||
const laser_line = Line{
|
||||
.p0 = laser.origin,
|
||||
.p1 = laser.origin.add(laser.dir.multiplyScalar(laser_length))
|
||||
};
|
||||
const laser_quad = laser_line.getQuad(laser.size);
|
||||
|
||||
for (self.enemies.items) |*enemy| {
|
||||
const enemy_rect = getCenteredRect(enemy.kinetic.pos, enemy.size);
|
||||
if (laser_quad.isRectOverlap(enemy_rect)) {
|
||||
enemy.dead = true;
|
||||
}
|
||||
}
|
||||
|
||||
frame.drawLine(
|
||||
laser_line.p0,
|
||||
laser_line.p1,
|
||||
rgb(200, 20, 255),
|
||||
laser.size
|
||||
);
|
||||
}
|
||||
|
||||
for (self.enemies.items) |*enemy| {
|
||||
const dir_to_player = self.player.kinetic.pos.sub(enemy.kinetic.pos).normalized();
|
||||
enemy.kinetic.vel = dir_to_player.multiplyScalar(50);
|
||||
@ -444,6 +513,7 @@ pub fn tick(self: *CombatScreen, state: *State, frame: *Engine.Frame) !TickResul
|
||||
|
||||
frame.drawTextFormat(.init(10, 30), text_opts, "{d}", .{ state.money });
|
||||
frame.drawTextFormat(.init(10, 50), text_opts, "{d}/{d}", .{ self.player.health, self.player.max_health });
|
||||
frame.drawTextFormat(.init(10, 70), text_opts, "{?}", .{ self.player.gun });
|
||||
|
||||
result.player_died = (self.player.health == 0);
|
||||
if (self.enemies.items.len == 0 and self.waves.items.len == 0 and self.spawned_waves.items.len == wave_infos.len) {
|
||||
|
||||
@ -7,6 +7,7 @@ const simgui = sokol.imgui;
|
||||
const sgl = sokol.gl;
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Line = Math.Line;
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec4 = Math.Vec4;
|
||||
const rgb = Math.rgb;
|
||||
@ -315,14 +316,22 @@ fn drawRectangle(opts: Command.DrawRectangle) void {
|
||||
}
|
||||
|
||||
fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
||||
const step = to.sub(from).normalized().multiplyScalar(width / 2);
|
||||
const line = Line{
|
||||
.p0 = from,
|
||||
.p1 = to,
|
||||
};
|
||||
|
||||
const top_left = from.add(step.rotateLeft90());
|
||||
const bottom_left = from.add(step.rotateRight90());
|
||||
const top_right = to.add(step.rotateLeft90());
|
||||
const bottom_right = to.add(step.rotateRight90());
|
||||
const quad = line.getQuad(width);
|
||||
|
||||
drawQuadNoUVs(.{ top_right, top_left, bottom_left, bottom_right }, color);
|
||||
drawQuadNoUVs(
|
||||
.{
|
||||
quad.top_right,
|
||||
quad.top_left,
|
||||
quad.bottom_left,
|
||||
quad.bottom_right
|
||||
},
|
||||
color
|
||||
);
|
||||
}
|
||||
|
||||
pub fn addFont(name: [*c]const u8, data: []const u8) !Font.Id {
|
||||
|
||||
@ -397,6 +397,33 @@ pub const Rect = struct {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getTopEdge(self: Rect) Line {
|
||||
return Line{
|
||||
.p0 = .init(self.left(), self.top()),
|
||||
.p1 = .init(self.right(), self.top()),
|
||||
};
|
||||
}
|
||||
pub fn getBottomEdge(self: Rect) Line {
|
||||
return Line{
|
||||
.p0 = .init(self.left(), self.bottom()),
|
||||
.p1 = .init(self.right(), self.bottom()),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getLeftEdge(self: Rect) Line {
|
||||
return Line{
|
||||
.p0 = .init(self.left(), self.top()),
|
||||
.p1 = .init(self.left(), self.bottom()),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getRightEdge(self: Rect) Line {
|
||||
return Line{
|
||||
.p0 = .init(self.right(), self.top()),
|
||||
.p1 = .init(self.right(), self.bottom()),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn left(self: Rect) f32 {
|
||||
return self.pos.x;
|
||||
}
|
||||
@ -431,12 +458,20 @@ pub const Rect = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isInside(self: Rect, pos: Vec2) bool {
|
||||
pub fn isPointInside(self: Rect, pos: Vec2) bool {
|
||||
const x_overlap = self.pos.x <= pos.x and pos.x < self.pos.x + self.size.x;
|
||||
const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;
|
||||
return x_overlap and y_overlap;
|
||||
}
|
||||
|
||||
pub fn checkEdgeLineOverlap(self: Rect, line: Line) bool {
|
||||
const left_overlap = line.hasOverlap(self.getLeftEdge());
|
||||
const right_overlap = line.hasOverlap(self.getRightEdge());
|
||||
const top_overlap = line.hasOverlap(self.getTopEdge());
|
||||
const bottom_overlap = line.hasOverlap(self.getBottomEdge());
|
||||
return left_overlap or right_overlap or top_overlap or bottom_overlap;
|
||||
}
|
||||
|
||||
pub fn hasOverlap(lhs: Rect, rhs: Rect) bool {
|
||||
return (lhs.left() < rhs.right() and lhs.right() > rhs.left()) and
|
||||
(lhs.top() < rhs.bottom() and lhs.bottom() > rhs.top());
|
||||
@ -461,9 +496,133 @@ pub const Rect = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Quad = struct {
|
||||
top_left: Vec2,
|
||||
bottom_left: Vec2,
|
||||
top_right: Vec2,
|
||||
bottom_right: Vec2,
|
||||
|
||||
pub fn isRectOverlap(self: Quad, rect: Rect) bool {
|
||||
const vertices = [4]Vec2{
|
||||
self.top_right,
|
||||
self.top_left,
|
||||
self.bottom_left,
|
||||
self.bottom_right,
|
||||
};
|
||||
const polygon = Polygon{
|
||||
.vertices = &vertices
|
||||
};
|
||||
return polygon.isRectOverlap(rect);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Polygon = struct {
|
||||
vertices: []const Vec2,
|
||||
|
||||
pub fn isPointInside(self: Polygon, point: Vec2) bool {
|
||||
var collision = false;
|
||||
|
||||
const py = point.y;
|
||||
const px = point.x;
|
||||
|
||||
for (0..self.vertices.len) |i| {
|
||||
const next_i = @mod(i + 1, self.vertices.len);
|
||||
// get the PVectors at our current position
|
||||
// this makes our if statement a little cleaner
|
||||
const vc = self.vertices[i]; // c for "current"
|
||||
const vn = self.vertices[next_i]; // n for "next"
|
||||
|
||||
// compare position, flip 'collision' variable
|
||||
// back and forth
|
||||
if (
|
||||
((vc.y > py and vn.y < py) or (vc.y < py and vn.y > py))
|
||||
and
|
||||
(px < (vn.x-vc.x)*(py-vc.y) / (vn.y-vc.y)+vc.x)
|
||||
) {
|
||||
collision = !collision;
|
||||
}
|
||||
}
|
||||
|
||||
return collision;
|
||||
}
|
||||
|
||||
pub fn isRectOverlap(self: Polygon, rect: Rect) bool {
|
||||
// go through each of the vertices, plus the next
|
||||
// vertex in the list
|
||||
for (0..self.vertices.len) |i| {
|
||||
const next_i = @mod(i + 1, self.vertices.len);
|
||||
|
||||
// get the PVectors at our current position
|
||||
// this makes our if statement a little cleaner
|
||||
const vc = self.vertices[i]; // c for "current"
|
||||
const vn = self.vertices[next_i]; // n for "next"
|
||||
|
||||
const polygon_edge = Line{
|
||||
.p0 = vc,
|
||||
.p1 = vn,
|
||||
};
|
||||
|
||||
// check against all four sides of the rectangle
|
||||
if (rect.checkEdgeLineOverlap(polygon_edge)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// optional: test if the rectangle is INSIDE the polygon
|
||||
// note that this iterates all sides of the polygon
|
||||
// again, so only use this if you need to
|
||||
if (self.isPointInside(rect.pos)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Line = struct {
|
||||
p0: Vec2,
|
||||
p1: Vec2
|
||||
p1: Vec2,
|
||||
|
||||
pub fn hasOverlap(lhs: Line, rhs: Line) bool {
|
||||
const x1 = lhs.p0.x;
|
||||
const y1 = lhs.p0.y;
|
||||
const x2 = lhs.p1.x;
|
||||
const y2 = lhs.p1.y;
|
||||
const x3 = rhs.p0.x;
|
||||
const y3 = rhs.p0.y;
|
||||
const x4 = rhs.p1.x;
|
||||
const y4 = rhs.p1.y;
|
||||
|
||||
// calculate the direction of the lines
|
||||
const uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
|
||||
const uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
|
||||
|
||||
// if uA and uB are between 0-1, lines are colliding
|
||||
if (uA >= 0 and uA <= 1 and uB >= 0 and uB <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn getQuad(self: Line, width: f32) Quad {
|
||||
const to = self.p1;
|
||||
const from = self.p0;
|
||||
|
||||
const step = to.sub(from).normalized().multiplyScalar(width / 2);
|
||||
|
||||
const top_left = from.add(step.rotateLeft90());
|
||||
const bottom_left = from.add(step.rotateRight90());
|
||||
const top_right = to.add(step.rotateLeft90());
|
||||
const bottom_right = to.add(step.rotateRight90());
|
||||
|
||||
return Quad{
|
||||
.top_left = top_left,
|
||||
.bottom_left = bottom_left,
|
||||
.top_right = top_right,
|
||||
.bottom_right = bottom_right
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
||||
@ -471,7 +630,7 @@ pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
||||
.pos = rect_pos,
|
||||
.size = rect_size
|
||||
};
|
||||
return rect.isInside(pos);
|
||||
return rect.isPointInside(pos);
|
||||
}
|
||||
|
||||
pub fn rgba(r: u8, g: u8, b: u8, a: f32) Vec4 {
|
||||
|
||||
@ -134,7 +134,7 @@ pub fn tick(self: *ShopScreen, state: *State, frame: *Frame) !TickResult {
|
||||
for (self.nodes.items) |*node| {
|
||||
const node_rect = Rect.initCentered(node.pos.x, node.pos.y, node_size.x, node_size.y);
|
||||
|
||||
const is_mouse_inside = mouse != null and node_rect.isInside(mouse.?);
|
||||
const is_mouse_inside = mouse != null and node_rect.isPointInside(mouse.?);
|
||||
const has_enough_money = state.money >= node.money_cost;
|
||||
|
||||
if (has_enough_money and is_mouse_inside and frame.isMousePressed(.left)) {
|
||||
@ -173,7 +173,7 @@ pub fn tick(self: *ShopScreen, state: *State, frame: *Frame) !TickResult {
|
||||
.color = rgb(255, 255, 255)
|
||||
});
|
||||
|
||||
if (frame.input.mouse_position != null and back_rect.isInside(frame.input.mouse_position.?) and frame.isMousePressed(.left)) {
|
||||
if (frame.input.mouse_position != null and back_rect.isPointInside(frame.input.mouse_position.?) and frame.isMousePressed(.left)) {
|
||||
result.back_to_combat = true;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user