1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-03-25 03:19:27 +00:00

Touch controls, Android support

This commit is contained in:
Alula 2020-12-20 21:57:17 +01:00
parent 3c5f5c7496
commit 82cce198ee
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
26 changed files with 637 additions and 86 deletions

View file

@ -14,7 +14,7 @@ min_sdk_version = 26
build_targets = ["aarch64-linux-android"]
package_name = "io.github.doukutsu_rs.android"
apk_label = "doukutsu-rs"
opengles_version = [2, 0]
opengles_version = [3, 1]
fullscreen = true
orientation = "sensorLandscape"
permission = [
@ -55,11 +55,11 @@ directories = "2"
gfx = "0.18"
gfx_core = "0.9"
gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"}
ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "aad56b0d173ca9f4aeb28599075b5af49ab9214e"}
glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "android-support"}
imgui = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "a990a538b66cb67dba3a072bf299b6a51c001447"}
imgui-gfx-renderer = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "a990a538b66cb67dba3a072bf299b6a51c001447"}
imgui-winit-support = {git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "a990a538b66cb67dba3a072bf299b6a51c001447"}
ggez = {git = "https://github.com/doukutsu-rs/ggez.git", rev = "43631b0401271d4bc8fe4a5afba8aad63976dba1"}
glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "master"}
imgui = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"}
imgui-gfx-renderer = {git = "https://github.com/Gekkio/imgui-rs.git", rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"}
imgui-winit-support = {git = "https://github.com/Gekkio/imgui-rs.git", default-features = false, features = ["winit-23"], rev = "7e2293bde67f869750ab0e649fbfbd842fb0c785"}
image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"]}
itertools = "0.9.0"
lazy_static = "1.4.0"
@ -77,9 +77,9 @@ strum_macros = "0.18.0"
# remove and replace when drain_filter is in stable
vec_mut_scan = "0.3.0"
webbrowser = "0.5.5"
winit = {version = "0.23.0", features = ["serde"]}
winit = {version = "0.24.0", features = ["serde"]}
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.2.0"
ndk-glue = "0.2.0"
ndk = "0.2"
ndk-glue = "0.2"
jni = "0.17"

View file

@ -67,10 +67,10 @@ impl BMFontRenderer {
pub fn draw_text<I: Iterator<Item=char>>(&self, iter: I, x: f32, y: f32, constants: &EngineConstants, texture_set: &mut TextureSet, ctx: &mut Context) -> GameResult {
self.draw_colored_text(iter, x, y, (255, 255, 255), constants, texture_set, ctx)
self.draw_colored_text(iter, x, y, (255, 255, 255, 255), constants, texture_set, ctx)
}
pub fn draw_colored_text<I: Iterator<Item=char>>(&self, iter: I, x: f32, y: f32, color: (u8, u8, u8),
pub fn draw_colored_text<I: Iterator<Item=char>>(&self, iter: I, x: f32, y: f32, color: (u8, u8, u8, u8),
constants: &EngineConstants, texture_set: &mut TextureSet, ctx: &mut Context) -> GameResult {
if self.pages.len() == 1 {
let batch = texture_set.get_or_load_batch(ctx, constants, self.pages.get(0).unwrap())?;

View file

@ -0,0 +1,13 @@
#version 300 es
uniform mediump sampler2D t_Texture;
varying mediump vec2 v_Uv;
varying mediump vec4 v_Color;
//uniform mediump mat4 u_MVP;
mediump vec4 Target0;
void main() {
gl_FragColor = texture2D(t_Texture, v_Uv) * v_Color;
}

View file

@ -0,0 +1,26 @@
#version 300 es
attribute mediump vec2 a_Pos;
attribute mediump vec2 a_Uv;
attribute mediump vec4 a_VertColor;
attribute mediump vec4 a_Src;
attribute mediump vec4 a_TCol1;
attribute mediump vec4 a_TCol2;
attribute mediump vec4 a_TCol3;
attribute mediump vec4 a_TCol4;
attribute mediump vec4 a_Color;
uniform mediump mat4 u_MVP;
varying mediump vec2 v_Uv;
varying mediump vec4 v_Color;
void main() {
v_Uv = a_Uv * a_Src.zw + a_Src.xy;
v_Color = a_Color * a_VertColor;
mat4 instance_transform = mat4(a_TCol1, a_TCol2, a_TCol3, a_TCol4);
vec4 position = instance_transform * vec4(a_Pos, 0.0, 1.0);
gl_Position = u_MVP * position;
}

View file

@ -0,0 +1,27 @@
#version 300 es
in vec2 a_Pos;
in vec2 a_Uv;
in vec4 a_Src;
in vec4 a_TCol1;
in vec4 a_TCol2;
in vec4 a_TCol3;
in vec4 a_TCol4;
in vec4 a_Color;
layout (std140) uniform Globals {
mat4 u_MVP;
};
out vec2 v_Uv;
out vec4 v_Color;
void main() {
v_Uv = a_Uv * a_Src.zw + a_Src.xy;
v_Color = a_Color;
mat4 instance_transform = mat4(a_TCol1, a_TCol2, a_TCol3, a_TCol4);
vec4 position = instance_transform * vec4(a_Pos, 0.0, 1.0);
gl_Position = u_MVP * position;
}

View file

@ -0,0 +1,54 @@
#version 300 es
precision mediump float;
uniform sampler2D t_Texture;
in vec2 v_Uv;
in vec4 v_Color;
out vec4 Target0;
layout (std140) uniform Globals {
mat4 u_MVP;
};
layout (std140) uniform WaterShaderParams {
vec2 u_Resolution;
vec2 u_FramePos;
float u_Tick;
};
void main() {
vec2 wave = v_Uv;
wave.x += sin((-u_FramePos.y / u_Resolution.y + v_Uv.x * 16.0) + u_Tick / 20.0) * 2.0 / u_Resolution.x;
wave.y -= cos((-u_FramePos.x / u_Resolution.x + v_Uv.y * 16.0) + u_Tick / 5.0) * 2.0 / u_Resolution.y;
float off = 0.4 / u_Resolution.y;
vec4 color = texture(t_Texture, wave);
color.r = texture(t_Texture, wave + off).r;
color.b = texture(t_Texture, wave - off).b;
Target0 = (vec4(0.4, 0.6, 0.8, 1.0) * 0.3) + (color * v_Color * 0.7);
}
/*
precision mediump float;
uniform sampler2D t_Texture;
varying vec2 v_Uv;
varying vec4 v_Color;
uniform mat4 u_MVP;
uniform vec2 u_Resolution;
uniform vec2 u_FramePos;
uniform float u_Tick;
void main() {
vec2 wave = v_Uv;
wave.x += sin((-u_FramePos.y / u_Resolution.y + v_Uv.x * 16.0) + u_Tick / 20.0) * 2.0 / u_Resolution.x;
wave.y -= cos((-u_FramePos.x / u_Resolution.x + v_Uv.y * 16.0) + u_Tick / 5.0) * 2.0 / u_Resolution.y;
float off = 0.4 / u_Resolution.y;
vec4 color = texture2D(t_Texture, wave);
color.r = texture2D(t_Texture, wave + off).r;
color.b = texture2D(t_Texture, wave - off).b;
gl_FragColor = (vec4(0.4, 0.6, 0.8, 1.0) * 0.3) + (color * v_Color * 0.7);
}*/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 967 B

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -111,9 +111,12 @@ impl BuiltinFS {
FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")),
FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.png")),
FSNode::File("pixtone.pcm", include_bytes!("builtin/pixtone.pcm")),
FSNode::File("touch.png", include_bytes!("builtin/touch.png")),
FSNode::Directory("shaders", vec![
FSNode::File("basic_150.vert.glsl", include_bytes!("builtin/shaders/basic_150.vert.glsl")),
FSNode::File("water_150.frag.glsl", include_bytes!("builtin/shaders/water_150.frag.glsl")),
FSNode::File("basic_es300.vert.glsl", include_bytes!("builtin/shaders/basic_es300.vert.glsl")),
FSNode::File("water_es300.frag.glsl", include_bytes!("builtin/shaders/water_es300.frag.glsl")),
]),
FSNode::Directory("lightmap", vec![
FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png")),

View file

@ -56,8 +56,8 @@ impl HUD {
}
}
impl GameEntity<(&Player, &Inventory)> for HUD {
fn tick(&mut self, state: &mut SharedGameState, (player, inventory): (&Player, &Inventory)) -> GameResult {
impl GameEntity<(&Player, &mut Inventory)> for HUD {
fn tick(&mut self, state: &mut SharedGameState, (player, inventory): (&Player, &mut Inventory)) -> GameResult {
let (ammo, max_ammo) = inventory.get_current_ammo();
let (xp, max_xp, max_level) = inventory.get_current_max_exp(&state.constants);
@ -104,6 +104,56 @@ impl GameEntity<(&Player, &Inventory)> for HUD {
self.weapon_x_pos += 2;
}
if player.cond.alive() {
if player.controller.trigger_next_weapon() {
state.sound_manager.play_sfx(4);
inventory.next_weapon();
self.weapon_x_pos = 32;
}
if player.controller.trigger_prev_weapon() {
state.sound_manager.play_sfx(4);
inventory.prev_weapon();
self.weapon_x_pos = 0;
}
}
// touch handler
if state.settings.touch_controls && self.weapon_count != 0 {
let mut rect = Rect::new(0, 0, 0, 16);
let weapon_offset = match self.alignment {
Alignment::Left => 0,
Alignment::Right => (state.canvas_size.0 - 104.0) as isize,
};
for a in 0..self.weapon_count {
let mut pos_x = ((a as isize - self.current_weapon) * 16) + self.weapon_x_pos as isize;
if pos_x < 8 {
pos_x += 48 + self.weapon_count as isize * 16;
} else if pos_x >= 24 {
pos_x += 48;
}
if pos_x >= 72 + ((self.weapon_count as isize - 1) * 16) {
pos_x -= 48 + self.weapon_count as isize * 16;
} else if pos_x < 72 && pos_x >= 24 {
pos_x -= 48;
}
let wtype = unsafe { *self.weapon_types.get_unchecked(a) };
if wtype != 0 {
rect = Rect::new_size(pos_x + weapon_offset - 4, 16 - 4, 24, 24);
if state.touch_controls.consume_click_in(rect) {
state.sound_manager.play_sfx(4);
inventory.current_weapon = a as u16;
self.weapon_x_pos = 32;
}
}
}
}
Ok(())
}

View file

@ -3,12 +3,14 @@ use ggez::{Context, GameResult};
use crate::common::Rect;
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::input::touch_controls::TouchControlType;
use crate::player::Player;
use crate::shared_game_state::SharedGameState;
use crate::text_script::ScriptMode;
pub struct StageSelect {
pub current_teleport_slot: u8,
prev_teleport_slot: u8,
stage_select_text_y_pos: usize,
tick: usize,
}
@ -17,6 +19,7 @@ impl StageSelect {
pub fn new() -> StageSelect {
StageSelect {
current_teleport_slot: 0,
prev_teleport_slot: 0,
stage_select_text_y_pos: 54,
tick: 0,
}
@ -30,6 +33,8 @@ impl StageSelect {
impl GameEntity<(&Player, &Player)> for StageSelect {
fn tick(&mut self, state: &mut SharedGameState, (player1, player2): (&Player, &Player)) -> GameResult {
state.touch_controls.control_type = TouchControlType::None;
let slot_count = state.teleporter_slots.iter()
.filter(|&&(index, _event_num)| index != 0)
.count();
@ -44,9 +49,9 @@ impl GameEntity<(&Player, &Player)> for StageSelect {
let left_pressed = player1.controller.trigger_left() || player2.controller.trigger_left();
let right_pressed = player1.controller.trigger_right() || player2.controller.trigger_right();
let ok_pressed = player1.controller.trigger_jump() || player1.controller.trigger_menu_ok()
let mut ok_pressed = player1.controller.trigger_jump() || player1.controller.trigger_menu_ok()
|| player2.controller.trigger_jump() || player2.controller.trigger_menu_ok();
let cancel_pressed = player1.controller.trigger_shoot() || player2.controller.trigger_shoot();
let mut cancel_pressed = player1.controller.trigger_shoot() || player2.controller.trigger_shoot();
if left_pressed {
if self.current_teleport_slot == 0 {
@ -62,7 +67,8 @@ impl GameEntity<(&Player, &Player)> for StageSelect {
}
}
if left_pressed || right_pressed {
if self.prev_teleport_slot != self.current_teleport_slot {
self.prev_teleport_slot = self.current_teleport_slot;
state.sound_manager.play_sfx(1);
if let Some(&(index, _event_num)) = state.teleporter_slots.get(self.current_teleport_slot as usize) {
state.textscript_vm.start_script(1000 + index);
@ -71,6 +77,34 @@ impl GameEntity<(&Player, &Player)> for StageSelect {
}
}
if state.settings.touch_controls {
let slot_offset = ((state.canvas_size.0 - 40.0 * slot_count as f32) / 2.0).floor();
let mut slot_rect;
for i in 0..slot_count {
slot_rect = Rect::new_size(slot_offset as isize + i as isize * 40 - 2, 64 - 8, 36, 32);
if state.touch_controls.consume_click_in(slot_rect) {
if self.current_teleport_slot as usize == i {
ok_pressed = true;
} else {
state.sound_manager.play_sfx(1);
self.current_teleport_slot = i as u8;
}
break;
}
}
slot_rect = Rect::new_size(state.canvas_size.0 as isize - 34, 8, 26, 26);
if state.touch_controls.consume_click_in(slot_rect) {
state.sound_manager.play_sfx(5);
cancel_pressed = true;
}
}
if ok_pressed || cancel_pressed {
self.reset();
state.textscript_vm.set_mode(ScriptMode::Map);
@ -121,6 +155,14 @@ impl GameEntity<(&Player, &Player)> for StageSelect {
batch.draw(ctx)?;
if state.settings.touch_controls {
let close_rect = Rect { left: 110, top: 110, right: 128, bottom: 128 };
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "builtin/touch")?;
batch.add_rect(state.canvas_size.0 - 30.0, 12.0, &close_rect);
batch.draw(ctx)?;
}
Ok(())
}
}

View file

@ -707,7 +707,7 @@ impl EngineConstants {
"Title" => (320, 48),
},
textscript: TextScriptConsts {
encoding: TextScriptEncoding::ShiftJIS,
encoding: TextScriptEncoding::UTF8,
encrypted: true,
animated_face_pics: false,
textbox_rect_top: Rect { left: 0, top: 0, right: 244, bottom: 8 },
@ -741,7 +741,7 @@ impl EngineConstants {
},
font_path: "builtin/builtin_font.fnt".to_string(),
font_scale: 1.0,
font_space_offset: -3.0,
font_space_offset: 0.0,
organya_paths: vec![
str!("/org/"), // NXEngine
str!("/base/Org/"), // CS+

View file

@ -14,7 +14,7 @@ impl DummyPlayerController {
}
impl PlayerController for DummyPlayerController {
fn update(&mut self, _state: &SharedGameState, _ctx: &mut Context) -> GameResult {
fn update(&mut self, _state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
Ok(())
}

View file

@ -23,7 +23,7 @@ bitfield! {
pub next_weapon, set_next_weapon: 8;
pub prev_weapon, set_prev_weapon: 9;
pub escape, set_escape: 10;
pub enter, set_enter: 10;
pub enter, set_enter: 11;
}
#[derive(Clone)]
@ -46,7 +46,7 @@ impl KeyboardController {
}
impl PlayerController for KeyboardController {
fn update(&mut self, state: &SharedGameState, ctx: &mut Context) -> GameResult {
fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let keymap = match self.target {
TargetPlayer::Player1 => &state.settings.player1_key_map,
TargetPlayer::Player2 => &state.settings.player2_key_map,

View file

@ -3,3 +3,4 @@ pub mod dummy_player_controller;
pub mod keyboard_player_controller;
pub mod player_controller;
pub mod touch_controls;
pub mod touch_player_controller;

View file

@ -3,7 +3,7 @@ use ggez::{Context, GameResult};
use crate::shared_game_state::SharedGameState;
pub trait PlayerController: PlayerControllerClone {
fn update(&mut self, state: &SharedGameState, ctx: &mut Context) -> GameResult;
fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult;
fn update_trigger(&mut self);

View file

@ -5,20 +5,37 @@ use crate::common::Rect;
use crate::engine_constants::EngineConstants;
use crate::texture_set::TextureSet;
struct TouchPoint {
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum TouchControlType {
None,
Dialog,
Controls,
}
#[derive(Copy, Clone)]
pub struct TouchPoint {
id: u64,
touch_id: u64,
position: (f64, f64),
last_position: (f64, f64),
}
pub struct TouchControls {
points: Vec<TouchPoint>,
pub control_type: TouchControlType,
pub points: Vec<TouchPoint>,
pub interact_icon: bool,
touch_id_counter: u64,
clicks: Vec<TouchPoint>,
}
impl TouchControls {
pub fn new() -> TouchControls {
TouchControls {
control_type: TouchControlType::None,
touch_id_counter: 0,
interact_icon: false,
points: Vec::with_capacity(8),
clicks: Vec::with_capacity(8),
}
}
@ -27,22 +44,61 @@ impl TouchControls {
TouchPhase::Started | TouchPhase::Moved => {
if let Some(point) = self.points.iter_mut().find(|p| p.id == touch.id) {
point.last_position = point.position;
point.position = (touch.location.x, touch.location.y);
point.position = (touch.location.x / scale as f64, touch.location.y / scale as f64);
} else {
self.points.push(TouchPoint {
self.touch_id_counter = self.touch_id_counter.wrapping_add(1);
let point = TouchPoint {
id: touch.id,
position: (touch.location.x, touch.location.y),
touch_id: self.touch_id_counter,
position: (touch.location.x / scale as f64, touch.location.y / scale as f64),
last_position: (0.0, 0.0),
});
};
self.points.push(point);
if touch.phase == TouchPhase::Started {
self.clicks.push(point);
}
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
self.points.retain(|p| p.id != touch.id);
self.clicks.retain(|p| p.id != touch.id);
}
}
}
pub fn draw(&self, constants: &EngineConstants, texture_set: &mut TextureSet, ctx: &mut Context) -> GameResult {
pub fn point_in(&self, bounds: Rect) -> Option<u64> {
for point in self.points.iter() {
if (point.position.0 as isize) > bounds.left
&& (point.position.0 as isize) < bounds.right
&& (point.position.1 as isize) > bounds.top
&& (point.position.1 as isize) < bounds.bottom {
return Some(point.touch_id);
}
}
None
}
pub fn consume_click_in(&mut self, bounds: Rect) -> bool {
self.clicks.retain(|p| p.touch_id != 0);
for point in self.clicks.iter_mut() {
if (point.position.0 as isize) > bounds.left
&& (point.position.0 as isize) < bounds.right
&& (point.position.1 as isize) > bounds.top
&& (point.position.1 as isize) < bounds.bottom {
point.touch_id = 0;
return true;
}
}
false
}
pub fn draw(&self, canvas_size: (f32, f32), constants: &EngineConstants, texture_set: &mut TextureSet, ctx: &mut Context) -> GameResult {
let batch = texture_set.get_or_load_batch(ctx, constants, "Caret")?;
let rect = Rect::new_size(104, 120, 24, 24);
for point in self.points.iter() {
@ -51,6 +107,38 @@ impl TouchControls {
batch.draw(ctx)?;
if self.control_type == TouchControlType::Controls {
let batch = texture_set.get_or_load_batch(ctx, constants, "builtin/touch")?;
let color = (255, 255, 255, 160);
for x in 0..3 {
for y in 0..3 {
let mut icon_x = x;
let icon_y = y;
if self.interact_icon && x == 1 && y == 2 {
icon_x = 3;
}
batch.add_rect_tinted(4.0 + 48.0 * x as f32 + 8.0,
(canvas_size.1 - 4.0 - 48.0 * 3.0) + 48.0 * y as f32 + 8.0,
color,
&Rect::new_size(icon_x * 32, icon_y * 32, 32, 32));
}
}
batch.add_rect_tinted(canvas_size.0 - (4.0 + 48.0) + 8.0, canvas_size.1 - (4.0 + 48.0) + 8.0,
color,
&Rect::new_size(3 * 32, 32, 32, 32));
batch.add_rect_tinted(canvas_size.0 - (4.0 + 48.0) + 8.0, canvas_size.1 - (4.0 + 48.0) * 2.0 + 8.0,
color,
&Rect::new_size(3 * 32, 0, 32, 32));
batch.draw(ctx)?;
}
Ok(())
}
}

View file

@ -0,0 +1,219 @@
use ggez::{Context, GameResult};
use crate::bitfield;
use crate::common::Rect;
use crate::input::player_controller::PlayerController;
use crate::input::touch_controls::TouchControlType;
use crate::shared_game_state::SharedGameState;
/// A no-op implementation of player controller.
#[derive(Clone)]
pub struct TouchPlayerController {
state: KeyState,
old_state: KeyState,
trigger: KeyState,
prev_touch_len: usize,
}
bitfield! {
#[derive(Clone, Copy)]
pub struct KeyState(u16);
impl Debug;
pub left, set_left: 0;
pub right, set_right: 1;
pub up, set_up: 2;
pub down, set_down: 3;
pub map, set_map: 4;
pub inventory, set_inventory: 5;
pub jump, set_jump: 6;
pub shoot, set_shoot: 7;
pub next_weapon, set_next_weapon: 8;
pub prev_weapon, set_prev_weapon: 9;
pub pause, set_pause: 10;
}
impl TouchPlayerController {
pub fn new() -> TouchPlayerController {
TouchPlayerController {
state: KeyState(0),
old_state: KeyState(0),
trigger: KeyState(0),
prev_touch_len: 0,
}
}
}
impl PlayerController for TouchPlayerController {
fn update(&mut self, state: &mut SharedGameState, _ctx: &mut Context) -> GameResult {
match state.touch_controls.control_type {
TouchControlType::None => {}
TouchControlType::Dialog => {
self.state.set_jump(state.touch_controls.point_in(Rect::new_size(0, 0, state.canvas_size.0 as isize, state.canvas_size.1 as isize)).is_some());
if state.touch_controls.points.len() > 1 && self.prev_touch_len != state.touch_controls.points.len() {
self.prev_touch_len = state.touch_controls.points.len();
self.old_state.set_jump(false);
}
}
TouchControlType::Controls => {
self.state.0 = 0;
// left
self.state.set_left(self.state.left() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 4 - 48 * 2, 48, 48)).is_some());
// up
self.state.set_up(self.state.up() || state.touch_controls.point_in(Rect::new_size(48 + 4, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some());
// right
self.state.set_right(self.state.right() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 4 - 48 * 2, 48, 48)).is_some());
// down
self.state.set_down(self.state.down() || state.touch_controls.point_in(Rect::new_size(48 + 4, state.canvas_size.1 as isize - 4 - 48, 48, 48)).is_some());
// left+up
self.state.set_left(self.state.left() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some());
self.state.set_up(self.state.up() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some());
// right+up
self.state.set_right(self.state.right() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some());
self.state.set_up(self.state.up() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 4 - 48 * 3, 48, 48)).is_some());
// left+down
self.state.set_left(self.state.left() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some());
self.state.set_down(self.state.down() || state.touch_controls.point_in(Rect::new_size(4, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some());
// right+down
self.state.set_right(self.state.right() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some());
self.state.set_down(self.state.down() || state.touch_controls.point_in(Rect::new_size(4 + 48 * 2, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some());
self.state.set_jump(self.state.jump() || state.touch_controls.point_in(Rect::new_size(state.canvas_size.0 as isize - 48 - 4, state.canvas_size.1 as isize - 48 - 4, 48, 48)).is_some());
self.state.set_shoot(self.state.shoot() || state.touch_controls.point_in(Rect::new_size(state.canvas_size.0 as isize - 48 - 4, state.canvas_size.1 as isize - (48 - 4) * 2, 48, 48)).is_some());
}
}
Ok(())
}
fn update_trigger(&mut self) {
let mut trigger = self.state.0 ^ self.old_state.0;
trigger &= self.state.0;
self.old_state = self.state;
self.trigger = KeyState(trigger);
}
fn move_up(&self) -> bool {
self.state.up()
}
fn move_left(&self) -> bool {
self.state.left()
}
fn move_down(&self) -> bool {
self.state.down()
}
fn move_right(&self) -> bool {
self.state.right()
}
fn prev_weapon(&self) -> bool {
self.state.prev_weapon()
}
fn next_weapon(&self) -> bool {
self.state.next_weapon()
}
fn jump(&self) -> bool {
self.state.jump()
}
fn shoot(&self) -> bool {
self.state.shoot()
}
fn trigger_up(&self) -> bool {
self.trigger.up()
}
fn trigger_left(&self) -> bool {
self.trigger.left()
}
fn trigger_down(&self) -> bool {
self.trigger.down()
}
fn trigger_right(&self) -> bool {
self.trigger.right()
}
fn trigger_prev_weapon(&self) -> bool {
self.trigger.prev_weapon()
}
fn trigger_next_weapon(&self) -> bool {
self.trigger.next_weapon()
}
fn trigger_jump(&self) -> bool {
self.trigger.jump()
}
fn trigger_shoot(&self) -> bool {
self.trigger.shoot()
}
fn trigger_menu_ok(&self) -> bool {
self.trigger.jump()
}
fn trigger_menu_back(&self) -> bool {
self.trigger.shoot()
}
fn trigger_menu_pause(&self) -> bool {
self.trigger.pause()
}
fn look_up(&self) -> bool {
self.state.up()
}
fn look_left(&self) -> bool {
self.state.left()
}
fn look_down(&self) -> bool {
self.state.down()
}
fn look_right(&self) -> bool {
self.state.right()
}
fn move_analog_x(&self) -> f64 {
if self.state.left() && self.state.right() {
0.0
} else if self.state.left() {
-1.0
} else if self.state.right() {
1.0
} else {
0.0
}
}
fn move_analog_y(&self) -> f64 {
if self.state.up() && self.state.down() {
0.0
} else if self.state.up() {
-1.0
} else if self.state.down() {
1.0
} else {
0.0
}
}
}

View file

@ -148,7 +148,7 @@ impl Game {
if let Some(scene) = self.scene.as_mut() {
scene.draw(&mut self.state, ctx)?;
if self.state.settings.touch_controls {
self.state.touch_controls.draw(&self.state.constants, &mut self.state.texture_set, ctx)?;
self.state.touch_controls.draw(self.state.canvas_size, &self.state.constants, &mut self.state.texture_set, ctx)?;
}
graphics::set_transform(ctx, self.def_matrix);
@ -244,6 +244,13 @@ pub fn android_main() {
init().unwrap();
}
#[cfg(target_os = "android")]
static BACKENDS: [Backend; 2] = [
Backend::OpenGLES { major: 3, minor: 0 },
Backend::OpenGLES { major: 2, minor: 0 }
];
#[cfg(not(target_os = "android"))]
static BACKENDS: [Backend; 4] = [
Backend::OpenGL { major: 3, minor: 2 },
Backend::OpenGLES { major: 3, minor: 2 },
@ -263,10 +270,15 @@ fn init_ctx<P: Into<path::PathBuf> + Clone>(event_loop: &winit::event_loop::Even
.backend(*backend)
.build(event_loop);
if let Ok(mut ctx) = ctx {
mount_vfs(&mut ctx, Box::new(BuiltinFS::new()));
match ctx {
Ok(mut ctx) => {
mount_vfs(&mut ctx, Box::new(BuiltinFS::new()));
return Ok(ctx);
return Ok(ctx);
}
Err(err) => {
log::warn!("Failed to create backend using config {:?}: {}", backend, err);
}
}
}

View file

@ -1,7 +1,8 @@
use crate::common::Rect;
use ggez::{Context, GameResult};
use crate::shared_game_state::SharedGameState;
use crate::common::Rect;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::shared_game_state::SharedGameState;
pub enum MenuEntry {
Active(String),
@ -9,6 +10,12 @@ pub enum MenuEntry {
Toggle(String, bool),
}
impl MenuEntry {
pub fn height(&self) -> f64 {
14.0
}
}
pub enum MenuSelectionResult<'a> {
None,
Canceled,
@ -160,7 +167,7 @@ impl Menu {
state.font.draw_text(name.chars(), self.x as f32 + 20.0, y, &state.constants, &mut state.texture_set, ctx)?;
}
MenuEntry::Disabled(name) => {
state.font.draw_colored_text(name.chars(), self.x as f32 + 20.0, y, (0xa0, 0xa0, 0xff), &state.constants, &mut state.texture_set, ctx)?;
state.font.draw_colored_text(name.chars(), self.x as f32 + 20.0, y, (0xa0, 0xa0, 0xff, 0xff), &state.constants, &mut state.texture_set, ctx)?;
}
MenuEntry::Toggle(name, value) => {
let value_text = if *value { "ON" } else { "OFF" };
@ -172,7 +179,7 @@ impl Menu {
}
}
y += 14.0;
y += entry.height() as f32;
}
Ok(())
@ -211,15 +218,23 @@ impl Menu {
}
}
if controller.trigger_ok() && !self.entries.is_empty() {
if let Some(entry) = self.entries.get_mut(self.selected) {
match entry {
MenuEntry::Active(_) | MenuEntry::Toggle(_, _) => {
state.sound_manager.play_sfx(18);
return MenuSelectionResult::Selected(self.selected, entry);
}
_ => {}
let mut y = self.y as f32 + 6.0;
for (idx, entry) in self.entries.iter_mut().enumerate() {
let entry_bounds = Rect::new_size(self.x, y as isize, self.width as isize, entry.height() as isize);
y += entry.height() as f32;
if !((controller.trigger_ok() && self.selected == idx)
|| state.touch_controls.consume_click_in(entry_bounds)) {
continue;
}
match entry {
MenuEntry::Active(_) | MenuEntry::Toggle(_, _) => {
self.selected = idx;
state.sound_manager.play_sfx(18);
return MenuSelectionResult::Selected(idx, entry);
}
_ => {}
}
}

View file

@ -286,6 +286,11 @@ impl Player {
}
}
if state.settings.touch_controls && npc.npc_flags.interactable() && flags.0 != 0 {
// todo make it less hacky
state.touch_controls.interact_icon = true;
}
if npc.npc_flags.interactable() && !state.control_flags.interactions_disabled() && flags.0 != 0 && self.cond.interacted() {
state.control_flags.set_tick_world(true);
state.control_flags.set_interactions_disabled(true);

View file

@ -29,6 +29,7 @@ use crate::text_script::{ConfirmSelection, ScriptMode, TextScriptExecutionState,
use crate::texture_set::SizedBatch;
use crate::ui::Components;
use crate::weapon::WeaponType;
use crate::input::touch_controls::TouchControlType;
pub struct GameScene {
pub tick: usize,
@ -492,7 +493,7 @@ impl GameScene {
}
fn draw_light(&self, x: f32, y: f32, size: f32, color: (u8, u8, u8), batch: &mut SizedBatch) {
batch.add_rect_scaled_tinted(x - size * 32.0, y - size * 32.0, color,
batch.add_rect_scaled_tinted(x - size * 32.0, y - size * 32.0, (color.0, color.1, color.2, 255),
size,
size,
&Rect::new(0, 0, 64, 64))
@ -1111,36 +1112,8 @@ impl GameScene {
weapon.shoot_bullet(&self.player2, TargetPlayer::Player2, &mut self.bullet_manager, state);
}
if self.player1.cond.alive() {
if self.player1.controller.trigger_next_weapon() {
state.sound_manager.play_sfx(4);
self.inventory_player1.next_weapon();
self.hud_player1.weapon_x_pos = 32;
}
if self.player1.controller.trigger_prev_weapon() {
state.sound_manager.play_sfx(4);
self.inventory_player1.prev_weapon();
self.hud_player1.weapon_x_pos = 0;
}
}
if self.player2.cond.alive() {
if self.player2.controller.trigger_next_weapon() {
state.sound_manager.play_sfx(4);
self.inventory_player2.next_weapon();
self.hud_player2.weapon_x_pos = 32;
}
if self.player2.controller.trigger_prev_weapon() {
state.sound_manager.play_sfx(4);
self.inventory_player2.prev_weapon();
self.hud_player2.weapon_x_pos = 0;
}
}
self.hud_player1.tick(state, (&self.player1, &self.inventory_player1))?;
self.hud_player2.tick(state, (&self.player2, &self.inventory_player2))?;
self.hud_player1.tick(state, (&self.player1, &mut self.inventory_player1))?;
self.hud_player2.tick(state, (&self.player2, &mut self.inventory_player2))?;
self.boss_life_bar.tick(state, &self.npc_map)?;
}
@ -1187,7 +1160,7 @@ impl GameScene {
let text = format!("{}:{}:{}", npc.id, npc.npc_type, npc.action_num);
state.font.draw_colored_text(text.chars(), ((npc.x - self.frame.x) / 0x200) as f32, ((npc.y - self.frame.y) / 0x200) as f32,
((npc.id & 0xf0) as u8, (npc.cond.0 >> 8) as u8, (npc.id & 0x0f << 4) as u8),
((npc.id & 0xf0) as u8, (npc.cond.0 >> 8) as u8, (npc.id & 0x0f << 4) as u8, 255),
&state.constants, &mut state.texture_set, ctx)?;
Ok(())
@ -1266,6 +1239,16 @@ impl Scene for GameScene {
self.player2.controller.update(state, ctx)?;
self.player2.controller.update_trigger();
state.touch_controls.control_type = if state.control_flags.control_enabled() {
TouchControlType::Controls
} else {
TouchControlType::Dialog
};
if state.settings.touch_controls {
state.touch_controls.interact_icon = false;
}
if self.intro_mode && (self.player1.controller.trigger_menu_ok() || self.tick >= 500) {
state.next_scene = Some(Box::new(TitleScene::new()));
}
@ -1400,21 +1383,21 @@ impl Scene for GameScene {
if self.player2.x + 8 * 0x200 < self.frame.x {
state.font.draw_colored_text(P2_LEFT_TEXT.chars(),
9.0, y + 1.0,
(0, 0, 130), &state.constants, &mut state.texture_set, ctx)?;
(0, 0, 130, 255), &state.constants, &mut state.texture_set, ctx)?;
state.font.draw_colored_text(P2_LEFT_TEXT.chars(),
8.0, y,
(96, 96, 255), &state.constants, &mut state.texture_set, ctx)?;
(96, 96, 255, 255), &state.constants, &mut state.texture_set, ctx)?;
} else if self.player2.x - 8 * 0x200 > self.frame.x + state.canvas_size.0 as isize * 0x200 {
let width = state.font.text_width(P2_RIGHT_TEXT.chars(), &state.constants);
state.font.draw_colored_text(P2_RIGHT_TEXT.chars(),
state.canvas_size.0 - width - 8.0 + 1.0, y + 1.0,
(0, 0, 130), &state.constants, &mut state.texture_set, ctx)?;
(0, 0, 130, 255), &state.constants, &mut state.texture_set, ctx)?;
state.font.draw_colored_text(P2_RIGHT_TEXT.chars(),
state.canvas_size.0 - width - 8.0, y,
(96, 96, 255), &state.constants, &mut state.texture_set, ctx)?;
(96, 96, 255, 255), &state.constants, &mut state.texture_set, ctx)?;
}
}
}

View file

@ -6,6 +6,7 @@ use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
use crate::scene::Scene;
use crate::shared_game_state::{SharedGameState, TimingMode};
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::input::touch_controls::TouchControlType;
#[derive(PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
@ -121,6 +122,7 @@ impl Scene for TitleScene {
}
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
state.touch_controls.control_type = TouchControlType::None;
self.controller.update(state, ctx)?;
self.controller.update_trigger();

View file

@ -5,6 +5,7 @@ use winit::event::VirtualKeyCode;
use crate::input::keyboard_player_controller::KeyboardController;
use crate::input::player_controller::PlayerController;
use crate::player::TargetPlayer;
use crate::input::touch_player_controller::TouchPlayerController;
#[derive(Serialize, Deserialize)]
pub struct Settings {
@ -31,6 +32,10 @@ impl Settings {
}
pub fn create_player1_controller(&self) -> Box<dyn PlayerController> {
if self.touch_controls {
return Box::new(TouchPlayerController::new());
}
Box::new(KeyboardController::new(TargetPlayer::Player1))
}

View file

@ -26,8 +26,8 @@ impl Shaders {
Ok(Shaders {
water_shader: Shader::new(
ctx,
"/builtin/shaders/basic_150.vert.glsl",
"/builtin/shaders/water_150.frag.glsl",
"/builtin/shaders/basic_es300.vert.glsl",
"/builtin/shaders/water_es300.frag.glsl",
water_shader_params,
"WaterShaderParams",
None,

View file

@ -197,8 +197,9 @@ fn run<T>(rx: Receiver<PlaybackMessage>, bank: SoundBank,
org_engine.set_sample_rate(sample_rate as usize);
org_engine.loops = usize::MAX;
let mut bgm_buf = vec![0x8080; 441];
let mut pxt_buf = vec![0x8000; 441];
let buf_size = sample_rate as usize * 30 / 1000;
let mut bgm_buf = vec![0x8080; buf_size];
let mut pxt_buf = vec![0x8000; buf_size];
let mut bgm_index = 0;
let mut pxt_index = 0;
let mut frames = org_engine.render_to(&mut bgm_buf);

View file

@ -79,6 +79,11 @@ impl SizedBatch {
self.add_rect_scaled(x, y, self.scale_x, self.scale_y, rect)
}
#[inline(always)]
pub fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>) {
self.add_rect_scaled_tinted(x, y, color, self.scale_x, self.scale_y, rect)
}
pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
return;
@ -100,7 +105,7 @@ impl SizedBatch {
self.batch.add(param);
}
pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
return;
}