mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Added explosion animation
This commit is contained in:
parent
549833f3e9
commit
4209a05c62
BIN
sprites/particles/Smoke.png
Normal file
BIN
sprites/particles/Smoke.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
@ -44,6 +44,8 @@ object SoundBuffers extends Resource {
|
||||
lazy val MinecraftClickRelease: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/click_release.ogg")
|
||||
lazy val MinecraftExplosion: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/explosion.ogg")
|
||||
|
||||
lazy val SelfDestructingCardCountdownBeep: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/countdown_beep.ogg")
|
||||
|
||||
lazy val NoteBlock: Map[String, SoundBuffer] = List(
|
||||
"banjo", "basedrum", "bass", "bell", "bit", "chime", "cow_bell", "didgeridoo", "flute", "guitar",
|
||||
"harp", "hat", "iron_xylophone", "pling", "snare", "xylophone",
|
||||
|
||||
@ -133,6 +133,6 @@ object SoundSource {
|
||||
lazy val MachineFloppyEject: SoundSource =
|
||||
SoundSource.fromBuffer(SoundBuffers.MachineFloppyEject, SoundCategory.Environment)
|
||||
|
||||
lazy val SelfDestructingCardBeep: SoundSource =
|
||||
BeepGenerator.newBeep(".", 1000, 100)
|
||||
lazy val SelfDestructingCardCountdownBeep: SoundSource =
|
||||
SoundSource.fromBuffer(SoundBuffers.SelfDestructingCardCountdownBeep, SoundCategory.Environment)
|
||||
}
|
||||
|
||||
@ -345,7 +345,40 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
||||
animation: Option[Animation] = None): Unit = {
|
||||
sprite = name
|
||||
foreground = color
|
||||
_rect(x, y, width, height, fixUV = true, animation)
|
||||
|
||||
val spriteRect = animation match {
|
||||
case Some(animation) =>
|
||||
val duration = animation.frames.map(_._2).sum
|
||||
var timeOffset = 0f
|
||||
var curFrame = 0
|
||||
|
||||
breakable {
|
||||
for ((idx, dur) <- animation.frames) {
|
||||
timeOffset += dur
|
||||
curFrame = idx
|
||||
if (timeOffset >= time % duration) break
|
||||
}
|
||||
}
|
||||
|
||||
val size = animation.frameSize match {
|
||||
case Some(size) => Size2D(this.spriteRect.w, this.spriteRect.w * size.height / size.width)
|
||||
case None => Size2D(this.spriteRect.w, this.spriteRect.w)
|
||||
}
|
||||
Some(this.spriteRect.copy(y = this.spriteRect.y + curFrame * size.height, h = size.height))
|
||||
|
||||
case None => None
|
||||
}
|
||||
|
||||
_rect(x, y, width, height, fixUV = true, spriteRect)
|
||||
}
|
||||
|
||||
def sprite(name: String, x: Float, y: Float, width: Float, height: Float,
|
||||
color: Color,
|
||||
spriteRect: Rect2D): Unit = {
|
||||
sprite = name
|
||||
foreground = color
|
||||
|
||||
_rect(x, y, width, height, fixUV = true, Some(spriteRect))
|
||||
}
|
||||
|
||||
def rect(r: Rect2D, color: Color): Unit = {
|
||||
@ -368,28 +401,8 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
||||
|
||||
private def _rect(x: Float, y: Float, width: Float, height: Float,
|
||||
fixUV: Boolean = true,
|
||||
animation: Option[Animation] = None): Unit = {
|
||||
val spriteRect = animation match {
|
||||
case None => this.spriteRect
|
||||
case Some(animation) =>
|
||||
val duration = animation.frames.map(_._2).sum
|
||||
var timeOffset = 0f
|
||||
var curFrame = 0
|
||||
|
||||
breakable {
|
||||
for ((idx, dur) <- animation.frames) {
|
||||
timeOffset += dur
|
||||
curFrame = idx
|
||||
if (timeOffset >= time % duration) break
|
||||
}
|
||||
}
|
||||
|
||||
val size = animation.frameSize match {
|
||||
case Some(size) => Size2D(this.spriteRect.w, this.spriteRect.w * size.height / size.width)
|
||||
case None => Size2D(this.spriteRect.w, this.spriteRect.w)
|
||||
}
|
||||
this.spriteRect.copy(y = this.spriteRect.y + curFrame * size.height, h = size.height)
|
||||
}
|
||||
spriteRectOptional: Option[Rect2D] = None): Unit = {
|
||||
val spriteRect = spriteRectOptional.getOrElse(this.spriteRect)
|
||||
|
||||
val uvTransform = Transform2D.translate(spriteRect.x, spriteRect.y) >>
|
||||
(if (fixUV)
|
||||
|
||||
@ -367,4 +367,10 @@ object IconSource {
|
||||
|
||||
val InnerBorderB: IconSource = IconSource(s"$prefix/InnerBorderB")
|
||||
}
|
||||
|
||||
// ----------------------- Particles -----------------------
|
||||
|
||||
object Particles {
|
||||
val Smoke: IconSource = IconSource("particles/Smoke")
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
|
||||
with SyncedInventory
|
||||
with DiskActivityHandler
|
||||
with OcelotLogParticleNode
|
||||
with SmokeParticleNode
|
||||
with ShiftClickNode
|
||||
with PositionalSoundSourcesNode {
|
||||
|
||||
@ -32,7 +33,10 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
|
||||
private def soundCardSource: SoundSource = soundCardSounds._2
|
||||
|
||||
// PositionalSoundSourcesNode
|
||||
override def soundSources: Seq[SoundSource] = Seq(SoundSource.MinecraftExplosion)
|
||||
override def soundSources: Seq[SoundSource] = Seq(
|
||||
SoundSource.MinecraftExplosion,
|
||||
SoundSource.SelfDestructingCardCountdownBeep
|
||||
)
|
||||
|
||||
eventHandlers += {
|
||||
case BrainEvent(event: MachineCrashEvent) =>
|
||||
@ -69,7 +73,7 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
|
||||
slot.get match {
|
||||
case Some(card: SelfDestructingCard) =>
|
||||
if (card.time > 0 && card.time % 20 == 0) {
|
||||
SoundSource.SelfDestructingCardBeep.play()
|
||||
SoundSource.SelfDestructingCardCountdownBeep.play()
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
|
||||
98
src/main/scala/ocelot/desktop/node/SmokeParticleNode.scala
Normal file
98
src/main/scala/ocelot/desktop/node/SmokeParticleNode.scala
Normal file
@ -0,0 +1,98 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.color.{Color, RGBAColorNorm}
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
import ocelot.desktop.util.Spritesheet
|
||||
import totoro.ocelot.brain.event.SelfDestructingCardBoomEvent
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.util.Random
|
||||
|
||||
trait SmokeParticleNode extends Node {
|
||||
private case class SmokeParticle(
|
||||
var velocity: Vector2D = Vector2D(randomParticleVelocityComponent, randomParticleVelocityComponent),
|
||||
var offset: Vector2D = Vector2D(0, 0),
|
||||
var color: RGBAColorNorm = randomParticleColor,
|
||||
var time: Float = 0,
|
||||
var duration: Float = randomParticleDuration
|
||||
)
|
||||
|
||||
private def randomParticleVelocityComponent: Float = Random.between(
|
||||
-SmokeParticleNode.SmokeParticleVelocityRange,
|
||||
SmokeParticleNode.SmokeParticleVelocityRange,
|
||||
)
|
||||
|
||||
private def randomParticleColor: RGBAColorNorm = {
|
||||
val channel = Random.between(0.7f, 1f)
|
||||
RGBAColorNorm(channel, channel, channel)
|
||||
}
|
||||
|
||||
private def randomParticleDuration: Float = Random.between(
|
||||
SmokeParticleNode.SmokeParticleAnimationDuration._1,
|
||||
SmokeParticleNode.SmokeParticleAnimationDuration._2
|
||||
)
|
||||
|
||||
private val smokeParticles = mutable.ArrayDeque.empty[SmokeParticle]
|
||||
|
||||
eventHandlers += {
|
||||
case BrainEvent(_: SelfDestructingCardBoomEvent) =>
|
||||
val count = Random.between(
|
||||
SmokeParticleNode.SmokeParticleCount._1,
|
||||
SmokeParticleNode.SmokeParticleCount._2
|
||||
)
|
||||
|
||||
for (_ <- 1 to count) {
|
||||
smokeParticles += SmokeParticle()
|
||||
}
|
||||
}
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
|
||||
smokeParticles.foreach(particle => {
|
||||
particle.time += UiHandler.dt
|
||||
particle.offset += (particle.velocity + SmokeParticleNode.SmokeParticleVolatilizationSpeed) * UiHandler.dt
|
||||
particle.velocity *= SmokeParticleNode.SmokeParticleVelocityDamping
|
||||
})
|
||||
|
||||
smokeParticles.filterInPlace(particle => particle.time <= particle.duration)
|
||||
}
|
||||
|
||||
override def drawParticles(g: Graphics): Unit = {
|
||||
super.drawParticles(g)
|
||||
|
||||
for (particle <- smokeParticles) {
|
||||
val spriteRect = Spritesheet.sprites(IconSource.Particles.Smoke.path)
|
||||
|
||||
val animationFrameCount = spriteRect.h / spriteRect.w
|
||||
val animationFrame = (particle.time / particle.duration * animationFrameCount).toInt
|
||||
|
||||
val particlePosition = bounds.center + particle.offset
|
||||
|
||||
g.sprite(
|
||||
IconSource.Particles.Smoke.path,
|
||||
particlePosition.x,
|
||||
particlePosition.y,
|
||||
SmokeParticleNode.SmokeParticleSize.width,
|
||||
SmokeParticleNode.SmokeParticleSize.height,
|
||||
particle.color,
|
||||
spriteRect.copy(
|
||||
y = spriteRect.y + animationFrame * spriteRect.w,
|
||||
h = spriteRect.w
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object SmokeParticleNode {
|
||||
private final val SmokeParticleSize: Size2D = Size2D(32, 32)
|
||||
private final val SmokeParticleVelocityRange: Float = 300
|
||||
private final val SmokeParticleVolatilizationSpeed: Vector2D = Vector2D(0, -50)
|
||||
private final val SmokeParticleVelocityDamping: Float = .99f
|
||||
private final val SmokeParticleCount: (Int, Int) = (7, 10)
|
||||
private final val SmokeParticleAnimationDuration: (Float, Float) = (1f, 4f)
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ocelot.desktop.util
|
||||
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D}
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.{IconSource, Texture}
|
||||
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user