- 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://src/Block.cs" type="Script" id=2] |
||||
|
||||
|
||||
[sub_resource type="RectangleShape2D" id=3] |
||||
[sub_resource type="RectangleShape2D" id=1] |
||||
extents = Vector2( 8, 8 ) |
||||
|
||||
[node name="Block" type="StaticBody2D"] |
||||
position = Vector2( 0, 96 ) |
||||
script = ExtResource( 2 ) |
||||
|
||||
[node name="RectangleShape" type="CollisionShape2D" parent="."] |
||||
shape = SubResource( 3 ) |
||||
shape = SubResource( 1 ) |
||||
|
||||
[node name="Sprite" type="Sprite" parent="."] |
||||
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://src/CreativeBuilding.cs" type="Script" id=2] |
||||
[ext_resource path="res://src/LocalPlayer.cs" type="Script" id=3] |
||||
|
||||
[node name="LocalPlayer" instance=ExtResource( 1 )] |
||||
script = ExtResource( 3 ) |
||||
|
||||
[node name="Camera" type="Camera2D" parent="." index="0"] |
||||
[node name="Camera" type="Camera2D" parent="." index="3"] |
||||
pause_mode = 2 |
||||
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