Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed Feb 2, 2025
1 parent 65e80a6 commit 9e65f93
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,31 @@ import groovy.transform.stc.SimpleType
*/
class GamepadControl extends Control<GamepadAxisEvent> {

private static final int[] AXIS_TYPES = [
GLFW_GAMEPAD_AXIS_LEFT_X, GLFW_GAMEPAD_AXIS_LEFT_Y,
GLFW_GAMEPAD_AXIS_RIGHT_X, GLFW_GAMEPAD_AXIS_RIGHT_Y
]
private static final float AXIS_THRESHOLD = 0.2f // TODO: Configurable deadzone

private static final int[] TRIGGER_TYPES = [
GLFW_GAMEPAD_AXIS_LEFT_TRIGGER,
GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER
]

private final int type
private final Closure handler
private final Closure releaseHandler

/**
* Create a new gamepad control to act on the given gamepad axis events.
*/
GamepadControl(int type, String name,
@ClosureParams(value = SimpleType, options = 'float') Closure handler) {
@ClosureParams(value = SimpleType, options = 'float') Closure handler, Closure releaseHandler = null) {

super(GamepadAxisEvent, name, determineBindingName(type))
this.type = type
this.handler = handler
this.releaseHandler = releaseHandler
}

/**
Expand All @@ -61,7 +74,18 @@ class GamepadControl extends Control<GamepadAxisEvent> {
void handleEvent(GamepadAxisEvent event) {

if (event.type == type) {
handler(event.value)
if (type in AXIS_TYPES) {
var value = event.value
if (Math.abs(value) > AXIS_THRESHOLD) {
handler(event.value)
}
else {
releaseHandler?.call()
}
}
else {
handler(event.value)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package nz.net.ultraq.redhorizon.shooter

import nz.net.ultraq.redhorizon.classic.filetypes.IniFile
import nz.net.ultraq.redhorizon.classic.filetypes.MapFile
import nz.net.ultraq.redhorizon.classic.filetypes.PalFile
import nz.net.ultraq.redhorizon.classic.maps.Map
import nz.net.ultraq.redhorizon.classic.nodes.GlobalPalette
Expand Down Expand Up @@ -93,9 +91,9 @@ class Shooter {

camera.follow(player)

var mapFile = resourceManager.loadFile('scm01ea.ini', IniFile)
var map = new Map(mapFile as MapFile, resourceManager)
scene << map
// var mapFile = resourceManager.loadFile('scm01ea.ini', IniFile)
// var map = new Map(mapFile as MapFile, resourceManager)
// scene << map
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2025, Emanuel Rabina (http://www.ultraq.net.nz/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package nz.net.ultraq.redhorizon.shooter.objects

import nz.net.ultraq.redhorizon.classic.nodes.PalettedSprite
import nz.net.ultraq.redhorizon.classic.nodes.Rotatable
import nz.net.ultraq.redhorizon.engine.scenegraph.Temporal
import nz.net.ultraq.redhorizon.filetypes.ImagesFile

import org.joml.Vector2f

/**
* A projectile fired by the player unit.
*
* @author Emanuel Rabina
*/
class Bullet extends PalettedSprite implements Rotatable, Temporal {

private static final float MOVEMENT_SPEED = 100

// TODO: Load once, create instances

private final Vector2f velocity // TODO: Velocity = ECS component candidate
private final Vector2f movement = new Vector2f()

/**
* Constructor, load the sprite for the bullet.
*/
Bullet(ImagesFile imagesFile, float heading) {

super(imagesFile)
bounds { ->
setMax(imagesFile.width, imagesFile.height)
}

var headings = 32 // "dragon" sprite has 32 headings
var degreesPerHeading = 360f / headings

// NOTE: C&C unit headings were ordered in a counter-clockwise order
// (maybe to match how radians work?), the reverse from how
// degrees-based headings are done.
var closestHeading = Math.round(heading / degreesPerHeading)
frame = closestHeading ? headings - closestHeading as int : 0

var headingInRadians = Math.toRadians(heading)
velocity = new Vector2f((float)Math.sin(headingInRadians), (float)Math.cos(headingInRadians)).normalize()
}

@Override
void update(float delta) {

movement.set(velocity).normalize().mul(MOVEMENT_SPEED).mul(delta).add(position.x(), position.y())
setPosition(movement.x, movement.y)

super.update(delta)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import nz.net.ultraq.redhorizon.engine.scenegraph.Scene
import nz.net.ultraq.redhorizon.engine.scenegraph.Temporal
import nz.net.ultraq.redhorizon.engine.scenegraph.UpdateHint
import nz.net.ultraq.redhorizon.engine.scenegraph.scripting.Script
import nz.net.ultraq.redhorizon.filetypes.ImagesFile

import org.joml.Vector2f
import org.joml.primitives.Rectanglef
Expand All @@ -41,6 +42,7 @@ import static org.lwjgl.glfw.GLFW.*

import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit

Expand All @@ -54,7 +56,6 @@ class Player extends Node<Player> implements Rotatable, Temporal {
private static final Logger logger = LoggerFactory.getLogger(Player)
private static final Rectanglef MOVEMENT_RANGE = Map.MAX_BOUNDS
private static final float MOVEMENT_SPEED = 200f
private static final float MOVEMENT_THRESHOLD = 0.2f
private static final float ROTATION_SPEED = 180f
private static final Vector2f up = new Vector2f(0, 1)

Expand All @@ -66,7 +67,6 @@ class Player extends Node<Player> implements Rotatable, Temporal {
private Vector2f velocity = new Vector2f()
private Vector2f direction = new Vector2f()
private Vector2f movement = new Vector2f()
private float movementHeading
private Vector2f lookAt = new Vector2f()
private Vector2f lastLookAt = new Vector2f()
private Vector2f relativeLookAt = new Vector2f()
Expand Down Expand Up @@ -99,6 +99,11 @@ class Player extends Node<Player> implements Rotatable, Temporal {
velocity.set(0, 0)
}

private final long rateOfFireMs = 1000
private ScheduledFuture<?> firingTask
private final ImagesFile bulletImagesFile
private ScheduledExecutorService firingService = Executors.newScheduledThreadPool(4)

/**
* Constructor, load the sprite and scripts for the player.
*/
Expand All @@ -123,6 +128,8 @@ class Player extends Node<Player> implements Rotatable, Temporal {
return true
}

bulletImagesFile = resourceManager.loadFile('dragon.shp', ShpFile)

attachScript(new PlayerScript())
}

Expand Down Expand Up @@ -159,7 +166,6 @@ class Player extends Node<Player> implements Rotatable, Temporal {
Math.clamp(movement.x, MOVEMENT_RANGE.minX, MOVEMENT_RANGE.maxX),
Math.clamp(movement.y, MOVEMENT_RANGE.minY, MOVEMENT_RANGE.maxY)
)
movementHeading = Math.toDegrees(velocity.angle(up)) as float
}
else {
stop.execute()
Expand All @@ -182,13 +188,21 @@ class Player extends Node<Player> implements Rotatable, Temporal {
else if (direction.length()) {
heading = Math.toDegrees(direction.angle(up)) as float
}
else {
heading = movementHeading
else if (velocity.length()) {
heading = Math.toDegrees(velocity.angle(up)) as float
}

// Gamepad firing
if (firing) {
logger.debug('Firing')
firingTask = firingService.scheduleAtFixedRate({ ->
var bullet = new Bullet(bulletImagesFile, heading)
bullet.position = globalPosition
addChild(bullet)
}, 0, rateOfFireMs, TimeUnit.MILLISECONDS)
}
else {
firingTask?.cancel()
}
}

Expand Down Expand Up @@ -252,41 +266,25 @@ class Player extends Node<Player> implements Rotatable, Temporal {

// Gamepad controls
scene.inputEventStream.addControls(
new GamepadControl(GLFW_GAMEPAD_AXIS_LEFT_X, 'Movement along the X axis', { value ->
if (value < -MOVEMENT_THRESHOLD || value > MOVEMENT_THRESHOLD) {
velocity.x = value
}
else {
velocity.x = 0
}
}),
new GamepadControl(GLFW_GAMEPAD_AXIS_LEFT_Y, 'Movement along the Y axis', { value ->
if (value < -MOVEMENT_THRESHOLD || value > MOVEMENT_THRESHOLD) {
velocity.y = -value
}
else {
velocity.y = 0
}
}),
new GamepadControl(GLFW_GAMEPAD_AXIS_RIGHT_X, 'Heading along the X axis', { value ->
if (value < -MOVEMENT_THRESHOLD || value > MOVEMENT_THRESHOLD) {
direction.x = value
}
else {
direction.x = 0
}
}),
new GamepadControl(GLFW_GAMEPAD_AXIS_RIGHT_Y, 'Heading along the Y axis', { value ->
if (value < -MOVEMENT_THRESHOLD || value > MOVEMENT_THRESHOLD) {
direction.y = -value
}
else {
direction.y = 0
}
}),
new GamepadControl(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, 'Fire', { value ->
firing = value > -1f
})
new GamepadControl(GLFW_GAMEPAD_AXIS_LEFT_X, 'Movement along the X axis',
{ value -> velocity.x = value },
{ -> velocity.x = 0 }
),
new GamepadControl(GLFW_GAMEPAD_AXIS_LEFT_Y, 'Movement along the Y axis',
{ value -> velocity.y = -value },
{ -> velocity.y = 0 }
),
new GamepadControl(GLFW_GAMEPAD_AXIS_RIGHT_X, 'Heading along the X axis',
{ value -> direction.x = value },
{ -> direction.x = 0 }
),
new GamepadControl(GLFW_GAMEPAD_AXIS_RIGHT_Y, 'Heading along the Y axis',
{ value -> direction.y = -value },
{ -> direction.y = 0 }
),
new GamepadControl(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, 'Fire',
{ value -> firing = value > -1f }
)
)
}

Expand Down

0 comments on commit 9e65f93

Please sign in to comment.