- Add CreativeBuilding node / script, handles placing and breaking of blocks - Move spawn block packets to static BlockPackets class, add destroy packet - Move Cursor to its own canvas layer so its position can be used in other scripts - Use viewport transform instead of camera position in Background scriptmain
parent
ef65a0e6ac
commit
b06dd6f610
15 changed files with 333 additions and 93 deletions
@ -1,16 +1,16 @@ |
|||||||
[gd_scene load_steps=3 format=2] |
[gd_scene load_steps=4 format=2] |
||||||
|
|
||||||
[ext_resource path="res://gfx/block.png" type="Texture" id=1] |
[ext_resource path="res://gfx/block.png" type="Texture" id=1] |
||||||
|
[ext_resource path="res://src/Block.cs" type="Script" id=2] |
||||||
|
|
||||||
|
[sub_resource type="RectangleShape2D" id=1] |
||||||
[sub_resource type="RectangleShape2D" id=3] |
|
||||||
extents = Vector2( 8, 8 ) |
extents = Vector2( 8, 8 ) |
||||||
|
|
||||||
[node name="Block" type="StaticBody2D"] |
[node name="Block" type="StaticBody2D"] |
||||||
position = Vector2( 0, 96 ) |
script = ExtResource( 2 ) |
||||||
|
|
||||||
[node name="RectangleShape" type="CollisionShape2D" parent="."] |
[node name="RectangleShape" type="CollisionShape2D" parent="."] |
||||||
shape = SubResource( 3 ) |
shape = SubResource( 1 ) |
||||||
|
|
||||||
[node name="Sprite" type="Sprite" parent="."] |
[node name="Sprite" type="Sprite" parent="."] |
||||||
texture = ExtResource( 1 ) |
texture = ExtResource( 1 ) |
||||||
|
@ -1,11 +1,15 @@ |
|||||||
[gd_scene load_steps=3 format=2] |
[gd_scene load_steps=4 format=2] |
||||||
|
|
||||||
[ext_resource path="res://scene/Player.tscn" type="PackedScene" id=1] |
[ext_resource path="res://scene/Player.tscn" type="PackedScene" id=1] |
||||||
|
[ext_resource path="res://src/CreativeBuilding.cs" type="Script" id=2] |
||||||
[ext_resource path="res://src/LocalPlayer.cs" type="Script" id=3] |
[ext_resource path="res://src/LocalPlayer.cs" type="Script" id=3] |
||||||
|
|
||||||
[node name="LocalPlayer" instance=ExtResource( 1 )] |
[node name="LocalPlayer" instance=ExtResource( 1 )] |
||||||
script = ExtResource( 3 ) |
script = ExtResource( 3 ) |
||||||
|
|
||||||
[node name="Camera" type="Camera2D" parent="." index="0"] |
[node name="Camera" type="Camera2D" parent="." index="3"] |
||||||
pause_mode = 2 |
pause_mode = 2 |
||||||
current = true |
current = true |
||||||
|
|
||||||
|
[node name="CreativeBuilding" type="Node2D" parent="." index="4"] |
||||||
|
script = ExtResource( 2 ) |
||||||
|
@ -0,0 +1,6 @@ |
|||||||
|
using Godot; |
||||||
|
|
||||||
|
public class Block : StaticBody2D |
||||||
|
{ |
||||||
|
// Empty, but useful to find out whether an object is a "block". |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using Godot; |
||||||
|
|
||||||
|
public static class BlockPackets |
||||||
|
{ |
||||||
|
public static void Register() |
||||||
|
{ |
||||||
|
Network.API.RegisterS2CPacket<SpawnBlockPacket>(OnSpawnBlockPacket); |
||||||
|
Network.API.RegisterS2CPacket<SpawnBlocksPacket>(OnSpawnBlocksPacket); |
||||||
|
Network.API.RegisterS2CPacket<DestroyBlockPacket>(OnDestroyBlockPacket); |
||||||
|
} |
||||||
|
|
||||||
|
private static void OnSpawnBlockPacket(SpawnBlockPacket packet) |
||||||
|
{ |
||||||
|
// Delete any block previously at this position. |
||||||
|
Game.Instance.GetBlockAt(packet.Position)?.QueueFree(); |
||||||
|
|
||||||
|
var block = Game.Instance.BlockScene.Init<Block>(); |
||||||
|
block.Position = packet.Position; |
||||||
|
block.Modulate = packet.Color; |
||||||
|
Game.Instance.BlockContainer.AddChild(block); |
||||||
|
} |
||||||
|
|
||||||
|
private static void OnSpawnBlocksPacket(SpawnBlocksPacket packet) |
||||||
|
{ |
||||||
|
Game.Instance.ClearBlocks(); |
||||||
|
foreach (var blockInfo in packet.Blocks) { |
||||||
|
var block = Game.Instance.BlockScene.Init<Block>(); |
||||||
|
block.Position = blockInfo.Position; |
||||||
|
block.Modulate = blockInfo.Color; |
||||||
|
Game.Instance.BlockContainer.AddChild(block); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static void OnDestroyBlockPacket(DestroyBlockPacket packet) |
||||||
|
=> Game.Instance.GetBlockAt(packet.Position)?.QueueFree(); |
||||||
|
} |
||||||
|
|
||||||
|
public class SpawnBlockPacket |
||||||
|
{ |
||||||
|
public Vector2 Position { get; } |
||||||
|
public Color Color { get; } |
||||||
|
public SpawnBlockPacket(Block block) |
||||||
|
{ Position = block.Position; Color = block.Modulate; } |
||||||
|
} |
||||||
|
|
||||||
|
public class SpawnBlocksPacket |
||||||
|
{ |
||||||
|
public List<SpawnBlockPacket> Blocks { get; } |
||||||
|
public SpawnBlocksPacket() |
||||||
|
=> Blocks = Game.Instance.BlockContainer.GetChildren().OfType<Block>() |
||||||
|
.Select(block => new SpawnBlockPacket(block)).ToList(); |
||||||
|
} |
||||||
|
|
||||||
|
public class DestroyBlockPacket |
||||||
|
{ |
||||||
|
public Vector2 Position { get; } |
||||||
|
public DestroyBlockPacket(Block block) |
||||||
|
{ Position = block.Position; } |
||||||
|
} |
@ -0,0 +1,175 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using Godot; |
||||||
|
|
||||||
|
public class CreativeBuilding : Node2D |
||||||
|
{ |
||||||
|
private enum BuildMode |
||||||
|
{ |
||||||
|
Placing, |
||||||
|
Breaking, |
||||||
|
} |
||||||
|
|
||||||
|
private static readonly Vector2[] _neighborPositions = new Vector2[]{ |
||||||
|
Vector2.Left*16, Vector2.Right*16, Vector2.Up*16, Vector2.Down*16 }; |
||||||
|
|
||||||
|
[Export] public int MaxLength { get; set; } = 6; |
||||||
|
|
||||||
|
private Texture _blockTex; |
||||||
|
private Vector2 _startPos; |
||||||
|
private Vector2 _direction; |
||||||
|
private int _length; |
||||||
|
private bool _canBuild; |
||||||
|
|
||||||
|
private BuildMode? _currentMode = null; |
||||||
|
|
||||||
|
private IEnumerable<Vector2> BlockPositions => |
||||||
|
Enumerable.Range(0, _length + 1).Select(i => _startPos + _direction * (i * 16)); |
||||||
|
|
||||||
|
public override void _Ready() |
||||||
|
{ |
||||||
|
_blockTex = GD.Load<Texture>("res://gfx/block.png"); |
||||||
|
} |
||||||
|
|
||||||
|
public override void _Process(float delta) |
||||||
|
{ |
||||||
|
Update(); |
||||||
|
|
||||||
|
if (EscapeMenu.Instance.Visible || !Game.Cursor.Visible) |
||||||
|
{ _currentMode = null; return; } |
||||||
|
|
||||||
|
switch (_currentMode) { |
||||||
|
case null: |
||||||
|
if (Input.IsActionJustPressed("interact_place")) |
||||||
|
if (_canBuild) _currentMode = BuildMode.Placing; |
||||||
|
if (Input.IsActionJustPressed("interact_break")) |
||||||
|
_currentMode = BuildMode.Breaking; |
||||||
|
break; |
||||||
|
case BuildMode.Placing: |
||||||
|
if (Input.IsActionJustPressed("interact_break")) _currentMode = null; |
||||||
|
else if (!Input.IsActionPressed("interact_place")) { |
||||||
|
if (_canBuild) |
||||||
|
foreach (var pos in BlockPositions) |
||||||
|
PlaceBlock(pos); |
||||||
|
_currentMode = null; |
||||||
|
} |
||||||
|
break; |
||||||
|
case BuildMode.Breaking: |
||||||
|
if (Input.IsActionJustPressed("interact_place")) _currentMode = null; |
||||||
|
else if (!Input.IsActionPressed("interact_break")) { |
||||||
|
foreach (var pos in BlockPositions) { |
||||||
|
var block = Game.Instance.GetBlockAt(pos); |
||||||
|
if (block != null) BreakBlock(block); |
||||||
|
} |
||||||
|
_currentMode = null; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (_currentMode != null) { |
||||||
|
var rad90 = Mathf.Deg2Rad(90.0F); |
||||||
|
var angle = Mathf.Round(_startPos.AngleToPoint(Game.Cursor.Position) / rad90) * rad90; |
||||||
|
_direction = new Vector2(-Mathf.Cos(angle), -Mathf.Sin(angle)); |
||||||
|
_length = Math.Min(MaxLength, Mathf.RoundToInt(_startPos.DistanceTo(Game.Cursor.Position) / 16)); |
||||||
|
} else { |
||||||
|
_startPos = (Game.Cursor.Position / 16).Round() * 16; |
||||||
|
_length = 0; |
||||||
|
} |
||||||
|
|
||||||
|
bool IsBlockAt(Vector2 pos) => Game.Instance.GetBlockAt(pos) != null; |
||||||
|
_canBuild = !IsBlockAt(_startPos) && _neighborPositions.Any(pos => IsBlockAt(_startPos + pos)); |
||||||
|
} |
||||||
|
|
||||||
|
private Block PlaceBlock(Vector2 position) |
||||||
|
{ |
||||||
|
if (Game.Instance.GetBlockAt(position) != null) return null; |
||||||
|
// FIXME: Test if there is a player in the way. |
||||||
|
|
||||||
|
var block = Game.Instance.BlockScene.Init<Block>(); |
||||||
|
block.Position = position; |
||||||
|
block.Modulate = Game.LocalPlayer.Color.Blend(Color.FromHsv(0.0F, 0.0F, GD.Randf(), 0.2F)); |
||||||
|
Game.Instance.BlockContainer.AddChild(block); |
||||||
|
|
||||||
|
if (Network.IsMultiplayerReady) { |
||||||
|
if (Network.IsServer) Network.API.SendToEveryone(new SpawnBlockPacket(block)); |
||||||
|
else Network.API.SendToServer(new PlaceBlockPacket(position)); |
||||||
|
} |
||||||
|
|
||||||
|
return block; |
||||||
|
} |
||||||
|
|
||||||
|
private void BreakBlock(Block block) |
||||||
|
{ |
||||||
|
// FIXME: Use a different (safer) way to check if a block is one of the default ones. |
||||||
|
if (block.Modulate.s < 0.5F) return; |
||||||
|
|
||||||
|
if (Network.IsMultiplayerReady) { |
||||||
|
if (Network.IsServer) Network.API.SendToEveryone(new DestroyBlockPacket(block)); |
||||||
|
else Network.API.SendToServer(new BreakBlockPacket(block)); |
||||||
|
} |
||||||
|
|
||||||
|
block.QueueFree(); |
||||||
|
} |
||||||
|
|
||||||
|
public override void _Draw() |
||||||
|
{ |
||||||
|
if (!Game.Cursor.Visible) return; |
||||||
|
|
||||||
|
var green = Color.FromHsv(1.0F / 3, 1.0F, 1.0F, 0.4F); |
||||||
|
var red = Color.FromHsv(0.0F, 1.0F, 1.0F, 0.4F); |
||||||
|
var black = new Color(0.0F, 0.0F, 0.0F, 0.65F); |
||||||
|
|
||||||
|
foreach (var pos in BlockPositions) { |
||||||
|
var hasBlock = Game.Instance.GetBlockAt(pos) != null; |
||||||
|
var color = (_currentMode != BuildMode.Breaking) |
||||||
|
? ((_canBuild && !hasBlock) ? green : red) |
||||||
|
: (hasBlock ? black : red); |
||||||
|
DrawTexture(_blockTex, ToLocal(pos - _blockTex.GetSize() / 2), color); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static void RegisterPackets() |
||||||
|
{ |
||||||
|
Network.API.RegisterC2SPacket<PlaceBlockPacket>(OnPlaceBlockPacket); |
||||||
|
Network.API.RegisterC2SPacket<BreakBlockPacket>(OnBreakBlockPacket); |
||||||
|
} |
||||||
|
|
||||||
|
private class PlaceBlockPacket |
||||||
|
{ |
||||||
|
public Vector2 Position { get; } |
||||||
|
public PlaceBlockPacket(Vector2 position) => Position = position; |
||||||
|
} |
||||||
|
private static void OnPlaceBlockPacket(Player player, PlaceBlockPacket packet) |
||||||
|
{ |
||||||
|
if (Game.Instance.GetBlockAt(packet.Position) != null) return; |
||||||
|
var block = Game.Instance.BlockScene.Init<Block>(); |
||||||
|
block.Position = packet.Position; |
||||||
|
block.Modulate = player.Color.Blend(Color.FromHsv(0.0F, 0.0F, GD.Randf(), 0.2F)); |
||||||
|
Game.Instance.BlockContainer.AddChild(block); |
||||||
|
|
||||||
|
Network.API.SendToEveryone(new SpawnBlockPacket(block)); |
||||||
|
} |
||||||
|
|
||||||
|
private class BreakBlockPacket |
||||||
|
{ |
||||||
|
public Vector2 Position { get; } |
||||||
|
public BreakBlockPacket(Block block) => Position = block.Position; |
||||||
|
} |
||||||
|
private static void OnBreakBlockPacket(Player player, BreakBlockPacket packet) |
||||||
|
{ |
||||||
|
var block = Game.Instance.GetBlockAt(packet.Position); |
||||||
|
if (block == null) return; |
||||||
|
|
||||||
|
if (block.Modulate.s < 0.5F) { |
||||||
|
// TODO: Respawn the block the client thought it destroyed? |
||||||
|
return; |
||||||
|
} |
||||||
|
// TODO: Further verify whether player can break a block at this position. |
||||||
|
|
||||||
|
Network.API.SendToEveryoneExcept(player, new DestroyBlockPacket(block)); |
||||||
|
block.QueueFree(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue