Fangames > Game Design

Bullet Hell coding in Java

(1/1)

Reply Code: Alpha:
Hey guys,

I just recently tried to emulate simple bullet patterns with Java and I couldn't even wrap my mind around the concept of how you would code something like that in Java. The first thing I wanted to do was multithread every single bullet in a container just as they told us at university but that would totally fuck up every computer.

Does anyone hear happen to have any example code on how to do stuff like that with Java? Would be very interesting.

Thanks in advance.

sharpobject:
The easiest solution is probably to use BulletML. BulletML is a godawful domain-specific language for writing bullet hell patterns. It is a little limited, in that you can't do things like bouncing off the edge of the screen or anchoring some bullet to another, and the BulletML script itself will have no concept of the size or type of bullet. You can get BulletML here. Here's some screenies of a game I scripted in BulletML forever ago. sdmkun and rRootage are awesome games scripted with BulletML, so you can open them up and look at their guts if you want.

If BulletML does not sound good, and you want to write your bullet patterns in the project's native language, you can use lightweight threads. If you have the ability to choose your JVM, the Da Vinci and Avian VMs expose coroutines or the right tools to build them (presumably you can also find libraries that have actually built them, idk). If you have the ability to choose your language, Scala's delimited continuations will give you the same thing on any JVM, and this seems like an acceptable coroutine library.

If you don't want to embed some other language in the project and none of the ways of getting lightweight threads on the JVM are acceptable, you'll probably have to phrase all your bullet patterns as functions that can be called once each frame. I'll provide a couple examples of how to do this, using bullet patterns from something I abandoned.

These two patterns look a lot like ZUN's shit.


--- Code: ---function border_of_wave_and_particle(self)
    local theta = 0
    local dtheta = 0
    local ddtheta = 0.2 * degrees
    self.child_color = colors.purple
    while true do
        local n = floor(3+2*rank)
        for i=tau/n, tau, tau/n do
            self:fire({speed=8, direction=theta+i})
        end
        dtheta = dtheta + ddtheta
        theta = (theta + dtheta) % tau
        self:wait(2)
    end
end
--- End code ---

would become


--- Code: ---function gen_border_of_wave_and_particle()
    local theta = 0
    local dtheta = 0
    local ddtheta = 0.2 * degrees
    local age = 0
    return function(self)
      if age == 0 then
        self.child_color = colors.purple
      end
      if age % 2 == 0 then
        local n = floor(3+2*rank)
        for i=tau/n, tau, tau/n do
            self:fire({speed=8, direction=theta+i})
        end
        dtheta = dtheta + ddtheta
        theta = (theta + dtheta) % tau
      end
      age = age + 1
    end
end
--- End code ---

and


--- Code: ---function mokou_197(self)
    local bouncer = function(self) while true do
        self:wait(1)
        if self.y < 0 or self.y > 600 then
            self.child_color = colors.blue
            self:fire({speed=self.speed/2, direction=self.direction+pi})
            return
        end
    end end
    local spawner = function(self) while true do
        self:wait(20)
        self.child_color = colors.pink
        self:fire({speed=4, direction=facing_up},   bouncer)
        self:fire({speed=4, direction=facing_down}, bouncer)
    end end
    while true do
        self.child_kind = "spawner"
        self.child_color = nil
        self:fire({speed=1.25, direction=facing_right}, spawner)
        self:fire({speed=1.25, direction=facing_left},  spawner)
        self.child_kind = "enemy_bullet"
        self.child_color = colors.pink
        self:fire({speed=4, direction=facing_up},   bouncer)
        self:fire({speed=4, direction=facing_down}, bouncer)
        self:wait(180 - min(45*rank,120))
    end
end
--- End code ---

would become


--- Code: ---function gen_mokou_197_bouncer()
    local fired = false
    return function(self)
        -- technically this is wrong, the original code does not check on the first frame...
        -- that really doesn't matter though
        if not fired and (self.y < 0 or self.y > 600) then
            self.child_color = colors.blue
            self:fire({speed=self.speed/2, direction=self.direction+pi})
            fired = true
        end
    end
end

function gen_mokou_197_spawner()
    local age = 0
    return function(self)
        if age>0 and age%20==0 then
            self.child_color = colors.pink
            self:fire({speed=4, direction=facing_up}, gen_mokou_197_bouncer())
            self:fire({speed=4, direction=facing_down}, gen_mokou_197_bouncer())
        end
        age = age + 1
    end
end

function gen_mokou_197()
    local timeout = 1
    local timer = 0
    return function(self)
        timer = timer + 1
        if timer >= timeout then
            self.child_kind = "spawner"
            self.child_color = nil
            self:fire({speed=1.25, direction=facing_right}, gen_mokou_197_spawner())
            self:fire({speed=1.25, direction=facing_left},  gen_mokou_197_spawner())
            self.child_kind = "enemy_bullet"
            self.child_color = colors.pink
            self:fire({speed=4, direction=facing_up},   gen_mokou_197_bouncer())
            self:fire({speed=4, direction=facing_down}, gen_mokou_197_bouncer())
            timer = 0
            timeout = floor(180 - min(45*rank,120))
        end
    end
end
--- End code ---


You would call gen_* to get the function that you would then call each frame on a particular enemy (probably Yukari, Satori, or mai waifu). To do this sort of thing in Java, the gen_* function would be a class, the closed-over locals would be class members, and the returned function would be a method. Each in-game entity could own a BulletAction[], and once per frame you can go through each object and call all of its BulletActions. Apparently I decided to do this after moving everything and before collision detection, but I don't really remember why.

At the last minute I realized that it may be possible to address the problem by doing nothing. Lots of shmups have so few entities that need to run their own particular code that you really could attach threads to each of them and be fine. For example, in DoDonPachi, none of the enemy projectiles ever change their direction or velocity or spawn other projectiles. One thread per on-screen enemy would be like 30 threads at the very most.

Reply Code: Alpha:
Thanks a lot for that reply!

I will check out the stuff you pointed out and see what I can do.

Navigation

[0] Message Index

Go to full version