mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Addressed comments
This commit is contained in:
parent
e39770d1d9
commit
45fef87f32
@ -1427,13 +1427,13 @@ impl Client {
|
||||
ServerMsg::Outcomes(outcomes) => {
|
||||
frontend_events.extend(outcomes.into_iter().map(Event::Outcome))
|
||||
},
|
||||
ServerMsg::Knockback(force) => {
|
||||
ServerMsg::Knockback(impulse) => {
|
||||
self.state
|
||||
.ecs()
|
||||
.read_resource::<EventBus<LocalEvent>>()
|
||||
.emit_now(LocalEvent::ApplyForce {
|
||||
.emit_now(LocalEvent::ApplyImpulse {
|
||||
entity: self.entity,
|
||||
force,
|
||||
impulse,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
@ -121,6 +121,7 @@ pub enum CharacterAbility {
|
||||
shockwave_angle: f32,
|
||||
shockwave_speed: f32,
|
||||
shockwave_duration: Duration,
|
||||
requires_ground: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@ -417,6 +418,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
shockwave_angle,
|
||||
shockwave_speed,
|
||||
shockwave_duration,
|
||||
requires_ground,
|
||||
} => CharacterState::GroundShockwave(ground_shockwave::Data {
|
||||
exhausted: false,
|
||||
buildup_duration: *buildup_duration,
|
||||
@ -426,6 +428,7 @@ impl From<&CharacterAbility> for CharacterState {
|
||||
shockwave_angle: *shockwave_angle,
|
||||
shockwave_speed: *shockwave_speed,
|
||||
shockwave_duration: *shockwave_duration,
|
||||
requires_ground: *requires_ground,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -369,6 +369,7 @@ impl Tool {
|
||||
shockwave_angle: 90.0,
|
||||
shockwave_speed: 20.0,
|
||||
shockwave_duration: Duration::from_millis(2000),
|
||||
requires_ground: true,
|
||||
},
|
||||
]
|
||||
} else {
|
||||
|
@ -11,8 +11,11 @@ use vek::*;
|
||||
pub enum LocalEvent {
|
||||
/// Applies upward force to entity's `Vel`
|
||||
Jump(EcsEntity),
|
||||
/// Applies the `force` to `entity`'s `Vel`
|
||||
ApplyForce { entity: EcsEntity, force: Vec3<f32> },
|
||||
/// Applies the `impulse` to `entity`'s `Vel`
|
||||
ApplyImpulse {
|
||||
entity: EcsEntity,
|
||||
impulse: Vec3<f32>,
|
||||
},
|
||||
/// Applies leaping force to `entity`'s `Vel` away from `wall_dir` direction
|
||||
WallLeap {
|
||||
entity: EcsEntity,
|
||||
@ -58,7 +61,7 @@ pub enum ServerEvent {
|
||||
},
|
||||
Knockback {
|
||||
entity: EcsEntity,
|
||||
force: Vec3<f32>,
|
||||
impulse: Vec3<f32>,
|
||||
},
|
||||
LandOnGround {
|
||||
entity: EcsEntity,
|
||||
|
@ -390,11 +390,9 @@ impl State {
|
||||
vel.0.z = HUMANOID_JUMP_ACCEL;
|
||||
}
|
||||
},
|
||||
LocalEvent::ApplyForce { entity, force } => {
|
||||
// TODO: this sets the velocity directly to the value of `force`, consider
|
||||
// renaming the event or changing the behavior
|
||||
LocalEvent::ApplyImpulse { entity, impulse } => {
|
||||
if let Some(vel) = velocities.get_mut(entity) {
|
||||
vel.0 = force;
|
||||
vel.0 = impulse;
|
||||
}
|
||||
},
|
||||
LocalEvent::WallLeap { entity, wall_dir } => {
|
||||
|
@ -25,6 +25,8 @@ pub struct Data {
|
||||
pub shockwave_speed: f32,
|
||||
/// How long the shockwave travels for
|
||||
pub shockwave_duration: Duration,
|
||||
/// Whether the shockwave requires the target to be on the ground
|
||||
pub requires_ground: bool,
|
||||
}
|
||||
|
||||
impl CharacterBehavior for Data {
|
||||
@ -47,6 +49,7 @@ impl CharacterBehavior for Data {
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else if !self.exhausted {
|
||||
// Attack
|
||||
@ -56,7 +59,7 @@ impl CharacterBehavior for Data {
|
||||
duration: self.shockwave_duration,
|
||||
damage: self.damage,
|
||||
knockback: self.knockback,
|
||||
requires_ground: true,
|
||||
requires_ground: self.requires_ground,
|
||||
owner: Some(*data.uid),
|
||||
};
|
||||
update.server_events.push_front(ServerEvent::Shockwave {
|
||||
@ -74,6 +77,7 @@ impl CharacterBehavior for Data {
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else if self.recover_duration != Duration::default() {
|
||||
// Recovery
|
||||
@ -89,6 +93,7 @@ impl CharacterBehavior for Data {
|
||||
shockwave_angle: self.shockwave_angle,
|
||||
shockwave_speed: self.shockwave_speed,
|
||||
shockwave_duration: self.shockwave_duration,
|
||||
requires_ground: self.requires_ground,
|
||||
});
|
||||
} else {
|
||||
// Done
|
||||
|
@ -155,7 +155,7 @@ impl<'a> System<'a> for Sys {
|
||||
if attack.knockback != 0.0 && damage.healthchange != 0.0 {
|
||||
server_emitter.emit(ServerEvent::Knockback {
|
||||
entity: b,
|
||||
force: attack.knockback
|
||||
impulse: attack.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
|
||||
});
|
||||
}
|
||||
|
@ -117,9 +117,9 @@ impl<'a> System<'a> for Sys {
|
||||
if let Some(entity) =
|
||||
uid_allocator.retrieve_entity_internal(other.into())
|
||||
{
|
||||
local_emitter.emit(LocalEvent::ApplyForce {
|
||||
local_emitter.emit(LocalEvent::ApplyImpulse {
|
||||
entity,
|
||||
force: knockback
|
||||
impulse: knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::unit_z()), 0.5),
|
||||
});
|
||||
}
|
||||
|
@ -83,22 +83,17 @@ impl<'a> System<'a> for Sys {
|
||||
// If shockwave is out of time emit destroy event but still continue since it
|
||||
// may have traveled and produced effects a bit before reaching it's
|
||||
// end point
|
||||
if end_time < time {
|
||||
if time > end_time {
|
||||
server_emitter.emit(ServerEvent::Destroy {
|
||||
entity,
|
||||
cause: HealthSource::World,
|
||||
});
|
||||
}
|
||||
|
||||
// Determine area that was covered by the shockwave in the last tick
|
||||
let frame_time = dt.min((end_time - time) as f32);
|
||||
if frame_time <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Note: min() probably uneeded
|
||||
// Determine area that was covered by the shockwave in the last tick
|
||||
let time_since_creation = (time - creation_time) as f32;
|
||||
let frame_start_dist = (shockwave.speed * (time_since_creation - frame_time)).max(0.0);
|
||||
let frame_start_dist = (shockwave.speed * (time_since_creation - dt)).max(0.0);
|
||||
let frame_end_dist = (shockwave.speed * time_since_creation).max(frame_start_dist);
|
||||
let pos2 = Vec2::from(pos.0);
|
||||
|
||||
@ -159,6 +154,11 @@ impl<'a> System<'a> for Sys {
|
||||
let pos_b_ground = Vec3::new(pos_b.0.x, pos_b.0.y, pos.0.z);
|
||||
let max_angle = 15.0_f32.to_radians();
|
||||
|
||||
// See if entities are in the same group
|
||||
let same_group = group
|
||||
.map(|group_a| Some(group_a) == groups.get(b))
|
||||
.unwrap_or(Some(*uid_b) == shockwave.owner);
|
||||
|
||||
// Check if it is a hit
|
||||
let hit = entity != b
|
||||
&& !stats_b.is_dead
|
||||
@ -166,78 +166,54 @@ impl<'a> System<'a> for Sys {
|
||||
&& {
|
||||
// TODO: write code to collide rect with the arc strip so that we can do
|
||||
// more complete collision detection for rapidly moving entities
|
||||
arc_strip.collides_with_circle(Circle {
|
||||
pos: pos_b2,
|
||||
radius: rad_b,
|
||||
}) || last_pos_b2_maybe.map_or(false, |pos| {
|
||||
arc_strip.collides_with_circle(Circle { pos, radius: rad_b })
|
||||
arc_strip.collides_with_circle(Disk::new(pos_b2, rad_b)) || last_pos_b2_maybe.map_or(false, |pos| {
|
||||
arc_strip.collides_with_circle(Disk::new(pos, rad_b))
|
||||
})
|
||||
}
|
||||
&& (pos_b_ground - pos.0).angle_between(pos_b.0 - pos.0) < max_angle
|
||||
&& (!shockwave.requires_ground || physics_state_b.on_ground);
|
||||
&& (!shockwave.requires_ground || physics_state_b.on_ground)
|
||||
&& !same_group;
|
||||
|
||||
if hit {
|
||||
// See if entities are in the same group
|
||||
let same_group = group
|
||||
.map(|group_a| Some(group_a) == groups.get(b))
|
||||
.unwrap_or(Some(*uid_b) == shockwave.owner);
|
||||
|
||||
// Don't damage in the same group
|
||||
if same_group {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Weapon gives base damage
|
||||
let source = DamageSource::Shockwave;
|
||||
|
||||
let mut damage = Damage {
|
||||
healthchange: -(shockwave.damage as f32),
|
||||
source,
|
||||
source: DamageSource::Shockwave,
|
||||
};
|
||||
|
||||
let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
||||
// TODO: investigate whether this calculation is proper for shockwaves
|
||||
&& ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0;
|
||||
|
||||
if let Some(loadout) = loadouts.get(b) {
|
||||
damage.modify_damage(block, loadout);
|
||||
}
|
||||
|
||||
if damage.healthchange < 0.0 {
|
||||
if damage.healthchange != 0.0 {
|
||||
let cause = if damage.healthchange < 0.0 {
|
||||
HealthSource::Attack {
|
||||
by: shockwave.owner.unwrap_or(*uid),
|
||||
}
|
||||
} else {
|
||||
HealthSource::Healing {
|
||||
by: Some(shockwave.owner.unwrap_or(*uid)),
|
||||
}
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause: HealthSource::Attack {
|
||||
by: shockwave.owner.unwrap_or(*uid),
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if damage.healthchange > 0.0 {
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid_b,
|
||||
change: HealthChange {
|
||||
amount: damage.healthchange as i32,
|
||||
cause: HealthSource::Healing {
|
||||
by: Some(shockwave.owner.unwrap_or(*uid)),
|
||||
},
|
||||
cause,
|
||||
},
|
||||
});
|
||||
}
|
||||
if shockwave.knockback != 0.0 && damage.healthchange != 0.0 {
|
||||
if shockwave.knockback < 0.0 {
|
||||
server_emitter.emit(ServerEvent::Knockback {
|
||||
entity: b,
|
||||
force: shockwave.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, -1.0)), 0.85),
|
||||
});
|
||||
let impulse = if shockwave.knockback < 0.0 {
|
||||
shockwave.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, -1.0)), 0.85)
|
||||
} else {
|
||||
server_emitter.emit(ServerEvent::Knockback {
|
||||
entity: b,
|
||||
force: shockwave.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5),
|
||||
});
|
||||
}
|
||||
shockwave.knockback
|
||||
* *Dir::slerp(ori.0, Dir::new(Vec3::new(0.0, 0.0, 1.0)), 0.5)
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Knockback { entity: b, impulse });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -269,34 +245,28 @@ struct ArcStrip {
|
||||
}
|
||||
|
||||
impl ArcStrip {
|
||||
fn collides_with_circle(self, c: Circle) -> bool {
|
||||
fn collides_with_circle(self, d: Disk<f32, f32>) -> bool {
|
||||
// Quit if aabb's don't collide
|
||||
if (self.origin.x - c.pos.x).abs() > self.end + c.radius
|
||||
|| (self.origin.y - c.pos.y).abs() > self.end + c.radius
|
||||
if (self.origin.x - d.center.x).abs() > self.end + d.radius
|
||||
|| (self.origin.y - d.center.y).abs() > self.end + d.radius
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let dist = self.origin.distance(c.pos);
|
||||
let dist = self.origin.distance(d.center);
|
||||
let half_angle = self.angle.to_radians() / 2.0;
|
||||
|
||||
if dist > self.end + c.radius || dist + c.radius < self.start {
|
||||
if dist > self.end + d.radius || dist + d.radius < self.start {
|
||||
// Completely inside or outside full ring
|
||||
return false;
|
||||
}
|
||||
|
||||
let inside_edge = Circle {
|
||||
pos: self.origin,
|
||||
radius: self.start,
|
||||
};
|
||||
let outside_edge = Circle {
|
||||
pos: self.origin,
|
||||
radius: self.end,
|
||||
};
|
||||
let inside_edge = Disk::new(self.origin, self.start);
|
||||
let outside_edge = Disk::new(self.origin, self.end);
|
||||
let inner_corner_in_circle = || {
|
||||
let midpoint = self.dir.normalized() * self.start;
|
||||
c.contains_point(midpoint.rotated_z(half_angle) + self.origin)
|
||||
|| c.contains_point(midpoint.rotated_z(-half_angle) + self.origin)
|
||||
d.contains_point(midpoint.rotated_z(half_angle) + self.origin)
|
||||
|| d.contains_point(midpoint.rotated_z(-half_angle) + self.origin)
|
||||
};
|
||||
let arc_segment_in_circle = || {
|
||||
let midpoint = self.dir.normalized();
|
||||
@ -306,7 +276,7 @@ impl ArcStrip {
|
||||
start: dir * self.start + self.origin,
|
||||
end: dir * self.end + self.origin,
|
||||
};
|
||||
c.contains_point(side.projected_point(c.pos))
|
||||
d.contains_point(side.projected_point(d.center))
|
||||
};
|
||||
segment_in_circle(half_angle) || segment_in_circle(-half_angle)
|
||||
};
|
||||
@ -316,7 +286,7 @@ impl ArcStrip {
|
||||
// Check intersection with line segments
|
||||
arc_segment_in_circle() || {
|
||||
// Check angle of intersection points on outside edge of ring
|
||||
let (p1, p2) = outside_edge.intersection_points(c, dist);
|
||||
let (p1, p2) = intersection_points(outside_edge, d, dist);
|
||||
self.dir.angle_between(p1 - self.origin) < half_angle
|
||||
|| self.dir.angle_between(p2 - self.origin) < half_angle
|
||||
}
|
||||
@ -327,54 +297,43 @@ impl ArcStrip {
|
||||
inner_corner_in_circle()
|
||||
|| (
|
||||
// Check that the circles aren't identical
|
||||
!inside_edge.is_approx_eq(c) && {
|
||||
let (p1, p2) = inside_edge.intersection_points(c, dist);
|
||||
inside_edge != d && {
|
||||
let (p1, p2) = intersection_points(inside_edge, d, dist);
|
||||
self.dir.angle_between(p1 - self.origin) < half_angle
|
||||
|| self.dir.angle_between(p2 - self.origin) < half_angle
|
||||
}
|
||||
)
|
||||
} else if c.radius > dist {
|
||||
} else if d.radius > dist {
|
||||
// Circle center inside ring
|
||||
// but center of ring is inside the circle so we can't calculate the angle
|
||||
inner_corner_in_circle()
|
||||
} else {
|
||||
// Circle center inside ring
|
||||
// Calculate extra angle to account for circle radius
|
||||
let extra_angle = (c.radius / dist).asin();
|
||||
self.dir.angle_between(c.pos - self.origin) < half_angle + extra_angle
|
||||
let extra_angle = (d.radius / dist).asin();
|
||||
self.dir.angle_between(d.center - self.origin) < half_angle + extra_angle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Circle {
|
||||
pos: Vec2<f32>,
|
||||
radius: f32,
|
||||
}
|
||||
impl Circle {
|
||||
// Assumes an intersection is occuring at 2 points
|
||||
// Uses precalculated distance
|
||||
// https://www.xarg.org/2016/07/calculate-the-intersection-points-of-two-circles/
|
||||
fn intersection_points(self, other: Self, dist: f32) -> (Vec2<f32>, Vec2<f32>) {
|
||||
let e = (other.pos - self.pos) / dist;
|
||||
|
||||
let x = (self.radius.powi(2) - other.radius.powi(2) + dist.powi(2)) / (2.0 * dist);
|
||||
let y = (self.radius.powi(2) - x.powi(2)).sqrt();
|
||||
|
||||
let pxe = self.pos + x * e;
|
||||
let eyx = e.yx();
|
||||
|
||||
let p1 = pxe + Vec2::new(-y, y) * eyx;
|
||||
let p2 = pxe + Vec2::new(y, -y) * eyx;
|
||||
|
||||
(p1, p2)
|
||||
}
|
||||
|
||||
fn contains_point(self, point: Vec2<f32>) -> bool {
|
||||
point.distance_squared(self.pos) < self.radius.powi(2)
|
||||
}
|
||||
|
||||
fn is_approx_eq(self, other: Self) -> bool {
|
||||
(self.pos - other.pos).is_approx_zero() && self.radius - other.radius < 0.001
|
||||
}
|
||||
// Assumes an intersection is occuring at 2 points
|
||||
// Uses precalculated distance
|
||||
// https://www.xarg.org/2016/07/calculate-the-intersection-points-of-two-circles/
|
||||
fn intersection_points(
|
||||
disk1: Disk<f32, f32>,
|
||||
disk2: Disk<f32, f32>,
|
||||
dist: f32,
|
||||
) -> (Vec2<f32>, Vec2<f32>) {
|
||||
let e = (disk2.center - disk1.center) / dist;
|
||||
|
||||
let x = (disk1.radius.powi(2) - disk2.radius.powi(2) + dist.powi(2)) / (2.0 * dist);
|
||||
let y = (disk1.radius.powi(2) - x.powi(2)).sqrt();
|
||||
|
||||
let pxe = disk1.center + x * e;
|
||||
let eyx = e.yx();
|
||||
|
||||
let p1 = pxe + Vec2::new(-y, y) * eyx;
|
||||
let p2 = pxe + Vec2::new(y, -y) * eyx;
|
||||
|
||||
(p1, p2)
|
||||
}
|
||||
|
@ -2,8 +2,9 @@ use crate::{sys, Server, StateExt};
|
||||
use common::{
|
||||
character::CharacterId,
|
||||
comp::{
|
||||
self, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body, Gravity, Item,
|
||||
ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel, WaypointArea,
|
||||
self, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body, Gravity,
|
||||
Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel,
|
||||
WaypointArea,
|
||||
},
|
||||
outcome::Outcome,
|
||||
util::Dir,
|
||||
|
@ -32,16 +32,15 @@ pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_knockback(server: &Server, entity: EcsEntity, force: Vec3<f32>) {
|
||||
pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3<f32>) {
|
||||
let state = &server.state;
|
||||
let ecs = state.ecs();
|
||||
let mut velocities = ecs.write_storage::<comp::Vel>();
|
||||
let mut velocities = state.ecs().write_storage::<comp::Vel>();
|
||||
if let Some(vel) = velocities.get_mut(entity) {
|
||||
vel.0 = force;
|
||||
vel.0 = impulse;
|
||||
}
|
||||
let mut clients = state.ecs().write_storage::<Client>();
|
||||
if let Some(client) = clients.get_mut(entity) {
|
||||
client.notify(ServerMsg::Knockback(force));
|
||||
client.notify(ServerMsg::Knockback(impulse));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,9 @@ impl Server {
|
||||
pos,
|
||||
ori,
|
||||
} => handle_shockwave(self, properties, pos, ori),
|
||||
ServerEvent::Knockback { entity, force } => handle_knockback(&self, entity, force),
|
||||
ServerEvent::Knockback { entity, impulse } => {
|
||||
handle_knockback(&self, entity, impulse)
|
||||
},
|
||||
ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change),
|
||||
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
||||
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
|
||||
|
@ -447,28 +447,24 @@ impl ParticleMgr {
|
||||
// 1 / 3 the size of terrain voxel
|
||||
let scale = 1.0 / 3.0;
|
||||
|
||||
let freqency_millis = shockwave.properties.speed * scale;
|
||||
let scaled_speed = shockwave.properties.speed * scale;
|
||||
|
||||
for heartbeat in 0..self
|
||||
.scheduler
|
||||
.heartbeats(Duration::from_millis(freqency_millis as u64))
|
||||
.heartbeats(Duration::from_millis(scaled_speed as u64))
|
||||
{
|
||||
let sub_tick_interpolation = freqency_millis * 1000.0 * heartbeat as f32;
|
||||
let sub_tick_interpolation = scaled_speed * 1000.0 * heartbeat as f32;
|
||||
|
||||
let distance =
|
||||
shockwave.properties.speed * (elapsed as f32 - sub_tick_interpolation);
|
||||
|
||||
for d in 0..((distance / scale) as i32) {
|
||||
let arc_position = (theta - (radians / 2.0)) + (dtheta * d as f32 * scale);
|
||||
let arc_position = theta - radians / 2.0 + dtheta * d as f32 * scale;
|
||||
|
||||
let position = pos.0
|
||||
+ Vec3::new(
|
||||
distance * arc_position.cos(),
|
||||
distance * arc_position.sin(),
|
||||
0.0,
|
||||
);
|
||||
let position =
|
||||
pos.0 + distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
|
||||
let position_snapped = (position / scale).round() * scale;
|
||||
let position_snapped = ((position / scale).floor() + 0.5) * scale;
|
||||
|
||||
self.particles.push(Particle::new(
|
||||
Duration::from_millis(250),
|
||||
|
Loading…
Reference in New Issue
Block a user