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 MinecraftClickRelease: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/click_release.ogg")
|
||||||
lazy val MinecraftExplosion: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/explosion.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(
|
lazy val NoteBlock: Map[String, SoundBuffer] = List(
|
||||||
"banjo", "basedrum", "bass", "bell", "bit", "chime", "cow_bell", "didgeridoo", "flute", "guitar",
|
"banjo", "basedrum", "bass", "bell", "bit", "chime", "cow_bell", "didgeridoo", "flute", "guitar",
|
||||||
"harp", "hat", "iron_xylophone", "pling", "snare", "xylophone",
|
"harp", "hat", "iron_xylophone", "pling", "snare", "xylophone",
|
||||||
|
|||||||
@ -133,6 +133,6 @@ object SoundSource {
|
|||||||
lazy val MachineFloppyEject: SoundSource =
|
lazy val MachineFloppyEject: SoundSource =
|
||||||
SoundSource.fromBuffer(SoundBuffers.MachineFloppyEject, SoundCategory.Environment)
|
SoundSource.fromBuffer(SoundBuffers.MachineFloppyEject, SoundCategory.Environment)
|
||||||
|
|
||||||
lazy val SelfDestructingCardBeep: SoundSource =
|
lazy val SelfDestructingCardCountdownBeep: SoundSource =
|
||||||
BeepGenerator.newBeep(".", 1000, 100)
|
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 = {
|
animation: Option[Animation] = None): Unit = {
|
||||||
sprite = name
|
sprite = name
|
||||||
foreground = color
|
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 = {
|
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,
|
private def _rect(x: Float, y: Float, width: Float, height: Float,
|
||||||
fixUV: Boolean = true,
|
fixUV: Boolean = true,
|
||||||
animation: Option[Animation] = None): Unit = {
|
spriteRectOptional: Option[Rect2D] = None): Unit = {
|
||||||
val spriteRect = animation match {
|
val spriteRect = spriteRectOptional.getOrElse(this.spriteRect)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
val uvTransform = Transform2D.translate(spriteRect.x, spriteRect.y) >>
|
val uvTransform = Transform2D.translate(spriteRect.x, spriteRect.y) >>
|
||||||
(if (fixUV)
|
(if (fixUV)
|
||||||
|
|||||||
@ -367,4 +367,10 @@ object IconSource {
|
|||||||
|
|
||||||
val InnerBorderB: IconSource = IconSource(s"$prefix/InnerBorderB")
|
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 SyncedInventory
|
||||||
with DiskActivityHandler
|
with DiskActivityHandler
|
||||||
with OcelotLogParticleNode
|
with OcelotLogParticleNode
|
||||||
|
with SmokeParticleNode
|
||||||
with ShiftClickNode
|
with ShiftClickNode
|
||||||
with PositionalSoundSourcesNode {
|
with PositionalSoundSourcesNode {
|
||||||
|
|
||||||
@ -32,7 +33,10 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
|
|||||||
private def soundCardSource: SoundSource = soundCardSounds._2
|
private def soundCardSource: SoundSource = soundCardSounds._2
|
||||||
|
|
||||||
// PositionalSoundSourcesNode
|
// PositionalSoundSourcesNode
|
||||||
override def soundSources: Seq[SoundSource] = Seq(SoundSource.MinecraftExplosion)
|
override def soundSources: Seq[SoundSource] = Seq(
|
||||||
|
SoundSource.MinecraftExplosion,
|
||||||
|
SoundSource.SelfDestructingCardCountdownBeep
|
||||||
|
)
|
||||||
|
|
||||||
eventHandlers += {
|
eventHandlers += {
|
||||||
case BrainEvent(event: MachineCrashEvent) =>
|
case BrainEvent(event: MachineCrashEvent) =>
|
||||||
@ -69,7 +73,7 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
|
|||||||
slot.get match {
|
slot.get match {
|
||||||
case Some(card: SelfDestructingCard) =>
|
case Some(card: SelfDestructingCard) =>
|
||||||
if (card.time > 0 && card.time % 20 == 0) {
|
if (card.time > 0 && card.time % 20 == 0) {
|
||||||
SoundSource.SelfDestructingCardBeep.play()
|
SoundSource.SelfDestructingCardCountdownBeep.play()
|
||||||
}
|
}
|
||||||
case _ =>
|
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
|
package ocelot.desktop.util
|
||||||
|
|
||||||
import ocelot.desktop.geometry.{Rect2D, Size2D}
|
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||||
import ocelot.desktop.graphics.{IconSource, Texture}
|
import ocelot.desktop.graphics.{IconSource, Texture}
|
||||||
|
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user