commit d640ad707fa4a7954ab82452424f7fc4441036a7 Author: David Lenfesty Date: Tue Sep 19 21:56:03 2023 -0600 Start the game! Just adds a basic player character with rough movement diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4709183 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/Sprites/hockey_player.png b/Sprites/hockey_player.png new file mode 100644 index 0000000..37ce2c9 Binary files /dev/null and b/Sprites/hockey_player.png differ diff --git a/Sprites/hockey_player.png.import b/Sprites/hockey_player.png.import new file mode 100644 index 0000000..a294335 --- /dev/null +++ b/Sprites/hockey_player.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bknjh6k2ocrri" +path="res://.godot/imported/hockey_player.png-2943b0999ca2f3e81e7d84176d3012c9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Sprites/hockey_player.png" +dest_files=["res://.godot/imported/hockey_player.png-2943b0999ca2f3e81e7d84176d3012c9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Sprites/hockey_player.xcf b/Sprites/hockey_player.xcf new file mode 100644 index 0000000..3ae7a0a Binary files /dev/null and b/Sprites/hockey_player.xcf differ diff --git a/game.gd b/game.gd new file mode 100644 index 0000000..f5ad70b --- /dev/null +++ b/game.gd @@ -0,0 +1,6 @@ +extends Node2D + + +# Called when the node enters the scene tree for the first time. +func _ready(): + $PlayerController.set_player_node($Player) diff --git a/game.tscn b/game.tscn new file mode 100644 index 0000000..37e101d --- /dev/null +++ b/game.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=4 format=3 uid="uid://d2skh3wa6s2j7"] + +[ext_resource type="PackedScene" uid="uid://b3llhcsie7y4u" path="res://player.tscn" id="1_2ej6p"] +[ext_resource type="Script" path="res://game.gd" id="1_swmdi"] +[ext_resource type="PackedScene" uid="uid://clntxmhd5uhlt" path="res://player_controller.tscn" id="3_v82a6"] + +[node name="Game" type="Node2D"] +script = ExtResource("1_swmdi") + +[node name="Player" parent="." instance=ExtResource("1_2ej6p")] + +[node name="PlayerController" parent="." instance=ExtResource("3_v82a6")] diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..b370ceb --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..5c23b70 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bg5wwos0fgs03" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/player.gd b/player.gd new file mode 100644 index 0000000..17c5c03 --- /dev/null +++ b/player.gd @@ -0,0 +1,78 @@ +extends Area2D + + + +# Maximum radial turning speed of the player +@export var turning_speed = PI/4 # (rad/sec) +# Maximum speed added to player's velocity per push +@export var skate_speed = 100 # (pixels/sec) +# Maximum speed deceleration while stopping +@export var braking_speed = 200 # (pixels/sec^2) +# "Natural" deceleration +@export var decel_ratio = 0.15 # (decel applied per second) + +var velocity: Vector2 + +func _ready(): + velocity = Vector2.ZERO + + +func _physics_process(delta: float): + # Flip sprite + if velocity.length() > 0: + $AnimatedSprite2D.flip_h = velocity.x > 0 + + # Update position + position = clamp_position(position + (velocity * delta)) + + # Decelerate naturally + # TODO fix vector math, need to apply to hypotenuse, not the sides + # TODO fix decel math, think compounding interest, it doesn't work this way + # TODO brake harder when almost stopped + velocity = velocity * (1 - decel_ratio * delta) + + +func push(effort: float, angle: float): + var delta_v = effort * skate_speed + velocity += delta_v * Vector2.from_angle(angle) + + +# TODO how to do doc comments? +# TODO should applying delta come from the controller or the player? It doesn't +# matter too much because these objects are pretty tightly coupled anyways. +func turn(effort: float, delta: float): + # Continuosly call with the effort while turning + + # effort is +ve right, -ve left? maybe, some orientation + velocity = velocity.rotated(effort * turning_speed * delta) + + +func brake(effort: float, delta: float): + var delta_v = effort * braking_speed * delta + delta_v = min(delta_v, velocity.length()) + velocity -= delta_v * velocity.normalized() + + +# TODO setup collision from world, and edges of arena, not the window +func clamp_position(new_pos: Vector2) -> Vector2: + var size = Vector2(get_window().size) + if new_pos.x > size.x: + if velocity.x > 0: + velocity.x = 0 + new_pos.x = size.x + if new_pos.x < 0: + if velocity.x < 0: + velocity.x = 0 + + new_pos.x = 0 + + if new_pos.y > size.y: + if velocity.y > 0: + velocity.y = 0 + new_pos.y = size.y + if new_pos.y < 0: + if velocity.y < 0: + velocity.y = 0 + new_pos.y = 0 + + return new_pos diff --git a/player.tscn b/player.tscn new file mode 100644 index 0000000..d24e85e --- /dev/null +++ b/player.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=5 format=3 uid="uid://b3llhcsie7y4u"] + +[ext_resource type="Texture2D" uid="uid://bknjh6k2ocrri" path="res://Sprites/hockey_player.png" id="1_dtrap"] +[ext_resource type="Script" path="res://player.gd" id="1_gh3p0"] + +[sub_resource type="SpriteFrames" id="SpriteFrames_oslmh"] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": ExtResource("1_dtrap") +}], +"loop": true, +"name": &"default", +"speed": 5.0 +}] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_exun0"] +size = Vector2(42, 108) + +[node name="Player" type="Area2D"] +process_physics_priority = 1 +script = ExtResource("1_gh3p0") +metadata/_edit_group_ = true + +[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] +sprite_frames = SubResource("SpriteFrames_oslmh") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(-7, -6) +shape = SubResource("RectangleShape2D_exun0") diff --git a/player_controller.gd b/player_controller.gd new file mode 100644 index 0000000..2dbd303 --- /dev/null +++ b/player_controller.gd @@ -0,0 +1,85 @@ +extends Node + +# Variables used by controller +@export var skate_push_start_len = 0.2 +@export var skate_push_end_len = 0.9 +@export var skate_min_time = 0.02 +@export var skate_max_time = 0.1 +@export var skate_max_turn_radius = PI / 8 +@export var brake_deadzone = 0.3 + +var skate_occurred: bool +var time_for_push: float + +var player_node: Node = null + +# Called when the node enters the scene tree for the first time. +func _ready(): + skate_occurred = false + time_for_push = 0 + + +# Called every physics processing step +func _physics_process(delta): + if player_node == null: + # Don't call the processing functions if the player node isn't set + return + + var joy = Input.get_vector("move_left", "move_right", "move_up", "move_down", 0) + handle_skate_push(joy, delta) + handle_turning(joy, delta) + handle_braking(joy, delta) + + +func set_player_node(node: Node): + player_node = node + + +func handle_skate_push(joy: Vector2, delta: float): + var pos = joy.length() + if pos >= skate_push_start_len and !skate_occurred: + time_for_push += delta + + if pos >= skate_push_end_len and !skate_occurred: + if time_for_push >= skate_min_time and time_for_push <= skate_max_time: + var effort = 1 - time_for_push / skate_max_time + player_node.push(effort, joy.angle()) + + skate_occurred = true + + elif pos < skate_push_start_len: + skate_occurred = false + time_for_push = 0 + + +func handle_turning(joy: Vector2, delta: float): + if joy.length() < 0.95: + # Only turn if the stick is at the edge + return + + # TODO this doesn't work well when you hit a wall + var radial_diff = player_node.velocity.angle_to(joy) + if (radial_diff >= PI/3): + # Only turn for roughlyone "half" of the stick. The other half is used for braking + return + + var is_negative: bool = radial_diff < 0 + var turn_effort = min(abs(radial_diff), skate_max_turn_radius) / skate_max_turn_radius + if is_negative: + turn_effort = -turn_effort + + player_node.turn(turn_effort, delta) + + +func handle_braking(joy: Vector2, delta: float): + if joy.length() < brake_deadzone: + # Only brake past a certain amount + return + + var radial_diff = player_node.velocity.angle_to(joy) + if (radial_diff < 3 * PI / 4): + # Only brake if controller is mostly facing away from velocity + return + + player_node.brake(joy.length(), delta) + diff --git a/player_controller.tscn b/player_controller.tscn new file mode 100644 index 0000000..89c64b4 --- /dev/null +++ b/player_controller.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://clntxmhd5uhlt"] + +[ext_resource type="Script" path="res://player_controller.gd" id="1_fj1gd"] + +[node name="PlayerController" type="Node"] +script = ExtResource("1_fj1gd") diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..1169ea8 --- /dev/null +++ b/project.godot @@ -0,0 +1,39 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Offside" +run/main_scene="res://game.tscn" +config/features=PackedStringArray("4.1", "Forward Plus") +config/icon="res://icon.svg" + +[input] + +move_left={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null) +] +} +move_right={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) +] +} +move_up={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null) +] +} +move_down={ +"deadzone": 0.5, +"events": [Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null) +] +}