179 lines
6 KiB
Zig
179 lines
6 KiB
Zig
const mach = @import("mach");
|
|
const math = mach.math;
|
|
const Renderer = @import("Renderer.zig");
|
|
|
|
const vec3 = math.vec3;
|
|
const vec2 = math.vec2;
|
|
const Vec2 = math.Vec2;
|
|
const Vec3 = math.Vec3;
|
|
|
|
const App = @This();
|
|
|
|
pub const mach_module = .app;
|
|
|
|
pub const mach_systems = .{ .main, .init, .deinit, .tick };
|
|
|
|
// Global state for our app module.
|
|
timer: mach.time.Timer,
|
|
player: mach.ObjectID,
|
|
direction: Vec2 = vec2(0, 0),
|
|
spawning: bool = false,
|
|
spawn_timer: mach.time.Timer,
|
|
|
|
pub const main = mach.schedule(.{
|
|
.{ mach.Core, .init },
|
|
.{ App, .init },
|
|
.{ mach.Core, .main },
|
|
});
|
|
|
|
pub const deinit = mach.schedule(.{
|
|
.{ Renderer, .deinit },
|
|
});
|
|
|
|
pub fn init(
|
|
core: *mach.Core,
|
|
app: *App,
|
|
app_mod: mach.Mod(App),
|
|
renderer: *Renderer,
|
|
) !void {
|
|
core.on_tick = app_mod.id.tick;
|
|
core.on_exit = app_mod.id.deinit;
|
|
|
|
const window = try core.windows.new(.{
|
|
.title = "custom renderer",
|
|
});
|
|
renderer.window = window;
|
|
|
|
// Create our player entity.
|
|
const player = try renderer.objects.new(.{
|
|
.position = vec3(0, 0, 0),
|
|
.scale = 1.0,
|
|
});
|
|
|
|
app.* = .{
|
|
.timer = try mach.time.Timer.start(),
|
|
.spawn_timer = try mach.time.Timer.start(),
|
|
.player = player,
|
|
};
|
|
}
|
|
|
|
pub fn tick(
|
|
core: *mach.Core,
|
|
renderer: *Renderer,
|
|
renderer_mod: mach.Mod(Renderer),
|
|
app: *App,
|
|
) !void {
|
|
var direction = app.direction;
|
|
var spawning = app.spawning;
|
|
while (core.nextEvent()) |event| {
|
|
switch (event) {
|
|
.key_press => |ev| {
|
|
switch (ev.key) {
|
|
.left => direction.v[0] -= 1,
|
|
.right => direction.v[0] += 1,
|
|
.up => direction.v[1] += 1,
|
|
.down => direction.v[1] -= 1,
|
|
.space => spawning = true,
|
|
else => {},
|
|
}
|
|
},
|
|
.key_release => |ev| {
|
|
switch (ev.key) {
|
|
.left => direction.v[0] += 1,
|
|
.right => direction.v[0] -= 1,
|
|
.up => direction.v[1] -= 1,
|
|
.down => direction.v[1] += 1,
|
|
.space => spawning = false,
|
|
else => {},
|
|
}
|
|
},
|
|
.window_open => |_| {
|
|
renderer_mod.call(.init);
|
|
},
|
|
.close => core.exit(),
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
// Keep track of which direction we want the player to move based on input, and whether we want
|
|
// to be spawning entities.
|
|
//
|
|
// Note that app. simply returns a pointer to a global singleton of the struct defined
|
|
// by this file, so we can access fields defined at the top of this file.
|
|
app.direction = direction;
|
|
app.spawning = spawning;
|
|
|
|
// Get the current player position
|
|
var player = renderer.objects.getValue(app.player);
|
|
defer renderer.objects.setValue(app.player, player);
|
|
|
|
// If we want to spawn new entities, then spawn them now. The timer just makes spawning rate
|
|
// independent of frame rate.
|
|
if (spawning and app.spawn_timer.read() > 1.0 / 60.0) {
|
|
_ = app.spawn_timer.lap(); // Reset the timer
|
|
for (0..5) |_| {
|
|
// Spawn a new object at the same position as the player, but smaller in scale.
|
|
const new_obj = try renderer.objects.new(.{
|
|
.position = player.position,
|
|
.scale = 1.0 / 6.0,
|
|
});
|
|
|
|
// Parent the object to the player, we'll make children 'follow' the parent below.
|
|
try renderer.objects.addChild(app.player, new_obj);
|
|
}
|
|
}
|
|
|
|
// Multiply by delta_time to ensure that movement is the same speed regardless of the frame rate.
|
|
const delta_time = app.timer.lap();
|
|
|
|
// Calculate the player position, by moving in the direction the player wants to go
|
|
// by the speed amount.
|
|
const speed = 1.0;
|
|
player.position.v[0] += direction.x() * speed * delta_time;
|
|
player.position.v[1] += direction.y() * speed * delta_time;
|
|
|
|
// Find the children of the player and make them 'follow' the player position.
|
|
var children = try renderer.objects.getChildren(app.player);
|
|
defer children.deinit();
|
|
for (children.items) |child_id| {
|
|
if (!renderer.objects.is(child_id)) continue;
|
|
var child = renderer.objects.getValue(child_id);
|
|
defer renderer.objects.setValue(child_id, child);
|
|
|
|
// Nested query to find all the other follower entities that we should move away from.
|
|
// We will avoid all other follower entities if we're too close to them.
|
|
// This is not very efficient, but it works!
|
|
const close_dist = 1.0 / 15.0;
|
|
var avoidance = Vec3.splat(0);
|
|
var avoidance_div: f32 = 1.0;
|
|
|
|
var children2 = try renderer.objects.getChildren(app.player);
|
|
defer children2.deinit();
|
|
for (children2.items) |child2_id| {
|
|
if (!renderer.objects.is(child2_id)) continue;
|
|
if (child_id == child2_id) continue;
|
|
const child2 = renderer.objects.getValue(child2_id);
|
|
if (child.position.dist(&child2.position) < close_dist) {
|
|
avoidance = avoidance.sub(&child.position.dir(&child2.position, 0.0000001));
|
|
avoidance_div += 1.0;
|
|
}
|
|
}
|
|
|
|
// Avoid the player if we're too close to it
|
|
var avoid_player_multiplier: f32 = 1.0;
|
|
if (child.position.dist(&player.position) < close_dist * 6.0) {
|
|
avoidance = avoidance.sub(&child.position.dir(&player.position, 0.0000001));
|
|
avoidance_div += 1.0;
|
|
avoid_player_multiplier = 4.0;
|
|
}
|
|
|
|
// Determine our new position, taking into account things we want to avoid
|
|
const move_speed = 1.0 * delta_time;
|
|
var new_position = child.position.add(&avoidance.divScalar(avoidance_div).mulScalar(move_speed * avoid_player_multiplier));
|
|
|
|
// Try to move towards the center of the world if we don't need to avoid something else
|
|
child.position = new_position.lerp(&vec3(0, 0, 0), move_speed / avoidance_div);
|
|
}
|
|
|
|
renderer_mod.call(.renderFrame);
|
|
}
|